Урок 5. Массивы
Массив — несколько переменных одного типа под одним именем. Вместо a1, a2, …, a100 — один массив a из ста элементов, к которым обращаются по номеру: a[0], a[1], …, a[99].
#include <stdio.h>
int main(void) {
int a[5] = {10, 20, 30, 40, 50};
printf("Первый: %d\n", a[0]);
printf("Последний: %d\n", a[4]);
a[2] = 99;
for (int i = 0; i < 5; i++) {
printf("%d ", a[i]);
}
printf("\n");
return 0;
}Первый: 10
Последний: 50
10 20 99 40 50Главное правило: нумерация с нуля. У массива из 5 элементов индексы от 0 до 4, элемента a[5] нет.
Объявление и начальные значения
int a[5];— пять элементов, внутри функции их значения не заданы (мусор).int a[5] = {1, 2, 3};— первые три заданы, остальные равны 0.int a[100] = {0};— все сто элементов равны 0.int a[] = {4, 8, 15};— размер не указан, компилятор считает его сам: 3.- Массив, объявленный вне функций (глобальный), сразу заполнен нулями.
Размер массива удобно задать один раз в начале программы:
#define N 100Дальше везде пишется int a[N]; и i < N — если размер понадобится другой, его меняют в одном месте.
Ввод и обработка массива
Типичная задача: первым числом дано количество, дальше — сами числа. Нужно найти сумму, наибольшее и вывести числа в обратном порядке.
#include <stdio.h>
#define N 1000
int main(void) {
int a[N];
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
int sum = 0, max = a[0];
for (int i = 0; i < n; i++) {
sum += a[i];
if (a[i] > max) {
max = a[i];
}
}
printf("Сумма: %d\n", sum);
printf("Наибольшее: %d\n", max);
for (int i = n - 1; i >= 0; i--) {
printf("%d ", a[i]);
}
printf("\n");
return 0;
}6
3 -7 12 5 0 8Сумма: 21
Наибольшее: 12
8 0 5 12 -7 3Обратите внимание: max начинается с a[0], а не с нуля. Если бы все числа были отрицательными, max = 0 дал бы неверный ответ. В scanf перед элементом массива тоже нужен &: &a[i].
Массив здесь объявлен с запасом — на 1000 элементов, а используется n. Так делают, когда известно наибольшее возможное n (в задаче оно всегда указано в условии). Если оно неизвестно, память выделяют под точный размер функцией malloc — это тема урока Динамическая память и файлы.
Выход за границы массива
C не проверяет индексы. Обращение к a[5] у массива из пяти элементов или к a[-1] компилятор пропустит, а при запуске программа прочитает или испортит чужую память.
#include <stdio.h>
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
for (int i = 0; i <= 5; i++) {
printf("%d\n", a[i]);
}
return 0;
}Здесь ошибка в условии i <= 5: на последнем шаге читается несуществующий a[5]. Результат не определён (в стандарте C это называется неопределённым поведением): программа может вывести лишнее случайное число, упасть с ошибкой segmentation fault или как будто сработать правильно. Последнее хуже всего — ошибка проявится позже, в другом месте. Как искать такие ошибки, рассказывает справка Segmentation fault.
Запись за границу массива может незаметно изменить соседние переменные. Если программа выдаёт странный ответ, а логика кажется верной, первым делом проверьте границы всех циклов по массивам.
Размер массива: sizeof
sizeof(a) — размер массива в байтах, sizeof(a[0]) — размер одного элемента. Их частное — число элементов:
#include <stdio.h>
int main(void) {
int a[] = {3, 1, 4, 1, 5, 9, 2, 6};
size_t n = sizeof(a) / sizeof(a[0]);
printf("Байт: %zu, элементов: %zu\n", sizeof(a), n);
return 0;
}Байт: 32, элементов: 8size_t — тип для размеров (целое без знака), его формат — %zu. Этот приём работает только там, где массив объявлен. Внутри функции, куда массив передан параметром, sizeof даёт размер указателя, а не массива — об этом в уроке про функции.
Двумерные массивы
Таблица из строк и столбцов — массив массивов: int m[3][4] — 3 строки по 4 элемента. Элемент — m[i][j], где i — номер строки, j — столбца.
#include <stdio.h>
int main(void) {
int m[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
for (int i = 0; i < 3; i++) {
int row = 0;
for (int j = 0; j < 4; j++) {
row += m[i][j];
}
printf("Сумма строки %d: %d\n", i, row);
}
printf("Угловой элемент: %d\n", m[2][3]);
return 0;
}Сумма строки 0: 10
Сумма строки 1: 26
Сумма строки 2: 42
Угловой элемент: 12Чего с массивами сделать нельзя
- Присвоить массив целиком.
b = a;— ошибка сборки. Копируют поэлементно в цикле или функциейmemcpyизstring.h:memcpy(b, a, sizeof(a));. - Сравнить целиком.
a == bсравнивает адреса массивов, а не их содержимое; сравнивают тоже в цикле. - Изменить размер. Размер массива задаётся при объявлении и потом не меняется. Изменяемый размер даёт только динамическая память.
- Сделать большой массив внутри функции. Массивы внутри функций лежат в стеке — небольшой области памяти (обычно 1–8 МБ).
int a[10000000]внутриmainпереполняет его, и программа падает сразу при запуске. Большие массивы объявляют вне функций.
Начиная с C99, размер массива внутри функции можно задать переменной: int a[n]; после чтения n. gcc это умеет, но с C11 такая возможность для компиляторов необязательна (Visual Studio её не поддерживает), а большой массив так же переполнит стек. Надёжнее — массив с запасом или malloc.
Коротко
int a[5]— пять элементов с индексами от 0 до 4; элементаa[5]нет.int a[N] = {0};— все нули; глобальный массив заполнен нулями сразу.- Перебор:
for (int i = 0; i < n; i++); ввод элемента —scanf("%d", &a[i]). - Индексы никто не проверяет: выход за границы — неопределённое поведение.
- Число элементов —
sizeof(a) / sizeof(a[0]), но только там, где массив объявлен. - Массивы не присваиваются и не сравниваются целиком.