C. Циклы
Время чтения: 5 минут
Цикл (loop) – это механизм, позволяющий выполнить множество схожих действий.
В языке си есть три вида циклов:
- for
- while
- do while
Цикл for
Вот пример определения цикла для решения задачи “выводим все чётные числа от 1 до n (не включительно)”:
int n;
scanf("%d", &n);
for(int i = 1; i < n; i++)
{
if(i % 2 == 0)
{
printf("%d\n", i);
}
}
Давай разберём из чего состоит цикл.
Создание временной переменной
int i = 1;
Этот код выполняется перед стартом цикла – обычно в нём создаётся временная переменная, которая будет использована внутри цикла.
Условие завершения
i < n;
Этот код исполняется перед каждым исполнением тела цикла, и возвращает булевое значение.
В этом примере временная переменная i сравнивается с введённым числом n.
Допустим, мы ввели число 10 – условие будет верно до тех пор, пока i меньше 10. В момент, когда выражение в условии вернёт значение ложь – цикл закончится.
Инкремент временной переменной
i++
Здесь обычно пишется инкремент временной переменной. Благодаря этому коду цикл изменяет значение i после прохода каждого цикла.
Тело цикла
{
if(i % 2 == 0)
{
printf("%d\n", i);
}
}
Обычно, здесь пишется код, который как-то использует временные переменные – здесь это переменная i, которая используется для проверки на делимость.
Всё вместе
Ещё раз напишу код с циклом и добавлю дополнительные логи (вывод сообщений), чтобы лучше понять что происходит:
int n;
scanf("%d", &n);
for(int i = 1; i < n; i++)
{
if(i % 2 == 0)
{
printf("%d\n", i);
}
}
Таким образом, последовательность применения выражений внутри определения цикла такова:
- Создание временной переменной:
int i = 1;
Переходим к шагу 2.
- Проверка условия:
i < n;
Если значение равно:
- истина – переходим к шагу 3
- ложь – переходим к шагу 5.
- Исполнение тела цикла:
{
if(i % 2 == 0)
{
printf("%d\n", i);
}
}
Переходим к шагу 4.
- Инкремент временной переменной:
i++
Переходим к шагу 2.
- Конец цикла.
Вот что выведет эта программа (если введём 10 с клавиатуры):
2
4
6
8
Видишь, строка “Проход цикла:” заканчивается на 8-ке, потому что когда i становится 10, условие i < n не выполняется, и цикл завершается.
Цикл while
Если ты разобрался с циклом for, то с while у тебя не возникнет проблем – он намного легче.
Вот как он выглядит (я описал с его помощью ту же задачу, что и выше):
int x;
scanf("%d", &x);
printf("Поиск делителей числа %d", x);
int div = 2;
while(div < x)
{
printf("Проход цикла: %d\n", div);
if(x % div == 0)
{
printf("Нашли делитель: %d\n", div);
}
div++;
}
Здесь нужно описать только условие, которое будет проверяться перед проходом каждого цикла. Создание временной переменной и инкремент я просто перенёс в соответствующие места.
Вывод программы будет таким же.
Очень важно не забывать про инкремент при использовании цикла while! Я уже и не упомню сколько раз у меня получался бесконечный цикл (условие цикла всегда истинно), из-за отсутствия инкремента переменной – она оставалась на начальном значении, и цикл бесконечно молотил впустую.
Цикл do while
Те же яйца, только сбоку – в цикле while проверка условия производится перед исполнением тела цикла, а в цикле do while – проверка производится после тела цикла.
int x;
scanf("%d", &x);
printf("Поиск делителей числа %d", x);
int div = 2;
if(div >= x)
{
return;
}
do
{
printf("Проход цикла: %d\n", div);
if(x % div == 0)
{
printf("Нашли делитель: %d\n", div);
}
div++;
}
while(div < x);
Вроде бы ничего особо не изменилось, но я зачем-то добавил проверку:
if(div >= x)
{
return;
}
Объясняю зачем – из-за того, что условие цикла проверится только после прохождения тела цикла, мне надо убедиться что введённое пользователем число подходит под условие div < x.
Представь что этого дополнительного условия нет – если пользователь введёт -10:
- Тело цикла начнёт выполняться
- Мы напишем “Проход цикла: 2”
- Мы найдём делитель -10 при dev == 2, и напишем “Нашли делитель: 2”
- И только после этого дойдём до проверки div < x (-10 < 2), которая вернёт ложь
А это уже баг!
Баг (от агл. bug – жук) – это жаргонный термин в программировании, обозначающее ошибку при написании программы, которая привела к нежелательному поведению. Есть версия, что впервые в отношении программной ошибки это термин применили в 1947 году, когда нашли вполне реального “жука” в реле вычислительной машины:
Прерывание цикла и пропуск итерации
В языке Си (а также в C++, Python, JS, Go и прочих языках) есть способ прервать выполнение цикла и пропустить текущую итерацию.
break
break позволяет выйти из цикла раньше времени.
Например, у нас задача “вывести 10 чётных чисел от 1 до 100”:
int count = 10;
for(int i = 1; i < 100; i++)
{
if(i % 2 == 0)
{
count--;
printf("%d\n", i);
if(count == 0)
break;
}
}
continue
continue позволяет пропустить текущую итерацию, и сразу начать следующую (произойдёт инкремент временной переменной).
Задача “на заданном промежутке посчитать сумму нечётных чисел и вычесть из неё сумму чисел делящихся на 3”:
Та же задача “вывести 10 чётных чисел от 1 до 100”:
int count = 10;
for(int i = 1; i < 100; i++)
{
if(i % 2 != 0)
continue;
count--;
printf("%d\n", i);
if(count == 0)
break;
}
Обычно continue используется для сокращения уровня вложенности условий в цикле.
Вложенные циклы
Внутри одного цикла, ты можешь запускать ещё циклы. Задача “вывести N простых чисел”:
int N;
scanf("%d", &N);
// Пока N больше нуля
for(int i = 2; N > 0; i++)
{
printf("%d\n", i);
char is_prime_number = 1;
// Для всех j от 2 до i
for(int j = 2; j < i; j++)
{
// Простое число может делиться только на 1 и на само себя.
// Если делится на что-то ещё, то оно не простое.
if(i % j == 0)
{
is_prime_number = 0;
break;
}
}
if(is_prime_number)
{
printf("%d\n", i);
N--;
}
}
То есть мы для каждого числа i создаём цикл, в котором пытаемся его поделить на все числа от 2 до i.
Задание на закрепление
Для всех числел на промежутке [2, N] (N вводится с клавиатуры), найти сумму квадратов чётных чисел и разделить её на их количество.
Заключение
Итого, ты изучил:
- Цикл for
- Цикл while
- Цикл do while
- Прерывание цикла (break)
- Пропуск итерации (continue)
- Вложенные циклы
С помощью циклов можно уже решать довольно сложные задачи – обходить большие массивы данных (про это будет потом), вычислять приближённые значения математических функций, искать числа с определёнными свойствами (например найти 1000 простых числел) и мнооогое другое.
Если что – пиши, я помогу и постараюсь объяснить лучше.
Дальше мы рассмотрим то, как можно структурировать твои программы – функции.