Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.
Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.
Классы, как и функции, могут быть параметризованы типами или константами. Такие классы называются шаблонными . Примерами шаблонов классов являются все контейнеры стандартной библиотеки. В этом параграфе мы напишем шаблонный класс «Матрица». Мы также рассмотрим на его примере не связанные с шаблонами вещи: перегрузку по константности и итерацию в цикле range-based for.
Матрица — это таблица чисел
Матрица — это таблица чисел, для которой определены математические операции сложения, вычитания и (при подходящих размерах) умножения. Элементы матрицы могут иметь разную природу: например, это могут быть целые, рациональные, комплексные числа или даже многочлены. Напишем класс-контейнер для хранения матрицы и выполнения операций над ней.
Выбор шаблонных параметров
Наш класс должен поддерживать работу с разными типами элементов. Поэтому вынесем тип элемента в шаблонные параметры:
Теперь решим, будут ли размеры матрицы известны во время компиляции. Если да, их можно сделать шаблонными параметрами, а хранить матрицу можно в двумерном контейнере std::array:
template <typename T, size_t Rows, size_t Columns>
std::array<std::array<T, Columns>, Rows> data;
Matrix<int, 3, 4> m; // матрица размера 3 x 4
Однако чаще размеры матрицы становятся известными только во время выполнения программы. Тогда шаблонные размеры не подойдут, так как аргументы шаблона должны быть известны в момент компиляции. В этом случае размеры должны содержаться в данных самой матрицы, а не в её типе. Хранить матрицу будем в двумерном векторе:
std::vector<std::vector<T>> data;
size_t GetColumns() const {
Конструкторы
Конструктор из вектора векторов
Напишем конструктор, который принимает двумерный вектор и соблюдает инвариант «в строках матрицы одинаковое количество элементов»:
std::vector<std::vector<T>> data;
for (const auto& row : data) {
if (row.size() > maxSize) {
Matrix(const std::vector<std::vector<T>>& d) : data(d) {
std::cout << m.GetRows() << "\n"; // 2
std::cout << m.GetColumns() << "\n"; // 3
Конструктор для нулевой матрицы
Добавим конструктор для создания нулевой матрицы заданных размеров:
std::vector<std::vector<T>> data;
Matrix(size_t rows, size_t columns) {
Matrix<int> m(3, 4); // создаём нулевую матрицу из 3 строк и 4 столбцов
Обращение к элементам и перегрузка по константности
Хочется обращаться к элементам матрицы так же, как и с двумерным массивом:
Для этого перегрузим оператор []. Однако нужно учесть два случая: чтение и запись. Для этого перегрузим оператор по константности:
std::vector<std::vector<T>> data;
const std::vector<T>& operator[](size_t i) const {
std::vector<T>& operator[](size_t i) {
Теперь первая версия будет применяться к константным матрицам, а вторая — к неконстантным.
Итерация по матрице
Чтобы можно было писать цикл range-based for по строкам матрицы, добавим функции begin и end:
std::vector<std::vector<T>> data;
using const_iterator = typename std::vector<std::vector<T>>::const_iterator;
const_iterator begin() const {
const_iterator end() const {
for (const auto& row : m) {
// обрабатываем строку row
Потоковый ввод и вывод
Перегрузим операторы << и >> для удобства работы с потоками:
std::ostream& operator<<(std::ostream& out, const Matrix<T>& matrix) {
const size_t rows = matrix.GetRows();
const size_t columns = matrix.GetColumns();
for (size_t i = 0; i != rows; ++i) {
for (size_t j = 0; j != columns; ++j) {
std::istream& operator>>(std::istream& in, Matrix<T>& matrix) {
const size_t rows = matrix.GetRows();
const size_t columns = matrix.GetColumns();
for (size_t i = 0; i != rows; ++i) {
for (size_t j = 0; j != columns; ++j) {
Арифметические операции
Реализуем оператор += для сложения матриц одинакового размера:
Matrix<T>& operator+=(const Matrix<T>& other) {
const size_t rows = GetRows();
const size_t columns = GetColumns();
if (rows != other.GetRows() || columns != other.GetColumns()) {
throw std::invalid_argument("Matrices have different size!");
for (size_t i = 0; i != rows; ++i) {
for (size_t j = 0; j != columns; ++j) {
data[i][j] += other.data[i][j];
Реализуем оператор +, используя уже написанный оператор +=:
Matrix<T> operator+(const Matrix<T>& m1, const Matrix<T>& m2) {
Сравнение матриц
Напишем операторы == и != для сравнения двух матриц:
bool operator==(const Matrix<T>& other) const {
const size_t rows = GetRows();
const size_t columns = GetColumns();
if (rows != other.GetRows() || columns != other.GetColumns()) {
for (size_t i = 0; i != rows; ++i) {
for (size_t j = 0; j != columns; ++j) {
if (!((*this)(i, j) == other(i, j))) {
bool operator!=(const Matrix<T>& m1, const Matrix<T>& m2) {
Таким образом, шаблонные классы позволяют создавать универсальные и гибкие структуры данных, которые легко адаптировать под разные задачи.