Лекция 11. Многомерные массивы

Двумерный массив (таблица) в языке Си определяется следующим образом:

int a[M][N];
// матрица 3x4
int a[3][4]; /* двумерный массив 3x4, или массив из 3-х элементов
            типа "массив из 4-х элементов". */

Двумерный массив можно понимать как таблицу значений элементов указанного типа. При этом первый индекс задаёт количество строк, второй – количество столбов.

3 строки, 4 столбца:

+-+-+-+-+
| | | | |
+-+-+-+-+
| | | | |
+-+-+-+-+
| | | | |
+-+-+-+-+

Пример инициализации двумерного массива:

int a[2][3] = {{10, 4, 5}, {1, 3, 8}};

+----+---+---+
| 10 | 4 | 5 |
+----+---+---+
| 1  | 3 | 8 |
+----+---+---+

Доступ к элементам

double a[4][7];  // неинициализированный массив, мусор в ячейках.

a[1][3] = 6;  // запись

printf("a[1][3] = %lf\n", a[1][3]);  // Чтение

После записи по индексам 1,3 получаем следующий массив (в незаполненных ячейках случайные цифры).

+-+-+-+-+-+-+-+
| | | | | | | |
+-+-+-+-+-+-+-+
| | | |6| | | |
+-+-+-+-+-+-+-+
| | | | | | | |
+-+-+-+-+-+-+-+
| | | | | | | |
+-+-+-+-+-+-+-+

Корректными индексами для массива M на N являются первый индекс от 0 до M - 1 включительно, и второй индекс от 0 до N - 1 включительно.

#define M 6
#define N 10

int main()
{
    int a[M][N];
    int i, j;
    for (i = 0; i < M; ++i) {
        for (j = 0; j < N; ++j) {
            a[i][j] = i * j;  // Таблица умножения 6x10.
        }
    }
    for (i = 0; i < M; ++i) {
        for (j = 0; j < N; ++j) {
            printf("%d\n", a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

Размещение многомерных массивов в памяти

(Картинка развёртки 2D-массива в 1D-массив).

Двумерный массив мы представляем себе как двумерную таблицу, и это правильно с точки зрения удобства программирования. Но на самом деле двумерный массив в памяти линеен, так как сама лента памяти в архитектуре фон Неймана линейна. Массивы в языке си укладываются в ленту памяти строка за строкой.

int a[M][N];
a[i][j]

Данное определение массива и чтение ячейки с индексами i, j на самом деле эквивалентно для компилятора следующему определению одномерного массива:

int a[M*N];
a[i * N + j];

Для трёхмерного массива

int b[L][M][N];
b[i][j][k];

развёртка в одномерный массив даётся формулой

int b[L * M * N];
b[i * M * N + j * N + k];

Для больших размерностей аналогичные преобразования можно легко получить по индукции.

Передача многомерных массивов в функции

Предположим, что мы передаём функции двумерный массив, допустим, размера 10 строк на 5 столбцов. Тогда эта функция должна знать количество строк, чтобы уметь вычислять положение ячейки с двумя индексами в физическом одномерном массиве. Записывается это так:

void print_2d_array(int rows, int arr[][5])
{
    int i, j;
    for (i = 0; i < rows; ++i) {
        for (j = 0; j < 5; ++j) {
            printf("%d ", arr[i][j]);
        }
    }
}

Если пристально посмотреть, то можно увидеть, что всё достаточно печально и эта функция работает только с массивами из 5 столбцов. Так как у нас в программе вполне вероятно будут массивы и с другим числом столбцов, нам придётся либо не пользоваться этой удобной функцией, либо писать по функции для каждого возможного размера, что, очевидно, будет не слишком красиво выглядеть. Но в стандарте C99 есть возможность задавать размер через один из аргументов функции, который обязательно должен идти перед самим массивом:

void print_2d_array(int rows, int cols, int arr[][cols])
{
    int i, j;
    for (i = 0; i < rows; ++i) {
        for (j = 0; j < col; ++j) {
            printf("%d ", arr[i][j]);
        }
    }
}

При передаче в функцию многомерных массивов обязательно указывать все индексы, кроме старшего (левого).