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

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.