Цикл — різновид керівної конструкції у високорівневих мовах програмування, призначений для організації багаторазового виконання набору інструкцій (команд). Також циклом може називатися будь-яка багаторазово виконувана послідовність команд, організована будь-яким чином (наприклад, із допомогою умовного переходу).
Визначення Послідовність інструкцій, призначена для багаторазового виконання, називається тілом циклу. Одноразове виконання тіла циклу називається ітерацією. Вираз, що визначає чи буде вчергове виконуватися ітерація, чи цикл завершиться, називається умовою виходу або умовою завершення циклу (або умовою продовження) залежно від того, як інтерпретується його істинність — як ознака необхідності завершення чи продовження циклу. Змінна, в якій зберігається номер поточної ітерації, називається лічильником ітерацій циклу або просто лічильником циклу. Цикл не обов’язково містить лічильник, також лічильник не зобов’язаний бути одним — умова виходу із циклу може залежати від декількох змінюваних в циклі змінних, а може визначатися зовнішніми умовами (наприклад, настанням певного часу), в останньому випадку лічильник взагалі не знадобиться.
Частинами виконання будь-якого циклу є початкова ініціалізація змінних циклу, перевірка умови виходу, виконання тіла циклу та оновлення змінної циклу на кожній ітерації. Крім того, більшість мов програмування надають засоби для дострокового керування циклом, наприклад, оператори завершення циклу, тобто виходу з циклу незалежно від істинності умови виходу (в мові С — break) і оператори пропущення ітерації (в мові С — continue).
Різновиди циклів
Безумовні цикли Іноді в програмах використовуються цикли, вихід з яких не передбачено логікою програми. Такі цикли називаються безумовними або нескінченними. Особливих синтаксичних засобів для створення таких циклів, через їхню нетиповість, мови програмування не передбачають, тому такі цикли створюються за допомогою конструкцій призначених для створення звичайних (або умовних) циклів. Для забезпечення нескінченного повторення перевірка умови в такому циклі відсутня (якщо дозволяє синтаксис, як, наприклад, у циклі LOOP … END LOOP мови Ада), або замінюється константним значенням (while true do … в Паскаль).
Цикл з передумовою Цикл з передумовою — цикл, що виконується доки істинна деяка умова, вказана перед його початком. Ця умова перевіряється до початку виконання тіла циклу, тому тіло може бути не виконане жодного разу (якщо умова з початку хибна). У більшості процедурних мов програмування здійснюється за допомогою інструкції while, звідси його друга назва — while-цикл.
На мові Паскаль цикл з передумовою має такий вигляд: while <умова> do begin <тіло циклу> end; На мові Сі: while(<умова>) { <тіло циклу> }
Цикл з післяумовою
Цикл з післяумовою — цикл, в якому умова перевіряється після виконання тіла циклу. Звідси випливає, що тіло циклу завжди виконується хоча б один раз. У мові Паскаль такий цикл здійснює інструкція repeat … until; у Сі — do … while.
На мові Паскаль цикл з післяумовою має такий вигляд: repeat <тіло циклу> until <умова> На мові Сі: do { <тіло циклу> } while(<умова>)
У трактуванні умови циклу з післяумовою в різних мовах є розбіжності. У Паскалі і мовах похідних від нього умова такого циклу трактується як умова виходу (цикл завершується, коли умова істинна), а в Сі та його нащадках — як умова продовження (цикл завершується, коли умова хибна).
Цикл з виходом з середини
Цикл з виходом з середини — найзагальніший тип умовного циклу. Синтаксично такий цикл оформляється за допомогою трьох інструкцій: початок циклу, кінець циклу та інструкції (команди) виходу з циклу. Інструкція початку позначає точку програми, з якої починається тіло циклу, інструкція кінця — точку, де тіло закінчується. Всередині тіла має бути присутня команда виходу з циклу, при виконанню якої цикл завершується і керування передається на оператор, наступний після інструкції кінця циклу. Природно, щоб цикл виконався більш ніж один раз, команда виходу має викликатися не безумовно, а тільки при виконанні умови виходу.
Принциповою відмінністю такого різновиду циклу від розглянутих вище є те, що частина тіла циклу, розташована після початку циклу і до команди виходу, виконується завжди (навіть якщо умова виходу з циклу істинна при першій ітерації), а частина тіла циклу, наступна за командою виходу, не виконується при останній ітерації.
Легко побачити, що за допомогою циклу з виходом з середини легко утворити як цикл з передумовою (розташувавши команду виходу на початку тіла циклу), так і цикл з післяумовою (розташувавши команду виходу в кінці тіла циклу).
Частина мов програмування містить особливі інструкції для утворення циклу з виходом з середини. Так, у мові Ада для цього використовується конструкція LOOP … END LOOP і команда виходу EXIT або EXIT WHEN: LOOP ... Частина тіла циклу EXIT WHEN <умова виходу>; ... Частина тіла циклу IF <умова виходу> THEN EXIT; END; ... Частина тіла циклу END LOOP: Тут всередині циклу може бути будь-яка кількість команд виходу обох типів. Самі команди виходу принципово не відрізняються, зазвичай EXIT WHEN застосовують, коли перевіряється тільки умова виходу, а просто EXIT — коли вихід з циклу здійснюється в одному з варіантів складного умовного оператора.
У тих мовах, де подібних конструкцій не передбачено, цикл з виходом з середини може бути побудований за допомогою будь-якого умовного циклу та інструкції дострокового виходу з циклу (такого, як break в Сі, exit в Турбо Паскалі т.п.), або інструкції безумовного переходу goto.
Цикл з лічильником
Цикл з лічильником — цикл, в якому деяка змінна змінює своє значення від заданого початкового значення до кінцевого значення з деяким кроком, і для кожного значення цієї змінної тіло циклу виконується один раз. В більшості процедурних мов програмування реалізується оператором for, в якому вказується лічильник (так звана «змінна циклу»), потрібна кількість проходів (або граничне значення лічильника) і, можливо, крок, з яким змінюється лічильник. Наприклад, в мові Оберон-2 такий цикл має вигляд: FOR v := b TO e BY s DO ... тіло циклу END (тут v — лічильник, b — початкове значення лічильника, e — межове значення лічильника, s — крок).
Неоднозначне питання про значення змінної по завершенні циклу, в якому ця змінна використовувалась як лічильник. Наприклад, якщо в програмі на мові Паскаль зустрінеться конструкція вигляду: i := 100; for i := 0 to 9 do begin ... тіло циклу end; k := i; виникає питання: яке значення буде в підсумку присвоєне змінній k: 9, 10, 100, може якесь інше? А якщо цикл завершиться достроково? Відповіді залежать від того, чи збільшується значення лічильника після ітерації і чи не змінює транслятор це значення додатково. Ще одне запитання: що буде, якщо всередині циклу лічильнику буде явно присвоєне нове значення?
Різні мови програмування мають різні підходи до цього питання. В деяких поведінку лічильника чітко регламентовано. В інших, наприклад, Паскалі, стандарт мови не визначає ані кінцевого значення лічильника, ані наслідків його явної зміни в циклі, але не радиться змінювати значення лічильника явно і використовувати його після завершення циклу без повторної ініціалізації. Програма на Паскалі, що ігнорує цю пораду, матиме невизначену поведінку, тобто може давати різні результати при виконанні на різних системах чи при використанні різних трансляторів або навіть різних режимів оптимізації одного й того ж транслятора.
Радикально вирішене питання в мові Ада: лічильник вважається описаним в заголовку циклу та поза циклом просто не існує. Навіть якщо ім’я лічильника вже використовується в програмі, всередині циклу як лічильник використовується окрема змінна. Лічильнику заборонено явно присвоювати будь-які значення, він може змінюватись лиш внутрішнім механізмом оператора циклу. В результаті конструкція i := 100; for i in (0..9) loop ... тіло циклу end loop; k := i; зовнішньо аналогічна вищенаведеному циклу на Паскалі, трактується однозначно: змінній k буде присвоєно значення 100, оскільки змінна i, використовувана поза межами даного циклу, не має жодного стосунку до лічильника циклу i, який змінюється всередині циклу. Подібне відокремлення лічильника зручне і безпечне: не потрібен окремий опис для нього та мінімізується ймовірність випадкових помилок, пов’язаних із випадковим руйнуванням зовнішніх стосовно циклу змінних. Якщо програмісту потрібно включити в готовий код цикл з лічильником, то його не турбує чи існує змінна з ім’ям, яке він обрав для лічильника, і йому не потрібно додавати опис свого лічильника. Він просто пише код зі змінною-лічильником, ім’я якої йому зручне, і може бути впевненим, що ніякої колізії імен не відбудеться.
Цикл з лічильником завжди можна написати як умовний цикл, перед початком якого лічильнику присвоюється початкове значення, а умовою виходу з циклу є досягнення лічильником кінцевого значення; в тіло циклу при цьому додається оператор зміни лічильника на потрібний крок. Однак спеціальні оператори циклу з лічильником можуть ефективніше транслюватися, бо формалізований вигляд такого циклу дозволяє використовувати спеціальні процесорні команди організації циклів.
В деяких мовах, наприклад, Сі та інших, похідних від неї, цикл for, незважаючи на синтаксичну форму циклу з лічильником, в дійсності є циклом з передумовою. Тобто в Сі конструкція циклу: for (i = 0; i < 10; ++i) { ... тіло циклу } фактично являє собою інший варіант запису конструкціїСтрого кажучи, тотожність не повна, бо інакше буде працювати оператор continue.: i = 0; while (i < 10) { ... тіло циклу ++i; } Тобто в конструкції for спочатку пишеться довільне речення ініціалізації циклу, потім — умова продовження і, насамкінець, виконувана після кожного тіла циклу деяка операція (це не обов’язково має бути зміна лічильника; це може бути модифікація вказівника або будь-яка зовсім стороння дія). Для мов такого типу вищезазначена проблема розв'язується дуже просто: змінна-лічильник поводиться цілком передбачувано і по завершені циклу зберігає своє останнє значення.
Цикл по колекції (foreach) Ще одним варіантом є цикл, який пробігає елементи з деякої множини без явного задання порядку перебору цих об’єктів. Такі цикли являють собою формальний запис інструкції виду: «Виконати дію X для всіх елементів множини M». Теоретично такий цикл ніяким чином не визначає, в якому порядку буде застосовуватись дія до елементів множини, хоча певні мови програмування, звісно, можуть встановлювати конкретний порядок перебору елементів. Довільність дає можливість оптимізації виконання циклу за рахунок організації доступу в найвигіднішому порядку, а не в порядку зазначеному програмістом. За наявності можливості паралельного виконання декількох операцій можливо навіть розподілення виконання циклу по колекції, коли одна і та сама операція одночасно виконується на різних обчислювальних модулях для різних об’єктів, при тому що логічно програма залишається послідовною.
Цикли по колекції реалізовано в деяких мовах програмування (C#, Eiffel, Java, JavaScript, Perl, Python, PHP, LISP, Tcl, C++11 та ін.) — вони дають змогу цикл по всіх елементах даної колекції об'єктів. У визначенні такого циклу треба вказати лише колекцію об’єктів і змінну, якій в тілі циклу буде присвоєне значення оброблюваного в цей час об’єкта (чи посилання на нього). В різних мовах програмування синтаксис оператора різний:
C#:foreach (type item in set) { //використання item }
C++, стандарт 2011 року:const char *str[] = {"one", "two", "forty-four"}; for (const char *p: str) { std::cout << p << std::endl; }
Perl:foreach (@set) { #використання $_ } або for(@set) { #використання $_ } або foreach $item(@set) { #використання $item }
Eiffel:across set as cursor loop -- використання cursor.item end
Java:for (type item : set) { //використання item }
JavaScript:for (txtProperty in objObject)
/* використання: objObject [txtProperty] */
PHP:foreach ($arr as $item) { /* використання $item*/ }
Visual Basic.NET:For Each item As type In set