+--------------------------+
|.------------------------.|
|| kee_reel@blog:~$ cd    ||
|| си python терминал     ||
|| opengl sql             ||
||                        ||
||                обо_мне ||
|.------------------------.|
+-::--------------------::-+
.--------------------------.
 // /ooooooooooooooooooooo\\ \\ 
 // /ooooooooooooooooooooooo\\ \\ 
//------------------------------\\
\\------------------------------//

C. Циклы

Цикл (loop) – это механизм, позволяющий выполнить множество схожих действий.

В языке си есть три вида циклов:

  • for
  • while
  • do while

Цикл for

Вот пример определения цикла (выводим все делители числа):

int x;
scanf("%d", &x);
printf("Поиск делителей числа %d\n", x);
for(int div = 2; div < x; div++)
{
    if(x % div == 0)
    {
        printf("%d\n", div);
    }
}

div это просто имя переменной – я так её назвал потому что div это сокращённая запись слова divisor (делитель). В программированнии часто сокращают названия, чтобы меньше писать – но тут главное знать меру, а то потом сам не разберёшь что написал.

Давай разберём из чего состоит цикл.

Создание временной переменной

int div = 2;

Этот код выполняется перед стартом цикла – обычно в нём создаётся временная переменная, которая будет использована внутри цикла.

Условие завершения

div < x;

Этот код исполняется перед каждым исполнением тела цикла, и возвращает булевое значение.

В этом примере временная переменная div сравнивается с введённым числом x.

Допустим, мы ввели число 10 – условие будет верно до тех пор, пока div меньше 10. В момент, когда выражение в условии вернёт значение ложь – цикл закончится.

Инкремент временной переменной

div++

Здесь обычно пишется инкремент временной переменной. Благодаря этому коду цикл изменяет значение div после прохода каждого цикла.

Тело цикла

{
    if(x % div == 0)
    {
        printf("%d\n", div);
    }
}

Обычно, здесь пишется код, который как-то использует временные переменные – здесь это переменная div, которая используется для проверки на делимость.

Всё вместе

Ещё раз напишу код с циклом и добавлю дополнительные логи (вывод сообщений), чтобы лучше понять что происходит:

int x;
scanf("%d", &x);
printf("Поиск делителей числа %d", x);
for(int div = 2; div < x; div++)
{
    printf("Проход цикла: %d\n", div);
    if(x % div == 0)
    {
        printf("Нашли делитель: %d\n", div);
    }
}

Таким образом, последовательность применения выражений внутри определения цикла такова:

  1. Создание временной переменной:
    int div = 2;
    

    Переходим к шагу 2.

  2. Проверка условия:
    div < x;
    

    Если значение равно:

    • истина – переходим к шагу 3
    • ложь – переходим к шагу 5.
  3. Исполнение тела цикла:
    {
     printf("Проход цикла: %d\n", div);
     if(x % div == 0)
     {
         printf("Нашли делитель: %d\n", div);
     }
    }
    

    Переходим к шагу 4.

  4. Инкремент временной переменной:
    div++
    

    Переходим к шагу 2.

  5. Конец цикла.

Вот что выведет эта программа:

Поиск делителей числа 10
Проход цикла: 2
Нашли делитель: 2
Проход цикла: 3
Проход цикла: 4
Проход цикла: 5
Нашли делитель: 5
Проход цикла: 6
Проход цикла: 7
Проход цикла: 8
Проход цикла: 9

Видишь, строка “Проход цикла:” заканчивается на 9-ке, потому что когда div становится 10, условие div < x не выполняется, и цикл завершается.

Цикл 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

Например, у нас задача “найти делитель числа, который заканчивается на 3”:

int x;
// Ставлю -1 чтобы после цикла проверить, что число не изменилось (в цикле оно не может стать -1)
int divisor = -1;
scanf("%d", &x);
for(int i = 2; i < x; i++)
{
    if(x % i == 0 && i % 10 == 3)
    {
        divisor = i;
        // Нашли нужный делитель, можно дальше не искать
        break;
    }
}
if(divisor != -1)
{
    printf("Делитель найден: %d\n", divisor);
}
else
{
    printf("Делитель не найден\n");
}

Представь если бы мы ввели число 1211 – его делители 7 и 173. Если бы не было break, то мы бы после нахождения 173 молотили бы ещё 1037 циклов до окончания цикла (когда условие “i < x” станет ложным).

continue

continue позволяет пропустить текущую итерацию, и сразу начать следующую (произойдёт инкремент временной переменной).

Задача “на заданном промежутке посчитать сумму нечётных чисел и вычесть из неё сумму чисел делящихся на 3”:

int start, end;
// Вводим сразу два числа через пробел
scanf("%d %d", &start, &end);
int sum1 = 0, sum2 = 0;
for(int i = start; i <= end; i++)
{
    // Пропускаем итерацию для чётного числа
    if(i % 2 == 0)
        continue;
    sum1 += i;
    // Пропускаем итерацию для чётного числа не делящегося на 3
    if(i % 3 != 0)
        continue;
    sum2 += i;
}
printf("Разность сумм на промежутке [%d, %d]: %d\n", start, end, sum1 - sum2);

Здесь можно обойтись и без continue, но тогда получается некрасивые вложенные условия:

for(int i = start; i <= end; i++)
{
    if(i % 2 != 0)
    {
        sum1 += i;
        if(i % 3 != 0)
            sum2 += i;
    }
}

В общем, обычно 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 простых числел) и мнооогое другое.

Если что – пиши, я помогу и постараюсь объяснить лучше.

Дальше мы рассмотрим то, как можно структурировать твои программы – функции.