Итак, мы поняли, что массивы на самом деле – синтаксический сахар над указателями. Рассмотрим простейший случай – определим массив целых чисел размером в 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;
}