Многомерные массивы
С ходу важное замечание, которое было проигнорировано ниже из-за неопытности автора: при итерации по двумерным массивам (матрицам) нужно называть переменные не i\j, a row\col, так будет намного сложнее запутаться.
int m[2][3] = {1, 2, 3,
4, 5, 6};
for (size_t i = 0; i != 2; ++i)
{
for (size_t j = 0; j != 4; ++j)
{
std::cout << m[i][j] << ' ';
}
std::cout << std::endl;
}
Указатели на многомерные массивы:
Создадим массив 3 на 4:
int a[3][4] = {1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12};
Элементы многомерного массива расположены в памяти построчно:
Благодаря чему, мы можем использовать (int )a__ (либо __a) как указатель на первый элемент массива, а с самим массивом работать как с одномерным:
a[i][j] = *((int *)a + i*4 + j);
a[1][3] = *((int *)a + 1*4 + 3) = 8;
Стоит обратить внимание, что длина строки (4) является константой времени (уровня) компиляции (должна быть известна во время компиляции). Тоесть мы смещаем указатель на такое количество элементов: количество строк i * количество элементов в строке (константа) + количество элементов j в i-ой строке до нашего элемента (грубо говоря идём по строкам, а потом по столбцам).
Почему мы приводим a к (int )a__? Потому что __а__ изначально типа __int()[4], т.е. указатель на массив из 4-х элементов (таким образом мы получаем указатель на первый элемент):
а указывает на {1, 2, 3, 4}
a + 1 указывает на {5, 6, 7, 8}
a + 2 указывает на {9, 10, 11, 12}
**a = 1
На самом деле int(*)[4] ничем не отличается от обычного указателя, кроме арифметики: когда мы применяем (допустим) инкремент к такому указателю он дополнительно умножается на длину строки, т.е. a + 1 это на самом деле a + 4, таким образом мы двигаемся по строкам массива.
Соответственно:
*(*(a + 1) + 2) = a[1][2]
int (*ptr)[4] = a; // указатель на массив а
std::cout << ptr[0][1];
Обратите внимание, что скобки здесь обязательны: int * ptr[4]
- массив указателей.
int *x = a[0]; // a[0] = *a - указатель на нулевую строку массива: {1, 2, 3, 4}
int *x = *a; // аналогично
Как еще одно объяснение (по определению квадратных скобок):
m[i][j] = *(m[i] + j) = *(*(m + i) + j)
Передача многомерного массива в функцию:
void func(int arr[][5], size_t N) // либо int (*ptr)[5]
{...}
int m[2][3] = {1, 2, 3,
4, 5, 6};
func(m, 2); // нет смысла передавать длину строк, она константа + и так известна
Динамические двумерные массивы
Динамические массивы немного отличаются от массивов на стеке: теперь m это указатель на массив, содержащий указатели на строки:
Соответственно, тип m - int **, тип m[i] - int *.
Создаим динамический массив 5 х 4:
int ** m = new int * [5]; // создали указатель на массив указателей
for (size_t i = 0; i != 5; ++i)
m[i] = new int [4]; // в каждый элемент массива записываем указатель на соотв. строку
Стоит отметить, что никто не запрещает нам выделить строки разного размера (не все по 4 элемента).
Создание и удаление массива:
int ** create_array(size_t a, size_t b)
{
int ** m = new int * [a];
for (size_t i = 0; i != a; ++i)
m[i] = new int[b];
return m;
}
void free_array(int ** m, size_t a)
{
for (size_t i = 0; i != a; ++i)
delete [] m[i];
delete [] m;
}
Минус такого подхода в том, что оператор new вызываеться (а + 1) раз, это приводит к фрагментации памяти, поскольку наш массив получается раскидан по памяти кусками. Куда эффективнее выделить область памяти под весь массив, а потом расставить в ней указатели на строки как нам надобно:
int ** create_array(size_t a, size_t b)
{
int ** m = new int * [a]; // массив с указателями на строки
m[0] = new int [a * b]; // выделяем область памяти под все строки разом
for (size_t i = 1; i != a; ++i)
m[i] = m[i - 1] + b;
return m;
}
void free_aray(int ** m, size_t a)
{
delete [] m[0];
delete [] m;
}