Глиф Учебники

Урок 5. Массивы

Курс «C» · урок 5 из 10 · 4 минуты чтения

Массив — несколько переменных одного типа под одним именем. Вместо a1, a2, …, a100 — один массив a из ста элементов, к которым обращаются по номеру: a[0], a[1], …, a[99].

C
#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] = {10, 20, 30, 40, 50}; a[2] = 99; 10 a[0] 20 a[1] 30 99 a[2] 40 a[3] 50 a[4] ? a[5] первый последний нет такого — чужая память 5 элементов: индексы от 0 до 4
Массив a из пяти элементов — пять ячеек подряд с индексами от 0 до 4; присваивание элементу с индексом 2 заменяет 30 на 99, а элемента с индексом 5 нет — за концом массива чужая память.

Объявление и начальные значения

Размер массива удобно задать один раз в начале программы:

C
#define N 100

Дальше везде пишется int a[N]; и i < N — если размер понадобится другой, его меняют в одном месте.

Ввод и обработка массива

Типичная задача: первым числом дано количество, дальше — сами числа. Нужно найти сумму, наибольшее и вывести числа в обратном порядке.

C
#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] компилятор пропустит, а при запуске программа прочитает или испортит чужую память.

C
#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]) — размер одного элемента. Их частное — число элементов:

C
#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, элементов: 8
int a[] = {3, 1, 4, 1, 5, 9, 2, 6}; 3 1 4 1 5 9 2 6 sizeof(a[0]) = 4 байта: один элемент sizeof(a) = 32 байта: весь массив sizeof(a) / sizeof(a[0]) = 32 / 4 = 8
Массив из восьми int занимает 32 байта, один элемент — 4 байта, поэтому sizeof(a), делённый на размер одного элемента, даёт число элементов: 32 / 4 = 8.

size_t — тип для размеров (целое без знака), его формат — %zu. Этот приём работает только там, где массив объявлен. Внутри функции, куда массив передан параметром, sizeof даёт размер указателя, а не массива — об этом в уроке про функции.

Двумерные массивы

Таблица из строк и столбцов — массив массивов: int m[3][4] — 3 строки по 4 элемента. Элемент — m[i][j], где i — номер строки, j — столбца.

C
#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
int m[3][4] 3 строки по 4 i = 0 j = 0 1 j = 1 2 j = 2 3 j = 3 4 10 i = 1 5 6 7 8 26 i = 2 9 10 11 12 42 сумма строки m[2][3] — строка 2, столбец 3
Двумерный массив m из 3 строк по 4 элемента: первый индекс i — номер строки, второй j — номер столбца; суммы строк — 10, 26 и 42, а угловой элемент в строке 2 и столбце 3 равен 12.

Чего с массивами сделать нельзя

Начиная с C99, размер массива внутри функции можно задать переменной: int a[n]; после чтения n. gcc это умеет, но с C11 такая возможность для компиляторов необязательна (Visual Studio её не поддерживает), а большой массив так же переполнит стек. Надёжнее — массив с запасом или malloc.

Коротко

Проверьте себя

1. Какие индексы у массива int a[10]?
2. Чему равны элементы массива после int a[4] = {7}; внутри функции?
3. Что произойдёт при обращении к a[5] у массива int a[5]?
4. Почему при поиске наибольшего max начинают с a[0], а не с 0?