# Глава 3. Условия
Вы познакомитесь с тремя вариантами условного выполнения кода:
- классическим `if-else`,
- лаконичным тернарным оператором,
- сопоставлением выражения с набором значений через `switch-case`.
## Условия if-else
Чтобы выполнять различные действия в зависимости от условия, используется конструкция `if-else`. Условием может быть любое выражение, приводимое к `bool`:
```c++
if (условное выражение)
Инструкция для true
else
Инструкция для false
```
Ветка `else` может отсутствовать.
Каждая ветка условия состоит строго из одной инструкции (statement). Как вы [помните,](/courses/cpp/chapters/cpp_chapter_0020/#block-statements) инструкции (statements) — это фрагменты кода, выполняемые последовательно. И они бывают трех видов: простые, составные и управляющие.
С простыми инструкциями вы уже знакомы. Они оканчиваются точкой с запятой:
```c++
return 0;
```
```c++
char quit = 'Q';
```
```c++
std::println("{}", a > b);
```
Составная инструкция (compound statement) — это обернутые фигурными скобками простые инструкции. Составные инструкции называют блоками.
В этом примере каждая ветка условия — это блок:
```c++ {.example_for_playground .example_for_playground_001}
const std::string filename = "scale_2400.png";
if (filename.contains('.'))
{
std::println("Processing file: {}", filename);
}
else
{
std::println("Please provide filename with extension");
handle_user_input();
}
```
И третий вид инструкций — управляющие. К ним, например, относятся условия и циклы. В данном примере `if-else` — это одна управляющая инструкция, ветки которой содержат по блоку:
```c++
if (a > b)
{
// ...
}
else
{
// ...
}
```
### Когда фигурные скобки обязательны, а когда — опциональны? {#block-braces}
Каждая ветка условия содержит _только одну_ инструкцию, будь то простая, составная или управляющая инструкция. Отсюда следуют правила расстановки фигурных скобок:
- Если в ветке условия требуется выполнить больше одной инструкции, то такие инструкции объединяются в блок с помощью фигурных скобок. То есть превращаются в составную инструкцию.
- Если же требуется выполнить только одну инструкцию, фигурные скобки **можно** опустить.
Если фигурные скобки нужны только в одной из веток условия, то для единообразия лучше ставить их и в другой ветке.
Эти же правила расстановки фигурных скобок действуют и для тела циклов.
В данном случае скобки обязательны:
```c++ {.example_for_playground .example_for_playground_002}
std::size_t cpu_count = 0;
if (cpu_count == 0)
{
std::println("Setting CPU count to default value");
cpu_count = 4;
}
```
А здесь скобки можно опустить:
```c++ {.example_for_playground .example_for_playground_003}
const std::string s = "Some user input";
if (s.empty())
std::println("Empty input!");
else
std::println("User entered: {}", s);
```
Чему равно значение `a`? {.task_text}
```c++
int a = 3;
if (++a < 4)
a++;
a *= 4;
```
```consoleoutput {.task_source #cpp_chapter_0030_task_0070}
```
Префиксный инкремент сначала увеличивает `a` на 1, а потом возвращает значение. Поэтому условие в `if` не выполняется: `a` равно 4. Тело `if` состоит из единственной инструкции `a++`. Вторая инструкция выполнится безусловно. {.task_hint}
```cpp {.task_answer}
16
```
Нужно реализовать функцию `vertical_flight_speed()`. Она вычисляет вертикальную скорость полета самолета. Для этого она принимает два показания высотомера и прошедшее между ними время. {.task_text}
Напишите тело этой функций, соответствующее многострочному комментарию в коде. Вам пригодятся: {.task_text #block-nan}
- Константа `NAN` — not a number, [нечисло](https://en.cppreference.com/w/c/numeric/math/NAN).
- Константа `INFINITY` — [бесконечность](https://en.cppreference.com/w/cpp/numeric/math/INFINITY).
- Функция [std::abs()](https://en.cppreference.com/w/cpp/numeric/math/abs) для получения модуля числа.
```c++ {.task_source #cpp_chapter_0030_task_0010}
/* cur_height — текущая высота, м.
prev_height — предыдущая высота, м.
elapsed_time — время (с), за которое произошло изменение высоты.
Возвращаемое значение — вертикальная скорость полета, м/c.
Если значение одной из высот ниже -500 м, то возвращается NAN.
Если время меньше либо равно 0, то возвращается INFINITY.
*/
double vertical_flight_speed(double cur_height,
double prev_height,
double elapsed_time)
{
}
```
Пример раннего выхода из функции: `if (elapsed_time <= 0) return INFINITY;`. {.task_hint}
```c++ {.task_answer}
/* cur_height — текущая высота, м.
prev_height — предыдущая высота, м.
elapsed_time — время (с), за которое произошло изменение высоты.
Возвращаемое значение — вертикальная скорость полета, м/с.
Если значение одной из высот ниже -500 м, то возвращается NAN.
Если время меньше либо равно 0, то возвращается INFINITY.
*/
double vertical_flight_speed(double cur_height,
double prev_height,
double elapsed_time)
{
const double min_height = -500.0;
if (cur_height < min_height || prev_height < min_height)
{
return NAN;
}
if (elapsed_time <= 0)
{
return INFINITY;
}
return std::abs(cur_height - prev_height) / elapsed_time;
}
```
Напишите функцию `is_valid()` для наивной валидации e-mail. Она должна принимать строку и возвращать `true`, если в строке соблюдены условия: есть символы `@` и `.`; после символа `@` есть `.`; `@` не идет первым. {.task_text}
Вам поможет метод строки [find().](https://en.cppreference.com/w/cpp/string/basic_string/find) Он принимает подстроку или символ и опциональный параметр — индекс, начиная с которого искать вхождение. По умолчанию это 0. Метод возвращает индекс первого вхождения либо константу `std::string::npos`, означающую, что подстрока не найдена. Тип возвращаемого значения — `std::size_t`. {.task_text}
```c++ {.task_source #cpp_chapter_0030_task_0020}
```
Пример проверки индекса символа: `email.find('.', i) != std::string::npos`. {.task_hint}
```c++ {.task_answer}
bool is_valid(std::string email)
{
const std::size_t i = email.find('@');
if (i == std::string::npos || i == 0)
{
return false;
}
return email.find('.', i) != std::string::npos;
}
```
### Вложенные условия
В C++ нет конструкций наподобии `if-elif-else` как в Python, позволяющих внутри одного условия организовать неограниченное количество проверок. На помощь приходят вложенные условия.
Перед вами цепочка `if-else` для сравнения цвета из RGB-палитры с тремя значениями. Префикс `0x` нужен для обозначения шестнадцатеричных чисел:
```c++
if (color_code == 0x80ED99)
{
std::println("Light green");
}
else
{
if (color_code == 0x22577A)
{
std::println("Lapis Lazuli");
}
else
{
if (color_code == 0xC7F9CC)
{
std::println("Tea green");
}
else
{
std::println("Color not in this palette");
}
}
}
```
Этот код «лесенкой» выглядит ужасно. Поэтому подобные вложенные условия лучше форматировать без отступов и избыточных фигурных скобок. Ведь `if-else` — это инструкция, а вокруг единственной инструкции скобки можно не ставить. Такой код проще воспринимать:
```c++ {.example_for_playground .example_for_playground_004}
if (color_code == 0x80ED99)
{
std::println("Light green");
} else if (color_code == 0x22577A)
{
std::println("Lapis Lazuli");
} else if (color_code == 0xC7F9CC)
{
std::println("Tea green");
} else
{
std::println("Color not in this palette");
}
```
Но в длинных цепочках вложенных `if-else` все равно легко допустить ошибку. И в некоторых случаях им на замену приходит конструкция `switch-case`, которую мы рассмотрим дальше.
## Тернарный оператор
Тернарный оператор (ternary operator) позволяет компактно записывать простые условия. А называется он так, потому что состоит из трех выражений: условия и веток выполнения.
```
(условное выражение) ? выражение для true : выражение для false;
```
Например:
```c++ {.example_for_playground .example_for_playground_005}
std::println("{}", price < 250 ? "cheap" : "expensive");
```
Условие записывается до символа `?`. За ним следует выражение, выполняющееся, если условие истинно. И после `:` указывается выражение для случая, если условие ложно.
Перепишем этот `if-else` на тернарный оператор:
```c++
int status_code = 0;
if (request_body_len > max_len)
{
status_code = -1;
}
else
{
status_code = handle_request();
}
```
Отказ от `if-else` позволяет сделать переменную `status_code` константой:
```c++
const int status_code = (request_body_len > max_len) ? -1 : handle_request();
```
Скомпилируется ли этот код? Зависит от того, какой тип возвращает `handle_request()`! Дело в том, что типы возвращаемых тернарным оператором значений должны быть приводимы один к другому. Поэтому если `handle_request()` возвращает `int` или `bool`, код скомпилируется. А если `void`, то нет:
```
error: right operand to ? is void, but left operand is of type 'int'
```
Перепишите функцию `max()` с использованием тернарного оператора. Тело функции должно состоять из единственной инструкции. {.task_text #block-max}
```c++ {.task_source #cpp_chapter_0030_task_0030}
int max(int a, int b)
{
if (a > b)
{
return a;
}
return b;
}
```
Оператор `return` должен вернуть результат применения тернарного оператора к условию `a > b`. {.task_hint}
```c++ {.task_answer}
int max(int a, int b)
{
return a > b ? a : b;
}
```
Кстати, в стандартной библиотеке C++ есть функции [std::max()](https://en.cppreference.com/w/cpp/algorithm/max) и [std::min()](https://en.cppreference.com/w/cpp/algorithm/min). При работе над реальными проектами руководствуйтесь правилом: [используй готовое.](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-lib) То есть прежде чем писать функцию самостоятельно, проверьте — вдруг она уже реализована в стандартной библиотеке?
## Конструкция switch-case {#switch-case}
Конструкция `switch-case` удобна, когда требуется сравнивать выражение с набором константных значений. Это [более читабельная](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-switch-if) замена вложенных `if-else`:
```c++
switch(выражение)
{
case значение_1:
инструкции_1;
break;
case значение_2:
инструкции_2;
break;
case значение_n:
инструкции_n;
break;
default:
инструкции;
}
```
Но у `switch-case` есть ограничения. Выражение для `switch` должно быть:
- целочисленным (например, `int`),
- символьного типа (например, `char`),
- либо перечислением `enum`, о котором вы скоро [узнаете.](/courses/cpp/chapters/cpp_chapter_0050/#block-enum)
То есть сравнивать строку `std::string` с использованием `switch` не получится.
Так выглядит `switch-case` для сопоставления символа со значениями:
```c++ {.example_for_playground .example_for_playground_006}
char user_input = 'y';
switch(user_input)
{
case '\n':
std::println("User pressed enter. Repeating question.");
show_question();
break;
case 'y':
std::println("Yes");
break;
case 'n':
std::println("No");
break;
default:
std::println("Invalid input");
}
```
```
Yes
```
Тело `switch` **обязательно** обрамляется фигурными скобками. Вокруг инструкций в блоках `case` и `default` скобки не ставятся.
Блок `default` опциональный. Он срабатывает, если не подошел ни один из блоков `case`.
### Оператор break
В большинстве случаев после выполнения `case` требуется выйти из `switch`. Для этого используется оператор `break`. Если его нет, то выполнение **продолжится** до следующего `break` или до самого конца `switch`:
```c++ {.example_for_playground .example_for_playground_007}
const std::size_t number_system = 10;
switch(number_system)
{
case 2:
std::println("Binary");
case 10:
std::println("Decimal");
case 16:
std::println("Hexadecimal");
default:
std::println("Other");
}
```
```
Decimal
Hexadecimal
Other
```
Забытый `break` — это _самая распространенная_ ошибка при использовании `switch-case`.
Что выведется в консоль? {.task_text}
```c++ {.example_for_playground .example_for_playground_008}
const std::size_t mark = 3;
switch(mark)
{
case 1:
std::print("e");
case 2:
std::print("d");
case 3:
std::print("c");
case 4:
std::print("b");
case 5:
std::print("a");
}
```
```consoleoutput {.task_source #cpp_chapter_0030_task_0040}
```
Условие попадает под `case 3`, поэтому выполнится его блок. Так как в блоке отсутствует `break`, выполнятся следующие блоки `case`. {.task_hint}
```cpp {.task_answer}
cba
```
Бывают и случаи, когда `break` _специально_ не ставится. Если несколько разных значений нужно обработать одинаково, то соответствующие им блоки `case` идут рядом. Первые остаются пустыми, а в последний размещается необходимая обработка.
Что выведется в консоль? {.task_text}
```c++ {.example_for_playground .example_for_playground_009}
const std::size_t mark = 1;
switch(mark)
{
case 1:
case 2:
case 3:
std::println("bad");
break;
case 4:
std::print("good");
break;
case 5:
std::print("excellent");
break;
default:
std::print("-");
}
```
```consoleoutput {.task_source #cpp_chapter_0030_task_0050}
```
Условие попадает под `case 1`. Это пустой блок, в котором нет `break`. Поэтому выполнится следующий блок `case `, а затем и `case 3`. В `case 3` есть `break`, прерывающий `switch`. {.task_hint}
```cpp {.task_answer}
bad
```
Перепишите эту функцию с применением `switch-case`. {.task_text}
```c++ {.task_source #cpp_chapter_0030_task_0060}
void log_state(int code)
{
std::string state = "";
if (code == 0)
{
state = "Operation succeded";
} else if (code == 1)
{
state = "Still in progress";
} else if (code == 2)
{
state = "Aborted";
} else {
state = "Invalid state";
}
std::println("State of user's operation: {}", state);
}
```
Не забудьте про `break` и `default`. {.task_hint}
```c++ {.task_answer}
void log_state(int code)
{
std::string state = "";
switch(code)
{
case 0:
state = "Operation succeded";
break;
case 1:
state = "Still in progress";
break;
case 2:
state = "Aborted";
break;
default:
state = "Invalid state";
}
std::println("State of user's operation: {}", state);
}
```
----------
## Резюме
- Инструкции (statements) — это фрагменты кода, выполняемые последовательно.
- Инструкции бывают простыми, составными и управляющими.
- Составные инструкции — это инструкции, объединенные в блок с помощью фигурных скобок.
- Для условного выполнения кода предусмотрены конструкции: `if-else`, тернарный оператор и `switch-case`.
- Конструкции `if-else` и `switch-case` относятся к управляющим инструкциям.
- Внутри блоков `switch-case` не забывайте ставить оператор `break`.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!