C++ Course
  • Описание курса
  • Материалы лекций
  • Задания
  • Об авторе
  • Гайды

On this page

  • Цикл for
  • Range-based for
  • Цикл while (с предусловием)
  • Цикл do-while (с постусловием)
  • Операторы break и continue
  • Вложенные циклы
  • Типовые приемы в задачах на циклы
    • Счетчик
    • Поиск максимума/минимума

Циклы

На прошлой паре были рассмотрены первые конструкции, которые позволяли выполнять программу нелинейно – применять ветвление. Теперь нужно научиться повторять блоки кода несколько раз. В этом нам помогут циклы.

Цикл в программировании – многократное повторение некоторых команд. Он может включать другие циклы, условия, инструкции.

Цикл состоит из:

  • тела цикла – того блока кода, который будет повторяться

  • условия продолжения выполнения цикла

  • итерацией будем называть однократное исполнение тела цикла.

Цикл for

Этот цикл мы, как правило, используем, когда знаем, какое количество итераций нужно будет совершить. Чуть позже будет показано, почему использована оговорка “как правило”.

Пример использования цикла for для вывода последовательных натуральных чисел от \(1\) до \(n\) не включительно.

#include <iostream>

int main() {
    int n;
    std::cin >> n;

    for (int i = 1; i < n; ++i) {
        printf("%d\n", i);
    }
}

Стоит обратить внимание на следующие вещи:

  • после ключевого слова for в круглых скобках можно перечислить через точку с запятой:

    • объявление локальных переменных цикла или изменить их значение перед первой итерацией

    • условие продолжения цикла. Пока оно истинно, цикл будет уходить на следующую итерацию

    • изменение переменных после каждой итерации. Эта инструкция, как правило, необходима: мы ведь хотим как-то влиять на условие, чтобы оно в какой-то момент изменилось

  • переменная i является локальной, ее использование возможно только внутри тела цикла

  • вместо i < n возможно написать любое условие, например, сделать так чтобы выводились последовательные натуральные числа от \(1\) до \(n\) включительно: i <= n

Следующий пример призван продемонстрировать, что описанные выше части цикла for могут быть разными:

#include <iostream>

int main() {
    int n;
    std::cin >> n;

    for (int i = n; i >= 0; i -= 1) {
        printf("%d\n", i);
    }
}

Рассмотрим задачу о нахождении следующей суммы:

\(1 + (1 + 2) + (1 + 2 + 3) + \dots + (1 + 2 + \dots + n)\)

#include <iostream>
// #include <cstdint>

int main() {
    unsigned long long int n; // uint64_t n;
    std::cin >> n;
    unsigned long long int sum = 0;
    unsigned long long int last_add = 1;

    for (unsigned long long int i = 2; i < n + 2; ++i) {
        sum += last_add;
        last_add += i;
    }
    std::cout << sum << '\n';
}

Чуть позже на курсе мы будем активно использовать циклы for для обработки массивов – наборов переменных известного типа. Такой выбор цикла можно объяснить тем, что во время его начала мы знаем размер массива, значит, там известно необходимое количество итераций.

Range-based for

Как можно было заметить, классический цикл for в C++ не является прямым аналогом цикла for из Python. Это исправляет возможность написать цикл так, чтобы он буквально итерировался по объекту. Например, по строке:

#include <iostream>
#include <string>

int main() {
    std::string name;
    printf("Enter your name:\n");
    std::getline(std::cin, name);

    printf("Let me spell it:\n");
    for (char letter: name) {
        std::cout << letter << '\t' << static_cast<int>(letter) << '\n';
    }
    std::cout << '\n';
}

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

Enter your name:
Nikolai
Let me spell it:
N   78
i   105
k   107
o   111
l   108
a   97
i   105

“Под капотом” это работает следующим образом:

  • берется “начало” объекта, по которому можно итерироваться (проходиться циклом)

  • пока мы не дошли до “конца” объекта, запускаем итерацию на текущем элементе и переходим к следующему. Мы можем перейти к следующему, благодаря свойству итерируемости

  • когда дошли до “конца” объекта, выходим из цикла

Мы подробно познакомимся и научимся работать с “началом” и “концом” объекта на предстоящих темах курса. Ими окажутся итераторы и указатели

Цикл while (с предусловием)

Этот цикл применяется, когда там неизвестно количество итераций, которое нужно будет совершить. Также его можно понимать как синтаксически облегченный цикл for, ведь все, что он предоставляет – это условие продолжения цикла и тело цикла.

Для примера вспомним, как получать цифры известного числа в некоторой системе счисления. Для получения \(i\)-той цифры с конца, нужно найти значение следующего выражения (нумерация с нуля):

\(\text{digit}_i = N / d^{\ i} \ \% \ d\)

Реализуем это в программе, заметим, что 0 при целочисленном делении получится только когда у нас оставалась последняя цифра. Действительно:

\(a / b = 0 (a, b > 0) \Leftrightarrow a < b\)

Сама программа, которая выводит на экран все цифры числа:

В каком порядке она это сделает? Как нужно модифицировать программу, чтобы она выводила цифры в системе счисления \(d\)? Как в таком случае поступить с остатками, которые больше \(9\)?

#include <iostream>

int main() {
    int num;
    std::cin >> num;
    while (num) {
        std::cout << num % 10 << '\n';
        num /= 10;
    }
}

Цикл do-while (с постусловием)

Если при написании программы мы уверены, что цикл должен выполниться хотя бы один раз, то мы можем использовать конструкцию do-while.

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

#include <iostream>

int main() {
    int n = 1;
    do {
        std::cout << n << "\t" << n * n << "\n";
        ++n;
    } while (n <= 10);
}

Операторы break и continue

  • break – прерывает исполнение итерации и выходит из цикла. Перемещает исполнение программы сразу после тела цикла

  • continue – прерывание исполнение итерации и переходит к следущей, если условие цикла по-прежнему выполняется. В случае цикла for инструкция после итерации будет выполнена ( ++i, например)

Например, сделаем наш калькулятор бесконечным, а выходить из цикла будем, если нам везде ввели нули:

#include <cstdint>
#include <iostream>

int main() {
    int64_t a, b;
    char operation;
    while (true) {
        std::cin >> a >> operation >> b;
        if (!a && !b && operation == '0') {
            break;
        }

        int64_t result = 0; // почему здесь не будет двойного объявления переменной?
        if (operation == '+') {
            result = a + b;
        } else if (operation == '-') {
            result = a - b;
        } else if (operation == '*') {
            result = a * b;
        } else if (operation == '/' || operation == ':') {
            if (!b) {
                continue;
            }
            result = a / b;
        } else if (operation == '%') {  // && b
            if (b) {
                result = a % b;
            }
        }

        std::cout << result << "\n";
    }
}

Условия можно писать множеством разных способов.

Здесь стоит обратить внимание на early exit (“ранний выход”) – когда в функции или цикле не делает множество проверок, которые увеличивают уровни вложенности, а вызывается прерывание итерации.

Например,

// early exit
if (!b) {
    continue;
}
// some very long and important code 1
// some very long and important code 2
// some very long and important code 3





// более стандартный поход
if (b) {
    // some very long and important code 1
    // some very long and important code 2
    // some very long and important code 3
}

Считается, что второй вариант менее читаем из-за того что нужно долгое время помнить о условии, блок кода которого мы сейчас рассматриваем. Но вообще codestyle (правила оформления кода) такого рода является очень относительным понятием :)

Вложенные циклы

Пример вывода таблицы умножения. Для этого мы используем вложенные циклы: для каждого значения переменной i мы печатаем целую строку с помощью цикла по переменной j.

#include <iostream>

int main() {
    for (int i = 1; i <= 10; ++i) {
        for (int j = 1; j <= 10; ++j) {
            std::cout << i * j << "\t";
        }
        std::cout << "\n";
    }
}
1   2   3   4   5   6   7   8   9   10
2   4   6   8   10  12  14  16  18  20
3   6   9   12  15  18  21  24  27  30
4   8   12  16  20  24  28  32  36  40
5   10  15  20  25  30  35  40  45  50
6   12  18  24  30  36  42  48  54  60
7   14  21  28  35  42  49  56  63  70
8   16  24  32  40  48  56  64  72  80
9   18  27  36  45  54  63  72  81  90
10  20  30  40  50  60  70  80  90  100

Использование инструкций break и continueизменит поведение только того цикла, в котором они были вызваны, и не повлияет на внешний цикл.

Типовые приемы в задачах на циклы

Счетчик

Хотим узнать, какое количество объектов удовлетворяет некоторому условию, для этого:

  • создаем переменную, которую будем инкрементировать – счетчик

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

  • если верно некоторое условие, то увеличиваем счетчик на 1

  • счетчик можно сбрасывать при некотором условии, если нас интересует непрерывное соблюдение условия

В конце такого обхода счетчик будет равен количество объектов, для которого верно условие.

Пример:

#include <iostream>

int main() {
    int new_num;
    std::cin >> new_num;

    int cnt = 0;
    while (new_num) {
        if (new_num % 2 == 0) {
           ++cnt;
        }
        std::cin >> new_num;
    }

    std::cout << cnt << std::endl;

    return 0;
}

Менее естественный (“в дикой природе” не встретится) пример той же самой логики с циклом for:

#include <iostream>

int main() {
    int new_num;
    std::cin >> new_num;

    int cnt;
    for (cnt = 0; new_num != 0; std::cin >> new_num) {
        cnt += (new_num + 1) % 2;
    }

    std::cout << cnt << std::endl;

    return 0;
}

Поиск максимума/минимума

Хотим по итогам прохода по отрезку или последовательности, иметь представление о наибольшем или наименьшем значении, которое встретилось, для этого:

  • создаем переменную, которая равна первому элементу (либо заведомо меньшему или большему значению, чем любой элемент)

  • для каждого элемента обновляем минимум или максимум

#include <iostream>
#include <algorithm>

int main() {
    int new_num;
    std::cin >> new_num;
    int max = new_num, min = new_num;

    while (new_num) {
        max = std::max(max, new_num);
        min = std::min(min, new_num);
        std::cin >> new_num;
    }

    printf("%d %d\n", min, max);

    return 0;
}
Denis Bakin ©

Build on Quart Academic Website Template adapted by Dr. Gang He