Как программа выбирает, что делать: условия, проверка данных и выбор из многих вариантов
if, if-else и цепочкой else if= вместо ==, ; после условия, точное сравнение doubleswitch и управлять проваливанием между caseif, когда switch, а когда условная операция ?:if и №4 по switch — на портале. Это первые работы, в которых сдаётся программа. Все программы страницы — полные; собирайте их командой g++ -std=c++17 -Wall -Wextra файл.cpp -o файл.exe.if и составной операторДо сих пор инструкции выполнялись одна за другой — следование из раздела 1. Ветвление выполняет инструкцию только при условии:
if (условие) {
инструкции // выполняются, если условие истинно
} else {
инструкции // выполняются, если условие ложно
}
Условие — любое выражение в круглых скобках, чаще всего сравнение или логическое выражение из раздела 3. Если у выражения не bool, а число, то 0 означает «ложь», всё остальное — «истину». Часть else не обязательна. Фигурные скобки делают из нескольких инструкций одну — составной оператор, или блок (раздел 2).
#include <iostream>
int main()
{
int number;
std::cout << "Number: ";
std::cin >> number;
if (number < 0) {
number = -number;
std::cout << "negative, abs = " << number << '\n';
}
if (number % 2 == 0) {
std::cout << "even\n";
} else {
std::cout << "odd\n";
}
return 0;
}
Number: -7 Number: 12
negative, abs = 7 even
odd
Первое if без else: при неотрицательном числе блок пропускается целиком. Второе выбирает одну из двух ветвей — выполнится ровно одна.
if относится только одна следующая инструкция; вторая, добавленная позже с тем же отступом, выполнится всегда — отступ компилятор не читает. Открывающая скобка ветви — в конце строки с if, else — на строке с закрывающей скобкой, как в примере (у функции скобка по-прежнему на отдельной строке).В разделе 1 у каждого исходного данного модели записаны ограничения, а у алгоритма — ветка «данные недопустимы». Теперь это код. Модель варианта 3 из раздела 1 — стоимость доставки посылки: до 1 кг — 250 ₽, каждый следующий начатый килограмм — 60 ₽, тяжелее 20 кг не принимаются.
// Стоимость доставки посылки — модель варианта 3 из раздела 1
#include <iostream>
#include <iomanip>
#include <cmath>
constexpr double BASE_PRICE = 250.0; // до 1 кг включительно
constexpr double PRICE_PER_KG = 60.0; // каждый следующий начатый килограмм
constexpr double MAX_WEIGHT = 20.0;
int main()
{
double weight;
std::cout << "Weight, kg: ";
std::cin >> weight;
if (weight <= 0 || weight > MAX_WEIGHT) {
std::cout << "Error: weight must be in (0; " << MAX_WEIGHT << "] kg\n";
return 1;
}
double cost = BASE_PRICE;
if (weight > 1) {
cost += PRICE_PER_KG * std::ceil(weight - 1);
}
std::cout << std::fixed << std::setprecision(2) << "Cost: " << cost << " RUB\n";
return 0;
}
Weight, kg: 3.2 Weight, kg: 1 Weight, kg: 25
Cost: 430.00 RUB Cost: 250.00 RUB Error: weight must be in (0; 20] kg
||: «хотя бы одно нарушено».return 1; завершает main досрочно. Код завершения 1 вместо 0 сообщает запустившему, что программа не выполнила работу (раздел 2, пункт 05). Остальной код после этого можно писать, уже не думая о недопустимых данных.std::ceil(x) из <cmath> округляет вверх — «начатый килограмм»; std::floor — вниз.std::fixed вместе с std::setprecision(2) выводит ровно два знака после запятой — так выводят деньги. Без std::fixed setprecision считает все значащие цифры (раздел 3, пункт 04).Что будет, если ввести не число, а буквы, — раздел 6: там появится проверка самого ввода.
elseВ ветви if может стоять другой if — это вложенное условие. Оно нужно, когда второе условие имеет смысл проверять только при выполнении первого. Главная ловушка — else без скобок:
if (age >= 14)
if (ticket)
std::cout << "pass\n";
else
std::cout << "too young\n";
По отступам кажется, что else относится к проверке возраста. Но правило языка: else относится к ближайшему if без else — здесь к if (ticket). Пятнадцатилетний без билета увидит «too young». g++ предупреждает:
dangling.cpp:7:8: warning: suggest explicit braces to avoid ambiguous 'else' [-Wdangling-else]
Со скобками смысл виден и компилятору, и читателю:
if (age >= 14) {
if (ticket) {
std::cout << "pass\n";
}
} else {
std::cout << "too young\n";
}
Больше двух-трёх уровней вложенности читать трудно. Часто вложенность заменяется одним условием с && (age >= 14 && ticket) или ранним выходом, как проверка данных в пункте 02.
else ifКогда вариантов больше двух — например, оценка по шкале курса 85 / 70 / 50, — условия выстраиваются цепочкой. Проверяются по порядку сверху вниз; выполняется ветвь первого истинного условия, остальные пропускаются.
#include <iostream>
int main()
{
int score;
std::cout << "Score: ";
std::cin >> score;
if (score < 0 || score > 100) {
std::cout << "Error: score must be 0..100\n";
return 1;
}
int mark;
if (score >= 85) {
mark = 5;
} else if (score >= 70) {
mark = 4;
} else if (score >= 50) {
mark = 3;
} else {
mark = 2;
}
std::cout << "Mark: " << mark << '\n';
return 0;
}
Score: 85 → Mark: 5 Score: 84 → Mark: 4
Score: 50 → Mark: 3 Score: 49 → Mark: 2
Второе условие — просто score >= 70, без && score < 85: если выполнение дошло до него, первое условие уже оказалось ложным. Последний else ловит всё, что не подошло, — поэтому mark получает значение на любом пути, и компилятор не предупреждает о неинициализированной переменной.
score >= 50, то и 95 баллов дадут «3»: первое же условие истинно, до остальных дело не дойдёт. Для порогов «не меньше» проверяйте от большего к меньшему. Проверяйте и стыки интервалов: для 85 и 84, 70 и 69 должны выходить разные оценки — ровно на них путают > и >=.?:Условная операция условие ? выражение1 : выражение2 — ветвление внутри выражения: её значение равно первому выражению, если условие истинно, иначе второму. Она нужна, когда выбирается значение, а не действие.
#include <iostream>
int main()
{
int a, b;
std::cout << "Two numbers: ";
std::cin >> a >> b;
int larger = (a > b) ? a : b;
std::cout << "max = " << larger << '\n';
std::cout << a << (a == 1 ? " file" : " files") << '\n';
return 0;
}
Two numbers: 1 5 Two numbers: 7 3
max = 5 max = 7
1 file 7 files
У ?: очень низкий приоритет — ниже <<, поэтому в выводе она всегда в скобках, как логические выражения (раздел 3, пункт 10). Оба выражения должны быть одного типа или приводиться к общему. Вложенные ?: (a ? b : c ? d : e) в курсе не используются — для трёх и больше вариантов есть цепочка else if.
= вместо ==. if (count = 0) собирается: это присваивание нуля, его значение 0 — «ложь». Ветвь не выполнится никогда, а count молча испорчен. g++ с -Wall предупреждает:
assignif.cpp:6:15: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
Точка с запятой после условия. if (balance < 0); — это if с пустой инструкцией ;. Строка ниже к условию не относится и выполняется всегда, как бы она ни была сдвинута:
emptyif.cpp:6:21: warning: suggest braces around empty body in an 'if' statement [-Wempty-body]
emptyif.cpp:6:5: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
Сравнение вещественных чисел — с допуском, а не через == (раздел 3, пункт 04): числа считаются равными, если модуль разности меньше малого числа EPS. std::fabs из <cmath> — модуль вещественного числа. Допуск подбирается по задаче: для денег в рублях — 0.005, для геометрии в метрах — 1e-9.
Сокращённое вычисление && защищает от деления на ноль: если divisor != 0 ложно, деление справа не выполняется.
#include <iostream>
#include <cmath>
constexpr double EPS = 1e-9;
int main()
{
double sum = 0.1 + 0.2;
if (std::fabs(sum - 0.3) < EPS) {
std::cout << "0.1 + 0.2 equals 0.3 within " << EPS << '\n';
}
int divisor;
std::cout << "Divisor: ";
std::cin >> divisor;
if (divisor != 0 && 100 / divisor > 5) {
std::cout << "100 / " << divisor << " > 5\n";
} else {
std::cout << "divisor is 0 or quotient <= 5\n";
}
return 0;
}
0.1 + 0.2 equals 0.3 within 1e-09
Divisor: 0 <- при 10: "100 / 10 > 5"
divisor is 0 or quotient <= 5
&& тоже важен: 100 / divisor > 5 && divisor != 0 делит раньше, чем проверяет.switchКогда действие выбирается по значению одной переменной — пункт меню, код операции, номер месяца, — цепочка if (x == 1) … else if (x == 2) … заменяется инструкцией switch:
switch (выражение) {
case константа1:
инструкции
break;
case константа2:
инструкции
break;
default:
инструкции // ни одна метка не подошла
}
Выражение вычисляется один раз, затем выполнение переходит на метку case с равным значением и идёт вниз до break, который выходит из switch. Если равной метки нет — переход на default, а если нет и его, switch ничего не делает. Калькулятор на четыре действия:
#include <iostream>
int main()
{
double left, right;
char operation;
std::cout << "Expression (e.g. 7 / 2): ";
std::cin >> left >> operation >> right;
switch (operation) {
case '+':
std::cout << left + right << '\n';
break;
case '-':
std::cout << left - right << '\n';
break;
case '*':
case 'x':
std::cout << left * right << '\n';
break;
case '/':
if (right == 0) {
std::cout << "Error: division by zero\n";
return 1;
}
std::cout << left / right << '\n';
break;
default:
std::cout << "Error: unknown operation '" << operation << "'\n";
return 1;
}
return 0;
}
Expression (e.g. 7 / 2): 7 / 2 -> 3.5
Expression (e.g. 7 / 2): 6 x 7 -> 42
Expression (e.g. 7 / 2): 1 / 0 -> Error: division by zero
Expression (e.g. 7 / 2): 1 % 2 -> Error: unknown operation '%'
std::cin >> в переменную char читает один символ, пропуская пробелы, — поэтому выражение вводится как в тетради. Метки case '*': и case 'x': стоят подряд: для обоих символов выполняется одно и то же. Здесь right == 0 без допуска — правильно: опасен только точный ноль, а не «почти ноль». Внутри ветви case можно писать любые инструкции, в том числе if.
switch есть default. Он обрабатывает недопустимые значения — это та же проверка входных данных из пункта 02. Оформление: case на уровне switch, инструкции ветви сдвинуты на 4 пробела, break — последней строкой ветви.caseМетка case — только точка входа, а не граница ветви. Без break выполнение идёт дальше — в инструкции следующей метки. Это проваливание (fall-through). Намеренно его используют, чтобы объединить несколько значений в одну ветвь, как '*' и 'x' выше. Число дней в месяце:
#include <iostream>
int main()
{
int month, year;
std::cout << "Month and year: ";
std::cin >> month >> year;
int days;
switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
days = 31;
break;
case 4: case 6: case 9: case 11:
days = 30;
break;
case 2: {
bool leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
days = leap ? 29 : 28;
break;
}
default:
std::cout << "Error: month must be 1..12\n";
return 1;
}
std::cout << "Days: " << days << '\n';
return 0;
}
Month and year: 2 2024 -> Days: 29 Month and year: 2 1900 -> Days: 28
Month and year: 2 2000 -> Days: 29 Month and year: 9 2026 -> Days: 30
Год високосный, если делится на 4, но не на 100, — или делится на 400: 1900 не високосный, 2000 — високосный. Метки без инструкций между ними (case 1: case 3: …) — законное объединение, о нём компилятор не предупреждает. А вот пропущенный break после инструкций -Wextra замечает:
fallthrough.cpp:8:22: warning: this statement may fall through [-Wimplicit-fallthrough=]
Если проваливание после инструкций действительно нужно, это пишут явно — строкой [[fallthrough]]; на месте break: предупреждение исчезнет, а читатель увидит, что так задумано.
case — только в фигурных скобках, как leap в ветви февраля. Все метки switch лежат в одном блоке, и переход на следующую метку «перепрыгнул» бы инициализацию переменной. Без скобок сборка останавливается ошибкой jump to case label с пояснением crosses initialization of 'bool leap'.switch; switch и enum classswitch проверяет только равенство целому значению. Отсюда ограничения — все проверяет компилятор:
| Нельзя | Сообщение g++ | Что делать |
|---|---|---|
выражение типа double или std::string | switch quantity not an integer | цепочка else if |
метка — переменная: case limit: | the value of 'limit' is not usable in a constant expression | метка — литерал, constexpr или константа enum |
| две одинаковые метки | duplicate case value | объединить ветви |
| диапазон: «от 70 до 84» | такой записи нет | цепочка else if (пункт 04) |
switch по перечислению — естественная пара: каждая константа enum — своя ветвь. Для enum class g++ проверяет, что разобраны все значения: если забыть одно, -Wall предупредит enumeration value 'GREEN' not handled in switch [-Wswitch]. Число из ввода превращается в enum class через static_cast — после проверки диапазона:
#include <iostream>
enum class Light { RED, YELLOW, GREEN };
int main()
{
int code;
std::cout << "Light (0 red, 1 yellow, 2 green): ";
std::cin >> code;
if (code < 0 || code > 2) {
std::cout << "Error: code must be 0..2\n";
return 1;
}
Light light = static_cast<Light>(code);
switch (light) {
case Light::RED:
std::cout << "stop\n";
break;
case Light::YELLOW:
std::cout << "wait\n";
break;
case Light::GREEN:
std::cout << "go\n";
break;
}
return 0;
}
Light (0 red, 1 yellow, 2 green): 2
go
Здесь default нет намеренно — это исключение из правила пункта 07: недопустимые значения отсеяны до switch, а без default компилятор напомнит о новой константе, если её добавят в Light.
| Задача | Инструкция |
|---|---|
| одно условие, одно или два действия | if / if-else |
| выбрать одно из двух значений | ?: |
интервалы, сравнения < >, вещественные числа, несколько переменных | цепочка else if |
одна целая переменная, char или enum, много отдельных значений | switch |
В Windows API switch встречается постоянно: по коду ошибки GetLastError() (раздел 14), по коду нажатой клавиши (раздел 15), по состоянию объекта ожидания (раздел 16).
if выполняет ветвь при истинном условии, else — при ложном; скобки { } — у каждой ветвиreturn 1;else относится к ближайшему if без else; в цепочке else if выполняется первая истинная ветвь — пороги от большего к меньшему= в условии, ; после условия и == для double — ошибки; первые две видны только по предупреждениямswitch переходит на равную метку и идёт до break; без break — проваливание в следующую ветвьswitch есть default; переменные внутри case — в фигурных скобках; метки — только константы целого типаreturn; раздел 3 — сравнения, логические операции и их приоритет, bool, enum class, точность double. Нужен для: разделов 5–6 — условия циклов и выход из них, проверка ввода и меню программы на switch; раздела 7 — функции, возвращающие результат проверки; разделов 14–19 — проверка результата каждой функции Windows API и разбор кода ошибки.