Урок №139. Перевантаження операторів через дружні функції

  Юрій  | 

  Оновл. 15 Лют 2021  | 

 167

На цьому уроці ми розглянемо перевантаження операторів через дружні функції в мові С++, на наступному — через звичайні функції, а потім — через методи класу.

Способи перевантаження операторів

Арифметичні оператори плюс (+), мінус (-), множення (*) і ділення (/) є одними з найбільш використовуваних операторів у мові C++. Всі вони є бінарними, тобто працюють тільки з двома операндами.

Є три різних способи перевантаження операторів:

   через дружні функції;

   через звичайні функції;

   через методи класу.

Перевантаження операторів через дружні функції

Використовуючи наступний клас:

Перевантажимо оператор плюс (+) для виконання операції додавання двох об’єктів класу Dollars:

Результат виконання програми:

I have 16 dollars.

Тут ми:

   оголосили дружню функцію operator+();

   задали в якості параметрів два операнди, з якими хочемо працювати — два об’єкти класу Dollars;

   вказали відповідний тип повернення — Dollars;

   записали реалізацію операції додавання.

Для виконання операції додавання двох об’єктів класу Dollars нам потрібно додати до змінної-члену m_dollars першого об’єкта m_dollars другого об’єкта. Оскільки наша перевантажена функція operator+() є дружньою класу Dollars, то ми можемо напряму звертатися до закритого члену m_dollars. Крім того, оскільки m_dollars є цілочисельним значенням, а C++ знає, як додавати цілочисельні значення, то компілятор буде використовувати вбудовану версію operator+() для роботи з типом int, тому ми можемо просто вказати оператор + в нашій операції додавання двох об’єктів класу Dollars.

Перевантаження оператора мінус () аналогічне:

Перевантаження оператора множення (*) і оператора ділення (/) аналогічні, тільки замість знаку мінус вказуєте * або /.

Дружні функції можуть бути визначені всередині класу

Незважаючи на те, що наші дружні функції не є членами класу, вони як і раніше можуть бути визначені всередині класу, якщо це необхідно:

Не рекомендується так робити, оскільки нетривіальні визначення функцій краще записувати в окремому файлі .cpp поза тілом класу.

Перевантаження операторів з операндами різних типів

Один оператор може працювати з операндами різних типів. Наприклад, ми можемо додати Dollars(5) до числа 5 для отримання результату Dollars(10).

Коли C++ обробляє вираз a + b, то a стає першим параметром, а b — другим параметром. Коли a і b одного і того ж типу даних, то не має значення, чи пишете ви a + b, чи b + a — в будь-якому випадку викликається одна і та ж версія operator+(). Однак, якщо операнди різних типів, то a + b — це вже не те ж саме, що b + a.

Наприклад, Dollars(5) + 5 призведе до виклику operator+(Dollars, int), а 5 + Dollars(5) призведе до виклику operator+(int, Dollars). Отже, всякий раз, при перевантаженні бінарних операторів для роботи з операндами різних типів, потрібно писати дві функції — по одній на кожен випадок. Наприклад:

Зверніть увагу, обидві перевантажені функції мають одну і ту ж реалізацію — це тому, що вони виконують одне і те ж, але просто приймають параметри в різному порядку.

Ще один приклад

Розглянемо інший приклад:

Клас Values відстежує мінімальне і максимальне значення. Ми перевантажили оператор плюс (+) 3 рази для виконання операції порівняння двох об’єктів класу Values і операції додавання цілочисельного значення з об’єктом класу Values.

Результат виконання програми:

Result: (4, 17)

Ми отримали мінімальне і максимальне значення з усіх, які вказали в vFinal. Розглянемо детально, як обробляється рядок Values vFinal = v1 + v2 + 6 + 9 + v3 + 17;:

   Пріоритет оператора + вище пріоритету оператора =, а асоціативність оператора + зліва направо, тому спочатку обчислюється v1 + v2. Це призводить до виклику operator+(v1, v2), яке повертає Values(7, 14).

   Наступною виконується операція Values(7, 14) + 6. Це призводить до виклику operator+(Values(7, 14), 6), яке повертає Values(6, 14).

   Потім виконується Values(6, 14) + 9, яке повертає Values(6, 14).

   Потім Values(6, 14) + v3 повертає Values(4, 14).

   І, нарешті, Values(4, 14) + 17 повертає Values(4, 17). Це і є нашим кінцевим результатом, який і присвоюється vFinal.

Іншими словами, вищенаведений вираз обробляється як Values vFinal = (((((v1 + v2) + 6) + 9) + v3) + 17), причому кожна наступна операція додавання повертає об’єкт класу Values, який стає лівим операндом для наступного оператора +.

Примітка: Ми визначили operator+(int, Values) викликом operator+(Values, int) (див. код вище). Це може бути менш ефективним, ніж окрема повна реалізація (за рахунок додаткового виклику функції), але таким чином наш код став коротшим і простішим в підтримці + ми зменшили дублювання коду. Коли це можливо, то визначайте перевантажений оператор викликом іншого перевантаженого оператора (як у нашому вищенаведеному прикладі)!

Тест

a) Напишіть клас Fraction, який має два цілочисельних члени: чисельник і знаменник. Реалізуйте функцію print(), яка виводитиме дріб.

Наступний фрагмент коду:

Повинен видавати наступний результат:

1/4
1/2

Відповідь а)

b) Додайте перевантаження оператора множення (*) для виконання операції множення об’єкта класу Fraction на цілочисельне значення і для множення двох об’єктів класу Fraction. Використовуйте спосіб перевантаження оператора через дружню функцію.

Підказка: Множення двох дробів здійснюється множенням двох чисельників, а потім окремо двох знаменників. Для виконання операції множення об’єкта на цілочисельне значення, помножте тільки чисельник на цілочисельне значення (знаменник не чіпайте).

Наступний фрагмент коду:

Повинен видавати наступний результат:

3/4
2/7
6/28
9/4
6/7
6/24

Відповідь b)

Додаткове завдання

c) Дріб 2/4 — це той же дріб, що і 1/2, тільки 1/2 не ділиться до мінімальних неподільних значень. Ми можемо зменшити будь-який заданий дріб до найменших значень, знайшовши найбільший спільний дільник (НСД) для чисельника і знаменника, а потім виконати ділення як чисельника, так і знаменника на НСД.

Нижче наведена функція пошуку НСД:

Додайте цю функцію в ваш клас і реалізуйте метод reduce(), який зменшуватиме дріб. Переконайтеся, що дріб буде максимально і коректно зменшений.

Наступний фрагмент коду:

Повинен видавати наступний результат:

3/4
2/7
3/14
9/4
6/7
1/4

Відповідь c)

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (2 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *