Menu

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

Как цикл while в C повторяется до изменения условия — правило «сначала условие», циклы со сторожевым значением, чтение ввода до EOF, while(1) и перевод между for и while.

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

Повторение без счёта

Цикл for — подходящий инструмент, когда вы знаете (или можете вычислить), сколько проходов нужно. Но очень многая работа не идёт со счётчиком. Прочитать каждую строку файла до её конца. Спрашивать пароль, пока он не окажется верным. Делить число пополам, пока оно не станет равно 1. Здесь вопрос не «сколько раз», а «до каких пор» — и это ровно то, что выражает while.

Никто заранее не вычислял «семь». Цикл просто продолжается, пока выполняется условие.

Синтаксис и правило «сначала условие»

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

Условие проверяется перед каждым проходом, включая первый. Три следствия:

  • Цикл, чьё условие ложно с самого начала, выполняется ноль раз. while (0) { ... } никогда не выполнит тело.
  • То, что проверяет условие, должно быть инициализировано до цикла, иначе вы проверяете мусорное значение.
  • Что-то в теле должно в итоге сделать условие ложным, иначе цикл не закончится.

Как и в if, круглые скобки обязательны, а точки с запятой после них нет. Эта случайная точка с запятой — цикловая версия той же ошибки:

/* БАГ: телом стала пустая инструкция - это крутится вечно */
while (i < 10);
{
    printf("%d\n", i);
    i++;
}

i никогда не увеличивается, поэтому условие не меняется. gcc -Wall указывает на пустое тело.

Правило истинности C работает здесь ровно так же, как в if/else: цикл продолжается, пока выражение ненулевое, и останавливается, когда оно равно нулю.

Стандартная форма: инициализация, проверка, обновление

Почти у каждого корректного цикла while есть три части, разнесённые по трём местам:

Это те же три части, что держит заголовок for, просто не собранные в одну строку. Отсюда честный ответ на вопрос «for или while?»: если все три части относятся к одному счётчику, for располагает их там, где читатель может проверить их вместе. Если обновление разбросано по длинному телу или условие говорит не о счёте, то while — более ясная форма.

Забыть часть 3 — это та самая ошибка цикла while. Если цикл завис, первое, что стоит проверить, — действительно ли тело меняет то, что читает условие.

Циклы со сторожевым значением

Сторожевое значение (sentinel) — особое значение, означающее «стоп». Суммируем числа, пока пользователь не введёт 0:

До 99 после 0 дело не доходит — сторожевое значение завершило цикл. Обратите внимание, что количество чисел заранее неизвестно, и именно поэтому здесь while.

Строки в C устроены так же: они заканчиваются сторожевым '\0', поэтому обход строки — хрестоматийный while:

Чтение ввода до EOF

Самый частый while в реальном коде читает ввод, пока он не закончится. Правило — проверять возвращаемое значение функции ввода, а не то значение, которое она сохранила.

getchar возвращает следующий символ или константу EOF, когда ввод закончился:

Две важные детали. c объявлен как int, потому что EOF — отрицательное значение, которое на многих системах не помещается в char; объявив его char, вы можете получить сравнение, которое никогда не сработает. И присваивание стоит внутри условия: (c = getchar()) != EOF читает символ, сохраняет его и сравнивает за один шаг. Внутренние скобки обязательны, потому что != связывает сильнее, чем =; без них вы сохранили бы в c результат сравнения. Это одно из немногих мест, где присваивание внутри условия — идиома, а не баг.

Та же форма работает со scanf, чьё возвращаемое значение — число успешно преобразованных элементов:

int n;
while (scanf("%d", &n) == 1) {
    /* n содержит корректное число */
}

Цикл заканчивается на конце файла и на первом вводе, который не является числом, — обычно это как раз то, что нужно.

Чего писать не стоит, так это while (!feof(f)). feof сообщает, дало ли чтение сбой уже, поэтому он всё ещё ложен, когда вы стоите на последнем элементе: тело выполняется ещё раз, чтение проваливается — и обрабатываются устаревшие данные. Проверяйте само чтение.

while (1) и break

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

while (1) всегда истинно, поэтому единственный выход — break (или return, или exit). Для той же цели вам встретится for (;;); они компилируются в одно и то же, а что предпочитает кодовая база — вопрос стиля.

Используйте эту форму, когда проверка выхода действительно принадлежит середине тела. Когда проверка естественно ложится наверх, поместите её в условие, где читатель её увидит, — while (1), чей единственный break стоит первой же инструкцией, это просто условие в маскировке.

Перевод между for и while

Любой цикл for можно записать как while и наоборот. Соответствие механическое:

/* for */                          /* эквивалентный while */
for (init; cond; update) {         init;
    body;                          while (cond) {
}                                      body;
                                       update;
                                   }

Поэтому эти два цикла печатают одно и то же:

Единственное место, где эквивалентность даёт течь, — continue. В цикле for continue переходит к обновлению в заголовке, поэтому счётчик всё равно продвигается. В версии с while continue возвращается прямо к условию и пропускает i++ в конце тела — мгновенный бесконечный цикл:

/* БАГ: continue пропускает i++, поэтому цикл вечно крутится на первом чётном числе */
int i = 0;
while (i < 10) {
    if (i % 2 == 0) {
        continue;
    }
    printf("%d ", i);
    i++;
}

Если цикл while использует continue, убедитесь, что обновление происходит до него, — либо возьмите for, где это гарантирует заголовок.

Распространённые подводные камни

  • Ничто не обновляет условие. Тело должно менять то, что читает условие. Если программа зависла, смотрите сюда первым делом.
  • Точка с запятой после заголовка. while (cond); делает телом пустую инструкцию.
  • Проверка неинициализированной переменной. Условие выполняется до тела, поэтому значение, которое оно читает, уже должно существовать.
  • char вместо int для getchar. EOF не помещается в char; тогда сравнение может никогда не стать истинным, а законный байт может быть принят за EOF.
  • while (!feof(f)). Делает на один проход больше. Проверяйте чтение.
  • Предположение, что тело выполнится хотя бы раз. Оно не выполнится. Когда вам нужен один гарантированный проход — вывести меню до проверки выбора — это цикл do-while.

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

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

while (условие) { тело }. Условие проверяется перед каждым проходом, поэтому тело выполняется только пока условие ненулевое, и не выполняется вовсе, если оно ложно с самого начала. Что-то внутри тела должно в конце концов изменить условие, иначе цикл никогда не закончится.

В чём разница между for и while в C?

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

Как читать ввод до EOF в C?

Проверяйте возвращаемое значение функции ввода, а не прочитанное ею значение: while (scanf("%d", &n) == 1) { ... } останавливается на конце файла или на первом нечисловом вводе, а while ((c = getchar()) != EOF) { ... } читает посимвольно. Никогда не делайте цикл while (!feof(f)) — он выполняется на один раз больше, чем нужно.

Почему мой цикл while бесконечный в C?

Обычно потому, что ничто в теле не меняет значение, которое проверяет условие, либо из-за случайной точки с запятой после заголовка (while (i < 10);), из-за которой телом стала пустая инструкция. И то, и другое компилируется без замечаний, поэтому проверьте, что тело действительно обновляет переменную из условия.

Coddy programming languages illustration

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

НАЧАТЬ