5.1 – Приоритет и ассоциативность операторов
Введение в главу
Данная глава основывается на концепциях урока «1.9 – Знакомство с литералами и операторами». Далее следует его краткий обзор.
В математике операция – это математическое вычисление, включающее ноль или более входных значений (называемых операндами), которые создают новое значение (называемое выходным значением). Конкретная выполняемая операция обозначается конструкцией (обычно символом или парой символов), называемой оператором.
Например, в детстве мы все узнаем, что 2 + 3 равно 5. В этом случае литералы 2 и 3 являются операндами, а символ + – это оператор, который говорит нам применять математическое сложение к операндам для получения нового значения 5.
В этой главе мы обсудим темы, связанные с операторами, и исследуем многие обычные операторы, поддерживаемые C++.
Приоритет операторов
Теперь давайте рассмотрим более сложное выражение, например, 4 + 2 * 3
. Чтобы вычислить это выражение, мы должны понимать, что делают операторы, и правильный порядок их применения. Порядок вычисления операторов в составном выражении определяется приоритетом операторов. Используя обычные математические правила приоритета (которые гласят, что умножение вычисляется перед сложением), мы знаем, что приведенное выше выражение должно вычисляться как 4 + (2 * 3), что дает в итоге значение 10.
В C++, когда компилятор встречает выражение, он должен аналогичным образом проанализировать выражение и определить, как оно должно быть вычислено. Для этого всем операторам назначается определенный уровень приоритета. Сначала вычисляются операторы с наивысшим уровнем приоритета.
В таблице ниже видно, что умножение и деление (уровень приоритета 5) имеют больший приоритет, чем сложение и вычитание (уровень приоритета 6). Таким образом, 4 + 2 * 3 вычисляется как 4 + (2 * 3), потому что умножение имеет более высокий уровень приоритета, чем сложение.
Ассоциативность операторов
Что произойдет, если два оператора в одном выражении имеют одинаковый уровень приоритета? Например, в выражении 3 * 4/2 операторы умножения и деления имеют уровень приоритета 5. В этом случае компилятор не может полагаться только на приоритет, чтобы определить, как вычислить результат.
Если два оператора с одинаковым уровнем приоритета соседствуют друг с другом в одном выражении, ассоциативность операторов сообщает компилятору, следует ли вычислять операторы слева направо или справа налево. Операторы на уровне приоритета 5 имеют ассоциативность слева направо, поэтому выражение вычисляется слева направо: (3 * 4) / 2 = 6.
Таблица операторов
Приведенная ниже таблица в первую очередь предназначена для использования в качестве справочного материала, к которому вы можете обратиться в будущем, чтобы решить любые вопросы о приоритете или ассоциативности, которые у вас возникнут.
Примечания:
- Уровень приоритета 1 – это самый высокий уровень приоритета, а уровень 17 – самый низкий. Операторы с более высоким уровнем приоритета вычисляются первыми.
- L → R означает ассоциативность слева направо (Left to Right).
- R → L означает ассоциативность справа налево (Right to Left).
Приор. / Ассоц. | Оператор | Описание | Шаблон использования |
---|---|---|---|
1 нет | :: | Глобальная область видимости (унарный) | ::имя |
:: | Область пространства имен (бинарный) | имя_класса::имя_члена | |
2 L → R | () | Круглые скобки | (выражение) |
() | Вызов функции | имя_функции(параметры) | |
() | Инициализация | тип имя(выражение) | |
{} | Унифицированная инициализация (С++11) | тип имя{выражение} | |
type() | Функциональное приведение типа | новый_тип(выражение) | |
type{} | Функциональное приведение типа (C++11) | новый_тип{выражение} | |
[] | Индекс массива | указатель[выражение] | |
. | Доступ к члену из объекта | объект.имя_члена | |
-> | Доступ к члену из указателя на объект | указатель_на_объект->имя_члена | |
++ | Постфиксный инкремент | lvalue++ | |
-- | Постфиксный декремент | lvalue-- | |
typeid | Динамическая идентификация типа данных | typeid(тип) или typeid(выражение) | |
const_cast | Приведение типа с удалением модификатора const | const_cast<тип>(выражение) | |
dynamic_cast | Приведение типа с проверкой типа во время выполнения | dynamic_cast<тип>(выражение) | |
reinterpret_cast | Приведение одного типа к другому | reinterpret_cast<тип>(выражение) | |
static_cast | Приведение типа с проверкой типа во время компиляции | static_cast<тип>(выражение) | |
sizeof... | Получить размер пакета параметров | sizeof...(выражение) | |
noexcept | Проверка исключения времени компиляции | noexcept(выражение) | |
alignof | Получить выравнивание в байтах указанного типа | alignof(тип) | |
3 R → L | + | Унарный плюс | +выражение |
- | Унарный минус | -выражение | |
++ | Префиксный инкремент | ++lvalue | |
-- | Префиксный декремент | --lvalue | |
! | Логическое НЕ (NOT) | !выражение | |
~ | Побитовое НЕ (NOT) | ~выражение | |
(тип) | Приведение типа в стиле C | (новый_тип)выражение | |
sizeof | Размер в байтах | sizeof(тип) или sizeof(выражение) | |
co_await | Ожидание асинхронного вызова | co_await выражение | |
& | Адрес операнда | &lvalue | |
* | Разыменование | *выражение | |
new | Динамическое выделение памяти | new тип | |
new[] | Динамическое выделение памяти для массива | new тип[выражение] | |
delete | Динамическое освобождение памяти | delete указатель | |
delete[] | Динамическое освобождение памяти из-под массива | delete[] указатель | |
4 L → R | ->* | Указатель на член указателя на объект | указатель_на_объект->*указатель_на_член |
.* | Указатель на член объекта | объект.*указатель_на_член | |
5 L → R | * | Умножение | выражение * выражение |
/ | Деление | выражение / выражение | |
% | Остаток от деления | выражение % выражение | |
6 L → R | + | Сложение | выражение + выражение |
- | Вычитание | выражение - выражение | |
7 L → R | << | Побитовый сдвиг влево | выражение << выражение |
>> | Побитовый сдвиг вправо | выражение >> выражение | |
8 L → R | <=> | Трехстороннее сравнение | выражение <=> выражение |
9 L → R | < | Сравнение меньше чем | выражение < выражение |
<= | Сравнение меньше чем или равно | выражение <= выражение | |
> | Сравнение больше чем | выражение > выражение | |
>= | Сравнение больше чем или равно | выражение >= выражение | |
10 L → R | == | Равно | выражение == выражение |
!= | Не равно | выражение != выражение | |
11 L → R | & | Побитовое И (AND) | выражение & выражение |
12 L → R | ^ | Побитовое исключающее ИЛИ (XOR) | выражение ^ выражение |
13 L → R | | | Побитовое ИЛИ (OR) | выражение | выражение |
14 L → R | && | Логическое И (AND) | выражение && выражение |
15 L → R | || | Логическое ИЛИ (OR) | выражение || выражение |
16 R → L | throw | Выбросить исключение | throw выражение |
co_yield | Передать выражение | co_yield выражение | |
?: | Условный тернарный оператор | выражение ? выражение : выражение | |
= | Присваивание | выражение = выражение | |
*= | Присваивание с умножением | выражение *= выражение | |
/= | Присваивание с делением | выражение /= выражение | |
%= | Присваивание с остатком от деления | выражение %= выражение | |
+= | Присваивание со сложением | выражение += выражение | |
-= | Присваивание с вычитанием | выражение -= выражение | |
<<= | Присваивание с побитовым сдвигом влево | выражение <<= выражение | |
>>= | Присваивание с побитовым сдвигом вправо | выражение >>= выражение | |
&= | Присваивание с побитовым И (AND) | выражение &= выражение | |
|= | Присваивание с побитовым ИЛИ (OR) | выражение |= выражение | |
^= | Присваивание с побитовым исключающим ИЛИ (XOR) | выражение ^= выражение | |
17 L → R | , | Оператор запятая | выражение, выражение |
Вы уже должны знать некоторые из этих операторов, такие как +
, -
, *
, /
, ()
и sizeof
. Однако если у вас нет опыта работы с другим языком программирования, большинство операторов в этой таблице, вероятно, будут прямо сейчас вам непонятны. На данный момент это ожидаемо. В этой главе мы рассмотрим многие из них, а остальные будут представлены по мере необходимости.
Вопрос: Где оператор возведения в степень?
В C++ нет оператора для возведения в степень (оператор ^
в C ++ имеет другую функцию). Возведение в степень мы обсудим подробнее в уроке «5.3 –Остаток от деления и возведение в степень».
Круглые скобки
В обычной арифметике вы узнали, что можно использовать круглые скобки для изменения порядка выполнения операций. Например, мы знаем, что 4 + 2 * 3 вычисляется как 4 + (2 * 3), но если вы хотите, чтобы это выражение вычислялось как (4 + 2) * 3, вы можете явно заключить его часть в скобки, чтобы оно вычислялось так, как вы хотите. Это работает и в C++, потому что круглые скобки имеют один из наивысших уровней приоритета, и поэтому круглые скобки обычно вычисляются до того, что находится внутри них.
Теперь рассмотрим такое выражение, как x && y || z
. Оно вычисляется как (x && y) || z
или x && (y || z)
? Вы можете посмотреть в таблице и увидеть, что &&
выше по приоритету, чем ||
. Но тут так много операторов и уровней приоритета, что их все сложно запомнить. Чтобы уменьшить количество ошибок и упростить понимание кода без подглядывания в таблицу приоритетов, рекомендуется заключать в скобки любое нетривиальное составное выражение, чтобы было ясно, каковы ваши намерения.
Лучшая практика
Используйте круглые скобки, чтобы показать, как должно вычисляться выражение, даже если они технически не нужны.
Небольшой тест
Вопрос 1
Из обычной математики вы знаете, что сначала вычисляются выражения, заключенные в круглые скобки. Например, в выражении (2 + 3) * 4 сначала вычисляется часть (2 + 3).
Для этого упражнения вам дается набор выражений без скобок. Используя правила приоритета и ассоциативности операторов из приведенной выше таблицы, добавьте круглые скобки к каждому выражению, чтобы было понятно, как компилятор будет вычислять выражение.
Используйте столбец шаблона в таблице выше, чтобы определить, является ли оператор унарным (имеет один операнд) или бинарным (имеет два операнда). Если вам нужно напомнить, что такое унарные и бинарные операторы, просмотрите урок «1.9 – Знакомство с литералами и операторами».
Пример задачи: x = 2 + 3 % 4
Бинарный оператор %
имеет более высокий приоритет, чем оператор +
или оператор =
, поэтому он вычисляется первым:
х = 2 + (3 % 4)
Бинарный оператор +
имеет более высокий приоритет, чем оператор =
, поэтому он вычисляется следующим:
Окончательный ответ: x = (2 + (3 % 4))
Теперь нам больше не нужна приведенная выше таблица, чтобы понять, как будет вычисляться это выражение.
a) х = 3 + 4 + 5;
Ответ
Бинарный оператор +
имеет более высокий приоритет, чем =
:
х = (3 + 4 + 5);
Бинарный оператор +
выполняется слева направо:
Окончательный ответ: x = ((3 + 4) + 5);
b) х = у = z;
Ответ
Бинарный оператор =
выполняется справа налево:
Окончательный ответ: x = (y = z);
c) z *= ++y + 5;
Ответ
Унарный оператор ++
имеет наивысший приоритет:
c *= (++у) + 5;
Бинарный оператор +
имеет следующий по высоте приоритет:
Окончательный ответ: z *= ((++y) + 5);
d) а || b && c || d;
Ответ
Бинарный оператор &&
имеет более высокий приоритет, чем ||
:
а || (b && c) || d;
Бинарный оператор ||
выполняется слева направо:
Окончательный ответ: (a || (b && c)) || d;