В этой главе вы научитесь повторять действия с помощью цикла for, накапливать сумму, находить наибольшее и наименьшее значения и запоминать предыдущий ввод.
Строение цикла for
Цикл for особенно удобен, когда заранее известно, сколько раз нужно повторить действие. Например, выведем пять чисел:
1
2
3
4
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Число: " + i);
}
Результат:
Число: 0
Число: 1
Число: 2
Число: 3
Число: 4
Заголовок цикла содержит три части:
int i = 0— объявление и начальное значение переменной. Выполняется один раз.i < 5— условие продолжения. Проверяется перед каждым выполнением тела цикла.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.