БлогNot. C++: как передать в функцию статическую и динамическую матрицу одним и тем же ко...

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.


теги: программирование учебное c++

26.03.2021, 13:04; рейтинг: 61