Menu

Цикл for в C: синтаксис, примеры и распространённые ошибки

Как повторять код с помощью цикла for в C: заголовок из трёх частей, счёт вверх и вниз, обход массивов с приёмом sizeof, вложенные циклы, бесконечные циклы, а также ошибки смещения на единицу и беззнаковых типов.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Зачем нужен цикл for

if и switch выбирают ветку и выполняют её один раз. Настоящим программам нужно делать что-то многократно: вывести каждый результат, просуммировать список чисел, нарисовать десять строк сетки, обработать каждый символ строки. Цикл for — рабочая лошадка C для повторения кода известное число раз, со счётчиком, которым вы управляете.

Всё, что нужно циклу for, помещается в один компактный заголовок, поэтому «сколько раз и как» видно в одной строке.

Заголовок из трёх частей

Заголовок for состоит из трёх частей, разделённых точкой с запятой: инициализатор, условие и обновление.

for (initializer; condition; update) {
    // тело - выполняется, пока условие ненулевое
}

Они выполняются в строго определённом порядке, и знание этого порядка объясняет любой цикл for, который вам встретится:

  1. Инициализатор выполняется один раз, раньше всего остального.
  2. Проверяется условие. Если оно равно нулю, цикл немедленно завершается.
  3. Выполняется тело.
  4. Выполняется обновление.
  5. Возврат к шагу 2.

Проследите цикл выше по этому списку: int i = 0 выполняется один раз. 0 < 5 истинно, поэтому тело выводит i = 0, затем i++ делает i равным 1. Условие проверяется снова, и так далее. Когда i достигает 5, проверка не проходит, тело пропускается и выводится готово. Тело выполнилось ровно пять раз, при этом i принимал значения от 0 до 4.

Отсюда прямо следуют два вывода. Условие проверяется до первого прохода, поэтому цикл, условие которого изначально ложно, выполняется ноль раз — for (int i = 10; i < 5; i++) вообще не заходит в тело. И обновление выполняется после тела, поэтому внутри тела i всё ещё хранит прежнее значение.

Объявление счётчика внутри заголовка (int i = 0) допустимо начиная с C99 и является правильным выбором по умолчанию: i тогда существует только внутри цикла, не может ни с чем столкнуться после него, и каждый цикл в функции может спокойно переиспользовать это имя.

Счёт вверх, вниз и с шагом

Обновление не ограничивается i++. Считайте вниз, шагайте на любую величину или удваивайте значение на каждом проходе:

Согласуйте условие с обновлением. Счёт вверх сочетается с < или <=; счёт вниз — с > или >=. Именно ошибка в этой паре и порождает цикл, который никогда не заканчивается.

Обратите внимание: второй цикл использует <= 10, потому что 10 — значение, которое мы хотим включить, а первый пример использовал < 5, потому что 5 включать не нужно. Именно здесь рождаются ошибки смещения на единицу, поэтому делайте этот выбор осознанно: i < n выполняется n раз начиная с 0; i <= n выполняется n + 1 раз.

Обход массива

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

Отсюда стоит вынести две вещи.

Приём с sizeof. sizeof(scores) — размер всего массива в байтах, а sizeof(scores[0]) — размер одного элемента, поэтому их частное даёт количество элементов. Оно подстраивается автоматически, когда вы добавляете элемент, чего жёстко вписанная 5 не делает. Подвох — и он серьёзный — в том, что это работает только там, где виден сам массив. Передайте массив в функцию, и он превратится в указатель, так что sizeof там даст размер указателя. Внутри функции всегда принимайте длину отдельным параметром.

Условие — это i < n, и никогда i <= n. У массива из пяти элементов допустимые индексы — от 0 до 4. Чтение scores[5] — неопределённое поведение: может вывестись мусор, программа может упасть или, казалось бы, сработать, незаметно повредив что-то ещё. C не выполняет никакой проверки границ, так что это полностью на вашей совести.

Обход массива в обратном порядке — та же идея наоборот:

Начинаем с n - 1 (последний допустимый индекс) и останавливаемся на 0 включительно — отсюда >=, а не >. Держите здесь i знаковым int; почему — объясняет следующий раздел.

Вложенные циклы

Поместите один for внутрь другого, чтобы работать с сетками, таблицами или всеми парами элементов. Внутренний цикл полностью отрабатывает на каждом отдельном шаге внешнего.

Внешний цикл фиксирует row; внутренний проходит по каждому col для этой строки; printf("\n") после внутреннего цикла завершает строку. Перенесите этот перевод строки внутрь внутреннего цикла — и вся таблица превратится в один столбец; попробуйте, это наглядно показывает структуру вложенности.

Давайте счётчикам разные имена. row/col или i/j подойдут; использование i для обоих затеняет внешний счётчик и даёт озадачивающие результаты. И следите за стоимостью: цикл на n внутри цикла на n выполняет тело n * n раз, так что пара циклов по 1000 итераций — это миллион проходов.

Вот вложенный цикл за настоящей работой — треугольник из звёздочек, где граница внутреннего цикла зависит от внешнего счётчика:

break и continue

Два ключевых слова меняют ход выполнения посреди цикла. break немедленно выходит из цикла; continue пропускает остаток текущего прохода и переходит к обновлению.

Первый цикл останавливается в тот момент, когда находит 7, и не проверяет оставшиеся 93 значения. Второй использует continue, чтобы пропустить вывод для чётных чисел; i++ из заголовка всё равно выполняется, поэтому цикл продолжает двигаться. Гораздо подробнее об обоих — в статье break и continue, включая то, как выйти из вложенного цикла, чего один break сделать не может.

Бесконечные циклы и пустые части

Все три части заголовка необязательны. Уберите условие — и оно будет считаться постоянно истинным:

for (;;) {
    /* выполняется бесконечно - выход через break или return */
}

for (;;) — идиоматический бесконечный цикл в C, и точки с запятой в нём обязательны. Это не ошибка, пока внутри есть что-то, что может сделать break, return или exit: циклы обработки событий и циклы «спрашивать, пока ввод не станет корректным» пишутся именно так.

Остальные части тоже можно опустить. Если счётчик уже существует, пропустите инициализатор; если его продвигает тело, пропустите обновление:

int i = 0;
for (; i < n; ) {
    /* ... */
    i += step;
}

Это допустимо, но читается хуже, чем цикл while, которым оно на самом деле и является. Предпочитайте for, когда счётчик, его предел и его шаг уместно держать вместе в заголовке, и while — когда нет.

Оператор «запятая» в заголовке

Инициализатор и обновление могут содержать по несколько выражений, разделённых запятыми, — именно так запускают два счётчика сразу:

int i = 0, j = len - 1 объявляет оба счётчика, а i++, j-- продвигает оба. Это оператор запятая, который вычисляет свои операнды по порядку, слева направо. Используйте его для действительно параллельных счётчиков; впихивание в заголовок посторонней работы лишь прячет её.

Распространённые ловушки

Горстка ловушек объясняет большинство багов циклов for в C.

Смещение на единицу. i <= n для массива с отсчётом от нуля читает один элемент за концом. Для «n раз начиная с 0» используйте i < n.

Точка с запятой после заголовка. Это компилируется и почти никогда не является тем, что вы имели в виду:

/* БАГ: телом цикла стал пустой оператор; printf выполняется один раз */
for (int i = 0; i < 5; i++);
{
    printf("%d\n", i);
}

; становится телом, поэтому цикл пять раз прокручивается вхолостую, а блок в фигурных скобках ниже выполняется один раз как обычный блок. gcc -Wall предупреждает об этом.

Беззнаковые счётчики при счёте вниз. Это бесконечный цикл:

/* БАГ: беззнаковое значение никогда не бывает < 0 */
for (size_t i = n - 1; i >= 0; i--) {
    process(a[i]);
}

i >= 0 всегда истинно для беззнакового типа. Когда i достигает 0, i-- оборачивается в огромное положительное число, и цикл уходит в память, которая ему не принадлежит. Для счёта вниз используйте знаковый int или запишите условие как for (size_t i = n; i-- > 0; ) — оно уменьшает и проверяет за один шаг и корректно останавливается на нуле.

Изменение счётчика сразу в двух местах. Изменение i и в теле, и в заголовке делает число итераций непредсказуемым. Выберите что-то одно.

Счётчики с плавающей точкой. for (double x = 0.0; x != 1.0; x += 0.1) может никогда не завершиться, потому что у 0.1 нет точного двоичного представления и сумма перешагивает 1.0, не попадая в него. Делайте цикл по целочисленному счётчику и вычисляйте значение внутри:

for (int i = 0; i <= 10; i++) {
    double x = i / 10.0;
    /* ... */
}

Изменение длины массива во время обхода. Условие i < n перечитывает n на каждом проходе, поэтому если тело уменьшает коллекцию, корректируйте индекс осознанно, а не позволяйте этим двум величинам разойтись.

Часто задаваемые вопросы

Как написать цикл for в C?

Поместите в заголовок три части, разделённые точкой с запятой, — инициализатор, условие и обновление: for (int i = 0; i < 5; i++) { printf("%d\n", i); }. Тело выполнится пять раз, при этом i примет значения от 0 до 4, и цикл остановится, как только условие станет ложным.

Как пройти по массиву в C?

Считайте индекс от 0 до длины, не включая её: for (int i = 0; i < n; i++) { sum += a[i]; }. Для массива, объявленного в той же функции, длину можно вычислить как sizeof(a) / sizeof(a[0]); но как только массив передан в функцию, этот приём перестаёт работать, поэтому передавайте длину отдельным параметром.

Почему мой цикл for в C выполняется на один раз больше, чем нужно?

Это ошибка смещения на единицу (off-by-one). У массива размера n с отсчётом от нуля допустимые индексы — от 0 до n - 1, поэтому i <= n делает одну лишнюю итерацию и читает за концом массива — неопределённое поведение. Безопасный вариант по умолчанию — i < n.

Можно ли объявить переменную цикла прямо в заголовке for в C?

Да, начиная с C99: for (int i = 0; i < n; i++). Переменная тогда существует только внутри цикла, что вам и нужно. Компилируйте с gcc -std=c99 или новее (современные GCC и clang по умолчанию используют C17, так что всё работает сразу); в старом стиле C89 i объявляли до цикла.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ