Операция new способна выделить память лишь под одномерный массив. А как быть, если массив двумерный?
Наиболее удобный способ - это представить двумерный массив как массив из массивов, т.е каждую строку матрицы - как одномерный массив. При этом под каждую строку матрицы память будет выделена по отдельности, как под одномерный массив; и на нее потребуется отдельный указатель. Поскольку строк в матрице может быть много, указателей тоже будет много, и уже для их хранения потребуется вспомогательный массив из указателей. А так как количество строк в матрице переменного размера заранее неизвестно, то этот вспомогательный массив тоже будет динамическим, и сначала нужно выделить память под него. Декларировать при этом придется только указатель на вспомогательный массив; он будет, таким образом, указателем на указатель (**).
Пример. Создать динамическую матрицу размера n1 х n2. Присвоить каждому ее элементу значение, равное сумме номеров его строки и столбца:
...
int **m, n1, n2;
puts("Vvedite chislo strok i stolbtsov");
|
|
scanf(“%d%d”, &n1, &n2);
m = new int * [n1]; // Захват памяти для массива указателей -
// на рисунке А (n1=3)
for (int i=0; i<n1; i++) // Захват памяти для каждого из
m[i] = new int [n2]; // массивов элементов (n1 таких массивов)
// - на рисунке B (n2=4)
...
for (i=0; i<n1; i++)
for (j=0; j<n2; j++)
m[i][j] = i+j; // *(*(m+i)+j) = i+j;
...
for (i=0; i<n1; i++) // Освобождение памяти
delete m[i];
delete m;
...
Устройство полученной "динамической матрицы" изображено на Рис. 19.1. Здесь имеется вспомогательный массив из 3 указателей и 3 массива для хранения данных, в каждом - по 4 элемента. Указатель m указывает на массив указателей, а каждый из указателей - на свою строку матрицы:
Указатели | m[0] | ® | m[0][0] | m[0][1] | m[0][2] | m[0][3] | m[i][j] ↔ *(*(m+i)+j) |
m[1] | ® | m[1][0] | m[1][1] | m[1][2] | m[1][3] | ||
m[2] | ® | m[2][0] | m[2][1] | m[2][2] | m[2][3] |
(А) (В)
Рис. 4
Несмотря на сложность полученной схемы, работа с ней предельно проста. Обращение к элементу матрицы имеет точно такой же вид, как и к элементу "обычного" двумерного массива:
m[i][j]
а выделение и освобождение динамической памяти для матрицы стандартно и не зависит от конкретной задачи:
m = new int * [n1]; // Выделение памяти
for (int i=0; i<n1; i++)
m[i] = new int [n2];
...
for (i=0; i<n1; i++) // Освобождение памяти
delete m[i];
delete m;
Преимущества динамических матриц:
1) Ее размер ограничен лишь размером доступной в данный момент памяти;
2) Под динамическую матрицу выделяется не больше памяти, чем требуется;
3) Ее строки могут быть не только одинаковой, но и разной длины (если нужно);
4) Чтобы поменять местами ее строки, не обязательно выполнять поэлементный обмен в цикле. Можно вместо этого переприсвоить указатели:
int *w=m[0];
m[0]=m[1];
m[1]=w;
Хотя все элементы матрицы остались на месте, 0-ая и 1-ая строки всё же поменялись местами, т.к. m[0] указывает теперь на бывшую 1-ую строку (и наоборот), и значение m[0][i] будет таким, каким до обмена было m[1][i].