Одномерные массивы

Массив — это полка с пронумерованными ячейками

Массив — набор однотипных значений, лежащих подряд в памяти. У каждой ячейки есть индекс (номер), начиная с нуля.

индекс:   0     1     2     3     4
значение: 10    20    30    40    50

Аналогия. Массив — как полка с ячейками: все ячейки одинакового размера, у каждой свой номер, и вся полка целиком имеет имя (a). Чтобы взять вещь, говорим «полка a, ячейка 2».

Четыре свойства, которые важно запомнить:

  1. Все элементы одного типа.
  2. Нумерация с нуля: у массива из N элементов индексы 0 … N−1.
  3. Массив занимает непрерывный участок памяти.
  4. Размер фиксируется при объявлении (для статического массива).

Объявление статического массива

Размер должен быть известен на этапе компиляции. В рамках курса мы будем задавать его именованной константой через #define:

#define MAX_N 100       /* предел по условию задачи */

int a[MAX_N];           /* статический массив на 100 целых чисел */

Такой способ работает в любом компиляторе C. Объявить массив и сразу заполнить можно перечислением:

int days[7] = { 31, 28, 31, 30, 31, 30, 31 };

Массив можно заполнить целиком нулями: int a[100] = { 0 };.

Три способа задать размер статического массива

В C есть три способа объявить именованную константу. Они не эквивалентны, и важно понимать разницу.

СпособПримерКомментарий
#define#define N 100константа времени компиляции, корректно задаёт размер массива
enumenum { N = 100 };константа времени компиляции, корректно задаёт размер массива, но является использованием enum не по назначению
const intconst int N = 100;может не работать в некоторых компиляторах (VLA)

Подробнее про каждый:

#define — директива препроцессора. Считается константой времени компиляции. Компилятор подставляет всюду вместо MAX_N число 100. Нет типа, нет области видимости, но работает везде. Для размеров массивов — самый безопасный вариант.

enum — перечисление. Тоже настоящая константа времени компиляции, но с областью видимости. Хорошо подходит, когда нужно несколько связанных констант. Не поддерживает float и double. Для задания размера массива является использованием не по назначению.

const int — переменная только для чтения. У неё есть тип и область видимости, её видно в отладчике. Но в C она не является константой времени компиляции: компилятор не обязан знать её значение заранее. Поэтому:

const int N = 100;
int a[N];       /* это VLA — массив переменной длины, а не статический */

Такой код работает, например, в GCC/MinGW, но не соберётся в MSVC. Кроме того, VLA выделяется на стеке, и при большом размере программа может упасть.

Для размеров массивов в лучше использовать #define. const int пригодится позже — для чисел с плавающей точкой или параметров функций, где VLA не возникает.

Индексация и границы

a[0] = 5;          /* первый элемент      */
a[9] = 7;          /* десятый элемент     */
int x = a[3];      /* прочитать четвёртый */

Выход за границы — классическая ошибка:

int a[5];
a[5] = 1;          /* ОШИБКА: допустимы индексы 0..4 */

Компилятор такое часто не замечает, а программа портит память. Сравните:

for (int i = 0; i <= n; i++) a[i] = 0;   /* ошибка: i доходит до n */
for (int i = 0; i <  n; i++) a[i] = 0;   /* правильно */

Обход массива циклом

Основной инструмент — цикл for (подробно о циклах — в предыдущей теме):

for (int i = 0; i < n; i++)
{
    printf("%d ", a[i]);
}

Обход массива «по указателю» (*(a + i)) разберём позже — в теме «Указатели и динамическая память». Пока работаем с индексами: это нагляднее.

Чтение массива с клавиатуры

Сначала читаем N, затем N чисел:

#include <stdio.h>

#define MAX_N 100

int main(void)
{
    int n;
    int a[MAX_N];

    printf("Введите N: ");
    if (scanf("%d", &n) != 1 || n < 1 || n > MAX_N)
    {
        printf("Ошибка: N должно быть от 1 до %d\n", MAX_N);
        return 1;
    }

    for (int i = 0; i < n; i++)
    {
        if (scanf("%d", &a[i]) != 1)
        {
            printf("Ошибка: ожидалось целое число\n");
            return 1;
        }
    }

    for (int i = 0; i < n; i++) printf("%d ", a[i]);
    printf("\n");
    return 0;
}

Обратите внимание: массив a объявлен на MAX_N = 100 элементов — это максимально возможное значение N по условию задачи. Но фактически мы используем только первые n ячеек.

Базовые приёмы, которые нужны в ЛР1

/* Сумма */
int sum = 0;
for (int i = 0; i < n; i++) sum += a[i];

/* Минимум и его индекс */
int min_idx = 0;
for (int i = 1; i < n; i++)
    if (a[i] < a[min_idx]) min_idx = i;

/* Подсчёт по условию */
int cnt = 0;
for (int i = 0; i < n; i++)
    if (a[i] % 2 == 0) cnt++;

/* Поиск с досрочным выходом */
int pos = -1;
for (int i = 0; i < n; i++)
{
    if (a[i] < 0) { pos = i; break; }
}

/* Обмен двух элементов (через временную переменную) */
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;

/* Разворот массива на месте */
for (int i = 0; i < n / 2; i++)
{
    int t = a[i];
    a[i] = a[n - 1 - i];
    a[n - 1 - i] = t;
}

Сортировка пузырьком

for (int pass = 0; pass < n - 1; pass++)
{
    for (int j = 0; j < n - 1 - pass; j++)
    {
        if (a[j] > a[j + 1])
        {
            int t = a[j];
            a[j] = a[j + 1];
            a[j + 1] = t;
        }
    }
}

Каждый проход «выталкивает» очередной максимум в конец — отсюда название «пузырёк».

Частые ошибки

ОшибкаПоследствие
a[n] в цикле (i <= n)выход за границы, порча памяти
Неинициализированные элементы«мусор» в вычислениях
int avg = sum / n;целое деление вместо дробного
Забыли про N = 1циклы for (i = 1; …) ничего не делают
Нет проверки вводапрограмма падает или считает ерунду
Использование const int для размера массиваVLA, проблемы в MSVC

Что запомнить

Индексы идут от 0 до N−1; цикл for — основной инструмент обхода; прежде чем считать, всегда проверяйте ввод и границы. Для размеров массивов используем #define.

Подтверждение

—