Щоб правильно обчислювати вирази (наприклад, 4 + 2 * 3
), ми повинні знати, що роблять певні оператори і в якій послідовності вони виконуються. Послідовність, в якій вони виконуються, називається пріоритетом операцій. Дотримуючись звичайних правил математики (в яких множення виконується перед додаванням), вираз, наведений вище, обробляється наступним чином: 4 + (2 * 3) = 10
.
У мові C++ всі оператори (операції) мають свій рівень пріоритету. Ті, в яких він вище, виконуються першими. У таблиці, наведеній нижче, можна побачити, що пріоритет операцій множення і ділення (5
) вище, ніж в операціях додавання і віднімання (6
). Компілятор використовує пріоритет операторів для визначення порядку обробки виразів.
А що робити, якщо у двох операторів в виразі однаковий рівень пріоритету, і розміщені вони поруч? Яку операцію компілятор виконає першою? А тут уже компілятор буде використовувати правила асоціативності, які вказують напрямок виконання операцій: зліва направо або справа наліво. Наприклад, у виразі 3 * 4 / 2
операції множення і ділення мають однаковий рівень пріоритету (5
). А асоціативність 5-го рівня відповідає виконанню операцій зліва направо, таким чином: (3 * 4) / 2 = 6
.
Таблиця пріоритету і асоціативності операцій
Декілька приміток:
1
означає найвищий рівень пріоритету, а 17
— найнижчий. Операції з більш високим рівнем пріоритету виконуються першими.
L -> R
означає зліва направо.
R -> L
означає справа наліво.
Асоціативність | Оператор | Опис | Приклад |
1. Ні | :: | Глобальна область видимості (унарний) | ::ідентифікатор |
:: | Область видимості класу (бінарний) | назва_класу::назва_члену | |
2. L -> R | () | Круглі дужки | (вираз) |
() | Виклик функції | назва_функції(параметри) | |
() | Ініціалізація | назва_типу(вираз) | |
{} | uniform-ініціалізація (C++11) | назва_типу{вираз} | |
type() | Конвертація типу | новий_тип(вираз) | |
type{} | Конвертація типу (C++11) | новий_тип{вираз} | |
[] | Індекс масиву | вказівник[вираз] | |
. | Доступ до члену об’єкта | об’єкт.назва_члену | |
-> | Доступ до члену об’єкта через вказівник | вказівник_об’єкту->назва_члену | |
++ | Пост-інкремент | lvalue++ | |
–– | Пост-декремент | lvalue–– | |
typeid | Інформація про тип під час виконання | typeid(тип) або typeid(вираз) | |
const_cast | Cast away const | const_cast(вираз) | |
dynamic_cast | Type-checked cast під час виконання | dynamic_cast(вираз) | |
reinterpret_cast | Конвертація одного типу в інший | reinterpret_cast(вираз) | |
static_cast | Type-checked cast під час компіляції | static_cast(вираз) | |
3. R -> L | + | Унарний плюс | +вираз |
– | Унарний мінус | -вираз | |
++ | Пре-інкремент | ++lvalue | |
–– | Пре-декремент | ––lvalue | |
! | Логічне НЕ (NOT) | !вираз | |
~ | Побітове НЕ (NOT) | ~вираз | |
(type) | C-style cast | (новий_тип)вираз | |
sizeof | Розмір в байтах | sizeof(тип) або sizeof(вираз) | |
& | Адреса | &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 | & | Побітове І (AND) | вираз & вираз |
11. L -> R | ^ | Побітове виключне АБО (XOR) | вираз ^ вираз |
12. L -> R | | | Побітове АБО (OR) | вираз | вираз |
13. L -> R | && | Логічне І (AND) | вираз && вираз |
14. L -> R | || | Логічне АБО (OR) | вираз || вираз |
15. R -> L | ?: | Тернарний умовний оператор | вираз ? вираз : вираз |
= | Присвоювання | lvalue = вираз | |
*= | Множення з присвоюванням | lvalue *= вираз | |
/= | Ділення з присвоюванням | lvalue /= вираз | |
%= | Ділення з остачею і з присвоюванням | lvalue %= вираз | |
+= | Додавання з присвоюванням | lvalue += вираз | |
-= | Віднімання з присвоюванням | lvalue -= вираз | |
<<= | Присвоювання з побітовим зсувом вліво | lvalue <<= вираз | |
>>= | Присвоювання з побітовим зсувом вправо | lvalue >>= вираз | |
&= | Присвоювання з побітовою операцією І (AND) | lvalue &= вираз | |
|= | Присвоювання з побітовою операцією АБО (OR) | lvalue |= вираз | |
^= | Присвоювання з побітовою операцією “Виключне АБО” (XOR) | lvalue ^= вираз | |
16. R -> L | throw | Генерація винятку | throw вираз |
17. L -> R | , | Оператор Кома | вираз, вираз |
Деякі оператори ви вже знаєте з попередніх уроків: +
, -
, *
, /
, ()
, =
, <
і >
. Їх значення однакові як в математиці, так і в мові C++.
Однак, якщо у вас немає досвіду роботи з іншими мовами програмування, то більшість з цих операторів вам зараз можуть бути незрозумілі. Це нормально. Ми розглянемо більшу їх частину в цьому розділі, а про решту розповімо по мірі необхідності.
Ця таблиця призначена в першу чергу для того, щоб ви могли в будь-який момент повернутися до неї для вирішення можливих проблем пріоритету або асоціативності.
Як піднести число до степеню в C++?
Ви вже повинні були помітити, що оператор ^
, який зазвичай використовується для позначення операції піднесення до степеню в звичайній математиці, не є таким в мові C++. В мові С++ це побітова операція XOR. А для піднесення числа до степеню в C++ використовується функція pow(), яка знаходиться в заголовку cmath:
1 2 3 |
#include <cmath> double x = pow(3.0, 4.0); // 3 в степені 4 |
Зверніть увагу, параметри і значення, що повертаються, функції pow() є типу double. А оскільки типи з плаваючою крапкою відомі помилками округлення, то результати pow() можуть бути злегка неточними (трохи менше або трохи більше).
Якщо вам потрібно піднести ціле число до степеню, то краще використовувати власну функцію, наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Примітка: Експонент не повинен бути від'ємним int pow(int base, int exp) { int result = 1; while (exp) { if (exp & 1) result *= base; exp >>= 1; base *= base; } return result; } |
Не переживайте, якщо тут щось не зрозуміло. Просто пам’ятайте про проблему переповнення, яка може виникнути, якщо один з аргументів буде занадто великим.
Тест
Зі шкільної математики нам відомо, що вирази всередині дужок виконуються першими. Наприклад, в (2 + 3) * 4
, частина (2 + 3)
виконуватиметься першою.
У цьому завданні є 4 вирази, в яких відсутні будь-які дужки. Використовуючи пріоритет операцій і правила асоціативності, наведені вище, додайте дужки в кожен вираз так, наче їх оброблює компілятор.
Підказка: Використовуйте колонку “Приклад” в таблиці пріоритету та асоціативності операцій, щоб визначити чи є оператор унарним (має 1 операнд) чи бінарним (має 2 операнди).
Наприклад: х = 2 + 3 % 4
Бінарний оператор %
має більш високий пріоритет, ніж оператор +
чи =
, тому він виконується першим: х = 2 + (3 % 4)
. Потім виконується бінарний оператор +
, оскільки він має більш високий пріоритет, ніж оператор =
.
Відповідь: х = (2 + (3 % 4))
.
Далі нам вже не потрібна таблиця, щоб зрозуміти хід обробки цього виразу компілятором.
Завдання:
Вираз №1: x = 3 + 4 + 5
Вираз №2: x = y = z
Вираз №3: z *= ++y + 5
Вираз №4: a || b && c || d
Відповідь
Вираз №1: x = 3 + 4 + 5
Рівень пріоритету бінарного оператора +
вище, ніж оператора =
, тому х = (3 + 4 + 5)
. Асоціативність бінарного оператора +
зліва направо, тому відповідь: х = ((3 + 4) + 5)
.
Вираз №2: x = y = z
Асоціативність бінарного оператора =
справа наліво, тому відповідь: x = (y = z)
.
Вираз №3: z *= ++y + 5
Унарний оператор ++
має найвищий пріоритет, тому z *= (++y) + 5
. Потім виконується бінарний оператор +
, тому відповідь: z *= ((++y) + 5)
.
Вираз №4: a || b && c || d
Бінарний оператор &&
має вищий пріоритет, ніж оператор ||
, тому a || (b && c) || d
. Асоціативність бінарного оператора ||
зліва направо, тому відповідь: (a || (b && c)) || d
.