5.1 – Приоритет и ассоциативность операторов

Добавлено 3 мая 2021 в 13:50

Введение в главу

Данная глава основывается на концепциях урока «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).
Таблица приоритета и ассоциативности операторов в C++
Приор. /
Ассоц.
ОператорОписаниеШаблон использования
1
нет
::Глобальная область видимости (унарный)::имя
::Область пространства имен (бинарный)имя_класса::имя_члена
2
L → R
()Круглые скобки(выражение)
()Вызов функцииимя_функции(параметры)
()Инициализациятип имя(выражение)
{}Унифицированная инициализация (С++11)тип имя{выражение}
type()Функциональное приведение типановый_тип(выражение)
type{}Функциональное приведение типа (C++11)новый_тип{выражение}
[]Индекс массивауказатель[выражение]
.Доступ к члену из объектаобъект.имя_члена
->Доступ к члену из указателя на объектуказатель_на_объект->имя_члена
++Постфиксный инкрементlvalue++
--Постфиксный декрементlvalue--
typeidДинамическая идентификация типа данныхtypeid(тип) или typeid(выражение)
const_castПриведение типа с удалением модификатора constconst_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;

Теги

C++ / CppДля начинающихОбучениеОператор (программирование)Программирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.