1. Вступ
Іноді цикл записується дуже просто: «поки i <= n — виконуй». Але в реальному житті умова зупинки може ховатися всередині тіла циклу: ми щось шукаємо, перевіряємо введення, чекаємо на «стоп-слово», виявляємо помилку або знаходимо відповідь раніше. У таких випадках зручніше мати явні команди керування потоком: break для виходу з циклу і continue для пропуску частини ітерації. Головне — користуватися ними усвідомлено: це як гострий кухонний ніж — корисний інструмент, але про обережність теж забувати не варто.
Для початку — коротка таблиця-навігація за змістом:
| Команда | Що робить | Де діє |
|---|---|---|
|
миттєво завершує найближчий цикл | лише один — найвнутрішніший цикл |
|
пропускає решту тіла й переходить до наступної ітерації | також діє лише в поточному, тобто найближчому, циклі |
2. break: «знайшли — виходимо»
break потрібен тоді, коли немає сенсу виконувати цикл до кінця, бо відповідь уже знайдено або продовжувати далі просто безглуздо. Особливо часто це трапляється під час пошуку: перший дільник числа, перше відповідне значення, перша помилка у введенні, команда «вихід» у меню. Важливо памʼятати просте правило: break не стрибає куди завгодно, а завершує лише найближчий цикл, усередині якого він знаходиться.
Мініприклад: шукаємо перший дільник
Уявімо, що ми шукаємо найменший дільник числа n (крім 1). Щойно ми його знайшли, далі перевіряти вже немає сенсу.
#include <iostream>
int main() {
int n = 0;
std::cin >> n;
int firstDiv = 0;
for (int d = 2; d <= n; d = d + 1) {
if (n % d == 0) {
firstDiv = d;
break; // знайшли — виходимо
}
}
std::cout << firstDiv << '\n'; // наприклад: 15 -> 3
}
Тут break робить код чеснішим: ми справді шукаємо перший відповідний результат, тож не зобовʼязані перевіряти всі значення аж до n.
break у while: читаємо, доки не скажуть «стоп»
Іноді умову зручніше тримати «всередині», а не в заголовку циклу. Наприклад, читаємо команди, доки не зустрінемо 0.
#include <iostream>
int main() {
while (true) {
int x = 0;
std::cin >> x;
if (x == 0) {
break; // 0 — це команда виходу
}
std::cout << "Прочитано: " << x << '\n'; // Прочитано: 5
}
}
Так, тут while (true) виглядає як «нескінченний» цикл, але насправді він скінченний, бо всередині є зрозумілий вихід через break. Це нормально, якщо умова виходу справді одна, а логіка залишається прозорою.
3. continue: «пропускаємо зайве, працюємо з потрібним»
continue корисний, коли в циклі трапляються «зайві» випадки, які ми хочемо пропустити. Типовий сценарій — фільтрація: друкувати лише непарні числа, підсумовувати лише додатні, ігнорувати значення поза діапазоном. Але continue має одну важливу особливість: усе, що написано нижче continue; у поточній ітерації, не виконається. Саме тому новачки часто запитують: «Чому програма нічого не робить?»
Small example: print only odd numbers
Мініприклад: друкуємо лише непарні
#include <iostream>
int main() {
for (int i = 1; i <= 10; i = i + 1) {
if (i % 2 == 0) {
continue; // парні пропускаємо
}
std::cout << i << '\n'; // 1 3 5 7 9
}
}
Сенс тут читається майже «по-людськи»: «якщо число парне — пропусти; інакше друкуй».
continue як захист від невідповідного введення
Поки що ми не розглядаємо складне відновлення std::cin після помилок, тому вважатимемо, що введення коректне, але може бути «невідповідним» — наприклад, відʼємним.
#include <iostream>
int main() {
int sum = 0;
for (int k = 0; k < 5; k = k + 1) {
int x = 0;
std::cin >> x;
if (x < 0) {
continue; // ігноруємо відʼємні
}
sum = sum + x;
}
std::cout << sum << '\n';
}
Тут continue допомагає не роздувати код великим if/else: ми одразу «відсікаємо» непотрібний випадок.
Нюанс: continue у for і while працює по-різному
На перший погляд здається, що continue просто «переходить до наступної ітерації». Загалом це так. Але є тонка різниця: важливо, що саме відбувається між continue і наступною перевіркою умови. У for після continue однаково виконується крок (i = i + 1), а у while окремого кроку немає — програма відразу переходить до перевірки умови. Іноді цей нюанс рятує від нескінченних циклів… а іноді, навпаки, їх створює.
Подивімося на схему (спрощено, без деталей):
flowchart TD
A["for: тіло"] --> B{"continue?"}
B -- "так" --> C["крок (i = i + 1)"]
B -- "ні" --> D["кінець тіла"]
D --> C
C --> E{"умова?"}
E -- "так" --> A
E -- "ні" --> F["вихід"]
W["while: тіло"] --> X{"continue?"}
X -- "так" --> Y{"умова?"}
X -- "ні" --> Z["кінець тіла"]
Z --> Y
Y -- "так" --> W
Y -- "ні" --> Q["вихід"]
Практичний висновок такий: якщо ваш «крок до завершення» стоїть нижче continue, він може не виконатися, і цикл почне підозріло нагадувати вічний двигун.
4. Вкладені цикли: «цикл усередині циклу» і для чого це потрібно
Вкладений цикл — це цикл усередині тіла іншого циклу. Уявіть одну коробку в іншій: зовнішній цикл керує «великими кроками», а внутрішній — «дрібними повтореннями» на кожному з них. Таку конструкцію використовують для таблиць, сіток, перебору пар значень, малювання прямокутників символами, роботи з координатами (рядок/стовпець). Важливо звикнути до думки, що внутрішній цикл запускається заново на кожній ітерації зовнішнього, тому кількість дій швидко зростає. Це нормально, якщо ви розумієте, навіщо саме так робите.
Таблиця множення 1..5
#include <iostream>
int main() {
for (int a = 1; a <= 5; a = a + 1) {
for (int b = 1; b <= 5; b = b + 1) {
std::cout << (a * b) << ' ';
}
std::cout << '\n';
}
}
Зовнішній цикл задає рядки (a), внутрішній — стовпці (b). Це зручна модель двовимірного подання, навіть якщо двовимірні масиви ми ще не вивчали.
Малюємо прямокутник із #
Тут теж маємо вкладені цикли: зовнішній відповідає за рядки, внутрішній — за кількість символів у кожному рядку.
#include <iostream>
int main() {
int h = 3;
int w = 6;
for (int row = 0; row < h; row = row + 1) {
for (int col = 0; col < w; col = col + 1) {
std::cout << '#';
}
std::cout << '\n';
}
}
Такі приклади добре тренують розуміння меж: row < h означає рівно h рядків, а col < w — рівно w символів у кожному рядку.
5. break у вкладених циклах: чому не виходить «із усього одразу»
Саме тут і зʼявляється найпоширеніша несподіванка: break виходить лише з найближчого циклу. Якщо ви перебуваєте всередині внутрішнього циклу, то break завершить саме його, а зовнішній продовжить працювати, ніби нічого не сталося.
Шукаємо пару a * b == target
#include <iostream>
int main() {
int target = 12;
bool found = false;
for (int a = 1; a <= 10; a = a + 1) {
for (int b = 1; b <= 10; b = b + 1) {
if (a * b == target) {
std::cout << a << " * " << b << " = " << target << '\n'; // 2 * 6 = 12
found = true;
break; // виходимо лише з внутрішнього циклу
}
}
if (found) {
break; // тепер виходимо і з зовнішнього
}
}
}
Тут прапорець found — це простий спосіб «передати назовні», що результат уже знайдено. Ми не використовуємо нічого складного: лише bool, if і два break.
6. Коли break/continue допомагають, а коли заважають
Дуже легко почати розвʼязувати будь-яку задачу за допомогою break і continue, а в підсумку отримати код, який читається як квест: «тут стрибни, тут пропусти, тут вийди, але тільки якщо не вийшов раніше». Хороший стиль — ставитися до цих операторів як до спецій: трохи робить страву смачнішою, а якщо висипати всю банку, вийде вже не їжа, а експеримент.
Розгляньмо два типові випадки.
continue як акуратний фільтр — це добре
Якщо на початку ітерації є «охоронець», який відсікає непотрібні випадки, то continue робить код коротшим і чистішим. Ми вже бачили приклад із непарними числами — він читається дуже просто.
Багато continue і break упереміш — зазвичай погано
Порівняймо два стилі. Перший — «перестрибування»:
#include <iostream>
int main() {
for (int i = 1; i <= 10; i = i + 1) {
if (i == 7) break;
if (i % 2 == 0) continue;
if (i == 3) continue;
std::cout << i << '\n';
}
}
Це ще не катастрофа, але вже доводиться тримати в голові надто багато винятків.
Другий стиль — «одна умова на друк»; часто він читається простіше:
#include <iostream>
int main() {
for (int i = 1; i <= 10; i = i + 1) {
bool shouldPrint = (i % 2 != 0) && (i != 3) && (i != 7);
if (shouldPrint) {
std::cout << i << '\n'; // 1 5
}
if (i == 7) {
break; // якщо справді потрібен достроковий вихід
}
}
}
Тут є один мінус: вираз стає довшим. Зате плюс у тому, що логіка «коли друкуємо» зібрана в одному місці й не ховається за переходами.
7. Меню в консолі: break/continue і вкладені цикли
Підтримаймо ідею «невеликого застосунку», яку ми почали в темі про do-while, на прикладі простого меню. Ми не перетворюватимемо main на величезного монстра, але акуратно розширимо приклад: додамо команду друку таблиці й команду пошуку дільника. Саме тут вкладені цикли та break виглядають цілком природно.
Каркас меню та команда «таблиця 1..N»
#include <iostream>
int main() {
int cmd = -1;
do {
std::cout << "1 - Таблиця множення\n";
std::cout << "2 - Перший дільник\n";
std::cout << "0 - Вихід\n";
std::cin >> cmd;
if (cmd == 1) {
int n = 0;
std::cin >> n;
for (int a = 1; a <= n; a = a + 1) {
for (int b = 1; b <= n; b = b + 1) {
std::cout << (a * b) << ' ';
}
std::cout << '\n';
}
}
} while (cmd != 0);
}
Зверніть увагу: ми не використовуємо switch (його ще не проходили), тому меню будуємо через if. Так, це трохи довше, зате повністю відповідає вашим поточним знанням.
Команда «перший дільник»: break + перевірка через continue
Тепер додамо другу команду й покажемо, як continue може «відсікати» неправильний сценарій. Наприклад, дільник має сенс шукати лише для n >= 2.
#include <iostream>
int main() {
int cmd = -1;
do {
std::cout << "1 - Таблиця множення\n";
std::cout << "2 - Перший дільник\n";
std::cout << "0 - Вихід\n";
std::cin >> cmd;
if (cmd == 2) {
int n = 0;
std::cin >> n;
if (n < 2) {
std::cout << "Потрібно n >= 2\n";
continue; // повертаємося до меню, не робимо зайвого
}
int firstDiv = 0;
for (int d = 2; d <= n; d = d + 1) {
if (n % d == 0) {
firstDiv = d;
break;
}
}
std::cout << firstDiv << '\n';
}
} while (cmd != 0);
}
Зауважте важливу річ: continue тут стосується циклу do-while, тобто означає: «пропусти решту тіла меню й покажи його знову». Це логічно: якщо n некоректне, ми просто повертаємося до меню.
8. Типові помилки: break/continue і вкладені цикли
Помилка № 1: очікування, що break «вийде з усіх циклів одразу».
Новачки часто думають, що break — це така червона кнопка «зупинити все». Насправді він завершує лише найближчий цикл. Якщо ви перебуваєте всередині вкладеної конструкції, зовнішній цикл продовжить роботу. Виправляють це або прапорцем (наприклад, bool found), або перебудовою логіки так, щоб зовнішній цикл теж бачив умову завершення.
Помилка № 2: continue стоїть вище «кроку до завершення», і цикл стає нескінченним.
Особливо легко припуститися цього у while, де крок часто пишуть наприкінці тіла. Якщо за певної умови ви виконуєте continue;, а зміна змінної стоїть нижче, вона не виконається, умова не зміниться, і цикл повторюватиметься без кінця. Щоб цього уникнути, варто або розміщувати «крок» до можливих continue, або будувати логіку так, щоб continue не оминав важливих змін стану.
Помилка № 3: код після continue «раптом не виконується», хоча здається, що має.
Це не вада компілятора і не «зламаний C++», а буквальна семантика continue: пропустити решту тіла. Тому небезпечно розміщувати важливу логіку нижче continue і забувати, що вона не спрацює. Якщо в ітерації потрібно і відфільтрувати випадок, і виконати обовʼязкові дії, ці частини краще розділити: обовʼязкове — до continue, решту — після.
Помилка № 4: вкладені цикли написані правильно, але межі випадково переплутані місцями.
Класична ситуація: хотіли row < h і col < w, а випадково написали row < w і col < h. Код компілюється, але результат виходить «кривим», і налагоджувати це неприємно, бо формально все виглядає розумно. Допомагає звичка називати змінні за змістом (row, col) і прокручувати в голові маленький приклад (наприклад, h = 2, w = 3), щоб уявити конкретну форму результату.
Помилка № 5: надто багато break/continue перетворюють цикл на «стрибаючий» сценарій.
break і continue зручні, але кожен із них додає точку нелінійного переходу. Коли таких переходів стає забагато, читати код важко: доводиться подумки симулювати його виконання. Якщо відчуваєте, що в одному циклі вже третій такий перехід через continue, часто краще переписати умову прямолінійніше: зробити один зрозумілий if для дії або винести частину перевірки в змінну bool із промовистою назвою.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ