Массивы в Си – довольно интересная структура данных. Простая и эффективная как топор!
Итак, мы поняли, что массивы на самом деле – синтаксический сахар над указателями. Рассмотрим простейший случай – определим массив целых чисел размером в 10 элементов.
int nums[10]; nums[0] = 0; nums[1] = 1; nums[2] = 4; nums[3] = 9; nums[4] = 16;
– как и во многих других языках, адресация в массиве начинается с нуля – сдвиг от адреса начала.
int nums[10] = { 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 };
Часто приятнее использовать циклы для работы с массивом:
#include <stdio.h> int main(void) { int nums[10]; for (int i = 0; i < 10; ++i) { // Заполнили квадратами nums[i] = i * i; } for (int i = 0; i < 10; ++i) { // Выводим всё по одному printf("%d\n", nums[i]); } return 0; }
Чему равен nums[10]
?
Для разминки, то же самое на указателях:
#include <stdio.h> int main(void) { int nums[10]; for ( // Создаём и инициализируем указатель адресом начала массива, инициализируем i нулём. // С помощью запятой можно несколько операторов объединить синтаксически в один. int *ptr = nums, i = 0; // Останавливаемся, когда указатель перейдёт за границы массива. ptr < nums + 10; // На каждом шаге двигаемся вперёд на один элемент // (помните, как работает арифметика указателей?) ++ptr, ++i ) { *ptr = i * i; // Кладём по адресу ptr значение i*i } for (int *ptr = nums; ptr < nums + 10; ++ptr) { printf("%d\n", *ptr); } return 0; }
Мы рассмотрели одноуровневые / одномерные массивы. Часто этого хватает, но, например, в случае определения 2D пространства нам этого не хватит.
Как и многое в Си, многомерные массивы устроены крайне просто (об этом чуть позже) и поддерживают довольного удобный способ доступа:
#include <stdio.h> int main(void) { int nums[4][10]; for (int i = 0; i < 4; ++i) { for (int j = 0; j < 10; ++j) { nums[i][j] = 1; if (i > 0) { for (int k = 0; k < i; ++k) { nums[i][j] *= j; } } } } for (int i = 0; i < 4; ++i) { for (int j = 0; j < 10; ++j) { printf("%d ", nums[i][j]); } printf("\n"); } return 0; }
Или на указателях:
#include <stdio.h> int main(void) { int nums[4][10]; for (int i = 0; i < 4; ++i) { for (int j = 0; j < 10; ++j) { nums[i][j] = 1; if (i > 0) { for (int k = 0; k < i; ++k) { nums[i][j] *= j; } } } } // Объявляем указатель на массив из 10 элементов. // То есть по сути - указатель на указатель. for (int (*i)[10] = nums; i != nums + 4; ++i) { for (int *j = *i; j != *i + 10; ++j) { printf("%d ", *j); } printf("\n"); } // Такой способ также работает благодаря устройству многомерных массивов в Си for (int *ptr = &nums[0][0], i = 0; i < 4 * 10; ++i) { printf("%d ", *(ptr + i)); if (i % 10 == 9) { printf("\n"); } } }
– уровни массива располагаются один за другим. То есть после nums[0][9]
следует nums[1][0]
.
Небольшая пояснительная схема:
+-----------------+-----------------+-----------------+-----------------+
| [0][0] - [0][9] | [1][0] - [1][9] | [2][0] - [2][9] | [3][0] - [3][9] |
+-----------------+-----------------+-----------------+-----------------+
– данные массива nums расположены элемент за элементом одним непрерывным куском памяти. Именно благодаря этому мы смогли так ловко на одном указателе пройти весь двумерный массив.
Ещё одно небольшое объяснение (запутывание):
*(*ptr+1) = *(*(ptr + 0) + 1 ) = *(ptr[0] + 1) = ptr[0][1] nums[0][0] = *(*(nums)) nums[i][j] = *((*(nums)) + (i * COLS + j)) nums[i][j] = *(*(nums + i) + j) nums[i][j] = *(nums[i] + j) nums[i][j] = (*(nums + i))[j] &nums[i][j] = ((*(nums)) + (i * COLS + j))
И для закрепления рассмотрим 3d массив:
#include<stdio.h> int main() { int arr[2][3][2] = { { {5, 10}, {6, 11}, {7, 12}, }, { {20, 30}, {21, 31}, {22, 32}, } }; int i, j, k; for (i = 0; i < 2; i++) { for (j = 0; j < 3; j++) { for (k = 0; k < 2; k++) { printf("%d\t", *(*(*(arr + i) + j) +k)); } printf("\n"); } } for (i = 0; i < 2 * 3 * 2; i++) { printf("%d ", *(**arr + i)); } printf("\n"); return 0; }