1. Знайомство з циклами
Коли ви тільки починаєте програмувати, логіка ніби підказує: «Ну, мені треба вивести 1, 2, 3… то я просто напишу три cout». Це працює рівно доти, доки не знадобиться вивести 1…100 або повторювати дію, поки введення не стане коректним. У цей момент копіювання вручну перетворюється на катастрофу: помилку доведеться виправляти у 100 місцях, а очі почнуть ненавидіти монітор.
Цикл розвʼязує цю проблему просто: замість ручного повторення ви описуєте правило. Якщо дуже грубо, цикл — це домовленість із компʼютером: «Я даю тобі умову й тіло, а ти повторюй акуратно, доки умова істинна». А компʼютер ніби відповідає: «О, нарешті нормальне завдання».
Синтаксис while і порядок виконання
Із while важливо подружитися на рівні «як він насправді працює», інакше ви писатимете цикли, які або не виконуються, або тривають без кінця. Гарна новина: правило дуже просте.
Загальний вигляд циклу:
while (умова) {
// тіло циклу
}
Суть така: перед кожним виконанням тіла перевіряється умова. Якщо вона істинна (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 міститься умова продовження. А всередині тіла має бути крок, який наближає нас до моменту, коли умова стане хибною.
Зручно тримати в голові таку табличку:
| Частина | Де знаходиться | Приклад | Навіщо потрібна |
|---|---|---|---|
| Підготовка | перед циклом | |
задати старт |
| Умова | у while (...) | |
вирішити, чи продовжуємо |
| Крок | усередині тіла | |
«рухатися» до завершення |
Подивімося на класичну задачу: обчислити суму чисел від 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 <= 3 → false, і цикл зупиняється.
Інваріант — ваш внутрішній компас. Якщо компас починає брехати, значить, ви не в лісі, а в циклі без кроку.
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 (...), і переконайтеся, що хоча б одна з них змінюється так, щоб умова стала хибною. Якщо жодна — у вас майже гарантовано вічний двигун, а патент на нього все ще не видали.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ