# Глава 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 ``` ## Логические операторы [Логические операторы](https://en.cppreference.com/w/cpp/language/operator_logical) применимы ко всем выражениям, которые приводятся к `bool`: - `&&` — «И»: `is_filled && is_valid`. - `||` — «ИЛИ»: `has_gps_location || connected_to_wifi`. - `!` — «НЕ» (отрицание): `!is_valid`. ```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); } ``` ## Арифметические операторы [Арифметические операторы](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`. ## Составное присваивание {#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; ``` ## Возвращаемое значение и побочные эффекты Операторы удобно рассматривать как функции, а их операнды — как аргументы этих функций. Тогда результат операции — это _возвращаемое значение_ соответствующей «функции». Упрощённо, _побочный эффект_ оператора — это изменение хотя бы одного из операндов в процессе вычисления оператора. С арифметическими операторами всё просто: их возвращаемое значение определяется смыслом соответствующего арифметического действия, а побочных эффектов они не имеют. Другое дело — операторы присваивания, в том числе составного. Очевидно, у них есть побочный эффект — это изменение своего левого операнда. Но также они _возвращают_ обновлённое значение своего левого операнда (точнее, ссылку на него, но сейчас это не так важно). Рассмотрим команду `y = (x += 2);`. Выражение в скобках вернёт изменённое значение переменной `x`, и оно будет присвоено переменной `y`. ## Инкремент и декремент Увеличение или уменьшение значения на единицу можно записывать ещё короче! [Оператор инкремента](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++; ^ ~~~ ``` ## Приоритет и ассоциативность операторов Перечислим уже знакомые вам операторы по убыванию приоритета. Если в строке несколько операторов, то приоритет у них одинаковый. - `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 ``` ---------- ## Резюме - Операторы сравнения: `==`, `!=`, `<`, `>`, `<=`, `>=`. - Логические операторы: `&&`, `||`, `!`. - Арифметические операторы: `+`, `-`, `*`, `/`, `%`. - Операторы составного присваивания — это краткая форма выполнения над переменной арифметического действия и присваивания ей. Например, `x *= 2`. - Операторы инкремента `++` и декремента `--` бывают префиксными и постфиксными.
Отправка...
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!