Как повторить действие нужное число раз или пока выполняется условие
while и for и выбирать между нимиfor и №6 по циклу while — на портале. Все программы страницы — полные; собирайте их командой g++ -std=c++17 -Wall -Wextra файл.cpp -o файл.exe.whileЦикл повторяет инструкции, пока выполняется условие. Цикл while — цикл с предусловием из раздела 1: условие проверяется до каждого повторения.
while (условие) {
инструкции // тело цикла
}
Если условие истинно — выполняется тело, и проверка повторяется. Как только условие стало ложным — выполнение переходит к инструкции после цикла. Одно выполнение тела называют итерацией. Сумма 1 + 2 + … + n:
// Сумма 1 + 2 + ... + n циклом while
#include <iostream>
int main()
{
int n;
std::cout << "n: ";
std::cin >> n;
if (n < 1) {
std::cout << "Error: n must be >= 1\n";
return 1;
}
int sum = 0;
int i = 1; // 1. начальное значение
while (i <= n) { // 2. условие продолжения
sum += i;
++i; // 3. изменение переменной цикла
}
std::cout << "1 + ... + " << n << " = " << sum << '\n';
return 0;
}
n: 4 n: 100
1 + ... + 4 = 10 1 + ... + 100 = 5050
У цикла три части, и ошибка в любой из них — самая частая ошибка раздела: начальное значение переменной цикла (i = 1), условие продолжения (i <= n) и изменение переменной в теле (++i). Проверить цикл без компьютера помогает трассировка — таблица значений переменных после каждой итерации. Для n = 4:
| Проверка | i до тела | i <= n | sum после тела | i после тела |
|---|---|---|---|---|
| 1 | 1 | true | 1 | 2 |
| 2 | 2 | true | 3 | 3 |
| 3 | 3 | true | 6 | 4 |
| 4 | 4 | true | 10 | 5 |
| 5 | 5 | false | выход из цикла | |
Условие проверяется на одну итерацию больше, чем выполняется тело: последняя проверка — та, что вернула false. После цикла i равно 5, а не 4.
++i, условие i <= n останется истинным навсегда: программа «зависнет», ничего не выводя. Компилятор об этом не предупреждает. Остановить программу в консоли — Ctrl+C. Правило курса: в каждом цикле должно быть видно, какая переменная приближает его к концу. Если это нельзя показать — цикл пишется заново.В разделе 1 для вклада построена итерационная модель: 100 000 ₽ под 12 % годовых с ежемесячной капитализацией — через сколько месяцев сумма впервые достигнет 110 000 ₽? Её блок-схема — цикл с предусловием. Теперь это код:
// Через сколько месяцев вклад достигнет цели — итерационная модель из раздела 1
#include <iostream>
#include <iomanip>
int main()
{
double principal, rate, target;
std::cout << "Sum, rate %, target: ";
std::cin >> principal >> rate >> target;
if (principal <= 0 || rate <= 0 || target <= principal) {
std::cout << "Error: need sum > 0, rate > 0, target > sum\n";
return 1;
}
double sum = principal;
int months = 0;
while (sum < target) {
sum *= 1 + rate / 1200; // проценты за месяц: годовая ставка / 12 / 100
++months;
}
std::cout << std::fixed << std::setprecision(2)
<< "Months: " << months << ", sum: " << sum << '\n';
return 0;
}
Sum, rate %, target: 100000 12 110000
Months: 10, sum: 110462.21
Число повторений здесь заранее неизвестно — его и ищет программа. Переменная months — счётчик: она не управляет циклом, а считает итерации. Управляет условие sum < target.
rate > 0 сумма каждый месяц растёт не меньше чем на principal · rate / 1200 — значит, рано или поздно превысит любую цель. При rate = 0 сумма не меняется, и цикл был бы бесконечным. Поэтому проверка rate <= 0 стоит в программе не «для порядка»: она гарантирует конец цикла. Для каждой итерационной модели в разделе 1 требовалось доказать, что повторение закончится; в программе это доказательство становится проверкой входных данных. Строго говоря, бесконечным цикл сделает и ставка 1e-20: 1 + 1e-22 в double равно 1 (раздел 3), и сумма не растёт. Поэтому в практических работах у ставки есть нижняя граница, а не только «больше нуля».Операции % 10 и / 10 из раздела 3 дают последнюю цифру целого числа и число без неё. Повторяя их, пока от числа что-то осталось, можно пройти все цифры:
// Количество и сумма цифр числа
#include <iostream>
int main()
{
int number;
std::cout << "Number: ";
std::cin >> number;
if (number < 0) {
number = -number;
}
int count = 0;
int digit_sum = 0;
int rest = number;
while (rest > 0) {
digit_sum += rest % 10; // последняя цифра
rest /= 10; // отбросить последнюю цифру
++count;
}
std::cout << number << ": digits " << count << ", sum " << digit_sum << '\n';
return 0;
}
Number: 30572 -> 30572: digits 5, sum 17
Number: -408 -> 408: digits 3, sum 12
Number: 0 -> 0: digits 0, sum 0 <- неверно: у нуля одна цифра
Цикл идёт по копии rest, а не по number: после цикла от числа остаётся 0, а в выводе нужно исходное. Для 30572 rest принимает значения 30572, 3057, 305, 30, 3, 0 — пять итераций, по одной на цифру.
Для нуля условие rest > 0 ложно с первой же проверки: тело цикла с предусловием может не выполниться ни разу. Иногда это правильно — сумма цифр нуля действительно 0, — а иногда нет, как с количеством цифр. Исправляется отдельной проверкой до цикла (if (number == 0)) или циклом с постусловием, у которого тело выполняется хотя бы раз, — он в разделе 6.
Сколько чисел введёт пользователь, заранее неизвестно; конец ввода обозначается особым значением — признаком конца, например нулём. Сам признак не обрабатывается:
// Сумма и среднее чисел, ввод до нуля
#include <iostream>
int main()
{
std::cout << "Numbers, 0 - end: ";
int value;
std::cin >> value; // первое чтение — до цикла
int count = 0;
int sum = 0;
while (value != 0) {
sum += value;
++count;
std::cin >> value; // следующее — в конце тела
}
if (count == 0) {
std::cout << "No numbers\n";
} else {
std::cout << "Count " << count << ", sum " << sum
<< ", average " << static_cast<double>(sum) / count << '\n';
}
return 0;
}
Numbers, 0 - end: 5 8 -3 10 0 -> Count 4, sum 20, average 5
Numbers, 0 - end: 7 2 0 -> Count 2, sum 9, average 4.5
Numbers, 0 - end: 0 -> No numbers
Чтение стоит в двух местах — перед циклом и последней инструкцией тела. Это чтение с упреждением: к моменту проверки условия очередное число уже прочитано, и признак конца не попадает в сумму. Числа можно вводить в одну строку через пробел или по одному в строке — >> пропускает пробелы и переводы строки.
count без проверки — при пустом вводе деление на ноль; sum / count без приведения к double — целое деление, 9 / 2 = 4 (раздел 3). Если вместо числа ввести букву, чтение не удастся, в value запишется 0 — и цикл закончится, будто введён признак конца: на вводе 5 8 x 3 0 программа выведет Count 2, sum 13 и не заметит ошибки. Как отличить неверный ввод от признака конца — раздел 6.forКогда число повторений известно до начала цикла, три части цикла удобно собрать в одну строку. Это цикл for:
for (начальное значение; условие; изменение) {
инструкции
}
Порядок выполнения: начальное значение — один раз; затем проверка условия, тело, изменение, снова проверка — и так, пока условие истинно. Любой for можно переписать через while и наоборот; сумма из пункта 01 выглядит так:
int sum = 0;
for (int i = 1; i <= n; ++i) {
sum += i;
}
Переменная, объявленная в заголовке for, существует только внутри цикла. После цикла её нет — это защищает от случайного использования «последнего значения»:
forscope.cpp:9:30: error: 'i' was not declared in this scope
Если значение после цикла нужно, переменная объявляется до цикла. Шаг может быть любым, в том числе отрицательным: for (int i = 10; i > 0; i -= 3) даёт 10, 7, 4, 1. Степени двойки:
// Степени двойки циклом for
#include <iostream>
int main()
{
int n;
std::cout << "n (0..30): ";
std::cin >> n;
if (n < 0 || n > 30) {
std::cout << "Error: n must be 0..30\n";
return 1;
}
long long power = 1;
for (int i = 0; i <= n; ++i) {
std::cout << "2^" << i << " = " << power << '\n';
power *= 2;
}
return 0;
}
n (0..30): 4
2^0 = 1
2^1 = 2
2^2 = 4
2^3 = 8
2^4 = 16
Почему long long, если выводится не больше 230? Умножение стоит после вывода, и на последней итерации power становится 231 — это уже больше максимума int (раздел 3). Значение не выводится, но переполнение int — неопределённое поведение, даже когда результат не используется.
| Задача | Цикл |
|---|---|
| число повторений известно до начала: от a до b, n раз, для каждого из n чисел | for |
| повторять, пока не выполнится условие: цель вклада, цифры числа, ввод до признака конца, поиск | while |
for: счётчик — целый; переменная цикла меняется только в заголовке, не в теле; фигурные скобки — у тела даже из одной инструкции; имена i, j, k — только для счётчиков, у остальных переменных имена по смыслу (row, col, month).Большинство задач с циклом сводится к нескольким типовым алгоритмам. У каждого есть переменная-«накопитель» и правило, каким значением её начинать:
| Алгоритм | Начальное значение | В теле цикла |
|---|---|---|
| сумма | sum = 0 | sum += value; |
| произведение | product = 1 | product *= value; |
| количество подходящих | count = 0 | if (условие) { ++count; } |
| среднее | сумма и количество | после цикла: static_cast<double>(sum) / count, если count > 0 |
| максимум | первое значение | if (value > max_value) { max_value = value; } |
// n чисел: сумма, среднее, максимум и его номер, количество положительных
#include <iostream>
int main()
{
int n;
std::cout << "How many numbers: ";
std::cin >> n;
if (n < 1) {
std::cout << "Error: n must be >= 1\n";
return 1;
}
std::cout << "Numbers: ";
int value;
std::cin >> value; // первое число — до цикла
long long sum = value;
int max_value = value;
int max_pos = 1;
int positive = (value > 0) ? 1 : 0;
for (int i = 2; i <= n; ++i) {
std::cin >> value;
sum += value;
if (value > max_value) {
max_value = value;
max_pos = i;
}
if (value > 0) {
++positive;
}
}
std::cout << "Sum " << sum << ", average " << static_cast<double>(sum) / n << '\n'
<< "Max " << max_value << " (number " << max_pos << ")\n"
<< "Positive: " << positive << '\n';
return 0;
}
How many numbers: 6 How many numbers: 3
Numbers: 4 -2 9 0 9 -5 Numbers: -7 -2 -9
Sum 15, average 2.5 Sum -18, average -6
Max 9 (number 3) Max -2 (number 2)
Positive: 3 Positive: 0
max_value = 0 второй пример выдал бы максимум 0 — числа, которого не было во вводе.> или >= решает, какой из равных максимумов запомнится: со строгим > — первый (номер 3), с >= — последний (номер 5). В условии задачи это должно быть сказано; если не сказано — выбирайте первый и пишите об этом в отчёте.long long. Каждое число помещается в int, а сумма многих — не обязательно.int, 13! = 6 227 020 800 — уже нет, и программа с int молча выводит 1932053504. Компилятор не предупреждает: он не знает, сколько раз выполнится цикл. В long long помещается до 20! = 2 432 902 008 176 640 000. Для каждого накопителя оценивайте наибольшее значение, как в разделе 3, и ограничивайте ввод так, чтобы оно поместилось в тип.Поиск перебирает кандидатов, пока не найдёт подходящего. Удобно вписать в условие цикла обе причины остановки: «кандидаты кончились» и «найдено». Простое ли число n — есть ли у него делитель от 2 до √n:
// Простое ли число; если нет — разложение с наименьшим делителем
#include <iostream>
int main()
{
int n;
std::cout << "n (2..2000000000): ";
std::cin >> n;
if (n < 2 || n > 2000000000) {
std::cout << "Error: n must be 2..2000000000\n";
return 1;
}
int divisor = 2;
while (divisor * divisor <= n && n % divisor != 0) {
++divisor;
}
if (divisor * divisor > n) {
std::cout << n << " is prime\n";
} else {
std::cout << n << " = " << divisor << " * " << n / divisor << '\n';
}
return 0;
}
n (2..2000000000): 97 -> 97 is prime
n (2..2000000000): 91 -> 91 = 7 * 13
n (2..2000000000): 2 -> 2 is prime
divisor * divisor > n) — делителя нет, число простое. Иначе цикл остановился на делителе.divisor * divisor <= n — без вещественного корня и его погрешности.divisor = 44 722, и 44 722² = 2 000 057 284 ещё помещается в int. Для n около максимума int квадрат переполнился бы — тогда нужен long long или условие divisor <= n / divisor.break — она в разделе 6. Условие поиска в заголовке цикла от неё не устаревает: по нему сразу видно, когда цикл заканчивается.Тело цикла может содержать другой цикл. Внутренний цикл выполняется целиком на каждой итерации внешнего. Таблица умножения — внешний цикл по строкам, внутренний по столбцам:
// Таблица умножения n x n — вложенные циклы
#include <iostream>
#include <iomanip>
int main()
{
int n;
std::cout << "n (1..12): ";
std::cin >> n;
if (n < 1 || n > 12) {
std::cout << "Error: n must be 1..12\n";
return 1;
}
for (int row = 1; row <= n; ++row) {
for (int col = 1; col <= n; ++col) {
std::cout << std::setw(4) << row * col;
}
std::cout << '\n';
}
return 0;
}
n (1..12): 5
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
std::setw(4) из <iomanip> задаёт ширину поля для следующего выводимого значения: оно дополняется пробелами слева до 4 символов, и столбцы выравниваются. В отличие от setprecision, setw действует один раз — поэтому стоит внутри цикла перед каждым числом. Перевод строки — после внутреннего цикла, в теле внешнего.
Граница внутреннего цикла может зависеть от переменной внешнего. Треугольник: в строке с номером row — row звёздочек.
for (int row = 1; row <= 4; ++row) { *
for (int k = 1; k <= row; ++k) { **
std::cout << '*'; ***
} ****
std::cout << '\n';
}
row, он испортит счётчик внешнего.Табулировать функцию — вывести таблицу её значений на отрезке [a; b] с шагом h. Напрашивается вещественный счётчик:
for (double x = 0; x <= 0.3; x += 0.1) {
std::cout << x << '\n';
}
0
0.1
0.2
Точки 0.3 нет. 0.1 в двоичной записи — бесконечная дробь (раздел 3), и после трёх сложений x равно 0.30000000000000004 — чуть больше 0.3, условие ложно. С другими a, b и h лишней может оказаться и точка за концом отрезка. Надёжный способ — заранее посчитать число шагов и вести целый счётчик, а x вычислять от начала отрезка:
// Таблица значений y = x^2 - 2x на отрезке [a; b] с шагом h
#include <iostream>
#include <iomanip>
constexpr double EPS = 1e-9;
int main()
{
double a, b, h;
std::cout << "a, b, h: ";
std::cin >> a >> b >> h;
if (h <= 0 || b < a) {
std::cout << "Error: need h > 0 and a <= b\n";
return 1;
}
int steps = static_cast<int>((b - a) / h + EPS); // число шагов, с допуском
std::cout << std::fixed;
for (int i = 0; i <= steps; ++i) {
double x = a + i * h;
double y = x * x - 2 * x;
std::cout << std::setprecision(2) << std::setw(6) << x
<< std::setprecision(4) << std::setw(10) << y << '\n';
}
return 0;
}
a, b, h: 0 0.3 0.1 a, b, h: 0 1 0.25
0.00 0.0000 0.00 0.0000
0.10 -0.1900 0.25 -0.4375
0.20 -0.3600 0.50 -0.7500
0.30 -0.5100 0.75 -0.9375
1.00 -1.0000
(b − a) / h для первого примера равно 2.9999999999999996, а не 3: преобразование в int отбрасывает дробную часть и дало бы 2 шага. Допуск EPS возвращает «почти целое» к целому. Если h не укладывается в отрезок целое число раз (0 1 0.3), последняя точка — 0.9, за конец отрезка таблица не выходит. Погрешность при таком способе не накапливается: каждое x получается одним умножением и одним сложением.
Точка с запятой после заголовка. for (…); — цикл с пустым телом: он пять раз ничего не делает, а строка под ним выполняется один раз, как бы она ни была сдвинута. g++ замечает это только по отступу:
for (int i = 0; i < 5; ++i);
std::cout << "*"; // выведет одну звёздочку, а не пять
emptyfor.cpp:6:5: warning: this 'for' clause does not guard... [-Wmisleading-indentation]
С while (i <= n); хуже: пустое тело не меняет i, и цикл бесконечный.
Беззнаковый счётчик вниз до нуля. У unsigned нет отрицательных значений: после 0 идёт 4 294 967 295 (раздел 3), условие k >= 0 истинно всегда.
for (unsigned k = 3; k >= 0; --k) { // 3 2 1 0 4294967295 4294967294 ...
unsigneddown.cpp:6:28: warning: comparison of unsigned expression in '>= 0' is always true [-Wtype-limits]
| Ошибка | Признак | Предупреждение | Как избежать |
|---|---|---|---|
; после заголовка | тело выполняется один раз или цикл бесконечный | только по отступу | скобки у тела, открывающая — на строке заголовка |
| переменная цикла не меняется | бесконечный цикл | нет | найти в теле строку, которая приближает конец |
ошибка «на единицу»: < вместо <=, начало с 0 вместо 1 | на одну итерацию меньше или больше | нет | трассировка первой и последней итерации |
| вещественный счётчик | теряется или добавляется последняя точка | нет | целый счётчик, x = a + i * h (пункт 09) |
unsigned вниз до 0 | бесконечный цикл | -Wtype-limits | счётчик int |
| переполнение накопителя | странные или отрицательные результаты | нет | оценить максимум, long long, ограничить ввод |
| накопитель не обнулён перед циклом | мусор в результате | -Wuninitialized (раздел 3), не всегда | начальное значение — в строке объявления |
while проверяет условие до каждой итерации; тело может не выполниться ни разуfor — когда число повторений известно заранее, while — когда повторять до условия; один переписывается через другойx вычисляется от него% и / для цифр, диапазоны типов и переполнение, погрешность double, ++; раздел 4 — условия, проверка входных данных, тесты на границах. Нужен для: раздела 6 — цикл с постусловием, break и continue, проверка ввода в цикле; раздела 8 — обход массивов и матриц, сортировка; раздела 10 — обработка строк; разделов 16–19 — циклы ожидания и обработки сообщений в Windows API.