Основы программирования на C++: Шаблонные классы
Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.
Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.
Здравствуйте, мои маленькие любители программирования!
Шаблонные классы
Классы, как и функции, могут быть параметризованы типами или константами. Такие классы называются шаблонными . Примерами шаблонов классов являются все контейнеры стандартной библиотеки. В этом параграфе мы напишем шаблонный класс «Матрица». Мы также рассмотрим на его примере не связанные с шаблонами вещи: перегрузку по константности и итерацию в цикле range-based for.
Матрица — это таблица чисел
Матрица — это таблица чисел, для которой определены математические операции сложения, вычитания и (при подходящих размерах) умножения. Элементы матрицы могут иметь разную природу: например, это могут быть целые, рациональные, комплексные числа или даже многочлены. Напишем класс-контейнер для хранения матрицы и выполнения операций над ней.
Выбор шаблонных параметров
Наш класс должен поддерживать работу с разными типами элементов. Поэтому вынесем тип элемента в шаблонные параметры:
template <typename T>
class Matrix;
Теперь решим, будут ли размеры матрицы известны во время компиляции. Если да, их можно сделать шаблонными параметрами, а хранить матрицу можно в двумерном контейнере std::array:
#include <array>
template <typename T, size_t Rows, size_t Columns>
class Matrix {
private:
std::array<std::array<T, Columns>, Rows> data;
};
Пример использования:
int main() {
Matrix<int, 3, 4> m; // матрица размера 3 x 4
}
Однако чаще размеры матрицы становятся известными только во время выполнения программы. Тогда шаблонные размеры не подойдут, так как аргументы шаблона должны быть известны в момент компиляции. В этом случае размеры должны содержаться в данных самой матрицы, а не в её типе. Хранить матрицу будем в двумерном векторе:
#include <vector>
template <typename T>
class Matrix {
private:
std::vector<std::vector<T>> data;
public:
size_t GetRows() const {
return data.size();
}
size_t GetColumns() const {
if (data.empty()) {
return 0;
}
return data[0].size();
}
};
Конструкторы
Конструктор из вектора векторов
Напишем конструктор, который принимает двумерный вектор и соблюдает инвариант «в строках матрицы одинаковое количество элементов»:
template <typename T>
class Matrix {
private:
std::vector<std::vector<T>> data;
void MakeRectangle() {
size_t maxSize = 0;
for (const auto& row : data) {
if (row.size() > maxSize) {
maxSize = row.size();
}
}
for (auto& row : data) {
row.resize(maxSize);
}
}
public:
Matrix(const std::vector<std::vector<T>>& d) : data(d) {
MakeRectangle();
}
};
Пример использования:
int main() {
Matrix<int> m({
{1, 2, 3},
{4, 5, 6},
});
std::cout << m.GetRows() << "\n"; // 2
std::cout << m.GetColumns() << "\n"; // 3
}
Конструктор для нулевой матрицы
Добавим конструктор для создания нулевой матрицы заданных размеров:
template <typename T>
class Matrix {
private:
std::vector<std::vector<T>> data;
public:
Matrix(size_t rows, size_t columns) {
data.resize(rows);
for (auto& row : data) {
row.resize(columns);
}
}
};
Пример использования:
int main() {
Matrix<int> m(3, 4); // создаём нулевую матрицу из 3 строк и 4 столбцов
}
Обращение к элементам и перегрузка по константности
Хочется обращаться к элементам матрицы так же, как и с двумерным массивом:
int main() {
Matrix<int> m(3, 4);
int element = m[0][0];
m[1][1] = 1;
m[2][3] = 5;
}
Для этого перегрузим оператор []. Однако нужно учесть два случая: чтение и запись. Для этого перегрузим оператор по константности:
template <typename T>
class Matrix {
private:
std::vector<std::vector<T>> data;
public:
const std::vector<T>& operator[](size_t i) const {
return data[i];
}
std::vector<T>& operator[](size_t i) {
return data[i];
}
};
Теперь первая версия будет применяться к константным матрицам, а вторая — к неконстантным.
Итерация по матрице
Чтобы можно было писать цикл range-based for по строкам матрицы, добавим функции begin и end:
template <typename T>
class Matrix {
private:
std::vector<std::vector<T>> data;
public:
using const_iterator = typename std::vector<std::vector<T>>::const_iterator;
const_iterator begin() const {
return data.cbegin();
}
const_iterator end() const {
return data.cend();
}
};
Пример использования:
int main() {
Matrix<int> m(3, 4);
for (const auto& row : m) {
// обрабатываем строку row
}
}
Потоковый ввод и вывод
Перегрузим операторы << и >> для удобства работы с потоками:
template <typename T>
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) {
if (j > 0) {
out << "\t";
}
out << matrix(i, j);
}
out << "\n";
}
return out;
}
template <typename T>
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) {
in >> matrix(i, j);
}
}
return in;
}
Пример использования:
int main() {
Matrix<int> m(3, 4);
std::cin >> m;
std::cout << m;
}
Арифметические операции
Оператор +=
Реализуем оператор += для сложения матриц одинакового размера:
template <typename T>
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];
}
}
return *this;
}
Оператор +
Реализуем оператор +, используя уже написанный оператор +=:
template <typename T>
Matrix<T> operator+(const Matrix<T>& m1, const Matrix<T>& m2) {
auto tmp = m1;
tmp += m2;
return tmp;
}
Сравнение матриц
Напишем операторы == и != для сравнения двух матриц:
template <typename T>
bool operator==(const Matrix<T>& other) const {
const size_t rows = GetRows();
const size_t columns = GetColumns();
if (rows != other.GetRows() || columns != other.GetColumns()) {
return false;
}
for (size_t i = 0; i != rows; ++i) {
for (size_t j = 0; j != columns; ++j) {
if (!((*this)(i, j) == other(i, j))) {
return false;
}
}
}
return true;
}
template <typename T>
bool operator!=(const Matrix<T>& m1, const Matrix<T>& m2) {
return !(m1 == m2);
}
Таким образом, шаблонные классы позволяют создавать универсальные и гибкие структуры данных, которые легко адаптировать под разные задачи.
Лига программистов
2K постов11.8K подписчиков
Правила сообщества
- Будьте взаимовежливы, аргументируйте критику
- Приветствуются любые посты по тематике программирования
- Если ваш пост содержит ссылки на внешние ресурсы - он должен быть самодостаточным. Вариации на тему "далее читайте в моей телеге" будут удаляться из сообщества