Глава 4 — Цикл for


Повторение действий, счётчики, накопители и сравнение соседних значений

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

Строение цикла for

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

1
2
3
4
for (int i = 0; i < 5; i++)
{
    Console.WriteLine("Число: " + i);
}

Результат:

Число: 0
Число: 1
Число: 2
Число: 3
Число: 4

Заголовок цикла содержит три части:

  1. int i = 0 — объявление и начальное значение переменной. Выполняется один раз.
  2. i < 5 — условие продолжения. Проверяется перед каждым выполнением тела цикла.
  3. i++ — увеличение i на единицу. Выполняется после тела цикла.

Каждое выполнение тела называют итерацией, или повторением. Здесь цикл выполняется пять раз. После вывода 4 значение i увеличивается до 5, условие становится false, и выполнение продолжается после цикла. Переменная i, объявленная в заголовке, за пределами цикла недоступна.

Дополнительно: видео о выполнении цикла (на иврите)

Сумма чисел: переменная-накопитель

Чтобы вычислить сумму чисел от 1 до 10, перед циклом создадим sum со значением 0. На каждом повторении прибавим очередное число:

1
2
3
4
5
6
int sum = 0;
for (int i = 1; i <= 10; i++)
{
    sum += i;
}
Console.WriteLine("Сумма: " + sum); // 55

sum += i означает то же, что sum = sum + i. Переменная sum хранит результат, накопленный за все уже выполненные повторения.

Таблица умножения

Выведем таблицу умножения на 3:

1
2
3
4
5
int number = 3;
for (int i = 1; i <= 10; i++)
{
    Console.WriteLine(number + " x " + i + " = " + (number * i));
}

Досрочное завершение: break

Команда break немедленно завершает ближайший цикл. В следующем коротком примере цикл останавливается, когда i становится равным 5:

1
2
3
4
5
6
7
8
for (int i = 1; i <= 10; i++)
{
    if (i == 5)
    {
        break; // Завершаем цикл при i == 5.
    }
    Console.WriteLine(i);
}

Результат:

1
2
3
4

Число 5 не выводится: break выполняется раньше команды вывода. Ниже мы применим досрочное завершение для проверки простого числа.

Проверяйте условие и изменение переменной цикла вместе. Если условие всегда остаётся истинным и нет другого выхода, цикл не завершится.

Упражнение: число и накопленная сумма

Напишите программу, которая выводит числа от 1 до 5 и после каждого числа — сумму всех чисел от 1 до него. Используйте for.

Пример решения
1
2
3
4
5
6
int sum = 0; // Накопитель.
for (int i = 1; i <= 5; i++)
{
    sum += i;
    Console.WriteLine("Число: " + i + ", накопленная сумма: " + sum);
}

Результат:

Число: 1, накопленная сумма: 1
Число: 2, накопленная сумма: 3
Число: 3, накопленная сумма: 6
Число: 4, накопленная сумма: 10
Число: 5, накопленная сумма: 15

Таблица трассировки

Этап i sum Вывод
Инициализация sum 0
Первое повторение 1 1 Число: 1, накопленная сумма: 1
Второе повторение 2 3 Число: 2, накопленная сумма: 3
Третье повторение 3 6 Число: 3, накопленная сумма: 6
Четвёртое повторение 4 10 Число: 4, накопленная сумма: 10
Пятое повторение 5 15 Число: 5, накопленная сумма: 15

Практика 4.2 — Счётчики, накопители, минимум и максимум

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

Чтобы найти максимум, сохраняем наибольшее значение из уже введённых. Если очередное число больше, обновляем максимум. Для минимума сравнение направлено в другую сторону.

Пример: максимум из пяти целых чисел
public static void QFindMax()
{
    int max = int.MinValue; // Наименьшее возможное значение int.

    for (int i = 0; i < 5; i++)
    {
        Console.Write("Введите число: ");
        int num = int.Parse(Console.ReadLine());

        Console.WriteLine(num);
        if (num > max)
            max = num;
    }
    Console.WriteLine($"Максимум: {max}");
}

Начальное значение int.MinValue позволяет правильно обработать и отрицательные числа. Если начать с 0, то при пяти отрицательных вводах получится неверный максимум 0, которого среди вводов вообще не было.

Другой способ: первый ввод становится начальным максимумом

Сначала считываем одно число, затем сравниваем с ним оставшиеся четыре. Всего вводов по-прежнему пять.

public static void QFindMax()
{
    Console.Write("Введите число: ");
    int max = int.Parse(Console.ReadLine());

    for (int i = 0; i < 4; i++)
    {
        Console.Write("Введите число: ");
        int num = int.Parse(Console.ReadLine());

        Console.WriteLine(num);
        if (num > max)
            max = num;
    }
    Console.WriteLine($"Максимум: {max}");
}

Это альтернативная версия того же метода. Используйте одну из двух версий.

Пример: минимум из пяти целых чисел
public static void QFindMin()
{
    int min = int.MaxValue; // Наибольшее возможное значение int.

    for (int i = 0; i < 5; i++)
    {
        Console.Write("Введите число: ");
        int num = int.Parse(Console.ReadLine());

        Console.WriteLine(num);
        if (num < min)
            min = num;
    }
    Console.WriteLine($"Минимум: {min}");
}

Учимся замечать ошибку в сравнении

Пусть max хранит текущий максимум, а num — очередной ввод. Рассмотрите по отдельности два варианта обновления:

// Вариант 1.
if (max <= Math.Max(num, max))
    max = num;
// Вариант 2.
if (max < Math.Max(num, max))
    max = num;

Для каждого варианта проверьте случай max = 5, num = 3. Какой вариант ошибочен? Как упростить правильный?

Разбор

Math.Max(3, 5) возвращает 5. В первом варианте условие 5 <= 5 истинно, поэтому максимум ошибочно заменяется на 3. Более того, это условие истинно при любых num и max.

Во втором варианте 5 < 5 ложно, поэтому максимум остаётся равным 5. Второй вариант работает, но вычисление Math.Max здесь лишнее. Прямое сравнение яснее:

if (num > max)
    max = num;

Простое число: флаг и break

Простое число — целое число больше 1, у которого ровно два положительных делителя: 1 и само число. Для начала будем проверять все возможные делители от 2 до числа, не включая его.

Первая попытка с ошибкой

Прочитайте код и объясните, почему он выводит противоречивые сообщения:

public static void QIsPrime()
{
    int num1 = 60;
    for (int i = 2; i < num1; i++)
    {
        if (num1 % i == 0)
        {
            Console.WriteLine("Число не простое");
        }
    }
    Console.WriteLine("Число простое");
}

Здесь сообщение «Число не простое» появляется для каждого найденного делителя, а сообщение «Число простое» выводится всегда. Нужно запомнить результат проверки и вывести один ответ.

Решение с логическим флагом
public static void QIsPrime()
{
    Console.Write("Введите целое число: ");
    int num1 = int.Parse(Console.ReadLine());
    bool isPrime = num1 >= 2; // Числа меньше 2 не являются простыми.

    for (int i = 2; i < num1; i++)
    {
        if (num1 % i == 0) // Нашли делитель без остатка.
        {
            isPrime = false;
            break; // Другие делители проверять уже не нужно.
        }
    }

    if (isPrime)
        Console.WriteLine("Число простое");
    else
        Console.WriteLine("Число не простое");
}

Переменную типа bool, которая хранит состояние проверки, называют флагом. Здесь флаг позволяет после цикла узнать, был ли найден делитель. Выражение if (isPrime) уже является условием: дописывать == true не требуется.

Для чисел меньше 2 флаг сразу равен false. Для 2 тело цикла не выполняется ни разу, а флаг остаётся равным true.

Эту проверку можно ускорить, уменьшив диапазон делителей. Пока задача — разобраться в работе флага и досрочного выхода.

Максимум вместе с дополнительным значением

Напишите программу, которая считывает имена и оценки десяти учеников и выводит имя ученика с наивысшей оценкой. Считайте, что оценки лежат в диапазоне от 0 до 100. Если наивысшую оценку получили несколько учеников, выведите имя первого из них.

Для проверки идеи можно временно генерировать оценки и заглавную латинскую букву вместо имени.

Заготовка: найдите и исправьте ошибку сохранения имени

Заготовка правильно находит наивысшую оценку, но в конце выводит последнюю сгенерированную букву. Измените её так, чтобы буква соответствовала лучшей оценке.

public static void Main()
{
    Random rnd = new Random();
    int max = -1; // Значение вне диапазона оценок.
    char myInitial = '-';

    for (int i = 0; i < 10; i++)
    {
        myInitial = (char)rnd.Next('A', 'Z' + 1);
        int num = rnd.Next(0, 101);

        Console.WriteLine(myInitial);
        Console.WriteLine(num);
        if (num > max)
            max = num;
    }
    Console.WriteLine($"Лучшая оценка: {max}");
    Console.WriteLine(myInitial);
}
Решение с вводом имён и оценок
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void Main()
{
    int max = -1;
    string bestName = "";

    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("Введите имя:");
        string name = Console.ReadLine();

        Console.Write("Введите оценку: ");
        int num = int.Parse(Console.ReadLine());

        if (num > max)
        {
            max = num;
            bestName = name; // Сохраняем имя вместе с новым максимумом.
        }
    }
    Console.WriteLine($"Лучшая оценка у {bestName}: {max}");
}

max и bestName обновляются в одном блоке. Поэтому они всегда относятся к одному ученику. Строгое сравнение > сохраняет первого ученика при равных лучших оценках.

Сохранение предыдущего значения

Иногда нужно сравнить очередной ввод с предыдущим: например, узнать, сколько раз значение изменилось. Для этого сохраняем предыдущий ввод в отдельной переменной, обычно prev или previous.

Сначала сравните текущее значение с предыдущим и только затем обновите prev. Если сделать это раньше, вы будете сравнивать число с ним самим.

Пример: считаем изменения в последовательности

Вводятся пять чисел. Посчитайте, сколько раз очередное число отличается от предыдущего.

Решение
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int count = 0;
int prev = 0;

for (int i = 0; i < 5; i++)
{
    Console.Write("Введите число: ");
    int num = int.Parse(Console.ReadLine());

    if (i > 0 && num != prev)
    {
        count++;
    }

    prev = num; // Текущий ввод станет предыдущим на следующем повторении.
}

Console.WriteLine("Количество изменений: " + count);

При первом вводе предыдущего числа ещё нет, поэтому условие i > 0 запрещает сравнение. Начальное значение prev = 0 в этом сравнении не участвует. После каждого ввода prev получает текущее значение.

Например, для 4, 4, 7, 7, 2 ответ равен 2: значение меняется при переходах от 4 к 7 и от 7 к 2.

Дальше