# Глава 4. Циклы
В C++ вам на выбор доступно три вида циклов: `while`, `do-while` и `for`. Причем у цикла `for` есть два варианта.
Все эти циклы — это управляющие инструкции. Их тело состоит из единственной инструкции. Это может быть простая инструкция, блок (составная инструкция) или управляющая инструкция (например, условие или вложенный цикл). Правила расстановки фигурных скобок в теле цикла [такие же,](/courses/cpp/chapters/cpp_chapter_0030/#block-braces) как для условий `if-else`.
Для досрочного выхода из цикла применяется оператор `break`. А для прерывания текущей итерации и перехода на следующую — оператор `continue`.
## Цикл while
Тело цикла `while` выполняется, пока справедливо условие:
```c++
while(условное выражение)
{
// Тело цикла
}
```
Цикл `while` используют, если в теле цикла требуется изменять его условие. Либо если количество итераций не известно заранее:
```c++
while (!user_pressed_stop && !end_of_playlist)
{
play_next_song();
}
```
Еще один распространенный сценарий применения `while` — вечный цикл:
```c++
while(true)
{
cmd = read_console_command();
if (!is_valid(cmd))
{
continue;
}
if (is_exit(cmd))
{
break;
}
handle_command(cmd);
}
```
Напишите функцию `gcd()`, которая находит наибольший общий делитель (GCD, greatest common divisor) чисел `a` и `b` типа `std::size_t`. Функция не должна быть рекурсивной. {.task_text}
Например, `gcd(25, 15)` равен 5, а `gcd(8, 3)` равен 1. {.task_text}
Реализуйте алгоритм Евклида. Он заключается в следующем: {.task_text}
- GCD равен `a`, если `a` и `b` совпадают.
- GCD равен GCD от `a - b` и `b`, если `a` больше `b`.
- GCD равен GCD от `a` и `b - a`, если `a` меньше `b`.
```c++ {.task_source #cpp_chapter_0040_task_0010}
```
В цикле `while` проверяйте условие `a != b`. Внутри цикла изменяйте значения `a` и `b`. После выхода из цикла `a` будет равен наибольшему общему делителю. {.task_hint}
```c++ {.task_answer}
std::size_t gcd(std::size_t a, std::size_t b)
{
while (a != b)
{
if (a > b)
{
a -= b;
}
else
{
b -= a;
}
}
return a;
}
```
К слову, в стандартной библиотеке C++ есть функция [std::gcd()](https://en.cppreference.com/w/cpp/numeric/gcd).
Для обхода элементов контейнера идиоматичнее использовать цикл `for`. Но никто не может запретить вам организовать обход через `while`:
```c++ {.example_for_playground .example_for_playground_001}
std::string s = "341453TNY";
std::size_t i = 0;
// При сравнении символов char сопоставляются ASCII-коды.
// Цифры 0, 1, ... 9 расположены в ASCII-таблице последовательно.
while (i < s.size() && s[i] >= '0' && s[i] <= '9')
{
s[i] = 'X';
++i;
}
std::println("{}", s);
```
```
XXXXXXTNY
```
## Цикл do-while
Цикл `do-while` отличается от `while` порядком действий: сначала исполняется тело цикла, а затем проверяется условие. Это означает, что вне зависимости от условия тело выполнится хотя бы один раз.
```c++
do
{
// Тело цикла
} while(условное выражение);
```
К циклу `do-while` часто прибегают при чтении данных из внешнего источника. Первое чтение произойдет в любом случае, а последующие — если останутся данные:
```c++
int data_size = 0;
do
{
data_size = read_chunked_data();
} while (data_size > 0);
```
## Цикл for
Цикл `for` удобен для обхода элементов контейнера и для выполнения действий заранее известное количество раз. Он применяется, если в теле цикла не требуется изменять его условие.
Вместо формального определения начнем с примера. Перепишем этот `while` на `for`:
```c++ {.example_for_playground .example_for_playground_002}
int i = 5;
while(i <= 25)
{
std::print("{} ", i);
i += 5;
}
```
```
5 10 15 20 25
```
Так выглядит цикл `for`, делающий то же самое:
```c++ {.example_for_playground .example_for_playground_003}
for (int i = 5; i <= 25; i += 5)
{
std::print("{} ", i);
}
```
Внутри круглых скобок цикла `for` точкой с запятой разделены три выражения:
- Инициализация. Срабатывает один раз в начале цикла. Мы создали счетчик `i` и присвоили ему стартовое значение 5.
- Условие. Проверяется перед каждым выполнением тела цикла. Как только условие возвращает `false`, цикл прерывается. В нашем случае условие — это `i <= 25`.
- Итерация. Это действие, совершаемое после завершения каждого витка цикла для перехода на новый. Здесь мы увеличиваем счетчик: `i += 5`.
Так цикл `for` выглядит в общем виде:
```c++
for (инициализация; условие; итерация)
{
// Тело цикла
}
```
Любое из этих выражений может отсутствовать. Если опустить все три, то получится вечный цикл:
```c++
for(;;)
{
// Бесконечный цикл
}
```
Напишите функцию `common_prefix_len()`, которая принимает две строки и возвращает `std::size_t` — длину их общего префикса. {.task_text}
Например, для строк `"sort"`, `"something"` функция вернет 2, потому что общий префикс равен `"so"`. А для строк `"test"`, `"cow"` функция вернет 0. {.task_text}
В своем решении используйте цикл `for`. {.task_text}
```c++ {.task_source #cpp_chapter_0040_task_0020}
```
В инициализаторе цикла заведите счетчик. В условии цикла проверьте, что он меньше длин обеих строк. В теле цикла сравните символы строк по этому индексу. {.task_hint}
```c++ {.task_answer}
std::size_t common_prefix_len(std::string s1, std::string s2)
{
std::size_t len = 0;
for (std::size_t i = 0; i < std::min(s1.size(), s2.size()); ++i)
{
if (s1[i] != s2[i])
{
break;
}
++len;
}
return len;
}
```
Напишите функцию, которая выводит в консоль таблицу умножения чисел от 1 до `n` в вертикальном представлении. {.task_text}
Если `n` меньше 1, функция не должна ничего выводить. {.task_text}
Для вывода текста с переносом строки есть функция `std::println()` (вызов от пустой строки `""` приведет к переносу строки). Для вывода без переноса есть функция `std::print()`. {.task_text}
Пример вывода для `n=3`: {.task_text}
1x1=1 2x1=2 3x1=3
1x2=2 2x2=4 3x2=6
1x3=3 2x3=6 3x3=9 {.task_text}
```c++ {.task_source #cpp_chapter_0040_task_0030}
void show_multiplication_table(int n)
{
}
```
Организуйте вложенный цикл. {.task_hint}
```c++ {.task_answer}
void show_multiplication_table(int n)
{
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
{
const std::string trailing_symbol = (j == n) ? "" : " ";
std::print("{}x{}={}{}", j, i, j*i, trailing_symbol);
}
std::println();
}
}
```
## Цикл range-for {#block-range-for}
С помощью `for` можно организовать еще один вариант циклов. Он известен как `range-for` и применяется для итерации по диапазону (range) значений. Например, по контейнеру.
Так выглядит подсчет гласных букв английского алфавита в строке с помощью `range-for`:
```c++ {.example_for_playground .example_for_playground_004}
const std::string vowels = "aeiou";
const std::string text = "what are vowels?";
std::size_t count = 0;
for (char c: text)
{
if (vowels.contains(c))
{
++count;
}
}
std::println("Number of vowels in text: {}", count);
```
```
Number of vowels in text: 5
```
Общий вид цикла `range-for`:
```c++
for(инициализация переменной: диапазон)
{
// Тело цикла
}
```
В круглых скобках цикла заводится переменная того же типа, что и элементы диапазона. Затем после двоеточия указывается сам диапазон.
Напишите функцию `count_letter()`, которая принимает два аргумента: строку и символ. Она должна вернуть значение типа `std::size_t` — количество вхождений данного символа в строку. {.task_text}
В своем решении воспользуйтесь циклом range-for. {.task_text}
```c++ {.task_source #cpp_chapter_0040_task_0040}
```
Пример цикла: `for (char letter: s)`. {.task_hint}
```c++ {.task_answer}
std::size_t count_letter(std::string s, char c)
{
std::size_t count = 0;
for (char letter: s)
{
if (letter == c)
{
++count;
}
}
return count;
}
```
----------
## Резюме
- Организовать цикл можно с помощью конструкций: `while`, `do-while`, `for`.
- У цикла `for` есть вариация: `range-for` для прохода по диапазонам.
- Оператор `break` нужен для досрочного выхода из цикла.
- Оператор `continue` прерывает итерацию цикла и запускает следующую.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!