Ми вже знаємо, що перевантаження функцій забезпечує механізм створення і виконання викликів функцій з одним і тим же ім’ям, але з різними параметрами. Це дозволяє одній функції працювати з декількома різними типами даних (без необхідності придумувати унікальні імена для кожної з функцій).
У мові C++ оператори реалізовані у вигляді функцій. Використовуючи перевантаження функції оператора, ви можете визначити свої власні версії операторів, які працюватимуть з різними типами даних (включаючи класи). Використання перевантаження функції для перевантаження оператора називається перевантаженням оператора.
Оператори, як функції
Розглянемо наступний фрагмент коду:
1 2 3 |
int a = 5; int b = 6; std::cout << a + b << '\n'; |
Тут компілятор використовує вбудовану версію оператора плюс (+
) для цілочисельних операндів — ця функція додасть два цілочисельних значення (a
і b
), і поверне цілочисельний результат. Коли ви бачите вираз a + b
, то думайте про нього, як про виклик функції operator+(a, b)
(де operator+
є ім’ям функції).
Тепер розглянемо наступний фрагмент коду:
1 2 3 |
double m = 4.0; double p = 5.0; std::cout << m + p << '\n'; |
Компілятор також надасть вбудовану версію оператора плюс (+
) для операндів типу double. Вираз m + p
призведе до виклику функції operator+(m, p)
, а, завдяки перевантаженню оператора, викличеться версія double (замість версії int).
Тепер розглянемо, що відбудеться, якщо ми спробуємо додати два об’єкти класу:
1 2 3 |
Mystring hello = "Hello, "; Mystring world = "World!"; std::cout << hello + world << '\n'; |
Як ви думаєте, яким буде результат? Напевно, вивід рядка Hello, World!
? Ні, результатом буде помилка, тому що клас Mystring є користувацьким типом даних, а компілятор не має вбудованої версії operator+() для використання з операндами Mystring. Для того, щоб зробити те, що ми хочемо, нам доведеться написати свою версію функції operator+() і вказати в ній алгоритм роботи з операндами типу Mystring. Те, як це зробити в коді, ми розглянемо на наступному уроці.
Виклик перевантажених операторів
При обробці виразу з оператором компілятор використовує наступні алгоритми дій:
Якщо всі операнди є фундаментальних типів даних, то викликати слід вбудовані відповідні версії операторів (якщо такі існують). Якщо таких не існує, то компілятор видасть помилку.
Якщо будь-який з операндів є користувацького типу даних (наприклад, об’єкт класу або типу перерахування), то компілятор шукатиме версію оператора, яка працює з таким типом даних. Якщо компілятор не знайде нічого підходящого, то спробує виконати конвертацію одного або декількох операндів користувацького типу даних в фундаментальні типи даних, щоб таким чином він міг використати відповідний вбудований оператор. Якщо це не спрацює — компілятор видасть помилку.
Обмеження в перевантаженні операторів
По-перше, майже будь-який існуючий оператор в мові C++ можна перевантажити. Винятками є:
тернарний оператор (?:
);
оператор дозволу області видимості (::
);
оператор вибору члена (.
);
вказівник, як оператор вибору члена (.*
).
По-друге, ви можете перевантажити тільки існуючі оператори. Ви не можете створювати нові або перейменовувати існуючі. Наприклад, ви не можете створити оператор **
для виконання операції піднесення до степеня.
По-третє, принаймні один з операндів перевантаженого оператора повинен бути користувацького типу даних. Це означає, що ви не можете перевантажити operator+() для виконання операції додавання значення типу int до значення типу double. Однак ви можете перевантажити operator+() для виконання операції додавання значення типу int до об’єкта класу Mystring.
По-четверте, початкову кількість операндів, підтримуваних оператором, змінити неможливо. Тобто з бінарним оператором використовуються тільки два операнди, з унарним — тільки один, з тернарним — тільки три.
Нарешті, всі оператори зберігають свої пріоритети і асоціативність за замовчуванням (незалежно від того, для чого вони використовуються), і це не може бути змінено.
Деякі початківці намагаються перевантажити побітовий оператор XOR (^
) для виконання операції піднесення до степеня. Однак в мові C++ в оператора ^
пріоритет нижче, ніж у базових арифметичних операторів (+
, -
, *
, /
), і це призведе до некоректної обробки виразів.
В математиці операція піднесення до степеня виконується ДО виконання базових арифметичних операцій, тому 2 + 5 ^ 2
обчислюється як 2 + (5 ^ 2) => 2 + 25 => 27
. Однак в мові C++ у базових арифметичних операторів пріоритет вище, ніж у оператора ^
, тому 2 + 5 ^ 2
обчислиться як (2 + 5) ^ 2 => 7 ^ 2 => 49
.
Вам потрібно буде явно помістити в дужки частину з піднесенням до степеня (наприклад, 2 + (5 ^ 2)
) кожен раз, коли ви хочете, щоб вона виконувалася першою, що дуже легко забути і, таким чином, наробити помилок. Тому проводити подібні експерименти не рекомендується.
Примітка: У мові C++ для піднесення до степеня використовується функція pow() з заголовку cmath. У вищенаведеному прикладі з виконанням виразу 2 + 5 ^ 2
в мові C++, мається на увазі, що ви перевантажите побітовий оператор XOR (^
) для виконання операції піднесення до степеня.
Правило: При перевантаженні операторів намагайтеся максимально наближено зберігати функціонал операторів з їх початковими призначеннями.
Навіщо використовувати перевантаження операторів? Ви можете перевантажити оператор +
для з’єднання об’єктів вашого класу String або для виконання операції додавання двох об’єктів вашого класу Fraction. Ви можете перевантажити оператор <<
для виводу вашого класу на екран (або запису в файл). Ви можете перевантажити оператор рівності (==
) для порівняння двох об’єктів класу і т.д. Подібні застосування роблять перевантаження операторів однією з найкорисніших особливостей мови C++, так як це спрощує процес роботи з класами і відкриває нові можливості.