Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Реализация с самостоятельным управлениям памятью

Программный модуль в С++ представлен двумя файлами – заголовочным (*.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 Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...