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 (...), і переконайтеся, що хоча б одна з них змінюється так, щоб умова стала хибною. Якщо жодна — у вас майже гарантовано вічний двигун, а патент на нього все ще не видали.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ