Условия: if, else, switch
В прошлом уроке появился bool — результат сравнения. Но сам по себе bool ничего не делает, кроме как хранит true/false. Условные инструкции — это то, что использует этот результат, чтобы решить, какой код выполнять: if, else и switch.
01Что ты изучишь
- Как
ifиспользуетbool, чтобы выполнить блок кода условно - Цепочки
else if/elseдля нескольких вариантов switch— альтернатива для сравнения одного значения с несколькими вариантами- Ловушку забытого
breakвswitch - Когда использовать
if, а когдаswitch
02Основная идея: развилка на дороге
Всё, что делают if, else и switch, — это развилка: программа доходит до определённой точки и должна выбрать, по какому из путей пойти дальше, в зависимости от какого-то условия. До сих пор код в этом курсе выполнялся строго сверху вниз, инструкция за инструкцией, без вариантов — как прямая дорога без поворотов. Условные инструкции — первый способ свернуть с этой прямой линии: одна и та же программа при разных значениях переменных пойдёт по разным путям и выполнит разный код. if/else — это развилка на два (или больше) направления по любому условию; switch — тот же принцип, но для частного случая, когда развилка строится вокруг одного конкретного значения.
03if — условное выполнение
Самый простой вариант развилки — «выполнить или не выполнить», без альтернативы. Именно это делает if: берёт выражение типа bool в круглых скобках, и если оно true — выполняет следующий за ним блок { }. Если false — блок целиком пропускается, будто его вообще не было, и программа продолжает со следующей инструкции после него.
int hp = 0;
if (hp == 0)
{
std::cout << "Game over\n";
}
hp == 0 — то самое выражение сравнения из прошлого урока, оно и есть условие. Блок { } после if — это ровно та же составная инструкция, что и тело main() (третий урок) — просто теперь она выполняется не всегда, а по условию.
04else и else if
else добавляет альтернативную ветку — код, который выполняется, когда условие if оказалось false. Проще всего увидеть это по шагам — от простого if/else с двумя ветками до полной цепочки на несколько условий.
if (hp <= 0)
{
std::cout << "Game over\n";
}
else
{
std::cout << "You're fine\n";
}
Ровно два исхода: либо условие hp <= 0 истинно и выполняется первая ветка, либо оно ложно и выполняется else. Третьего не дано — else здесь ловит буквально «всё остальное».
if (hp <= 0)
{
std::cout << "Game over\n";
}
else if (hp < 30)
{
std::cout << "Low health!\n";
}
else
{
std::cout << "You're fine\n";
}
else if — это буквально else, внутри которого сразу стоит новый if: «если не первое условие, то проверь ещё вот это». Условия проверяются по порядку сверху вниз, и выполняется ровно одна ветка — та, для которой первое встреченное условие оказалось true (или последняя else, если ни одно условие не подошло). Как только одна ветка выполнена, остальные проверки в этой цепочке пропускаются — даже если следующее условие тоже было бы true. Цепочку можно продолжать сколько угодно раз — else if можно повторять, финальный else не обязателен, но без него какие-то значения могут остаться вообще без обработки.
05switch — сравнение одного значения с вариантами
«Ранг 1 — золото, ранг 2 — серебро, ранг 3 — бронза», записанное через else if, выглядит так: if (rank == 1) ... else if (rank == 2) ... else if (rank == 3) ... — слово rank повторяется три раза подряд, хотя сравнивается всегда одно и то же. Для этого частного случая (одно значение, несколько конкретных вариантов) в C++ есть более компактная запись — switch. Тоже по шагам:
int rank = 2;
switch (rank)
{
case 1:
std::cout << "Gold\n";
break;
default:
std::cout << "No medal\n";
break;
}
switch (rank) — какое значение сравниваем; case 1: — с чем сравниваем; default: — что делать, если ни один case не совпал (аналог финального else). Сейчас rank == 2, ни один case не подходит, поэтому сработает default и выведется «No medal».
int rank = 2;
switch (rank)
{
case 1:
std::cout << "Gold\n";
break;
case 2:
std::cout << "Silver\n";
break;
case 3:
std::cout << "Bronze\n";
break;
default:
std::cout << "No medal\n";
break;
}
Теперь rank == 2 совпадает с case 2:, выполняется std::cout << "Silver\n";, затем break; останавливает выполнение switch целиком — оставшиеся case 3 и default даже не проверяются. break; в конце каждой ветки — не декоративный элемент, а обязательная часть.
int rank = 1;
switch (rank)
{
case 1:
std::cout << "Gold\n";
// break; ← убрали нарочно
case 2:
std::cout << "Silver\n";
break;
case 3:
std::cout << "Bronze\n";
break;
default:
std::cout << "No medal\n";
break;
}
// вывод при rank == 1:
// Gold
// Silver ← неожиданно, и это не опечатка в примере
Без break; после case 1: выполнение не останавливается на своей ветке — оно проваливается в следующую (case 2:) целиком, даже несмотря на то, что rank не равен 2. Это называется fallthrough, и это разобрано отдельно в разделе «Типичные ошибки» ниже — по умолчанию это почти всегда баг, а не задумка.
06Вложенные условия: if и switch внутри друг друга
Программа-персонаж продолжает расти: рядом с уже знакомым hp появляется difficulty — уровень сложности, влияющий на то, что выводится игроку. Тело if/else — это блок { }, а внутри блока может быть любая инструкция, включая ещё один if или целый switch. Ничего особенного здесь нет — это тот же принцип вложенности, что и с обычными { } из раздела «else и else if», просто теперь внутри не только std::cout, а целая условная конструкция.
int hp = 60;
int difficulty = 2;
if (hp <= 0)
{
std::cout << "Game over\n";
}
else
{
switch (difficulty)
{
case 1:
std::cout << "Easy mode - alive\n";
break;
case 2:
std::cout << "Normal mode - alive\n";
break;
default:
std::cout << "Hard mode - alive\n";
break;
}
}
Внешний if отвечает на один вопрос («жив ли персонаж»), а switch внутри его else-ветки — на другой («какая сложность»), но только для случая, когда персонаж действительно жив. Проверять difficulty, когда hp <= 0, не нужно вовсе — вложенность позволяет не тратить на это отдельную проверку.
Тот же приём работает и в обратную сторону — if внутри if: например, сначала проверить hp <= 0, а внутри этой ветки — ещё отдельный if (hasRevivePotion), чтобы решить, продолжится игра или будет полный game over. Правило то же: чем больше уровней вложенности, тем сложнее читать код, поэтому на практике редко имеет смысл вкладывать глубже двух-трёх уровней — если условий становится больше, обычно лучше вынести часть логики в отдельную функцию (об этом — в одном из следующих уроков).
07if или switch — таблица
if / else if | switch | |
|---|---|---|
| Условие | любое выражение bool (диапазоны, комбинации через &&/||) | сравнение одного значения с конкретными константами |
| Когда удобнее | диапазоны (hp < 30), сложные условия | много конкретных вариантов одного значения (ранг, тип предмета, код команды) |
| Забыть нельзя | фигурные скобки при нескольких инструкциях в ветке | break; в конце каждого case |
| Catch-all ветка | else — необязательна, но без неё часть значений может остаться без обработки | default: — то же самое, необязательна по синтаксису |
| Вложенность | любая инструкция, включая ещё один if или switch, может быть телом ветки | то же самое — внутри case может быть целый if/else |
Пример для rank | if (rank == 1) ... else if (rank == 2) ... else if (rank == 3) ... | switch (rank) { case 1: ... case 2: ... case 3: ... } |
На одном и том же примере с rank видно, откуда берётся выигрыш switch: слово rank в варианте с if повторяется три раза (по разу на каждое сравнение), а в switch — один раз, в самом начале.
08Хуже → лучше: пропущенные фигурные скобки
Задача — при hp <= 0 вывести две строки: «Game over» и «Restarting...». Отступы в коде выглядят одинаково во всех трёх версиях — но не отступы решают, что относится к if.
int hp = 50;
if (hp <= 0)
std::cout << "Game over\n";
std::cout << "Restarting...\n";
При hp = 50 условие hp <= 0 ложно — казалось бы, обе строки должны быть пропущены:
if без { } относится только к одной следующей инструкции — второй std::cout выполняется всегда, независимо от условия, хотя отступ визуально предполагает обратное.
int hp = 50;
if (hp <= 0)
{
std::cout << "Game over\n";
std::cout << "Restarting...\n";
}
Теперь обе строки — часть блока, и при hp = 50 не выполнится ни одна из них: вывода нет вообще, как и задумано.
int hp = 50;
if (hp <= 0)
{
std::cout << "Game over\n";
std::cout << "Restarting...\n";
}
else
{
std::cout << "Still alive\n";
}
Добавленный else не обязателен для исправления бага — его исправили скобки. Но else делает код надёжнее иначе: теперь оба исхода (hp кончился / hp ещё есть) написаны явно, и при чтении кода не возникает вопроса «а что происходит, если условие ложно?» — ответ виден сразу.
09Попробуй изменить
- В примере из раздела «else и else if» поменяй
hpна 50 — сработает веткаelse(«You're fine»). - В примере из раздела «switch» убери
break;послеcase 1:— приrank = 1программа выведет и «Gold», и «Silver» подряд (fallthrough). - Добавь в switch ещё один
case 4:с новым текстом — синтаксис такой же, как у остальных.
10Живая отладка: типичные ошибки
Как и в прошлом уроке, ни одна из этих ошибок не остановит компиляцию — все три проявляются только во время выполнения.
int rank = 1;
switch (rank)
{
case 1:
std::cout << "Gold\n";
// break; ← забыли
case 2:
std::cout << "Silver\n";
break;
}
Иногда fallthrough используется намеренно (несколько case подряд для одного и того же кода), но по умолчанию это почти всегда баг. Многие компиляторы предупреждают об этом отдельным warning (-Wimplicit-fallthrough в g++/Clang).
Полный разбор с примером вывода — в разделе «Хуже → лучше» выше. Правило этого курса: фигурные скобки у if/else — всегда, даже для одной инструкции внутри.
= вместо == внутри ifif (hp = 0) // присваивание вместо сравнения — та же ошибка, что в операторах
Здесь она особенно коварна: результат присваивания (0) приводится к bool как false, поэтому ветка if никогда не выполнится, а заодно hp будет молча обнулён. Компилятор обычно предупреждает об этом (warning), но не всегда останавливает компиляцию.
11Проверь себя
Три вопроса: предсказание выполнения по цепочке условий, диагностика fallthrough и выбор между if и switch.
hp = 25?if (hp <= 0) { std::cout << "Game over"; }
else if (hp < 30) { std::cout << "Low health!"; }
else { std::cout << "You're fine"; }Показать объяснение
Выведет «Low health!». Условия проверяются по порядку: hp <= 0 ложно (25 не ≤ 0), поэтому проверяется hp < 30 — истинно (25 < 30), эта ветка выполняется, а else пропускается целиком, даже не проверяясь.
rank = 1 эта программа выводит и «Gold», и «Silver». Как это исправить?switch (rank) {
case 1: std::cout << "Gold\n";
case 2: std::cout << "Silver\n"; break;
}Показать объяснение
После case 1: нет break; — выполнение не останавливается на своей ветке, а проваливается в следующую (case 2:) целиком, даже хотя rank не равен 2. Это fallthrough (§05). Исправление — добавить break; сразу после std::cout << "Gold\n";.
Показать объяснение
switch — одно значение (номер дня) сравнивается с конкретными константами 1..7, ровно тот случай, для которого switch и создан (§05, §07). Для сравнения: задачу «дёшево/средне/дорого» по диапазонам цены (< 10, < 100) switch решить не может вообще — там нужен if/else if, потому что сравниваются диапазоны, а не конкретные значения.
12Практика
★★ По значению hp выведи «Game over» (hp == 0 или меньше), «Low health!» (hp меньше 30) или «You're fine» — с помощью if / else if / else.
Три ветки подряд: if (hp == 0), else if (hp < 30), else — и в каждой из трёх обязательно должен быть свой std::cout с соответствующим текстом.
Решение: if (hp == 0) { std::cout << "Game over\n"; } else if (hp < 30) { std::cout << "Low health!\n"; } else { std::cout << "You're fine\n"; }
★★ По значению rank (1, 2 или 3) выведи «Gold», «Silver» или «Bronze» через switch, с break в каждой ветке и default на прочие случаи.
Четыре ветки: case 1, case 2, case 3, default — в каждой свой std::cout и обязательный break; (кроме, возможно, самой последней ветки).
Решение: switch (rank) { case 1: std::cout << "Gold\n"; break; case 2: std::cout << "Silver\n"; break; case 3: std::cout << "Bronze\n"; break; default: std::cout << "No medal\n"; break; }
★★★ По значениям hp и difficulty выведи результат с помощью вложенных условий, как в разделе «Вложенные условия» выше: если hp <= 0 — выведи «Game over» и дальше ничего не проверяй. Иначе — используя switch (difficulty) внутри ветки else, выведи «Easy mode - alive» (1), «Normal mode - alive» (2) или «Hard mode - alive» (для остальных, через default).
Внешний if/else отвечает на вопрос «жив ли персонаж» и должен сам выводить «Game over» в своей ветке if — switch идёт целиком внутри else, а не рядом с ним.
Внутри switch (difficulty) — три ветки (case 1, case 2, default), и в каждой свой std::cout и break;, ровно как в примере раздела «Вложенные условия».
Решение: if (hp <= 0) { std::cout << "Game over\n"; } else { switch (difficulty) { case 1: std::cout << "Easy mode - alive\n"; break; case 2: std::cout << "Normal mode - alive\n"; break; default: std::cout << "Hard mode - alive\n"; break; } }
13Термины
| Термин | Значит |
|---|---|
| Ветвление (branching) | выполнение разного кода в зависимости от условия — общее название для того, что делают if/else/switch |
| Условие (condition) | выражение типа bool, определяющее, какая ветка выполнится |
| Fallthrough | провал выполнения из одной ветки case в следующую из-за отсутствующего break; |
| Вложенность (nesting) | размещение одной условной конструкции внутри тела другой |
14Что ты теперь умеешь
До этого урока код выполнялся всегда одинаково, сверху вниз. Теперь программа может выбирать путь:
- Ты можешь написать цепочку
if/else if/elseдля нескольких взаимоисключающих вариантов и предсказать, какая именно ветка выполнится при конкретном значении. - Ты можешь выбрать между
ifиswitchдля конкретной задачи — и объяснить, почемуswitchне годится для диапазонов. - Ты можешь найти в чужом
switchпропущенныйbreak;и объяснить, к какому конкретно выводу приведёт fallthrough. - Ты можешь вложить одну условную конструкцию в другую (
switchвнутриelse,ifвнутриif) и объяснить, зачем это позволяет не дублировать проверку. - Ты можешь объяснить, почему пропущенные фигурные скобки у
if— не стилистическая мелочь, а источник настоящего бага.
15Кратко
if (условие) { ... }— выполняет блок, только если условиеtrue.else if/else— цепочка альтернатив, выполняется ровно одна ветка.switch— компактная альтернатива для сравнения одного значения с несколькими константами; каждая веткаcaseобязательно заканчиваетсяbreak;, иначе fallthrough в следующую.- Фигурные скобки у
if/else— всегда, даже для одной инструкции. =вместо==внутри условия — не ошибка компиляции, а логическая ошибка (обычно с warning).
Условия выбирают путь один раз за проход программы. Следующий урок — циклы: тот же выбор «выполнять или нет», но повторяющийся снова и снова, пока условие остаётся истинным.
if/else/switch работают в Unreal C++ без изменений — это часть языка, а не движка. В Blueprint (визуальном скриптинге Unreal) им соответствуют ноды Branch и Switch — та же логика, только в виде графа вместо текста:
| C++ | Blueprint-нода | Как выглядит |
|---|---|---|
if (условие) { ... } else { ... } | Branch | один вход с bool-условием, два выхода-пина: True и False — выполнение продолжается по одному из них, как две ветки if/else |
switch (значение) { case ... } | Switch on Int / Switch on Enum | один вход со значением, отдельный выход-пин на каждый вариант плюс пин Default — прямой аналог case и default: |
Вложенность работает точно так же: выход True одной Branch-ноды может вести во вход следующей Branch или Switch — визуальный аналог switch внутри else из раздела «Вложенные условия» выше. Когда дойдёт до Blueprint-интеграции курса, эта параллель уже будет знакомой.