JavaRush /Курсы /C++ SELF /Цикл while в C++

Цикл while в C++

C++ SELF
4 уровень , 1 лекция
Открыта

1. Знакомство с циклами

Когда вы только начинаете программировать, кажется логичным: “Ну мне надо вывести 1, 2, 3… так я и напишу три cout”. Это работает ровно до тех пор, пока не понадобится вывести 1…100 или повторить действие до тех пор, пока ввод не станет корректным. В этот момент копипаст превращается в катастрофу: ошибку вы будете исправлять в 100 местах, а глаза начнут ненавидеть монитор.

Цикл решает эту проблему просто: вместо “повторить руками” вы описываете правило повтора. Если очень грубо, цикл — это договор с компьютером: “Я даю тебе условие и тело, а ты повторяй аккуратно, пока условие правда”. И компьютер такой: “О, наконец-то нормальная задача”.

Цикл в реальной жизни: Пока Барни в баре — Мо выбрасывает его.

Синтаксис while и порядок выполнения

С while важно подружиться на уровне “как оно реально исполняется”, иначе вы будете писать циклы, которые либо не выполняются, либо выполняются вечно. Хорошая новость: правило очень простое.

Форма цикла:

while (условие) {
    // тело цикла
}
Общая форма цикла while в C++

Смысл: перед каждым выполнением тела вычисляется условие. Если оно истинно (true) — тело выполняется. Если ложно (false) — цикл заканчивается (или вообще не начинается).

Мини‑пример “обратный отсчёт”:

#include <iostream>

int main() {
    int x = 3;

    while (x > 0) {
        std::cout << x << '\n';  // 3, потом 2, потом 1
        x = x - 1;
    }

    std::cout << "Go!\n";        // Go!
}

Здесь происходит очень “честная” вещь: пока x > 0, печатаем x и уменьшаем. Как только x станет 0, условие станет ложным и цикл остановится.

Обратите внимание на важную мысль: while может не выполниться ни разу. Например:

#include <iostream>

int main() {
    int x = 0;

    while (x > 0) {
        std::cout << "Inside\n";
    }

    std::cout << "Outside\n"; // Outside
}

Это не баг и не “странность C++”. Это нормальная логика: если условие ложное сразу, то выполнять нечего.

2. Три части хорошего while: подготовка, условие, шаг

Большинство нормальных (то есть завершающихся) циклов while можно мысленно разложить на три части. И это очень помогает находить ошибки.

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

Удобно держать такую табличку в голове:

Часть Где находится Пример Зачем нужна
Подготовка перед циклом
int i = 1;
задать старт
Условие в while (...)
i <= n
решить, продолжаем ли
Шаг внутри тела
i = i + 1;
“двигаться” к завершению

Посмотрим на классическую задачу: посчитать сумму чисел от 1 до n.

#include <iostream>

int main() {
    int n = 0;
    std::cin >> n;

    int i = 1;        // подготовка
    int sum = 0;      // подготовка

    while (i <= n) {  // условие продолжения
        sum = sum + i;
        i = i + 1;    // шаг к завершению
    }

    std::cout << sum << '\n';
}

Здесь легко “прочитать” идею: пока i не дошёл до n, добавляем i в сумму и увеличиваем i. Шаг гарантирует, что когда-нибудь i станет n + 1, условие i <= n станет ложным, и цикл остановится.

3. Инвариант цикла: внутренняя “клятва”

Слово “инвариант” звучит как имя босса из RPG, но на практике это очень простая штука. Инвариант — это утверждение, которое остаётся верным на каждой итерации (на каждом “круге” цикла). Он нужен не для математики, а для самопроверки: “А я вообще делаю то, что думаю?”

Возьмём сумму 1..n. В этом цикле удобный инвариант звучит так: “Перед каждой итерацией sum содержит сумму чисел от 1 до i - 1”. Это предложение кажется занудным, но оно сразу защищает вас от типичных ошибок: если вы случайно начнёте с i = 0 или забудете i = i + 1, инвариант перестанет быть правдой (или вы не сможете объяснить, почему он правдив), а значит, логика сломалась.

Давайте посмотрим на маленьком n = 3 и проверим инвариант “вручную”:

  • до входа в цикл: i = 1, sum = 0. “Сумма от 1 до 0” — это 0. Инвариант верен.
  • после первой итерации: добавили 1, стало sum = 1, увеличили i = 2. Теперь “сумма от 1 до 1” — это 1. Верно.
  • после второй: sum = 3, i = 3. “сумма от 1 до 2” — это 3. Верно.
  • после третьей: sum = 6, i = 4. “сумма от 1 до 3” — это 6. Верно. Потом i <= n становится 4 <= 3false, цикл остановится.

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

4. Полезные шаблоны while на коротких примерах

Сейчас мы потренируемся на задачах, которые встречаются постоянно. Тут цель не “решить задачу”, а увидеть повторяющийся паттерн: подготовка, условие, шаг.

Подсчёт цифр в числе

Иногда “шаг к завершению” — это не увеличение счётчика, а уменьшение объекта задачи.

#include <iostream>

int main() {
    int n = 0;
    std::cin >> n;

    int digits = 0;

    while (n > 0) {
        digits = digits + 1;
        n = n / 10;          // шаг: уменьшаем число
    }

    std::cout << digits << '\n';
}

Здесь важно, что n постепенно становится меньше: 1234 → 123 → 12 → 1 → 0. И цикл заканчивается.

Если вы введёте 0, цикл не выполнится ни разу и напечатает 0. Это логично, но иногда хочется считать, что у числа 0 “одна цифра”. Это пример того, как требования задачи влияют на логику цикла — но сегодня мы не углубляемся, просто замечаем поведение.

Поиск первого делителя без break

Пока мы ещё не проходили break (это будет дальше в уровне), поэтому сделаем пример без досрочного выхода: через “флаг результата”.

#include <iostream>

int main() {
    int n = 0;
    std::cin >> n;

    int d = 2;
    int first_divisor = 0;

    while (d <= n && first_divisor == 0) {
        if (n % d == 0) {
            first_divisor = d;
        }
        d = d + 1;
    }

    std::cout << first_divisor << '\n';
}

Тут условие продолжения составное: “идём по делителям, пока не дошли до n и пока ещё не нашли делитель”. Это хороший пример того, что while не обязан быть “ровно N раз”: он может остановиться, когда цель достигнута.

Чтение чисел до стоп-значения

Это очень жизненный сценарий: читаем числа, пока пользователь не введёт 0 (или -1, или любое другое значение‑сигнал).

#include <iostream>

int main() {
    int x = 0;
    std::cin >> x;

    int sum = 0;

    while (x != 0) {
        sum = sum + x;
        std::cin >> x;       // шаг: читаем следующее значение
    }

    std::cout << sum << '\n';
}

Здесь “шаг к завершению” — это новый ввод, который потенциально может стать 0 и остановить цикл. Это важная мысль: шагом может быть изменение переменной через cin.

5. Типичные бесконечные циклы: как они появляются и как их “вылечить”

Бесконечный цикл — это не всегда зло (иногда так и задумано), но для новичка бесконечность чаще всего означает: “я забыл шаг” или “условие сформулировано неправильно”. То есть программа не зависла “потому что C++ сложный”, а честно делает то, что вы попросили… просто вы попросили вечность.

Небольшой факт из мира стандартизации: у C++‑комитета даже встречается отдельное обсуждение “тривиальных бесконечных циклов” как формального случая поведения программ.
Мы же будем приземлённее: научимся находить бесконечность в своём коде, пока она не нашла вас.

Ошибка №1: переменная в условии не меняется

Смотрите, как просто “сломать” цикл:

#include <iostream>

int main() {
    int x = 3;

    while (x > 0) {
        std::cout << x << '\n';
        // x = x - 1;  // забыли шаг
    }
}

Условие x > 0 зависит от x, но x не меняется. Значит условие всегда истинно. Итог: бесконечная печать “3”.

Лечение банальное: вернуть шаг (x = x - 1;). Но полезная привычка такая: перед запуском цикла отвечать себе на вопрос “что меняется за одну итерацию и почему это приближает к завершению?”

Ошибка №2: случайный ; после while

Это одна из самых коварных ошибок, потому что глазами она почти не видна. Вот так:

#include <iostream>

int main() {
    int x = 3;

    while (x > 0); {          // <- ВНИМАНИЕ: лишняя точка с запятой
        std::cout << x << '\n';
        x = x - 1;
    }
}

Что происходит? while (x > 0); — это цикл с пустым телом. Он будет “крутиться” и ничего не делать. А блок { ... } после него — просто обычный блок, который выполнится один раз (после того, как цикл вдруг завершится… но он не завершится, ведь x внутри цикла не меняется).

Чтобы такое не ловить, в учебных примерах всегда пишите фигурные скобки и ставьте { на следующей строке так, чтобы ; был заметнее.

Ошибка №3: “шаг есть, но не тот”

Иногда вы честно пишете шаг… только меняете не ту переменную, которая влияет на условие.

#include <iostream>

int main() {
    int x = 3;
    int y = 0;

    while (x > 0) {
        y = y + 1;           // шаг меняет y, но условие зависит от x
        std::cout << y << '\n';
    }
}

Это тоже бесконечность: x как был 3, так и остался.

Здесь помогает простая техника: выпишите переменные, которые участвуют в условии (x), и проверьте, что они изменяются в теле так, чтобы условие стало ложным.

Ошибка №4: цикл ввода без нового ввода

Очень частая ситуация: вы хотите читать много чисел, но забываете прочитать “следующее”.

#include <iostream>

int main() {
    int x = 0;
    std::cin >> x;

    while (x != 0) {
        std::cout << x << '\n';
        // std::cin >> x;     // забыли обновить x
    }
}

Если x был не 0, цикл будет печатать одно и то же число бесконечно. В циклах чтения “шаг” почти всегда выглядит как повторный cin >> ... или изменение значения, которое вы читаете.

6. Мини‑пример и блок‑схема while

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

Идея: пользователь вводит числа (например, “очки за раунд”), а 0 означает “всё, хватит”. Мы считаем сумму, количество и максимальное значение. Пока не используем массивы и векторы (они будут гораздо позже), поэтому храним только агрегаты.

#include <iostream>

int main() {
    int x = 0;
    std::cin >> x;

    int sum = 0;
    int count = 0;
    int max_value = 0;

    while (x != 0) {
        sum = sum + x;
        count = count + 1;

        if (x > max_value) {
            max_value = x;
        }

        std::cin >> x;
    }

    std::cout << "count=" << count << '\n';
    std::cout << "sum=" << sum << '\n';
    std::cout << "max=" << max_value << '\n';
}

Если вы введёте 5 2 9 0, программа напечатает примерно так:

count=3
sum=16
max=9

Тут хорошо видно, как while дружит с тем, что вы уже знаете: переменные, if, сравнения, ввод/вывод. И это, по сути, один из самых частых “реальных” сценариев использования циклов.

Блок‑схема: как думает while

Чтобы окончательно зафиксировать порядок работы, полезно представить while как простую схему. Это не магия, а очень предсказуемый автомат:

flowchart TD
    A[Старт] --> B[Вычислить условие]
    B -->|false| E[Выход из цикла]
    B -->|true| C[Выполнить тело]
    C --> D[Вернуться к проверке]
    D --> B

Если вы когда-нибудь сомневаетесь, “почему цикл не заходит внутрь” или “почему не заканчивается”, возвращайтесь к этой картинке и спрашивайте: условие false? или условие всегда true? или шаг отсутствует?

7. Типичные ошибки при работе с while

Ошибка №1: нет шага к завершению, и условие никогда не меняется.
Обычно это выглядит как “печатает одно и то же” или “программа зависла”. Почти всегда причина в том, что переменная из условия не изменяется в теле. Лечится дисциплиной: перед запуском цикла объяснить вслух, какая строка делает шаг и почему условие однажды станет ложным.

Ошибка №2: лишняя точка с запятой после while (условие);.
Эта ошибка особенно неприятна тем, что компилятор не обязан ругаться: синтаксис формально корректный. Но логика меняется радикально: цикл становится пустым, а блок после него выполняется отдельно. Привычка писать тело в { ... } и аккуратно форматировать строки сильно снижает шанс поймать эту “невидимую стену”.

Ошибка №3: неверная граница (< вместо <= или наоборот).
В while это проявляется как “на одну итерацию больше/меньше”. Чтобы не гадать, полезно прогнать в голове первые два значения и последнее ожидаемое. Если вы хотите включить n в суммирование, условие должно быть i <= n. Если не хотите включать — i < n. Выбор знака — это не косметика, а смысл.

Ошибка №4: в циклах ввода забыли обновить читаемое значение.
Если вы читаете x до цикла, а внутри не делаете std::cin >> x ещё раз, то цикл будет работать с одним и тем же значением бесконечно. В цикле “читаем много значений” шаг чаще всего и есть повторный ввод: без него цикл не двигается.

Ошибка №5: шаг меняет не ту переменную.
Иногда кажется, что “я же что-то меняю”, но условие зависит от другой переменной. Это лечится простым приёмом: подчеркнуть переменные, которые встречаются в условии while (...), и убедиться, что хотя бы одна из них меняется так, чтобы условие стало ложным. Если ни одна — у вас почти гарантированно вечный двигатель (а патент на него всё ещё не выдали).

1
Задача
C++ SELF, 4 уровень, 1 лекция
Недоступна
Запуск ракеты
Запуск ракеты
1
Задача
C++ SELF, 4 уровень, 1 лекция
Недоступна
Копилка до стоп-слова
Копилка до стоп-слова
1
Задача
C++ SELF, 4 уровень, 1 лекция
Недоступна
Заряд аккумулятора в степени
Заряд аккумулятора в степени
1
Задача
C++ SELF, 4 уровень, 1 лекция
Недоступна
Проверка на “простое” по-честному
Проверка на “простое” по-честному
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ