C++: как передать в функцию статическую и динамическую матрицу одним и тем же кодом?
Возможно, мы могли бы сделать такое в "чистом" C (из-за иного кастинга типов), но не в C++. Функция в наших примерах пусть занимается просто выводом матрицы в консоль.
Вариант 1 - применяем указатели вида тип **матрица
. Удобно для динамических матриц, но не для статических.
Обратите внимание на закомментированную строчку
print_matrix(n,m, (double **)&a[0][0]);
в листинге. Явное приведение типа вида (double **)&a[0][0]
откомпилируется, но создаст ошибку времени исполнения.
#include <iostream> #include <iomanip> #include <cstdlib> using namespace std; void print_matrix(int n, int m, double** a) { cout.precision(3); cout << endl; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cout << fixed << setw(8) << a[i][j]; } cout << endl; } } int main() { const int n = 3, m = 3; //Создаём и выводим статическую матрицу функцией print_matrix double a[n][m] = { {1,2,3}, {4,5,6}, {7,8,9} }; //print_matrix(n,m, (double **)&a[0][0]); //откомпилируется, но даст неопределённое поведение в C++! double** a_ptr = new double* [n]; for (int i = 0; i < n; i++) a_ptr[i] = &a[i][0]; print_matrix(n, m, a_ptr); //а вот так - пожалуйста //Создаём и выводим динамическую матрицу функцией print_matrix double** b = new double *[n]; if (!b) exit(1); for (int i = 0; i < n; i++) { b[i] = new double[m]; if (!b[i]) exit(1); for (int j = 0; j < m; j++) b[i][j] = i + j; } print_matrix(n, m, b); cin.get(); return 0; }
Вариант 2. Использовать шаблоны, если ваши массивы - статические, и вы хотите одним кодом печатать матрицы разных размерностей.
Но в этом случае так просто не напечатать динамическую матрицу.
#include <iostream> #include <iomanip> #include <cstdlib> using namespace std; template <int n, int m> //применяем шаблон void print_matrix(double(&a)[n][m]) { cout.precision(3); cout << endl; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cout << fixed << setw(8) << a[i][j]; } cout << endl; } } int main() { const int n = 3, m = 3; double a[n][m] = { {1,2,3}, {4,5,6}, {7,8,9} }; print_matrix(a); //Выводим статическую матрицу 3 x 3 double c[2][2] = { {1,2}, {3,4} }; print_matrix(c); //И матрицу 2 x 2 тем же кодом cin.get(); return 0; }
Попытаться "помучить" так динамическую матрицу можно, но работать это не будет.
Например, добавим перед cin.get();
такой код:
double** b = new double* [n]; if (!b) exit(1); for (int i = 0; i < n; i++) { b[i] = new double[m]; if (!b[i]) exit(1); for (int j = 0; j < m; j++) b[i][j] = i + j; } double *b_ptr[n]; for (int i = 0; i < n; i++) b_ptr[i] = &b[i][0]; print_matrix ((double(&)[n][m])b_ptr); //Также откомпилируется, но работать правильно не будет
Можно передать в функцию и
reinterpret_cast <double(&)[n][m]> (b_ptr[0][0])
- тоже не поможет :)
Дело в том, что типизированность C++ никто не отменял и массив указателей - не то же самое, что массив массивов.
Когда мы пишем
double m[3][4] { {2, 4, 5, 7}, {4, 5, 1, 12}, {9, 12, 13, -4} };
компилятор, фактически, создаёт массив массивов. Да, в памяти это будет
double _m[] = {2, 4, 5, 7, 4, 5, 1, 12, 9, 12, 13, -4};
но, благодаря типизированности языка, компилятор запоминает,
что тип m
- это double[3][4]
. В частности, фиксируются размеры 3
и 4
.
Когда Вы пишете m[i][j]
, компилятор заменяет это на _m[i * 4 + j]
(4
берётся из второй размерности в double[3][4]
). Например, m[1][2] == 1 и _m[1 * 4 + 2] == _m[6] == 1
.
Двойной указатель **
- это другой тип, который не несёт информации
о размерностях. Чтобы рассматривать double **a
как матрицу a[3][4]
,
a[0]
, a[1]
и a[2]
нужно сделать указателями на double
(то есть, они будут иметь тип double *
), указывающими на первый элемент соответствующей строки.
Вы можете добиться этого, например, с помощью кода
double* rows[] = { &m[0][0], &m[1][0], &m[2][0] }; double** a = &rows[0];
Простое приведение типа не создаст этих указателей.
Ну и "безразмерного" типа [][]
тоже не существует, хотя можно
фиксировать вторую размерность и создать указатель на будущие строки матрицы
одинакового размера:
double* e[n]; for (int i = 0; i < n; i++) e[i] = &a[i][0]; print_matrix(n, m, e); //печатаем матрицу a for (int i = 0; i < n; i++) e[i] = &b[i][0]; print_matrix(n, m, e); //а теперь матрицу b
(добавить в вариант 1 кода перед cin.get()
).
Вариант 3. Оптимальное, на мой взгляд, решение - эмулируем матрицу через одномерный массив.
#include <iostream> #include <iomanip> #include <cstdlib> using namespace std; void print_matrix (size_t n, size_t m, double *a) { cout.precision(3); cout << endl; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cout << fixed << setw(8) << a[i * m + j]; } cout << endl; } } int main() { const int n = 3, m = 3; double a[n * m] = { 1,2,3, 4,5,6, 7,8,9 }; print_matrix(n,m,&a[0]); //печать статической матрицы 3 x 3 double c[2 * 2] = { 1,2, 3,4 }; print_matrix(2,2,c); //печать статической матрицы 2 x 2 double * b = new double [n * m]; if (!b) exit(1); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) b[i * m + j] = i + j; } print_matrix (n, m, b); //печать динамической матрицы 3 x 3 double d[4] = { 1,2,3,4 }; print_matrix(4, 1, d); //напечатает "вектор-столбец" print_matrix(1, 4, d); //напечатает "вектор-строку" cin.get(); return 0; }
Теперь функция работает одинаково для статики и динамики плюс может
работать и с "просто одномерными" массивами, такими как d
.
Если не хочется каждый раз вычислять a[i * m + j]
вместо a[i]
[j], можно
обернуть код в класс и переопределить в этом классе оператор [][].
Конечно, в этом варианте функцию тоже можно было сделать шаблонной, чтобы компилятор сам генерировал код для различных типов данных.
#include <iostream> #include <iomanip> #include <cstdlib> using namespace std; template <typename T> void print_matrix(size_t n, size_t m, T* a) { cout.precision(3); cout << endl; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cout << fixed << setw(8) << a[i * m + j]; } cout << endl; } } int main() { const int n = 3, m = 3; double a[n * m] = { 1.5,2,3, 4,5,6, 7,8,9 }; print_matrix(n, m, &a[0]); //печать вещественной матрицы 3 x 3 int c[2 * 2] = { 1,2, 3,4 }; print_matrix(2, 2, c); //печать целочисленной матрицы 2 x 2 unsigned char* b = new unsigned char[n * m]; if (!b) exit(1); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) b[i * m + j] = 'A' + i + j; } print_matrix(n, m, b); //печать динамической матрицы char 3 x 3 cin.get(); return 0; }
Все коды из заметки проверялись в консолях актуальных сборок Visual Studio 2019 и QT 5.X.
26.03.2021, 13:04 [3491 просмотр]