# Глава 2.2. Операторы
Вы уже читали примеры кода, в которых используется [оператор присваивания](https://en.cppreference.com/w/cpp/language/operator_assignment) `=`, оператор взятия элемента по индексу `[]` и оператор `.` для доступа к члену класса. Рассмотрим ещё несколько категорий операторов, которые пригодятся в первую очередь.
Чтобы поменять порядок выполнения операторов, они группируются скобками:
```cpp
int x = (a + 5) * 8;
```
## Операторы сравнения
[Операторы сравнения](https://en.cppreference.com/w/cpp/language/operator_comparison) (comparison operators) применимы к большинству фундаментальных типов:
- `==` — равенство.
- `!=` — неравенство.
- `<`, `>` — меньше, больше.
- `<=`, `>=` — меньше или равно, больше или равно.
Выражения сравнения приводятся к типу `bool`. **Выражение** (expression) — это последовательность операторов и операндов.
```cpp
bool a = 8.1 < 16; // true
bool b = -5 != -5; // false
std::string s = "";
bool c = s.empty(); // true
bool d = s.size() == 4; // false
```
Чему равно значение `b`? {.task_text}
```cpp {.example_for_playground .example_for_playground_005}
std::string text = "Operator";
bool b = text[text.size() - 1] == text[3];
```
```consoleoutput {.task_source #cpp_chapter_0022_task_0090}
```
`text.size()` вернёт 8. Символ по индексу 7 равен `r`. Символ по индексу 3 тоже равен `r`. {.task_hint}
```cpp {.task_answer}
true
```
## Логические операторы {#block-logical-operators}
[Логические операторы](https://en.cppreference.com/w/cpp/language/operator_logical) применимы ко всем выражениям, которые приводятся к `bool`.
Оператор `&&` — это логическое «И»: `is_filled && is_valid`.
```
┌───────┬───────┬────────┐
│ a │ b │ a && b │
├───────┼───────┼────────┤
│ false │ false │ false │
├───────┼───────┼────────┤
│ false │ true │ false │
├───────┼───────┼────────┤
│ true │ false │ false │
├───────┼───────┼────────┤
│ true │ true │ true │
└───────┴───────┴────────┘
```
Оператор `||` — это логическое «ИЛИ»: `has_gps_location || connected_to_wifi`.
```
┌───────┬───────┬────────┐
│ a │ b │ a || b │
├───────┼───────┼────────┤
│ false │ false │ false │
├───────┼───────┼────────┤
│ false │ true │ true │
├───────┼───────┼────────┤
│ true │ false │ true │
├───────┼───────┼────────┤
│ true │ true │ true │
└───────┴───────┴────────┘
```
Оператор `!` — это логическое «НЕ» (отрицание): `!is_valid`.
```
┌───────┬───────┐
│ a │ !a │
├───────┼───────┤
│ false │ true │
├───────┼───────┤
│ true │ false │
└───────┴───────┘
```
Пример:
```cpp {.example_for_playground .example_for_playground_001}
bool is_online = true;
bool is_updated = false;
std::println("{}", is_online || is_updated); // true
std::println("{}", !(is_online && is_updated)); // true
```
XOR — это булева функция, также известная как [исключающее «ИЛИ».](https://ru.wikipedia.org/wiki/%D0%98%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D1%8E%D1%89%D0%B5%D0%B5_%C2%AB%D0%B8%D0%BB%D0%B8%C2%BB) Она принимает два флага и возвращает `true`, если один из них истинен, а другой — ложен. В остальных случаях она возвращает `false`. {.task_text}
Напишите свою реализацию `hello_xor()`. {.task_text}
```cpp {.task_source #cpp_chapter_0022_task_0050}
```
Функция возвращает `true` тогда и только тогда, когда один из аргументов равен `true`, а другой — `false`. {.task_hint}
```cpp {.task_answer}
bool hello_xor(bool a, bool b)
{
return (!a && b) || (a && !b);
}
```
Логические операторы в C++ [выполняются по короткой схеме](https://ru.wikipedia.org/wiki/%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BF%D0%BE_%D0%BA%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D0%BE%D0%B9_%D1%81%D1%85%D0%B5%D0%BC%D0%B5) (short-circuit evaluation). Это значит, что как только результат выражения с логическими операторами становится очевидным, его вычисление _прекращается._ Например, выражение `false && f()` всегда равно `false` вне зависимости от того, какое значение вернет функция `f()`. Поэтому она даже не будет вызвана: {#block-short-circuit}
```cpp
import std;
bool f()
{
std::println("Calling f()");
return true;
}
int main()
{
const bool res = false && f();
std::println("res={}", res);
}
```
```
res=false
```
Обратите внимание: в консоли нет вывода функции `f()`!
Что будет выведено в консоль? {.task_text}
В случае ошибки напишите `err`. {.task_text}
```cpp {.example_for_playground}
import std;
bool f()
{
std::print("f ");
return false;
}
bool g()
{
std::print("g ");
return true;
}
int main()
{
std::print("{}", f() || true || g());
}
```
```consoleoutput {.task_source #cpp_chapter_0022_task_0040}
```
Результат выражения `f() || true || g()` всегда равен `true`. В соответствии со стратегией выполнения по короткой схеме `g()` не будет вызвана. {.task_hint}
```cpp {.task_answer}
f true
```
## Арифметические операторы {#block-arithmetic}
[Арифметические операторы](https://en.cppreference.com/w/cpp/language/operator_arithmetic) (arithmetic operators) применимы к любым выражениям, которые приводятся к числам. Они позволяют осуществлять:
- `+` — сложение: `5 + 6 == 11`.
- `-` — вычитание. `8 - 9 == -1`.
- `*` — умножение. `3 * 7 == 21`.
- `/` — деление. `10 / 4 == 2`.
- `%` — деление по модулю, то есть получение остатка от деления. `11 % 3 == 2`.
Перечисленные операторы называются бинарными. Они применяются к двум операндам: `a + b`. В C++ есть и унарные операторы — унарные плюс и минус: `+a`, `-a`.
Также к арифметическим операторам относятся **побитовые операторы** (bitwise operators). Они нужны для манипуляции над отдельными битами целых чисел. Чем они полезны и как их применяют на практике, рассмотрим в следующей главе.
## Возвращаемое значение и побочные эффекты
Операторы удобно рассматривать как функции, а их операнды — как аргументы этих функций. Тогда результат операции — это _возвращаемое значение_ соответствующей «функции».
Упрощённо, _побочный эффект_ оператора — это изменение хотя бы одного из операндов в процессе вычисления оператора.
С арифметическими операторами всё просто: их возвращаемое значение определяется смыслом соответствующего арифметического действия, а побочных эффектов они не имеют. Другое дело — операторы присваивания, в том числе составного. Очевидно, у них есть побочный эффект — это изменение своего левого операнда. Но также они _возвращают_ обновлённое значение своего левого операнда (точнее, ссылку на него, но сейчас это не так важно).
Рассмотрим команду `y = (x += 2);`. Выражение в скобках вернёт изменённое значение переменной `x`, и оно будет присвоено переменной `y`.
## Инкремент и декремент {#block-increment}
Увеличение или уменьшение значения на единицу можно записывать ещё короче! [Оператор инкремента](https://en.cppreference.com/w/cpp/language/operator_incdec) `++` увеличивает значение на 1, а оператор декремента `--` уменьшает. Эти операторы применимы _только_ к целым числам.
```cpp
++x; // Эквивалентно x+=1
--x; // Эквивалентно x-=1
```
В этом примере приведена префиксная форма операторов: пре-инкремент и пре-декремент.
Есть и постфиксная форма: в ней `++` и `--` указываются после переменной. Это называется пост-инкрементом и пост-декрементом:
```cpp
x++;
x--;
```
Обе формы имеют один и тот же побочный эффект: они изменяют переменную. Разница в возвращаемом значении.
Префиксный оператор возвращает обновлённое значение переменной: {#block-pre-increment}
```cpp
a = 2;
b = ++a; // a=3, b=3
```
Постфиксный оператор возвращает старое значение переменной:
```cpp
a = 2;
b = a++; // a=3, b=2
```
Постфиксные операторы инкремента и декремента имеют более высокий приоритет, чем префиксные.
И ещё одно важное отличие: префиксные формы возвращают саму переменную, а постфиксные — её неизменяемую копию. Поэтому такой код не скомпилируется:
```cpp
--i++;
```
Вначале выполнится постфиксный оператор и вернёт неизменяемую копию переменной: `--(i++)`. А так как неизменяемое значение нельзя уменьшить, компилятор прервёт сборку программы с ошибкой:
```
error: expression is not assignable
--i++;
^ ~~~
```
## Составное присваивание {#block-compound-assignment}
[Операторы составного присваивания](https://en.cppreference.com/w/cpp/language/operator_assignment) (compound assignment) объединяют присваивание переменной с арифметическим действием над ней. Их ввели в язык, чтобы записывать простые арифметические действия более кратко:
```cpp
x += 5; // x = x + 5
x -= y; // x = x - y
x *= 10; // x = x * 10
x /= y; // x = x / y
x %= 2; // x = x % 2
```
## Приоритет и ассоциативность операторов
Перечислим уже знакомые вам операторы по убыванию приоритета. Если в строке несколько операторов, то приоритет у них одинаковый.
- `a::b` — разрешение области видимости, например `std::println()`. Это оператор с наивысшим приоритетом.
- `a++`, `a--`, `a[b]`, `a.b` — постфиксные инкремент и декремент, взятие по индексу, доступ к члену класса.
- `!a`, `+a`, `-a`, `++a`, `--a` — логическое отрицание, унарные плюс и минус, префиксные инкремент и декремент.
- `a * b`, `a / b`, `a % b` — умножение, деление, деление по модулю.
- `a + b`, `a - b` — сложение, вычитание.
- `a < b`, `a <= b`, `a > b`, `a >= b` — больше, меньше.
- `a == b`, `a != b` — равенство, неравенство.
- `a && b` — логическое «И».
- `a || b` — логическое «ИЛИ».
- `a = b`, `a += b`, `a -= b`, `a *= b`, `a /= b`, `a %= b` — присваивание, составное присваивание.
Порядок вычисления можно изменять с помощью скобок.
Если рядом стоят два оператора с равным приоритетом, то порядок вычислений определяется ассоциативностью. Так, поскольку `+` — левоассоциативный оператор, то выражение `a + b + c` будет вычисляться как `(a + b) + c.` Оператор присваивания, наоборот, правоассоциативный, именно поэтому присваивание `a = b = 0` работает ожидаемым образом. Ведь оно эквивалентно записи `a = (b = 0).`
Таблицу с приоритетом и ассоциативностью _всех_ операторов C++ вы можете [посмотреть на cppreference.](https://en.cppreference.com/w/cpp/language/operator_precedence)
Нужны ли скобки, чтобы это выражение вычислилось как ожидается? `y/n`. {.task_text}
```cpp
width < 0 || volume / length <= max_val
```
```consoleoutput {.task_source #cpp_chapter_0022_task_0060}
```
Приоритет деления `/` выше, чем сравнения `<=`. А приоритет `||` меньше, чем сравнения. {.task_hint}
```cpp {.task_answer}
n
```
Как быть, если вы сомневаетесь, нужны ли в выражении скобки? [Если без скобок код трудно читать, то ставьте их!](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#res-parens) Например, выражение из задачи выше со скобками выглядит проще: `(width < 0) || (volume / length <= max_val)`.
Какое значение у переменной `x`? {.task_text}
В случае ошибки напишите `err`. {.task_text}
Ремарка: это пример плохого кода. В реальных проектах избегайте подобных трудночитаемых конструкций. Однако они встречаются на собеседованиях. {.task_text}
```cpp {.example_for_playground .example_for_playground_002}
int a = 1, b = 2, c = 3;
int x = a-- - b++ - c--;
```
```consoleoutput {.task_source #cpp_chapter_0022_task_0070}
```
Приоритет постфиксных операторов выше, чем у оператора вычитания `-`. Постфиксный оператор сначала возвращает значение переменной, а потом изменяет его. Поэтому `x` равен `1 - 2 - 3`. {.task_hint}
```cpp {.task_answer}
-4
```
Что будет выведено в консоль? {.task_text}
В случае ошибки напишите `err`. {.task_text}
```cpp {.example_for_playground .example_for_playground_003}
int c = 2;
int C = 5;
std::print("{}", c++ * ++C);
```
```consoleoutput {.task_source #cpp_chapter_0022_task_0080}
```
В этом выражении у постфиксного оператора максимальный приоритет. Следующим по приоритету выполнится префиксный оператор. И лишь затем — оператор умножения. Мы получим `2 * 6`. {.task_hint}
```cpp {.task_answer}
12
```
----------
## Резюме
- Операторы сравнения: `==`, `!=`, `<`, `>`, `<=`, `>=`.
- Логические операторы: `&&`, `||`, `!`.
- Логические операторы в C++ выполняются по короткой схеме (short-circuit evaluation).
- Арифметические операторы: `+`, `-`, `*`, `/`, `%`. Также к арифметическим операторам относятся побитовые операторы.
- Операторы составного присваивания — это краткая форма выполнения над переменной арифметического действия и присваивания ей. Например, `x *= 2`.
- Операторы инкремента `++` и декремента `--` бывают префиксными и постфиксными.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!