Класс алгебраических векторов Vector
Для класса алгебраических векторов создадим модуль UnVector. Представим алгебраический вектор как динамический массив с элементами типа double. В заголовочном файле модуля поместим объявление класса.
//Файл UnVector.h
#ifndef UnVectorH
#define UnVectorH
#include <iostream.h>
class Vector { // Класс алгебраических векторов
double* v; // Компоненты вектора
int size; // Размерность вектора;
public:
Vector(int r=1); // Конструктор
Vector(const Vector&); // Конструктор копирования
~Vector() // Деструктор освобождает
{ delete[] v; } // память от вектора
double& operator[](int i) // Доступ к элементу
{ return v[i]; }
double operator[](int i) const // Получить значения элемента
{ return v[i]; }
int GetSize() const // Получить размер вектора
{ return size; }
Vector& operator=(const Vector&); // Оператор присваивания
Vector operator+(const Vector&); // Сложение векторов
Vector operator-(const Vector&); // Вычитание векторов
Vector operator*(const double); // Умножение вектора на число
int NumbMainElement(int i); // Возвращает номер максимального по
// модулю элемента массива, начиная с номера i
friend ostream& operator<<(ostream&, const Vector&); // Вывод вектора
friend istream& operator>>(istream&, Vector&); // Ввод вектора
};
#endif
Алгебраические вектора реализованы в виде динамического массива. Для доступа к элементам вектора перегружен оператор [], возвращающий ссылку на компонент вектора. Значение элемента вектора возвращает константная функция-оператор []. Константная функция не изменяет значения объекта, для которого вызвана.
Для повышения эффективности аргументы в функции передаются по ссылке. В этом случае не создаются их копии в функции. В случае, если передаваемый по ссылке аргумент не должен изменяться внутри функции, перед ним можно поставить const, тогда компилятор будет выдавать ошибку при попытке изменить такой аргумент. Такой прием уменьшает вероятность ошибок.
В составе класса предусмотрены функции, реализующие обычные операции над векторами.
В файл UnVector.cpp поместим реализацию класса Vector.
// Файл UnVector.cpp
#include <math.h>
#include "UnVector.h"
// Реализация класса алгебраических векторов
Vector::Vector(int n) // Конструктор
{
size = n;
v = new double[n]; // При создании вектора
for(int i = 0; i < n; i++) // его элементы
v[i] = 0.0; // обнуляются
}
Vector :: Vector(const Vector& b) // Конструктор копирования
{
size = b.size; // Создается
v = new double[size]; // копия
for(int i = 0; i < size; i++) // вектора b
v[i] = b.v[i];
}
Vector& Vector::operator=(const Vector& b) // Оператор присваивания
{
if(this != &b){ // Если присваивание не самому себе,
delete[] v; // освобождение старой памяти
size = b.size; // Новый размер
v = new double[size]; // Выделение новой памяти
for(int i = 0; i < b.size; i++) // Копирование
v[i] = b.v[i]; // компонентов вектора
}
return *this;
}
Vector Vector :: operator+(const Vector& b) // Сложение векторов
{
Vector sum = *this; // Копия первого слагаемого
for(int i = 0; i < size; i++) // Добавляем
sum[i] += b.v[i]; // второе слагаемое
return sum;
}
Vector Vector :: operator-(const Vector& b) // Вычитание векторов
{
Vector dif = *this; // Копия уменьшаемого
for(int i = 0; i < size; i++) // Вычитание элементов
dif.v[i] -= b.v[i]; // второго вектора
return dif;
}
Vector Vector :: operator*(const double d) // Умножение на число
{
Vector prod = *this; // Копия вектора
for(int i = 0; i < size; i++) // Умножение элементов
prod.v[i] *= d; // вектора на число
return prod;
}
int Vector :: NumbMainElement(int i) // Возвращает номер максимального
{ // по модулю (главного) элемента массива, номер которого >= i
double MainEl = fabs(v[i]); // Значение главного элемента
int NumbMain = i; // Номер главного элемента
for(int k = i + 1; k < size; k++)
if(MainEl < fabs(v[k])){
MainEl = fabs(v[k]);
NumbMain = k;
}
return NumbMain;
}
ostream& operator<<(ostream& stream, const Vector& b) // Оператор
{ // вывода вектора
for(int i = 0; i < b.size; i++)
stream << b.v[i] << endl;
return stream;
}
istream& operator>>(istream& stream, Vector& b) // Оператор
{ // ввода вектора
for(int i = 0; i < b.size; i++)
stream >> b.v[i];
return stream;
}
Вспомогательная функция NumbMainElement используется при обращении матриц методом Гаусса. Главным называется максимальный по модулю элемент столбца матрицы. Для исключения неизвестных по методу Гаусса следует использовать строку, содержащую главный элемент, что гарантирует от случайного деления на нуль для неособенной матрицы. Кроме того, при использовании главного элемента уменьшаются ошибки округления.
Класс прямоугольных матриц
Возможно различное представление матрицы в памяти. Здесь выбрано представление в виде совокупности векторов-столбцов, схема которого показана на рис. 47.
Работать с матрицей будем через указатель a на массив из указателей на вектора. Выражения типа a + 1 являются указателями на элементы массива указателей. Значения элементов массива указателей, то есть выражения типа *(a + 1) или a[1], являются указателями на вектора-столбцы матрицы. Выражения вида *a[1] есть отдельные вектора-столбцы матрицы. Для доступа к отдельным элементам матрицы можно использовать средства класса Vector.
Рис. 47. Схема представления матрицы в памяти
Класс прямоугольных матриц поместим в модуль UnMatrix.
Объявление класса Matrix
Объявление класса матриц поместим в заголовочный файл
UnMatrix.h.
// Файл UnMatrix.h
#ifndef UnMatrixH
#define UnMatrixH
#include "UnVector.h"
class Matrix{ // Матрица – совокупность векторов-столбцов
int nrow, ncol; // Число строк и столбцов матрицы
Vector **a; // Указатель на массив указателей на вектора-столбцы
public:
Matrix(int n = 1, int m = 1); // Конструктор
Matrix(const Matrix&); // Конструктор копирования
~Matrix(); // Деструктор
Matrix& operator=(const Matrix&); // Оператор присваивания
double& operator()(int i, int j) // Ссылка на элемент матрицы
{ return (*a[j])[i]; }
double operator()(int i, int j) const // Значение элемента матрицы
{ return (*a[j])[i]; }
Matrix operator~(); // Обращение матрицы
Matrix operator+(const Matrix&); // Сложение матриц
Matrix operator-(const Matrix&); // Вычитание матриц
Matrix operator*(const Matrix&); // Умножение матрицы на матрицу
Vector operator*(const Vector&); // Умножение матрицы на вектор
void SwapLines(int i, int j); // Поменять местами строки i и j
void DivLine(int i, double divisor); // Деление i-й строки на divisor
void DiffLines(int k, int i, double factor); // Вычитание из k-й строки
// i-й строки, умноженной на factor
Vector GetLine(int i); // Получить i-ю строку матрицы
struct MatrixException{ // Класс исключений
char* Mess; // Сообщение о проблеме
MatrixException(char* problem) // Конструктор класса исключений
{ Mess = problem; }
};
friend ostream& operator<<(ostream&, const Matrix&);// Вывод матрицы
friend istream& operator>>(istream&, Matrix&);// Ввод матрицы из потока
};
#endif
Для доступа к элементам матрицы перегружен оператор вызова функции (), что позволяет обращаться к j-му элементу i-й строки некоторой матрицы A в достаточно естественном виде A(i, j). В реализации данного оператора выражение *a[j] представляет j-й столбец матрицы, i - й элемент которого получается с помощью перегруженного оператора []. Неконстантный вариант оператора () возвращает ссылку на элемент матрицы, что позволяет изменять его, а константный вариант позволяет получать значение элемента матрицы.
Для обозначения унарной по своей природе операции обращения матрицы выбран унарный оператор ~.
В состав класса Matrix включен локальный класс MatrixException, предназначенный для передачи информации о возможных проблемах при обращении матриц.
Реализация класса Matrix
// Файл UnMatrix.cpp
#include "UnMatrix.h"
Matrix :: Matrix(int n, int m) // Конструктор
{
nrow = n; ncol = m;
a = new Vector*[ncol]; // Выделение памяти
// под массив указателей на вектора
for(int j=0; j < ncol; j++) // Выделение памяти
a[j] = new Vector(nrow); // под вектора
}
Matrix :: ~Matrix() // Деструктор
{
for(int j = 0; j < ncol; j++) // Освобождаем память
delete a[j]; // от столбцов матрицы
delete[] a; // Удаление массива указателей на вектора
}
Matrix::Matrix(const Matrix& B) // Конструктор копирования
{ // Создает копию матрицы B
nrow = B.nrow; ncol = B.ncol; // Копирование размеров
a = new Vector*[ncol]; // Выделение памяти
// под массив указателей на вектора
for(int j=0; j < ncol; j++){ // Выделение памяти
a[j] = new Vector(nrow); // под вектора
for(int i = 0; i < nrow; i++) // Копирование
(*a[j])[i] = B(i, j); // элементов матрицы
}
}
Matrix& Matrix :: operator=(const Matrix& B) // Присваивание матриц
{
if(this == &B) // Если присваивание самому себе,
return *this; // ничего не делаем
if(nrow != B.nrow || ncol!=B.ncol){ // Если размерности не совпадают,
for(int j = 0; j < ncol; j++) // освобождаем память
delete a[j]; // от столбцов
delete[] a; // и массива указателей на столбцы
nrow = B.nrow; ncol = B.ncol; // Приравниваем размерности
a = new Vector*[ncol]; // Выделяем
for(int j = 0; j < ncol; j++) // новую
a[j]= new Vector(nrow); // память
}
for(int j = 0; j < ncol; j++) // Копирование
for(int i = 0; i < nrow; i++) // элементов
(*this)(i, j) = B(i, j); // матрицы
return *this;
}
Matrix Matrix :: operator+(const Matrix& B) // Сумма матриц
{
Matrix sum(*this); // Копия первого слагаемого
for(int i = 0; i < nrow; i++) // Добавление к первому слагаемому
for(int j = 0; j < ncol; j++) // второго
sum(i, j) += B(i, j); // слагаемого
return sum;
}
Matrix Matrix :: operator-(const Matrix& B) // Разность матриц
{
Matrix div(*this); // Копия уменьшаемого
for(int i = 0; i < nrow; i++) // Вычитание из уменьшаемого
for(int j = 0; j < ncol; j++) // элементов
div(i, j) -= B(i, j); // второй матрицы
return div;
}
Vector Matrix :: operator*(const Vector& b)//Умножение матрицы на вектор
{
if(ncol != b.GetSize())
throw MatrixException("Несоответствие размера умножаемой матрицы”
” и вектора");
Vector prod(nrow); // Вектор с нулевыми элементами
for(int i = 0; i < nrow; i++) // Строки матрицы
for(int j = 0; j < ncol; j++) // умножаются на вектор
prod[i] += (*this)(i, j) * b[j]; // Накапливание суммы
return prod;
}
Vector Matrix :: GetLine(int i) // Получение i-й строки матрицы
{
Vector ai(ncol); // Вектор для i-й строки
for(int j =0; j < ncol; j++) // Заполнение элементами
ai[j] = (*this)(i, j); // i-й строки
return ai;
}
Matrix Matrix :: operator*(const Matrix& B) // Умножение матрицы
{ // на матрицу A*B
if(ncol != B.nrow)
throw MatrixException("Размеры перемножаемых матриц"
" не соответствуют друг другу");
Matrix c(nrow, B.ncol); // Матрица-произведение
double tmp;
for(int i = 0; i < nrow; i++)
for(int j = 0; j < B.ncol; j++){
tmp = 0.0;
for(int k = 0; k < ncol; k++) // Умножение i-й строки
tmp += (*this)(i, k) * B(k, j); // на j-й столбец
c(i, j) = tmp;
}
return c;
}
void Matrix :: SwapLines(int i, int j) // Перестановка i-й и j-й строк матрицы
{
double tmp;
for(int k = 0; k < ncol; k++){
tmp = (*this)(i, k);
(*this)(i, k) = (*this)(j, k);
(*this)(j, k) = tmp;
}
}
void Matrix :: DivLine(int i, double divisor) // Деление i-й строки на divisor
{
for(int j = 0; j < ncol; j++)
(*this)(i, j)/= divisor;
}
void Matrix :: DiffLines(int k, int i, double factor) // Вычитание из k-й строки
{ // i-й строки, умноженной на factor
for(int j = 0; j < ncol; j++)
(*this)(k, j)-= (*this)(i, j) * factor;
}
Matrix Matrix::operator~() // Обращение матрицы
/* Исходная матрица путем элементарных преобразований превращается в единичную. Те же преобразования параллельно выполняются над другой матрицей E, которая вначале является единичной. В результате матрица E превращается в матрицу, обратную исходной матрице.
Элементарные преобразования реализуют метод Гаусса с выбором главного элемента.
При создании матрицы E ее столбцы обнуляются в конструкторе векторов, а на главную диагональ помещаются единицы. */
{
int i, imain, k;
if(nrow != ncol)// Если матрица не квадратная, генерируется исключение
throw MatrixException("Попытка обратить прямоугольную матрицу");
Matrix E(nrow, nrow); // Вспомогательная матрица
Matrix Tmp(*this); // Запоминаем исходную матрицу
for(i = 0; i < nrow; i++) // Делаем вспомогательную матрицу
E(i, i) = 1.0; // единичной
for(i = 0; i < nrow; i++){ // Перебор строк
imain = (*a[i]).NumbMainElement(i);// Номер главн. элем. i-го столбца
SwapLines(i, imain); // Перестановка строк, чтобы на
E.SwapLines(i, imain); // диагонали стоял главный элемент
double divisor = (*this)(i, i); // Главный элемент
if(0 == divisor)
throw MatrixException("Главный элемент = 0."
” Матрица вырожденная");
DivLine(i, divisor); // Деление строки на главный элемент
E.DivLine(i, divisor);
for(k = 0; k < nrow; k++){ // Вычитание i-й строки
if(k == i) continue; // из остальных строк для
double factor = (*this)(k, i); // обнуления всех элементов
DiffLines(k, i, factor); // i -го столбца,
E.DiffLines(k, i, factor); // кроме диагонального
}
}
*this = Tmp; // Восстановление исходной матрицы
return E; // Возвращение обратной матрицы
}
ostream& operator<<(ostream& stream, const Matrix& m) // Оператор
{ // вывода для матрицы
for(int i = 0; i < m.nrow; i++){
for(int j = 0; j < m.ncol; j++)
stream << " " << m(i, j);
stream << endl;
}
return stream;
}
istream& operator>>(istream& stream, Matrix& m) // Оператор ввода
{ // для матрицы
for(int i = 0; i < m.nrow; i++)
for(int j = 0; j < m.ncol; j++)
stream >> m(i, j);
return stream;
}