Циклы в C и C++: for, while, do-while, break и continue

Циклы в C и C++: for, while, do-while, break и continue Полезное

Цикл — это управляющая конструкция, которая повторяет блок кода, пока истинно заданное условие. В C и C++ есть три цикла: for (со счетчиком), while (с предусловием, проверка до тела) и do-while (с постусловием, проверка после тела). Базовый синтаксис в обоих языках одинаков; отличается только range-based for — он есть в C++ с 2011 года и отсутствует в чистом C. Ниже — рабочие примеры каждого цикла, операторы break и continue, вложенные циклы и три типичные ошибки с их фактическим выводом. Все примеры проверены компилятором Apple clang 21 (стандарты C11 и C++11).

Три вида циклов: когда какой брать

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

Цикл Проверка условия Когда брать
for перед телом, счетчик рядом число итераций известно (перебор диапазона, массива)
while перед телом повтор «пока выполняется условие», число шагов заранее неизвестно
do-while после тела нужен минимум один проход (меню, чтение ввода до валидного)

Все три работают в C и C++ одинаково. Дальше — по одному минимальному примеру на каждый.

Цикл for (со счетчиком)

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

#include <stdio.h>

int main(void) {
    int sum = 0;
    for (int i = 1; i <= 5; i++) {   // i от 1 до 5 включительно
        printf("i = %d\n", i);
        sum += i;
    }
    printf("sum = %d\n", sum);
    return 0;
}

Фактический вывод:

i = 1
i = 2
i = 3
i = 4
i = 5
sum = 15

Объявление счетчика прямо в заголовке (for (int i = ...) допустимо в C начиная со стандарта C99 и во всех версиях C++. В старом C89 переменную нужно было объявлять до цикла. Приращение i++ (постфиксный инкремент) увеличивает i на 1; для шага в обратную сторону пишут i--, для другого шага — например i += 2.

В C++ синтаксис тот же. Например, сумма чисел от 1 до 1000:

#include <iostream>

int main() {
    long long total = 0;
    for (int i = 1; i <= 1000; i++) {
        total += i;
    }
    std::cout << total << "\n";   // выведет 500500
    return 0;
}

Тип long long взят с запасом; для суммы до 1000 хватило бы и int, но на больших диапазонах узкий тип переполнится, а результат станет неверным без ошибки компиляции.

Цикл while (с предусловием)

while проверяет условие до тела. Если оно ложно с самого начала, тело не выполнится ни разу. Такой цикл удобен, когда число повторений заранее неизвестно и определяется условием.

#include <stdio.h>

int main(void) {
    int n = 3;
    while (n > 0) {
        printf("осталось %d\n", n);
        n--;               // без этого шага условие никогда не станет ложным
    }
    printf("start\n");
    return 0;
}

Фактический вывод:

осталось 3
осталось 2
осталось 1
start

Ключевой момент: внутри тела должно быть действие, которое рано или поздно сделает условие ложным (здесь n--). Забыть его — частая причина зацикливания, к этому вернемся в разделе про ошибки.

Цикл do-while (с постусловием)

do-while сначала выполняет тело, а условие проверяет после. Поэтому тело гарантированно отрабатывает хотя бы один раз, даже если условие изначально ложно.

#include <stdio.h>

int main(void) {
    int i = 6;
    do {
        printf("%d ", i);
        i--;
    } while (i > 0);       // точка с запятой после while обязательна
    printf("\n");
    return 0;
}

Фактический вывод:

6 5 4 3 2 1 

Обратите внимание на точку с запятой после while (i > 0) — в do-while она обязательна, в отличие от обычного while. Этот цикл берут там, где первый проход нужен всегда: показать меню, прочитать ввод и повторить запрос, если он неверный.

break и continue

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

break — выйти из цикла досрочно:

#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 100; i++) {
        if (i > 5) {
            break;         // как только i стало 6 - выходим
        }
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

Вывод: 1 2 3 4 5 — цикл заявлен до 100, но break обрывает его на шестом шаге.

continue — пропустить текущую итерацию. Сумма только нечетных чисел от 1 до 10:

#include <stdio.h>

int main(void) {
    int sum = 0;
    for (int i = 1; i <= 10; i++) {
        if (i % 2 == 0) {  // четное - остаток от деления на 2 равен 0
            continue;      // пропускаем, к сумме не прибавляем
        }
        sum += i;
    }
    printf("%d\n", sum);   // 1+3+5+7+9 = 25
    return 0;
}

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

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

Цикл можно поместить внутрь другого. На каждый проход внешнего цикла полностью отрабатывает внутренний. Классический пример — таблица (двумерный перебор):

#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            printf("%d ", i * j);
        }
        printf("\n");      // перенос строки после каждой строки таблицы
    }
    return 0;
}

Фактический вывод:

1 2 3 
2 4 6 
3 6 9 

break внутри вложенного цикла прерывает только тот цикл, в котором он написан, — внешний продолжит работу. В C и C++ нет break с меткой (как в Java); чтобы выйти сразу из обоих, используют флаг-переменную, вынос кода в функцию с return или, реже, goto.

range-based for — только в C++ (с C++11)

Отдельная особенность C++: цикл range-based for, который перебирает элементы контейнера или массива без явного индекса. В чистом C его нет — там перебор идет по индексу или указателю.

#include <iostream>
#include <vector>

int main() {
    std::vector<int> nums = {10, 20, 30};
    int sum = 0;
    for (int x : nums) {          // x поочередно принимает 10, 20, 30
        sum += x;
    }
    std::cout << "sum = " << sum << "\n";   // sum = 60

    for (const auto& x : nums) {  // ссылка, без копирования элемента
        std::cout << x << " ";
    }
    std::cout << "\n";            // 10 20 30
    return 0;
}

Запись int x : nums копирует каждый элемент в x; для тяжелых объектов берут const auto& x, чтобы не копировать, а если элемент нужно менять — auto& x. Range-based for доступен с C++11; при компиляции более старым стандартом (-std=c++98) он не соберется. В C тот же перебор пишут через индекс: for (int i = 0; i < 3; i++) sum += nums[i];.

Типичные ошибки

Три ошибки встречаются у новичков чаще всего. Показываю каждую тройкой: неверный код, его фактический результат, исправление.

1. Off-by-one (промах на единицу). Нужно вывести числа от 1 до 5, но условие со строгим < теряет последнее значение:

for (int i = 1; i < 5; i++) {   // хотели до 5, а получим до 4
    printf("%d ", i);
}
// фактический вывод: 1 2 3 4

Исправление — i <= 5 (или i < 6), тогда вывод 1 2 3 4 5. Зеркальная версия этой ошибки опаснее: при переборе массива из n элементов условие i <= n вместо i < n читает элемент за границей массива. Индексы идут от 0 до n-1, а обращение к arr[n] — это неопределенное поведение (может быть мусор, а может и аварийное завершение).

2. Бесконечный цикл. Условие никогда не становится ложным, потому что счетчик не приближается к границе:

int i = 1;
while (i != 10) {   // i идет 1, 3, 5, 7, 9, 11 ... и 10 пропускает
    i += 2;
}

Здесь i проходит только нечетные значения и в точку 10 не попадает никогда — цикл не завершится. Исправление — использовать нестрогое сравнение while (i < 10) вместо проверки на точное равенство. Общее правило: сравнивать счетчик по </>, а не по ==/!=, если шаг может «перепрыгнуть» границу.

3. Модификация счетчика внутри тела. Счетчик меняют и в заголовке, и в теле — шаг фактически удваивается:

for (int i = 0; i < 6; i++) {   // приращение i++ уже в заголовке
    printf("%d ", i);
    i++;                        // лишнее изменение счетчика в теле
}
// фактический вывод: 0 2 4

Вместо 0 1 2 3 4 5 получаем 0 2 4. Clang и GCC предупреждают об этом (-Wfor-loop-analysis: variable incremented both in the loop header and in the loop body), поэтому компилируйте с флагом -Wall и читайте предупреждения. Исправление — не трогать счетчик в теле цикла for; если нужен другой шаг, задайте его в заголовке (i += 2).

Выводы

  • В C и C++ три цикла: for (число итераций известно), while (проверка условия до тела) и do-while (тело выполняется минимум один раз).
  • Базовый синтаксис циклов, а также break (выход) и continue (следующая итерация) в обоих языках идентичны.
  • range-based for (for (x : контейнер)) — особенность C++ с версии C++11; в чистом C его нет, там перебор идет по индексу или указателю.
  • break во вложенном цикле выходит только из своего цикла; в C/C++ нет break с меткой — нужен флаг, return или goto.
  • Главные ловушки: off-by-one (< против <=), сравнение счетчика по != при шаге больше 1 (зацикливание) и лишнее изменение счетчика в теле. Компиляция с -Wall ловит часть из них.

Где применяется / связь с практикой

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

Освойте тему на практике

Системно разобраться в языке, указателях, памяти и стандартной библиотеке помогает курс C++ Basic: там циклы и работа с данными идут в связке с реальными задачами и проверкой кода наставником. Прежде чем выбирать курс, можно посмотреть, как устроены занятия, на бесплатных открытых уроках — разбор конкретной темы в прямом эфире с преподавателем.

Смежные темы: Циклы в Bash.

FAQ

Чем for отличается от while, если оба перебирают числа?
Различие в стиле, а не в возможностях: любой for можно переписать через while и наоборот. for компактно держит счетчик, условие и шаг в одном заголовке — его берут, когда число итераций известно. while удобнее, когда повтор зависит от условия, а не от счетчика.

Можно ли объявлять переменную счетчика прямо в for в языке C?
Да, начиная со стандарта C99 (for (int i = 0; ...)). В старом C89 это запрещено — счетчик объявляли до цикла. В C++ объявление в заголовке допустимо во всех версиях.

Как выйти сразу из двух вложенных циклов?
Один break выходит только из своего цикла. Чтобы прервать оба, заводят флаг-переменную и проверяют ее во внешнем цикле, выносят код в функцию и делают return, либо используют goto на метку после циклов. В C и C++ break с меткой не поддерживается.

OTUS Журнал