Skip to the content.

Многомерные массивы


С ходу важное замечание, которое было проигнорировано ниже из-за неопытности автора: при итерации по двумерным массивам (матрицам) нужно называть переменные не 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};

Элементы многомерного массива расположены в памяти построчно:

array_elements_on_stack

Благодаря чему, мы можем использовать (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 это указатель на массив, содержащий указатели на строки:

array_on_heap

Соответственно, тип 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;
}