Одномерные массивы
Массив — это полка с пронумерованными ячейками
Массив — набор однотипных значений, лежащих подряд в памяти. У каждой ячейки есть индекс (номер), начиная с нуля.
индекс: 0 1 2 3 4
значение: 10 20 30 40 50
Аналогия. Массив — как полка с ячейками: все ячейки одинакового размера, у каждой свой номер, и вся полка целиком имеет имя (
a). Чтобы взять вещь, говорим «полкаa, ячейка 2».
Четыре свойства, которые важно запомнить:
- Все элементы одного типа.
- Нумерация с нуля: у массива из
Nэлементов индексы0 … N−1. - Массив занимает непрерывный участок памяти.
- Размер фиксируется при объявлении (для статического массива).
Объявление статического массива
Размер должен быть известен на этапе компиляции. В рамках курса мы будем задавать его именованной константой через #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 | константа времени компиляции, корректно задаёт размер массива |
enum | enum { N = 100 }; | константа времени компиляции, корректно задаёт размер массива, но является использованием enum не по назначению |
const int | const 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.