каждого i
В классе Matrix предусмотрено также много полезных функций из традиционных математических библиотек.
Matrix<int> a3 = scale_and_add(a,8,a2); // объединенное умножение
// и сложение
int r = dot_product(a3,a); // скалярное произведение
Операцию scale_and_add() часто называют
объединенным умножением и сложением (fused multiply-add), или просто
fma; ее определение выглядит так: result(i)=arg1(i)*arg2+arg3(i) для каждого i в объекте класса Matrix. Скалярное произведение также известно под именем inner_product и описано в разделе 21.5.3; ее определение выглядит так: result+=arg1(i)*arg2(i) для каждого i в объекте класса Matrix, где накопление объекта result начинается с нуля.
Одномерные массивы очень широко распространены; их можно представить как в виде встроенного массива, так и с помощью классов vector и Matrix. Класс Matrix следует применять тогда, когда необходимо выполнять матричные операции, такие как *=, или когда объект класса Matrix должен взаимодействовать с другими объектами этого класса, имеющими более высокую размерность.
Полезность этой библиотеки можно объяснить тем, что она лучше согласована с математическими операциями, а также тем, что при ее использовании не приходится писать циклы для работы с каждым элементом матрицы. В любом случае в итоге мы получаем более короткий код и меньше возможностей сделать ошибку. Операции класса Matrix, например копирование, присваивание всем элементам и операции над всеми элементами, позволяют не использовать циклы (а значит, можно не беспокоиться о связанных с ними проблемах).
Класс Matrix имеет два конструктора для копирования данных из встроенных массивов в объект класса Matrix. Рассмотрим пример.
void some_function(double* p, int n)
{
double val[] = { 1.2, 2.3, 3.4, 4.5 };
Matrix<double> data(p,n);
Matrix<double> constants(val);
// ...
}
Это часто бывает полезным, когда мы получаем данные в виде обычных массивов или векторов, созданных в других частях программы, не использующих объекты класса Matrix.
Обратите внимание на то, что компилятор может самостоятельно определить количество элементов в инициализированном массиве, поэтому это число при определении объекта constants указывать не обязательно — оно равно — 4. С другой стороны, если элементы заданы всего лишь указателем, то компилятор не знает их количества, поэтому при определении объекта data мы должны задать как указатель p, так и количество элементов n.
24.5.3. Двумерный объект класса Matrix
Общая идея библиотеки Matrix заключается в том, что матрицы разной размерности на самом деле в большинстве случаев очень похожи, за исключением ситуаций, в которых необходимо явно указывать размерность. Таким образом, большинство из того, что мы можем сказать об одномерных объектах класса Matrix, относится и к двумерным матрицам.
Matrix<int,2> a(3,4);
int s = a.size(); // количество элементов
int d1 = a.dim1(); // количество элементов в строке
int d2 = a.dim2(); // количество элементов в столбце
int* p = a.data(); // извлекаем данные с помощью указателя в стиле
// языка С
Мы можем запросить общее количество элементов и количество элементов в каждой размерности. Кроме того, можем получить указатель на элементы, размещенные в памяти в виде матрицы.
Мы можем использовать индексы.
a(i,j); // (i,j)-й элемент (в стиле языка Fortran) с проверкой
// диапазона
a[i]; // i-я строка (в стиле языка C) с проверкой диапазона
a[i][j]; // (i,j)-й элемент (в стиле языка C)
В двумерном объекте класса Matrix индексирование с помощью конструкции [i] создает одномерный объект класса Matrix, представляющий собой i-ю строку. Это значит, что мы можем извлекать строки и передавать их операторам и функциям, получающим в качестве аргументов одномерные объекты класса Matrix и даже встроенные массивы (a[i].data()). Обратите внимание на то, что индексирование вида a(i,j) может оказаться быстрее, чем индексирование вида a[i][j], хотя это сильно зависит от компилятора и оптимизатора.
Мы можем получить срезки.
a.slice(i); // строки от a[i] до последней
a.slice(i,n); // строки от a[i] до a[i+n–1]
Срезка двумерного объекта класса Matrix сама является двумерным объектом этого класса (возможно, с меньшим количеством строк). Распределенные операции над двумерными матрицами такие же, как и над одномерными. Этим операциям неважно, как именно хранятся элементы; они просто применяются ко всем элементам в порядке их следования в памяти.
Matrix<int,2> a2 = a; // копирующая инициализация
a = a2; // копирующее присваивание
a *= 7; // пересчет (и +=, –=, /= и т.д.)
a.apply(f); // a(i,j)=f(a(i,j)) для каждого элемента a(i,j)
a.apply(f,7); // a(i,j)=f(a(i,j),7) для каждого элемента a(i,j)
b=apply(f,a); // создаем новую матрицу с b(i,j)==f(a(i,j))
b=apply(f,a,7); // создаем новую матрицу с b(i,j)==f(a(i,j),7)
Оказывается, что перестановка строк также полезна, поэтому мы предусмотрим и ее.
a.swap_rows(1,2); // перестановка строк a[1] <–> a[2]
Перестановки столбцов swap_columns() не существует. Если она вам потребуется, то вы сможете написать ее самостоятельно (см. упр. 11). Из-за построчной схемы хранения матриц в памяти строки и столбцы не совсем равноправны. Эта асимметрия проявляется также в том, что оператор [i] возвращает только строку (а для столбцов аналогичный оператор не предусмотрен). Итак, в тройке (i,j) первый индекс i выбирает строку. Эта асимметрия имеет глубокие математические корни.
Количество действий, которые можно было бы выполнить над двумерными матрицами, кажется бесконечным.
enum Piece { none, pawn, knight, queen, king, bishop, rook };
Matrix<Piece,2> board(8,8); // шахматная доска
const int white_start_row = 0;
const int black_start_row = 7;
Piece init_pos[] = {rook,knight,bishop, queen,king,bishop,knight,rook};
Matrix<Piece> start_row(init_pos); // инициализация элементов из
// init_pos
Matrix<Piece> clear_row(8); // 8 элементов со значениями
// по умолчанию
Инициализация объекта clear_row использует возможность задать условие none==0 и то, что эти элементы по умолчанию инициализируются нулем. Мы могли бы предпочесть другой код.
Matrix<Piece> start_row = {rook,knight,bishop,queen,king,bishop,knight,rook};
Однако он не работает (по крайней мере, пока не появится