Реализация с самостоятельным управлениям памятью
Программный модуль в С++ представлен двумя файлами – заголовочным (*.h) и файлом реализации (*.cpp). В заголовочном файле объявляются типы, классы, функции (интерфейс модуля), а во втором файле помещается их реализация. В рассматриваемом примере заголовочный файл в рамках пространства имён MyLinearAlgebra содержит объявления трёх классов – TVector, TMatrix и TQuaternion, причем последний агрегирует объект типа TVector (для хранения векторной части кватерниона). В классах TVector и TMatrix есть защищённая секция, в которой объявлены переменные для хранения данных – массива значений и количества элементов в нем (для матрицы отдельно хранится количество строк и столбцов). В классе TQuaternion в защищённой секции объявлены переменные для хранения скалярной и векторной части кватеорниона. Использование для инкапсуляции полей с данными спецификатора protected вместо private вызвано возможной необходимостью наследования этих классов с сохранением прямого доступа к данным. В классе TVector поле для доступа к данным представляет собой указатель на массив чисел типа double, который создаётся в динамической памяти при вызове конструктора или метода изменения размера массива void resize(int). В классе TMatrix аналогичное поле содержит указатель на массив указателей, каждый из которых, в свою очередь, адресует массив чисел (строк матрицы). Изменение размеров и количества этих массивов происходит при вызове конструктора или метода void resize(int, int), в который передаётся два параметра – желаемое количество строк и столбцов матрицы. В качестве типа числовых данных предлагается использовать double, так как переменные этого типа имеют одинаковое представление в памяти на различных платформах и компиляторах (занимая 64 бита), и точности расчётов с его использованием достаточно для большинства математических моделей динамики интегирированных систем ЛА. В среде разработки Embarcadero RAD Studio на платформе x86 для повышения точности расчётов можно использовать тип long double, занимающий 80 бит и являющийся аналогом типа Extended в Delphi. Для компилятора С++ от Microsoft или Embarcadero RAD Studio на x64 тип long double является синонимом обычного double, поэтому преимуществ в точности от его использования не будет. Некоторые платформы и компиляторы (например, GNU C++ Complilier на Linux x64) отводят для размещения чисел типа long double 128 бит, обеспечивая наиболее высокую точность математических расчётов стандартными средствами.
В С++ возможна перегрузка операторов для пользовательских типов, причём в отличие от Delphi, эти типы могут быть представлены и классами. В рассматриваемом примере эта возможность используется для последующего упрощения записи вычислительных выражений с векторами, матрицами и кватернионами. Ниже представлен пример содержимого заголовочного файла модуля mylinalg.h, в котором объявляются классы TVector, TMatrix и TQuaternion. Листинг 8 namespace MyLinearAlgebra { // Опережающие объявления class TMatrix; class TQuaternion; // Объявление класса векторов class TVector { protected: // Размерность вектора int n; // Элементы вектора double *data; public: // Конструктор по умолчанию TVector(); // Конструктор с заданным кол-вом элементов TVector(int n); // Конструктор копий TVector(const TVector& rvalue); // Оператор присваивания TVector& operator = (const TVector& rvalue); // Деструктор virtual ~TVector(); // Функция получение кол-ва элементов вектора inline int size() const { return n; } // Функция получения индекса последнего элемента inline int high() const { return n - 1; } // Функция задания кол-ва элементов вектора void resize (int n); // Оператор доступа к элементам вектора inline double& operator [](int i) { return data[i]; } // Оператор константного доступа к элементам вектора inline const double& operator [](int i) const { return data[i]; }
// Оператор - унарный минус TVector operator - () const; // Оператор вычитания векторов TVector operator - (const TVector& arg) const; // Оператор сложения векторов TVector operator + (const TVector& arg) const; // Оператор умножения вектора на число TVector operator * (double arg) const; // Оператор скалярного умножения векторов double operator * (const TVector& arg) const; // Оператор умножения вектора на матрицу TVector operator * (const TMatrix& arg) const; // Оператор умножения вектора на кватернион TQuaternion operator * (const TQuaternion& arg) const; // Оператор векторного умножения векторов TVector operator ^ (const TVector& arg) const; // Дружественная функция - оператор умножения числа на вектор friend TVector operator * (double lvalue, const TVector& rvalue); // Функция получения модуля вектора double length() const; // Функция нормирования вектора TVector& norm(); // Поворот вектора вокруг заданной оси на заданный угол при помощи формулы Родрига TVector rotateByRodrigFormula(double phi, const TVector& axis) const; // Поворот вектора вокруг заданной оси на заданный угол при помощи кватерниона TVector rotate(double phi, const TVector& axis) const; // Поворот векора при помощи заданного кватерниона TVector rotateByQuaternion(const TQuaternion& L) const; }; // Объявление класса матриц class TMatrix { protected: // Размерность матрицы (число строк и столбцов) int n, m; // Элементы матрицы double **data; public: // Конструктор по умолчанию TMatrix(); // Конструктор с заданной размерностью TMatrix(int n, int m); // Конструктор копий TMatrix(const TMatrix& rvalue); // Оператор присваивания TMatrix& operator = (const TMatrix& rvalue); // Деструктор virtual ~TMatrix(); // Функция получения количества строк inline int rowCount() const { return n; } // Функция получения кол-ва столбцов inline int colCount() const { return m; } // Функция получения индекса последней строки inline int rowHigh() const { return n-1; } // Функция получения индекса последнего столбца inline int colHigh() const { return m-1; } // Функция задания размерности void resize(int n, int m); // Оператор доступа к элементам матрицы inline double& operator ()(int i, int j) { return data[i][j]; } // Оператор константного доступа к элементам вектора inline const double& operator ()(int i, int j) const { return data[i][j]; } // Оператор - унарный минус TMatrix operator - () const; // Оператор вычитания матриц TMatrix operator - (const TMatrix& arg) const; // Оператор сложения матриц TMatrix operator + (const TMatrix& arg) const; // Оператор умножения матрицы на число TMatrix operator * (double arg) const; // Оператор умножения матриц TMatrix operator * (const TMatrix& arg) const;
// Оператор умножения матрицы на вектор TVector operator * (const TVector& arg) const; // Дружественная функция - оператор умножения числа на матрицу friend TMatrix operator * (double lvalue, const TMatrix& rvalue); // Оператор обращения матриц (метод Гаусса) TMatrix operator! () const throw (int); // Функция вычисления детерминанта double det() const; // Функция транспонирования TMatrix t() const; // Функция формирования единичной матрицы static TMatrix E(int n); // Функция перестановки строк TMatrix& swapRows(int i, int j); }; class TQuaternion { protected: // Скалярная часть double q0; // Векторная часть TVector Q; public: // Конструктор по умолчанию TQuaternion(); // Конструктор по компонентам кватерниона TQuaternion(double l0, double l1, double l2, double l3); // Конструктор по углу поворота (рад.) и оси вращения TQuaternion(double phi, const TVector& e); // Конструктор копирования TQuaternion(const TQuaternion& rvalue); // Оператор присваивания TQuaternion& operator = (const TQuaternion& rvalue); // Оператор вычитания кватернионов TQuaternion operator - (const TQuaternion& arg) const; // Оператор сложения кватернионов TQuaternion operator + (const TQuaternion& arg) const; // Оператор умножения кватернионов TQuaternion operator * (const TQuaternion& arg) const; // Оператор умножения кватерниона на вектор TQuaternion operator * (const TVector& arg) const; // Оператор умножения кватерниона на число TQuaternion operator * (double arg) const; // Дружественная функция - оператор умножения числа на кватернион friend TQuaternion operator * (double lvalue, const TQuaternion & rvalue); // Оператор обращения кватерниона TQuaternion operator! () const; // Доступ к скалярной части inline double scal() const { return q0; } // Доступ к векторной части inline TVector vect() const { return Q; } // Функция нормирования кватерниона TQuaternion& norm(); // Функция получения сопряженного кватерниона TQuaternion conj() const; // Функция получения матрицы вращения из компонентов кватерниона TMatrix toRotateMatrix() const; // Производящая функция для создания кватерниона по углам Крылова static TQuaternion fromKrylovAngles(double yaw, double pitch, double roll); }; } Следует обратить внимание на специальные методы: конструктор копирования – метод, вызываемый при передаче в функцию динамического объекта (по значению) и возврате его из функции, а также оператор присваивания – метод, вызываемый при присваивании переменной типа TVector, TMatrix или TQuaternion значения другой аналогичной переменной соответствующего типа. Реализация обоих этих методов должна предполагать создание копии объекта, являющегося их аргументом, но в операторе присваивания нужно ещё и удалить старое содержимое памяти, если до совершения присваивания она уже была выделена. Конструктор копирования, как и обычный конструктор, всегда имеет имя, совпадающее с именем класса, а аргумент этого конструтора (имеющий тип данного класса) должен быть объявлен константынм и перадаваться по ссылке.
Также важно отметить, что для доступа к элементам вектора используется перегруженный оператор operator [] (int) в двух модификациях – константной (с модификатором const для применения к констанатным объектам только на чтение значений) и неконстантной (для чтения и записи значений). Для доступа к элементам матрицы используется перегруженный оператор operator () (int, int) также в двух модификациях. Использование для матрицы именно оператора «круглые скобки» вызвано тем, что этот оператор поддерживает передачу ему нескольких аргументов (в частности, индексов строки и столбца матрицы), в отличие от оператора «квадратные скобки». При этом с точки зрения соблюдения принципа инкапсуляции нежелательно использовать последний оператор в «составном» виде ([ i ][ j ]), предоставляя прямой незащищённый доступ к полю с данными. Широкое использование модификатора const является хорошим тоном в программировании, повышает надежность кода, позволяя избежать ошибок, связанных с непреднамерной модификацией объектов внутри функций. Применение модификатора inline перед объявлением заголовка функции указывает компилятору на необходимость включения её кода непосредственно в вызывающий код. Этот модификатор позволяет оптимизировать быстродействие при наличии большого количества вызвов коротких функций (правда, за счёт некоторого проигрыша в объёме машинного кода). Большинство компиляторов могут игнорировать этот модификатор, если с точки зрения заданного кртиерия оптимизации кода такая подстановка функции окажется нецелесообразна. Ещё один приём, направленный на повышение быстродействия (а это весьма важная характеристика моделирующего комплекса), состоит в передаче параметров со сложной структурой по ссылке (с применением модификатора const для запрета изменений таких параметров), а не по значению. В таком случае упомянутый выше конструктор копирования не вызывается, а внутрь функции попадает лишь ссылка на существующий вовне объект. Также можно отметить применение модификатора static к методам классов TMatrix, TQuaternion. Этот модификатор делает объявленный с ним метод «классовым» или статическим – вызов такого метода возможен без создания экземпляра класса. Часто такие методы используются в качестве «производящих» или «фабричных» (как и в отмеченных случаях), осуществляя создание экземпляра класса на основе переданных параметров (формирование единичной матрицы заданного размера и кватерниона по значениям углов Крылова, см. раздел Ошибка! Источник ссылки не найден.).
Операторы «^» и «!», перегруженные в классах TVector и TMatrix соответственно, предназначены для вычисления векторного произведения векторов и обращения матриц. Следует заметить, что приоритет оператора «^» ниже приоритета логических и арифметических операторов, поэтому его использование в выражениях должно сопровождаться явным указанием приоритета вычислений при помощи круглых скобок. Модификатор friend используется для объявления функции – не члена класса, но «дружественной» ему (внутри такой функции можно получать доступ к закрытым полям класса). В нашем примере функции с таким модификатором используются для умножения числа на вектор, матрицу и кватернион (т.к. первым операндом в этих функциях является число, то они не могут быть членами соответствующих классов, и мы вынуждены объявить эти функции «дружественными» им). Представленные объявления классов могут быть расширены различными методами, необходимыми для решения задач моделирования (например, перестановки столбцов, получения алгебраических дополнений, решения проблемы собственных значений и т.д.). Учитывая примененный объектно-ориентированный подход, модификация и расширение методов легко могут быть выполнены при помощи наследования. Далее приводится фрагмент файла mylinalg.cpp, в котором с комментариями представлен исходный код методов работы с памятью, таких как конструкторы, деструкторы, операторы присваивания, функции изменения размерности векторов и матриц. Также в качестве примера приводится реализация некоторых алгебраических методов соответствующих классов: сложение векторов, поворот вектора при помощи заданного кватерниона, сложение матриц, производящая функция для получения единичной матрицы заданной размерности, умножение кватерниона на другой кватернион и на вектор. Листинг 9 #include "mylinalg.h" #include <cstring> #include <math.h> namespace MyLinearAlgebra { // Векторы // Конструктор по умолчанию TVector::TVector(): n(0), data(NULL) {} // Конструктор по количеству элементов TVector::TVector(int n): n(0), data(NULL) { resize(n); } // Конструктор копирования TVector::TVector(const TVector& rvalue): n(0), data(NULL) { (* this) = rvalue; } // Оператор присваивания TVector& TVector:: operator = (const TVector& rvalue) { // Если левый операнд не совпадает с правым if (this!= &rvalue) { // Если размер левого операнда не совпадает с правым, то память выделяется заново if (n!= rvalue.n) { // Если память уже была выделена, удалить её if (data) { delete [] data; } // Выделение новой памяти data = new double [rvalue.n]; // Сохранение нового размера n = rvalue.n; } // Перенос данных из правого операнда в левый memcpy(data, rvalue.data, sizeof (double) * n); } // Возврат ссылки на левый операнд для возможности цепочки присваиваний return * this; } // Деструктор TVector::~TVector() { // Если блок данных ранее был инициализирован, удаляем его if (data) { delete [] data; n = 0; data = NULL; } } // Функция задания кол-ва элементов вектора void TVector::resize(int n) { #ifdef _DEBUG if (n < 0) throw 1; #endif // Если новый размер совпадает со старым - выходим if (n == this ->n) return; // Новый блок памяти double *newData = new double [n]; // Если блок данных ранее был инициализирован... if (data) { // Минимальный из старого и нового размера блока int min_n = (this ->n < n)? this ->n: n; // Перенос данных из старого блока в новый memcpy(newData, data, sizeof (double)*min_n); // Удаление старого блока delete [] data; } // Прикрепление нового блока к объекту вектора data = newData; // Сохранение нового размера this ->n = n; } // Оператор сложения векторов TVector TVector:: operator + (const TVector& arg) const { #ifdef _DEBUG if (n!= arg.n) throw 1; #endif TVector V(n); for (int i = 0; i < n; i++) V[i] = data[i] + arg[i]; return V; } // Поворот вектора при помощи заданного кватерниона TVector TVector::rotateByQuaternion(const TQuaternion& L) const { return (L * (* this) * L.conj()).vect(); } // Матрицы // Конструктор по умолчанию TMatrix::TMatrix(): n(0), m(0), data(NULL) {} // Конструктор с заданной размерностью TMatrix::TMatrix(int n, int m): n(0), m(0), data(NULL) { resize(n, m); } // Конструктор копий TMatrix::TMatrix(const TMatrix& rvalue): n(0), m(0), data(NULL) { (* this) = rvalue; } // Оператор присваивания TMatrix& TMatrix:: operator = (const TMatrix& rvalue) { // Если левый операнд не совпадает с правым if (this!= &rvalue) { // Удаление ранее выделенной памяти this ->~TMatrix(); // Выделение новой памяти по размерам правого операнда resize(rvalue.n, rvalue.m); // Перенос данных из правого операнда в левый построчно for (int i = 0; i < n; i++) memcpy(data[i], rvalue.data[i], sizeof (double)*m); } // Возврат ссылки на левый операнд для возможности цепочки присваиваний return (* this); } // Деструктор объекта матрицы TMatrix::~TMatrix() { if (data) { for (int i = 0; i < n; i++) delete [] data[i]; delete [] data; data = NULL; n = m = 0; } } // Функция задания размерности матрицы void TMatrix::resize(int n, int m) { // Кол-во строк, которые нужно перенести в новые блоки данных int min_n = this ->n < n? this ->n: n; // Если кол-во столбцов не совпадает if (this ->m!= m) { // Кол-во столбцов, которые нужно перенести в новые блоки данных int min_m = this ->m < m? this ->m: m; // Цикл построчного переноса данных в новые блоки for (int i = 0; i < min_n; i++) { // Создание нового блока-строки double *newDataRow = new double [m]; // Перенос данных в новый блок-строку memcpy(newDataRow, data[i], sizeof (double)*min_m); // Удаление старого блока строки на этом месте delete [] data[i]; // Прикрепление нового блока-строки на старое место data[i] = newDataRow; } // Сохранение нового размера this ->m = m; } // Если кол-во строк не совпадает if (this ->n!= n) { // Создание нового блока-контейнера double **newData = new double *[n]; // Перенос содержимого старого контейнера в новый memcpy(newData, data, sizeof (double *)*min_n); // Удаление лишних строк из старого контейнера for (int i = n; i < this ->n; i++) { delete [] data[i]; } // Удаление старого контейнера if (data) { delete [] data; } // Создание недостающих строк в новом контейнере for (int i = this ->n; i < n; i++) { newData[i] = new double [m]; } // Привязка старого контейнера к новому data = newData; this ->n = n; } } // Оператор сложения матриц TMatrix TMatrix:: operator + (const TMatrix& arg) const { #ifdef _DEBUG if ((n!= arg.n) || (m!= arg.m)) throw 1; #endif TMatrix M(n, m); for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) M(i,j) = data[i][j] + arg(i,j); return M; } // Производящая функция для формирования единичной матрицы TMatrix TMatrix::E(int n) { TMatrix E(n,n); for (int i = 0; i < n; i++) { E(i,i) = 1; for (int j = i+1; j < n; j++) { E(i,j) = E(j,i) = 0; } } return E; } // Кватернионы // Конструктор по умолчанию TQuaternion::TQuaternion(): q0(0), Q(3) {} // Конструктор по компонентам кватерниона TQuaternion::TQuaternion(double q0, double q1, double q2, double q3) : q0(q0), Q(3) { Q[0] = q1; Q[1] = q2; Q[2] = q3; } // Конструктор по углу поворота (радианы) и оси вращения TQuaternion::TQuaternion(double phi, const TVector& e) { q0 = cos(phi/2); Q = e; Q.norm(); Q = Q * sin(phi/2); } // Конструктор копирования TQuaternion::TQuaternion(const TQuaternion& rvalue) { * this = rvalue; } // Оператор присваивания TQuaternion& TQuaternion:: operator = (const TQuaternion& rvalue) { if (this!= &rvalue) { q0 = rvalue.q0; Q = rvalue.Q; } return * this; } // Оператор умножения кватернионов TQuaternion TQuaternion:: operator * (const TQuaternion& arg) const { TQuaternion L; L.q0 = q0 * arg.q0 – Q * arg.Q; L.Q = Q * arg.q0 + arg.Q * q0 + (Q^arg.Q); return L; } // Оператор умножения кватерниона на вектор TQuaternion TQuaternion:: operator * (const TVector& arg) const { TQuaternion L(0, arg[0], arg[1], arg[2]); return (* this) * L; } … } В представленном примере методы выделения, изменения размера и освобождения памяти реализованы непосредственно с использованием операторов new и delete [], причём код метода установки размерности матриц void TMatrix::resize(int, int) оптимизирован для ситуации, при которой количество строк в матрице увеличивается в процессе выполнения вычислений. Обработка организована так, что при изменении количества строк заново выделяется только память под указатели на строки, сами же строки остаются в памяти неизменными. Это существенно повышает быстродействие и снижает ресурсоёмкость вычислений. Следует обратить внимание на использование в реализации директивы условной компиляции #ifdef … #endif. Её назначение здесь аналогично описанному в примере для Delphi – при компиляции в режиме отладки обеспечить проверку корректности параметров и предоставить разработчику информацию о типе возникшей ошибки (см. раздел 3.1.1). Для контроля режима компиляции используется макроопределение _DEBUG, которое в режиме отладки принимает значение «истина» (макроопределение с таким именем используется во многих средах разработки). Для информирования разработчика (или внешних автоматизированных средств тестирования) о типе ошибки внутри функции может генерироваться объект исключения – целое число, значение которого соответствует коду ошибки (вообще же объект исключения может быть любого типа). Ниже представлен фрагмент программного кода, в котором демонстрируется работа с объектами описанных классов. Листинг 10 // Указание на использование пространства имён using MyLinearAlgebra; // Объявление переменных типа матрица с заданием размеров TMatrix A(3, 3), B(3, 3), C; // Инициализация элементов матриц A(0,0):= 1; A(0,1):= 2; … B(0,0):= 10; A(0,1):= 20; … // Сложение матриц А и В C = A + B; // Обращение матрицы А B =!A; // Умножение матриц А и В A = A * B; // Объявление переменных типа вектор с заданием размеров TVector X(3), e(3), Z; // Инициализация элементов векторов X[0]:= 1; X[1]:= 2; X[2]:= 3; e[0]:= 5; e[1]:= 0; e[2]:= 0; // Умножение матрицы на вектор Z = C * X; // Создание объекта кватерниона по углу и оси вращения TQuaternion Q(M_PI / 2, e); // Поворот вектора при помощи кватерниона Z = X.rotateByQuaternion(Q);
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|