На цьому уроці ми детально розглянемо цикл while, його конструкцію, особливості та використання.
Цикл while
Цикл while є найпростішим з 4 циклів в мові С++ і дуже схожий на розгалуження if/else:
while (умова)
тіло циклу;
Цикл while оголошується з використанням ключового слова while. Спочатку оброблюється умова
. Якщо її значенням є true (будь-яке ненульове значення), то тоді виконується тіло циклу
.
Однак, на відміну від оператора if, після завершення виконання тіла циклу
, управління повертається назад до while і процес перевірки умови
повторюється. Якщо умова
знову є true, то тоді тіло циклу
виконується ще раз.
Наприклад, наступна програма виводить всі числа від 0 до 9:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> int main() { int count = 0; while (count < 10) { std::cout << count << " "; ++count; } std::cout << "done!"; return 0; } |
Результат виконання програми:
0 1 2 3 4 5 6 7 8 9 done!
Розглянемо детально цю програму. По-перше, виконується ініціалізація змінної: int count = 0;
. Умова 0 < 10
має значення true, тому виконується тіло циклу. У першому стейтменті ми виводимо 0
, а в другому — виконуємо інкремент змінної count
. Потім управління повертається до початку циклу while для повторної перевірки умови. Умова 1 < 10
має значення true, тому тіло циклу виконується ще раз. Тіло циклу буде повторно виконуватися до тих пір, поки змінна count
не дорівнюватиме 10
. Тільки тоді, коли результат умови 10 < 10
буде false, цикл завершиться.
Цикл while може і взагалі не виконуватися. Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> int main() { int count = 15; while (count < 10) { std::cout << count << " "; ++count; } std::cout << "done!"; return 0; } |
Умова 15 < 10
відразу приймає значення false, і тіло циклу пропускається. Єдине, що виведе ця програма:
Нескінченні цикли
З іншого боку, якщо умова циклу завжди приймає значення true, то і сам цикл буде виконуватися нескінченно. Це називається нескінченним циклом. Наприклад:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { int count = 0; while (count < 10) // ця умова ніколи не буде false std::cout << count << " "; // тому цей рядок виконуватиметься постійно return 0; // а цей рядок ніколи не виконається } |
Оскільки змінна count
не збільшується на одиницю в програмі, то умова count < 10
завжди буде true. Відповідно, цикл ніколи не завершиться і програма буде постійно виводити 0 0 0 0 0 ...
.
Ми можемо навмисно оголосити нескінченний цикл наступним чином:
1 2 3 4 |
while (1) // або while (true) { // Цей цикл виконуватиметься постійно } |
Єдиний спосіб вийти з нескінченного циклу — використати один з наступних операторів: return, break, exit, goto або згенерувати виняток.
Програми, які працюють до тих пір, поки користувач не вирішить зупинити їх, іноді навмисно використовують нескінченні цикли разом з операторами return, break або exit для завершення циклу. Поширена така практика в серверних веб-додатках, які працюють безперервно і постійно обслуговують веб-запити.
Лічильник циклу while
Часто нам потрібно буде, щоб цикл виконувався певну кількість разів. Для цього зазвичай використовується змінна у вигляді лічильника циклу. Лічильник циклу — це цілочисельна змінна, яка оголошується з єдиною метою: рахувати, скільки разів виконався цикл. У вищенаведених прикладах змінна count
є лічильником циклу.
Лічильникам циклу часто дають прості імена, такі як i
, j
чи k
. Проте в цих іменах є одна серйозна проблема. Якщо ви захочете дізнатися, де у вашій програмі використовується лічильник циклу і скористаєтеся функцією пошуку символів i
, j
чи k
, то в результаті отримаєте половину своєї програми, так як i
, j
та k
використовуються в багатьох іменах. Отже, краще використовувати iii
, jjj
чи kkk
в якості імен для лічильників. Вони більш унікальні, їх значно простіше знайти, і вони виділяються в коді. А ще краще використовувати «реальні» імена для змінних, наприклад, count
або будь-яке інше ім’я, яке надає контекст використання цієї змінної.
Також для лічильників циклу краще використовувати тип signed int. Використання unsigned int може призвести до несподіваних результатів, наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> int main() { unsigned int count = 10; // Рахуємо від 10 до 0 while (count >= 0) { if (count == 0) std::cout << "blastoff!"; else std::cout << count << " "; --count; } return 0; } |
Погляньте на вищенаведену програму ще раз і постарайтеся знайти помилку.
Виявляється, ця програма є нескінченним циклом. Вона починається з виводу 10 9 8 7 6 5 4 3 2 1 blastoff!
, як і передбачалося, але потім все йде шкереберть і починається відлік з 4294967295
. Чому? Тому що умова циклу count >= 0
ніколи не буде хибною! Коли count = 0
, то і умова 0 >= 0
має значення true, виводиться blastoff
, а потім виконується декремент змінної count
, відбувається переповнення і значенням змінної стає 4294967295
. А оскільки умова 4294967295 >= 0
є істинною, то програма продовжує своє виконання. Лічильник циклу є типу unsigned, він ніколи не зможе бути від’ємним і цикл ніколи не завершиться.
Правило: Завжди використовуйте тип signed int для лічильників циклу.
Ітерації
Кожне виконання циклу називається ітерацією (або “повтором”).
Оскільки тіло циклу зазвичай є блоком, і оскільки цей блок виконується з кожним повтором по-новому, то будь-які змінні, оголошені всередині тіла циклу, створюються, а потім і знищуються по-новому. У наступному прикладі змінна z
створюється і знищується 6 разів:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> int main() { int count = 1; int result = 0; // змінна result визначена тут, оскільки вона нам знадобиться пізніше (поза тілом циклу) while (count <= 6) // буде 6 ітерацій { int z; // змінна z створюється тут по-новому з кожною ітерацією std::cout << "Enter integer #" << count << ':'; std::cin >> z; result += z; // Збільшуємо значення лічильника циклу на одиницю ++count; } // змінна z знищується тут по-новому з кожною ітерацією! std::cout << "The sum of all numbers entered is: " << result; return 0; } |
Для фундаментальних типів змінних це нормально. Для нефундаментальних типів змінних (таких як структури або класи) це може позначитися на продуктивності. Отже, нефундаментальні типи змінних краще визначати перед циклом.
Зверніть увагу, що змінна count
оголошена поза тілом циклу. Це важливо і необхідно, оскільки нам потрібно, щоб значення змінної зберігалося протягом усіх ітерацій (а не знищувалося по-новому з кожною ітерацією).
Іноді нам може знадобитися виконати щось при досягненні певної кількості ітерацій, наприклад, вставити символ нового рядка. Це легко здійснити, використовуючи оператор залишку від ділення з лічильником циклу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> int main() { int count = 1; while (count <= 50) { // Виводимо числа до 10 (додаємо до кожного числа 0) if (count < 10) std::cout << "0" << count << " "; else std::cout << count << " "; // виводимо інші числа // Якщо лічильник циклу ділиться на 10 без залишку, то тоді вставляємо символ нового рядка if (count % 10 == 0) std::cout << "\n"; // Збільшуємо значення лічильника циклу на одиницю ++count; } return 0; } |
Результат виконання програми:
01 02 03 04 05 06 07 08 09 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
Вкладені цикли while
Одні цикли while можуть бути вкладені всередині інших циклів while. У наступному прикладі внутрішній і зовнішній цикли мають свої власні лічильники. Однак, зверніть увагу, що умова внутрішнього циклу використовує лічильник зовнішнього циклу!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> int main() { int outer = 1; while (outer <= 5) { int inner = 1; while (inner <= outer) std::cout << inner++ << " "; // Вставляємо символ нового рядка в кінці кожного рядка std::cout << "\n"; ++outer; } return 0; } |
Результат виконання програми:
Тест
Завдання №1
Чому у вищенаведеній програмі змінна inner
оголошена всередині блоку while, а не відразу після оголошення змінної outer
(поза блоком while)?
Відповідь №1
Змінна inner
оголошена всередині блоку while так, щоб вона була відновлена (і повторно ініціалізована значенням 1
) кожен раз, коли виконується зовнішній цикл. Якби змінна inner
була оголошена поза циклом while, то її значення ніколи не було б скинуто до 1
, або нам би довелося це зробити самостійно за допомогою операції присвоювання. Крім того, оскільки змінна inner
використовується тільки всередині зовнішнього циклу while, то є сенс оголосити її саме там. Пам’ятайте, що змінні потрібно оголошувати максимально близько до їх першого використання!
Завдання №2
Напишіть програму, яка виводить букви англійського алфавіту від a
до z
разом з відповідними кодами з ASCII-таблиці.
Підказка: Щоб виводити символи як цілі числа — використовуйте оператор static_cast.
Відповідь №2
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int main() { char mychar = 'a'; while (mychar <= 'z') { std::cout << mychar << " " << static_cast<int>(mychar) << "\n"; ++mychar; } return 0; } |
Завдання №3
Інвертуйте програму з останнього підрозділу “Вкладені цикли while” так, щоб вона виводила наступне:
5 4 3 2 1
4 3 2 1
3 2 1
2 1
1
Відповідь №3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> int main() { int outer = 5; while (outer >= 1) { int inner = outer; while (inner >= 1) std::cout << inner-- << " "; // Вставляємо символ нового рядка в кінець кожного рядка std::cout << "\n"; --outer; } return 0; } |
Завдання №4
Тепер зробіть так, щоб цифри виводилися наступним чином (використовуючи програму з попереднього завдання):
1
2 1
3 2 1
4 3 2 1
5 4 3 2 1
Підказка: Розберіться спочатку, як виводити числа наступним чином:
X X X X 1
X X X 2 1
X X 3 2 1
X 4 3 2 1
5 4 3 2 1
Відповідь №4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include <iostream> int main() { // Цикл з 1 до 5 int outer = 1; while (outer <= 5) { // Числа в рядах з'являються в порядку спадання, тому цикл починаємо з 5 і до 1 int inner = 5; while (inner >= 1) { // Перше число в будь-якому ряді співпадає з номером цього рядка, // тому числа повинні виводитися тільки, якщо <= номер рядка (в протилежному випадку виводиться пробіл) if (inner <= outer) std::cout << inner << " "; else std::cout << " "; // вставляємо додаткові пробіли --inner; } // Цей рядок вивели, тому переходимо до наступного std::cout << "\n"; ++outer; } } |
Завдання ніби прості, але прийшлось поламати голову. Ніяк з першого разу не міг придумати код. Методом проб і помилок все ж виконав. З кожним новим розділом відчуваю прогрес. Мабуть це найкращий ресурс по С++. Дякую за такі зрозумілі уроки українською.