Многомерные массивы
Урок разбирает двумерные массивы (матрицы): как они объявляются, почему в памяти лежат построчно одним непрерывным блоком и как обходить их вложенными циклами.
Двумерный массив в C — это не «таблица», а один непрерывный блок памяти, разложенный построчно. Понимание этой раскладки объясняет и доступ по индексам, и поведение при передаче в функции.
Матрица объявляется с двумя размерами — строками и столбцами. Доступ к элементу — по двум индексам:
int grid[2][3] = {
{1, 2, 3}, // строка 0
{4, 5, 6} // строка 1
};
printf("%d\n", grid[0][0]); // 1
printf("%d\n", grid[1][2]); // 6
grid[0][1] = 99; // меняем элемент строки 0, столбца 1
Первый индекс — номер строки, второй — столбца. Несмотря на «двумерную» запись, в памяти эти элементы лежат подряд, строка за строкой.
Как работает под капотом
Память линейна, поэтому двумерность — иллюзия. Компилятор раскладывает матрицу построчно (row-major) и вычисляет адрес по формуле:
int grid[2][3] = {{1,2,3},{4,5,6}};
Логически (как мы думаем): В памяти (как на самом деле):
столбцы 0 1 2 один непрерывный блок:
строка 0 [1][2][3] [1][2][3][4][5][6]
строка 1 [4][5][6] ^строка0^ ^строка1^
Адрес grid[i][j] = база + (i * ЧИСЛО_СТОЛБЦОВ + j) * sizeof(int)
grid[1][2] -> база + (1*3 + 2)*4 = база + 5*4
-> шестой элемент -> 6
Вот почему число столбцов всегда нужно знать: оно входит в формулу адреса. При передаче двумерного массива в функцию вы обязаны указать размер столбцов: void f(int m[][3], int rows) — иначе компилятор не сможет вычислять адреса строк.
// Обход матрицы вложенными циклами
int grid[2][3] = {{1,2,3},{4,5,6}};
int sum = 0;
for (int i = 0; i < 2; i++) { // по строкам
for (int j = 0; j < 3; j++) { // по столбцам
sum += grid[i][j];
}
}
printf("Сумма: %d\n", sum); // 21
Частые ошибки
- Перепутанные индексы.
grid[столбец][строка]вместоgrid[строка][столбец]— обращение не туда. - Выход за границы по любому измерению. Как и в одномерном массиве, проверки нет:
grid[2][0]при двух строках — чужая память. - Пропуск размера столбцов при передаче в функцию. Без него компилятор не вычислит раскладку строк.
- Перепутанный порядок циклов. Обход по столбцам во внешнем цикле работает, но менее эффективен из-за раскладки в памяти.
Best practices
- Запомните порядок: первый индекс — строка, второй — столбец. Держите его единообразным во всём коде.
- Обходите матрицу «строка за строкой» (внешний цикл по строкам) — это совпадает с раскладкой в памяти и работает быстрее.
- При передаче в функцию всегда указывайте размер столбцов и передавайте число строк отдельным параметром.
Двумерность в Python обычно делают списком списков, но смоделируем именно C-раскладку: один плоский список плюс формула адреса. Так видна суть row-major.
# Эмулируем 2D-массив C как ПЛОСКИЙ блок памяти (row-major)
ROWS, COLS = 2, 3
flat = [1, 2, 3, 4, 5, 6] # тот же блок, что и в C
def at(i, j):
# формула адреса: i * COLS + j (как в C)
return flat[i * COLS + j]
print("grid[0][0] =", at(0, 0)) # 1
print("grid[1][2] =", at(1, 2)) # 6
total = 0
for i in range(ROWS):
for j in range(COLS):
total += at(i, j)
print("Сумма матрицы:", total) # 21
Та же логика на Python ▶ — формула i * COLS + j — это ровно то, что вычисляет компилятор C. Двумерность создаётся арифметикой над плоской памятью.
Динамические двумерные массивы
Статическая матрица grid[2][3] удобна, но её размеры фиксированы на этапе компиляции. Когда размеры известны только в рантайме, двумерность собирают из динамической памяти, и тут есть два подхода. Первый, простой и быстрый, — один большой плоский блок malloc(rows * cols * sizeof(int)) с доступом по знакомой формуле arr[i * cols + j]: данные лежат подряд, дружелюбны к кэшу, и освобождать нужно один блок. Второй — «массив указателей на строки»: массив int *, каждый элемент которого указывает на отдельно выделенную строку. Он позволяет строки разной длины и привычный синтаксис arr[i][j], но требует несколько malloc и стольких же free, а память разбросана. Выбор между ними — типичное инженерное решение C: простота и скорость против гибкости.
Итоги
Двумерный массив в C — это непрерывный блок, разложенный построчно (row-major). Адрес элемента вычисляется по формуле i * число_столбцов + j, поэтому число столбцов критично и обязательно при передаче в функцию. Доступ — по двум индексам (строка, столбец), обход — вложенными циклами. Границы по-прежнему не проверяются, а обход «строка за строкой» эффективнее из-за раскладки в памяти.