Введение
После условных конструкций переходим к циклам — for, while, do-while — трём конструкциям повторения, различающимся в основном тем, когда проверяется условие продолжения и насколько компактно выражается типичная структура «инициализация-условие-шаг».
Концепция
for объединяет в одной строке инициализацию переменной цикла, условие продолжения, и операцию изменения переменной на каждой итерации — наиболее идиоматичный выбор, когда заранее известно (или легко выражается) количество итераций или правило их изменения. while проверяет условие ПЕРЕД каждой итерацией (включая самую первую) — тело цикла может не выполниться вовсе, если условие изначально ложно, что отличает его от do-while, проверяющего условие ПОСЛЕ выполнения тела, гарантируя как минимум одно выполнение тела цикла независимо от условия. break немедленно завершает выполнение всего (самого внутреннего) цикла, тогда как continue пропускает оставшуюся часть текущей итерации, переходя непосредственно к проверке условия следующей итерации (для for — включая выполнение шага изменения переменной перед этой проверкой).
Пример кода
#include <iostream>
int main()
{
// for — типичный случай известного количества итераций
for (int i = 0; i < 5; ++i) {
std::cout << "Итерация " << i << std::endl;
}
// while — условие, не выражаемое простым счётчиком
int value = 100;
while (value > 1) {
value /= 2; // деление пополам, пока значение не станет 1 или меньше
std::cout << "Текущее значение: " << value << std::endl;
}
// do-while — гарантированное выполнение хотя бы один раз, даже если условие изначально ложно
int userInput;
do {
std::cout << "Введите положительное число: ";
std::cin >> userInput;
} while (userInput <= 0); // тело выполнится хотя бы РАЗ, прежде чем условие будет впервые проверено
return 0;
}
// break и continue — управление потоком выполнения внутри цикла
#include <iostream>
int main()
{
for (int i = 0; i < 10; ++i) {
if (i == 7) {
break; // полностью завершает ВЕСЬ цикл — итерации 7, 8, 9 НЕ выполнятся вовсе
}
if (i % 2 == 0) {
continue; // пропускает ОСТАЛЬНУЮ часть текущей итерации, переходя к следующей
}
std::cout << "Нечётное число: " << i << std::endl; // выполнится только для нечётных i до 7
}
return 0;
}
// Вложенные циклы и проблема break/continue, затрагивающих только ВНУТРЕННИЙ цикл
#include <iostream>
int main()
{
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (j == 1) {
break; // прерывает только ВНУТРЕННИЙ цикл (по j), внешний (по i) продолжается нормально
}
std::cout << i << "," << j << " ";
}
}
// Для прерывания ОБОИХ циклов одновременно нужен либо флаг, либо (реже используемый) goto
return 0;
}
Пояснения к коду
Три примера циклов показывают типичный выбор конструкции согласно характеру задачи — for для известного, легко выражаемого диапазона итераций; while для условия, проверяемого до каждой итерации, без заранее известного количества повторений; do-while для случая, когда тело цикла должно выполниться хотя бы один раз независимо от условия (типичный пример — запрос пользовательского ввода, который физически должен произойти хотя бы раз, прежде чем можно проверить его корректность). Пример с break/continue показывает их принципиально разное действие — break для i == 7 полностью останавливает цикл (последующие итерации 8 и 9 не выполняются вовсе), тогда как continue для чётных i лишь пропускает вывод для текущей конкретной итерации, не прерывая выполнение последующих итераций цикла. Пример с вложенными циклами демонстрирует важное ограничение — break (и аналогично continue) воздействует только на самый внутренний охватывающий его цикл, и для прерывания одновременно нескольких уровней вложенных циклов нужен дополнительный механизм (явный флаг, проверяемый во внешнем цикле, или, значительно реже в современном коде, goto).
Подводные камни
- Использование
do-while, когда условие должно быть проверено ДО первого выполнения тела цикла, по ошибке или недостаточно внимательному выбору конструкции —do-whileгарантирует выполнение тела хотя бы один раз, что может привести к нежелательному побочному эффекту (как минимум одна, потенциально некорректная или нежелательная итерация) для случаев, где это поведение не было осознанно задумано разработчиком, и выбор междуwhileиdo-whileдолжен явно отражать, действительно ли нужно гарантированное первое выполнение независимо от условия. - Бесконечный цикл из-за ошибки в условии или забытого изменения переменной, влияющей на это условие — классическая, частая ошибка (особенно для
while, где, в отличие отfor, шаг изменения переменной не настолько визуально близок к условию в самой структуре цикла), и внимательная проверка, что условие цикла действительно может стать ложным при нормальном выполнении тела цикла — необходимая часть написания корректного кода циклов. - Использование
breakдля прерывания внешнего цикла из вложенного, внутреннего цикла без понимания, чтоbreakдействует только на самый внутренний охватывающий его цикл — это частое заблуждение для разработчиков, ожидающих (по аналогии с некоторыми другими языками программирования, поддерживающими «помеченные» break) чтоbreakвнутри вложенного цикла как-то автоматически распространится и на внешний цикл, тогда как в C/C++ для этого нужен явный, дополнительный механизм (флаг, отдельная функция сreturnвместоbreak, или редко применяемыйgoto). - Чрезмерное использование
continueв коде со сложной, многоусловной логикой, затрудняющее понимание полного потока выполнения цикла без внимательного прослеживания каждого отдельного условияcontinue— хотяcontinueсам по себе не является «плохой практикой» в умеренном использовании, избыточное количество условий раннегоcontinueв одном цикле может усложнить понимание реального условия, при котором основная, «содержательная» часть тела цикла действительно выполняется, и иногда инверсия условия (выполнение основного кода внутриif, а не пропуск черезcontinue) даёт более читаемую структуру для сложных случаев.