C++. Сборник рецептов.

11.14. Реализация динамической матрицы.

Проблема.

Требуется реализовать числовые матрицы, размерности которых (количество строк и столбцов) неизвестны на этапе компиляции.

Решение.

В примере 11.28 показана универсальная и эффективная реализация класса динамической матрицы, использующая итератор с шагом из рецепта 11.12 и valarray.

Пример 11.28. matrix.hpp.

#ifndef MATRIX_HPP.

#define MATRIX_HPP.

#include "stride_iter.hpp" // см. рецепт 11.12.

#include <valarray>

#include <numeric>

#include <algorithm>

Template<class Value_T>

Class matrix {

Public:

 // открытые имена, вводимые typedef.

 typedef Value_T value_type;

 typedef matrix self;

 typedef value_type* iterator;

 typedef const value_type* const_iterator;

 typedef Value_T* row_type;

 typedef stride_iter<value_type*> col_type;

 typedef const value_type* const_row_type;

 typedef stride_iter<const value_type*> const_col_type;

 // конструкторы.

 matrix() : nrows(0), ncols(0), m() {}.

 matrix(int r, int c) : nrows(r), ncols(c), m(r * с) {}.

 matrix(const self& x) : m(x.m), nrows(x.nrows), ncols(x.ncols) {}.

 template<typename T>

 explicit matrix(const valarray<T>& x).

  : m(x.size() + 1), nrows(x.size()), ncols(1) {

  for (int i=0; i<x.size(); ++i) m[i] = x[i];

 }

 // позволить конструирование из матриц других типов.

 template<typename T> explicit matrix(const matrix<T>& x).

  : m(x.size() + 1), nrows(x.nrows), ncols(x.ncols) {

  copy(x.begin(), x.end(), m.begin());

 }

// открытые функции.

 int rows() const { return nrows; }.

 int cols() const { return ncols; }.

 int size() const { return nrows * ncols; }.

 // доступ к элементам.

 row_type row begin(int n) { return &m[n * cols()]; }.

 row_type row_end(int n) { return row_begin() + cols(); }.

 col_type col_begin(int n) { return col_type(&m[n], cols()); }.

 col_type col_end(int n) { return col_begin(n) + cols(); }.

 const_row_type row_begin(int n) const { return &m[n * cols()]; }.

 const_row_type row_end(int n) const { return row_begin() + cols(); }.

 const_col_type col_begin(int n) const { return col_type(&m[n], cols()); }.

 const_col_type col_end(int n) const { return col_begin() + cols(); }.

 iterator begin() { return &m[0]; }.

 iterator end() { return begin() + size(); }.

 const_iterator begin() const { return &m[0]; }.

 const_iterator end() const { return begin() + size(); }.

 // операторы.

 self& operator=(const self& x) {

  m = x.m;

  nrows = x.nrows;

  ncols = x.ncols;

  return *this;

 }

 self& operator=(value_type x) { m = x; return *this; }.

 row_type operator[](int n) { return row_begin(n); }.

 const_row_type operator[](int n) const { return row_begin(n); }.

 self& operator+=(const self& x) { m += x.m; return *this; }.

 self& operator-=(const self& x) { m -= x.m; return *this; }.

 self& operator+=(value_type x) { m += x; return *this; }.

 self& operator-=(value_type x) { m -= x; return *this; }.

 self& operator*=(value_type x) { m *= x; return *this; }.

 self& operator/=(value_type x) { m /= x; return *this; }.

 self& operator%=(value_type x) { m %= x; return *this; }.

 self operator-() { return -m; }.

 self operator+() { return +m; }.

 self operator!() { return !m; }.

 self operator~() { return ~m; }.

 // дружественные операторы.

 friend self operator+(const self& x, const self& y) { return self(x) += y; }.

 friend self operator-(const self& x, const self& y) { return self(x) -= y; }.

 friend self operator+(const self& x, value_type y) { return self(x) += y; }.

 friend self operator-(const self& x, value_type y) { return self(x) -= y; }.

 friend self operator*(const self& x, value type y) { return self(x) *= y; }.

 friend self operator/(const self& x, value_type y) { return self(x) /= y; }.

 friend self operator%(const self& x, value_type y) { return self(x) %= y; }.

Private:

 mutable valarray<Value_T> m;

 int nrows;

 int ncols;

};

#endif.

Пример 11.29 показывает, как можно использовать шаблонный класс matrix.

Пример 11.29. Применение шаблона matrix.

#include "matrix.hpp".

#include <iostream>

Using namespace std;

Int main() {

 matrix<int> m(2,2);

 m = 0;

 m[0][0] = 1;

 m[1][1] = 1;

 m *= 2;

 cout << "(" << m[0][0] << "," << m[0][1] << ")" << endl;

 cout << "(" << m[1][0] << "," << m[1][1] << ")" << endl;

}

Программа примера 11.29 выдает следующий результат.

(2,0).

(0,2).

Обсуждение.

Проект шаблона матрицы, представленный в примере 11.28, в значительной степени инспирирован шаблоном матрицы Бьерна Страуструпа (Bjarne Stroustrup) из его книги «The C++ Programming Language», 3-е издание (издательство «Addison Wesley»). Реализация Страуструпа отличается тем, что его итератор использует класс slice и указатель на valarray для индексации. Реализованная в примере 11.27 матрица использует вместо них итератор с шагом из рецепта 11.12, что делает итераторы более компактными и при некоторых реализациях более эффективными.

Шаблонный класс matrix позволяет индексировать элемент i-й строки и j-го столбца, используя операцию двойной индексации. Например:

Matrix<int> m(100,100);

Cout << "the element at row 24 and column 42 is " << m[24][42] << endl;

Шаблонный класс matrix также имеет функции-члены begin и end, т.е. его легко можно использовать в различных алгоритмах STL.

Пример 11.28 содержит строку, которая, возможно, вызывает у вас некоторое удивление. Имеется в виду следующее объявление.

Mutable valarray<Value_T> m;

Объявление поля-члена m со спецификатором mutable вынужденно. В противном случае я не мог бы обеспечить итераторы со спецификатором const, потому что нельзя создать итератор для const valarray.

Смотри также.

Рецепты 11.15 и 11.16.