Перевантаження операторів в C++. Операторна функція. Ключове слово operator. Перевантаження базових арифметичних операторів +, –, *, /. Приклади реалізації вбудованих та операторних функцій
Дана тема відображає можливості мови C++ з реалізації перевантаження операторів. Не всі сучасні мови програмування підтримують перевантаження операторів. Добре розуміння процесу програмування перевантажених операторів є показником професійності та високого класу сучасного програміста.
Перед розглядом даної теми рекомендується ознайомитись з наступною темою:
Зміст
- 1. Що таке унарні та бінарні оператори?
- 2. У чому полягає суть перевантаження операторів? Що таке операторна функція?
- 3. Якими способами можна реалізувати операторну функцію для заданого класу? Які є різновиди операторних функцій?
- 4. Загальна форма операторної функції, що реалізована в класі. Ключове слово operator
- 5. Приклад перевантаження унарних та бінарних операторів для класу, який містить одиночні дані. Операторна функція реалізована всередині класу
- 6. Приклад перевантаження оператора ‘*‘, що обробляє клас, який містить масив дійсних чисел. Операторна функція реалізована всередині класу
- 7. Приклад додавання двох масивів. Операторна функція operator+() розміщується всередині класу
- 8. Які обмеження накладаються на перевантажені оператори?
- 9. Які оператори не можна перевантажувати?
- 10. Об’єкти яких типів може повертати операторна функція? Приклади операторних функцій, які повертають об’єкти різних типів
- 11. Чи можна змінювати значення операндів в оператоній функції?
- 12. Чи можна реалізувати операторні функції в класі, які перевантажують однаковий оператор, отримують однакові параметри але повертають різні значення?
- 13. Чи можна реалізувати дві і більше операторних функцій в класі, які перевантажують однаковий оператор і отримують різні (відмінні між собою) параметри?
- Зв’язані теми
Пошук на інших ресурсах:
1. Що таке унарні та бінарні оператори?
Розрізняють три основні види операторів: унарні, бінарні та n-арні.
Унарні оператори – це оператори, які для обчислення вимаюгають одного операнда, який може розміщуватись справа або зліва від самого оператора.
Приклади унарних операторів:
i++ --a -8
Бінарні оператори – це оператори, що для обчислення вимагають двох операндів. Наприклад, нижче наведено фрагменти виразів з бінарними операторами +, –, %, *
a+b f1-f2 c%d x1*x2
n-арні оператори для обчислень потребують більше двох операндів. У мові C++ є тернарна операція ?:, яка для своєї роботи потребує трьох операндів. Більш детально про тернарну операцію ?: описується тут.
⇑
2. У чому полягає суть перевантаження операторів? Що таке операторна функція?
Мова C++ має широкі можливості для перевантаження більшості операторів. Перевантаження оператору означає використання оператору для оперування об’єктами класів. Перевантаження оператору – спосіб оголошення та реалізації оператору таким чином, що він обробляє об’єкти конкретних класів або виконує деякі інші дії. При перевантаженні оператору в класі викликається відповідна операторна функція (operator function), яка виконує дії, що стосуються даного класу.
Якщо оператор “перевантажено”, то його можна використовувати в інших методах у звичному для нього вигляді. Наприклад, команди поелементного сумування двох масивів a1 та a2
a1.add(a2); a3 = add(a1, a2);
краще викликати більш природнім способом:
a1 = a1 + a2; a3 = a1 + a2;
У даному прикладі оператор ‘+’ вважається перевантаженим.
⇑
3. Якими способами можна реалізувати операторну функцію для заданого класу? Які є різновиди операторних функцій?
Для заданого класу операторну функцію в класі можна реалізувати:
- всередині класу. У цьому випадку, операторна функція є методом класу;
- за межами класу. У цьому випадку операторна функція оголошується за межами класу як “дружня” (з ключовим словом friend). Більш детально про реалізацію “дружніх” функцій за межами класу описується тут.
⇑
4. Загальна форма операторної функції, що реалізована в класі. Ключове слово operator
Загальна форма операторної функції, яка реалізована в класі, має такий вигляд:
return_type ClassName::operator#(arguments_list) { // деякі операції // ... }
де
- return_type – тип значення, що повертається операторною функцією;
- ClassName – ім’я класу, в якому реалізована операторна функція;
- operator# – ключове слово, що визначає операторну функцію в класі. Символ # замінюється оператором мови C++, який перевантажується. Наприклад, якщо перевантажується оператор +, то потрібно вказати operator+;
- argument_list – список параметрів, які отримує операторна функція. Якщо перевантажується бінарний оператор, то argument_list містить один аргумент. Якщо перевантажується унарний оператор, то список аргументів пустий.
⇑
5. Приклад перевантаження унарних та бінарних операторів для класу, який містить одиночні дані. Операторна функція реалізована всередині класу
Оголошується клас Point, що реалізує точку на координатній площині. У класі реалізовано:
- дві внутрішні змінні x, y, що є координатами точки;
- два конструктори класу;
- методи доступу до внутрішніх змінних класу GetX(), GetY(), SetX(), SetY();
- дві операторні функції operator+() та operator-(). Операторна функція operator+() перевантажує бінарний оператор ‘+’. Операторна функція operator-() перевантажує унарний оператор ‘-‘.
// Клас, що реалізує точку на координатній площині // клас містить дві операторні функції class Point { private: int x, y; // координати точки public: // конструктори класу Point() { x = y = 0; } Point(int nx, int ny) { x = nx; y = ny; } // методи доступу до членів класу int GetX(void) { return x; } int GetY(void) { return y; } void SetX(int nx) { x = nx; } void SetY(int ny) { y = ny; } // перевантажений бінарний оператор '+' Point operator+(Point pt) { // p - тимчасовий об'єкт, який створюється з допомогою конструктора без параметрів Point p; p.x = x + pt.x; p.y = y + pt.y; return p; } // перевантажений унарний оператор '-' Point operator-(void) { Point p; p.x = -x; p.y = -y; return p; } };
Як видно з вищенаведеного коду, операторна функція operator+() отримує один параметр. Це означає, що ця функція реалізує бінарний оператор ‘+’. Цей параметр відповідає операнду, що розміщується у правій частині бінарного оператора ‘+’. Операнд, що розміщується в лівій частині оператора ‘+’ передається операторній функції неявно з допомогою покажчика this цього класу.
Виклик операторної функції здійснює об’єкт, який розміщується в лівій частині оператора присвоювання.
Демонстрація використання перевантажених операторів класу Point в іншому методі:
// оголошення змінних - об'єктів класу CPoint Point P1(3,4); Point P2(5,7); Point P3; int x, y; // додаткові змінні // 1. Використання перевантаженого бінарного оператора '+' P3 = P1 + P2; // об'єкт P1 викликає операторну функцію // перевірка x = P3.GetX(); // x = 8 y = P3.GetY(); // y = 11 // 2. Використання перевантаженого унарного оператора '-' P3 = -P2; x = P3.GetX(); // x = -5 y = P3.GetY(); // y = -7
У вищенаведеному коді в операції додавання ‘+’ об’єкт P1 викликає операторну функцію. Тобто фрагмент рядка
P1 + P2
замінюється викликом
P1.operator+(P2)
Реалізувати операторну функцію operator+() в класі можна й по іншому
Point operator+(Point pt) { // виклик конструктора з двома параметрами return Point(x+pt.x, y+pt.y); // створюється тимчасовий об'єкт, який потім копіюється }
У вищенаведеній функції в операторі return створюється тимчасовий об’єкт шляхом виклику конструктора з двома параметрами, який реалізований в класі. Якщо (у даному випадку) з тіла класу забрати конструктор з двома параметрами
// конструктор з двома параметрами Point(int nx, int ny) { // ... }
то вищенаведений варіант функції operator+() працювати не буде, тому що для створення об’єкту типу Point ця функція використовує конструктор з двома параметрами. У цьому випадку компілятор видасть повідомлення
Point::Point : no overloaded function takes 2 arguments
що означає: немає методу (конструктора) Point::Point() який приймає 2 аргументи.
⇑
6. Приклад перевантаження оператора ‘*’, що обробляє клас, який містить масив дійсних чисел. Операторна функція реалізована всередині класу
У прикладі реалізується операторна функція operator*(), яка множить поелементно значення внутрішніх масивів об’єктів класу ArrayFloat. Якщо розмір масивів неоднаковий, то перемножується тільки та кількість елементів, яка є мінімальною між двома розмірами масивів.
// масив дійсних чисел class ArrayFloat { private: float A[10]; // масив дійсних чисел, фіксований розмір масиву int size; public: // конструктори ArrayFloat() { size = 0; } ArrayFloat(int nsize, float nA[]) { size = nsize; for (int i=0; i<nsize; i++) A[i] = nA[i]; } // методи доступу float GetAi(int i) { if ((i>=0) && (i<=size-1)) return A[i]; else return 0; } void SetAi(int i, float value) { if ((i>=0) && (i<=size-1)) A[i] = value; } // перевантажений оператор '*' ArrayFloat operator*(ArrayFloat AF) { ArrayFloat tmp; int n; if (size<AF.size) n = AF.size; else n = size; for (int i=0; i<n; i++) tmp.A[i] = A[i] * AF.A[i]; tmp.size = n; return tmp; } };
Використання класу ArrayFloat в іншому методі
// додаткові змінні та масиви float x, y; float AF1[] = { 2, 5, 7, 9, 12 }; float AF2[] = { 3, 4, 9, 8, 10, 13 }; // створити об'єкти класу ArrayFloat ArrayFloat A1(5, AF1); ArrayFloat A2(6, AF2); ArrayFloat A3; // виклик операторної функції operator* A3 = A1 * A2; // здійснюється поелементне множення // перевірка x = A3.GetAi(0); // x = 6 y = A3.GetAi(1); // y = 20 x = A3.GetAi(2); // x = 63 y = A3.GetAi(4); // y = 120
⇑
7. Приклад додавання двох масивів. Операторна функція operator+() розміщується всередині класу
Задано клас ArrayFloat, що реалізує динамічний масив чисел типу float. У класі реалізовано:
- внутрішні змінні size, A, які визначають розмір масиву та сам масив;
- два конструктори, які ініціалізують початковими значення елементи масиву;
- конструктор копіювання;
- методи доступу GetSize(), SetSize(), GetAi(), SetAi(), які реалізують доступ до внутрішніх змінних масиву з відповідними операціями (виділення пам’яті, перевірка на допустимі межі);
- операторну функцію operator=(), яка реалізує копіювання об’єктів;
- операторну функцію operator+(), яка реалізує перевантаження оператора ‘+’ для масивів типу ArrayFloat. Операторна функція додає поелементно значення масивів, які є операндами операції ‘+’;
- деструктор.
// клас - масив типу float class ArrayFloat { private: int size; // розмір масиву float * A; // динамічний розмір масиву public: // конструктори класу // конструктор без параметрів ArrayFloat() { size = 0; A = NULL; } // конструктор з двома параметрами ArrayFloat(int nsize, float * nA) { if (size>0) delete A; size = nsize; A = new float[size]; for (int i=0; i<nsize; i++) A[i] = nA[i]; }
// конструктор копіювання
ArrayFloat(const ArrayFloat& _A)
{
size = _A.size;
A = new float[size];
for (int i = 0; i < size; i++)
A[i] = _A.A[i];
}
// методи доступу int GetSize(void) { return size; } void SetSize(int nsize) { if (size>0) { delete A; size = 0; } size = nsize; A = new float[size]; // виділити новий фрагмент пам'яті // заповнити масив нулями for (int i=0; i<size; i++) A[i] = 0.0f; } float GetAi(int index) { if ((index>=0) && (index<size)) return A[index]; else return 0; } void SetAi(int index, float value) { if ((index>=0) && (index<size)) A[index] = value; }
// оператор копіювання operator=()
ArrayFloat operator=(const ArrayFloat& _A)
{
if (size > 0)
delete[] A;
size = _A.size;
A = new float[size]; // виділити пам'ять
for (int i = 0; i < size; i++)
A[i] = _A.A[i];
return *this;
}
// перевантаження оператора '+', // операторна функція ArrayFloat operator+(ArrayFloat AF2) { int n; // взяти мінімальний розмір з двох масивів if (size<AF2.size) n = size; else n = AF2.size; ArrayFloat tmpA; // об'єкт класу tmpA.SetSize(n); // новий розмір масиву // поелементне додавання for (int i=0; i<n; i++) tmpA.A[i] = A[i] + AF2.A[i]; return tmpA; }
// деструктор
~ArrayFloat()
{
if (size > 0)
delete[] A;
}
};
Нижче продемонстровано використання класу ArrayFloat та операторної функції operator+() цього класу.
// додаткові змінні, масиви float x, y; float F1[] = { 3.8f, 2.9f, 1.5f }; float F2[] = { 4.3f, 1.5f, 7.0f, 3.3f }; // оголошення об'єктів класу ArrayFloat ArrayFloat AF1(3, F1); ArrayFloat AF2(4, F2); // перевірка x = AF1.GetAi(0); // x = 3.8 y = AF2.GetAi(3); // y = 3.3 // оголошення додаткового об'єкта - результату ArrayFloat AF3; AF3 = AF1 + AF2; // виклик операторної функції operator+ // перевірка x = AF3.GetAi(0); // x = 3.8 + 4.3 = 8.1 y = AF3.GetAi(1); // y = 2.9 + 1.5 = 4.4 // ще один виклик AF3 = AF1 + AF1 + AF2; x = AF3.GetAi(1); // x = 2.9 + 2.9 + 1.5 = 7.3 y = AF3.GetAi(2); // y = 1.5 + 1.5 + 7.0 = 10.0
⇑
8. Які обмеження накладаються на перевантажені оператори?
На використання перевантажених операторів накладаються наступні обмеження:
- при перевантаженні оператору не можна змінити пріоритет цього оператора;
- не можна змінити кількість операндів оператора. Однак, у коді операторної функції можна один з параметрів (операндів) не використовувати;
- не можна перевантажувати оператори :: . * ?:;
- не можна викликати операторну функцію з аргументами за замовчуванням. Виняток – операторна функція виклику функції operator()().
⇑
9. Які оператори не можна перевантажувати?
Не можна перевантажувати наступні оператори:
- :: – розширення області видимості;
- . (крапка) – вибір члена класу або структури;
- * – доступ за покажчиком;
- ?: – тернарний оператор ?:.
⇑
10. Об’єкти яких типів може повертати операторна функція? Приклади операторних функцій, які повертають об’єкти різних типів
Операторна функція може повертати об’єкти будь-яких типів. Найчастіше операторна функція повертає об’єкт типу класу, в якому вона реалізована або з якими вона працює.
Приклад. Задано клас Complex, в якому перевантажується два оператори:
- унарний оператор ‘+’, який повертає модуль комплексного числа (тип double);
- бінарний оператор ‘+’, який повертає суму комплексних чисел. Операторна функція повертає об’єкт типу Complex;
- бінарний оператор ‘+’, який додає до комплексного числа деяке дійсне число. У цьому випадку операторна функція отримує вхідним параметром дійсне число і повертає об’єкт типу Complex.
Текст класу наступний:
// клас Complex class Complex { private: float real; // дійсна частина float imag; // уявна частина public: // конструктори Complex(void) { real = imag = 0; } Complex(float _real, float _imag) { real = _real; imag = _imag; } // методи доступу float GetR(void) { return real; } float GetI(void) { return imag; } void SetRI(float _real, float _imag) { real = _real; imag = _imag; } // оголошення операторної функції, яка перевантажує бінарний '+' // функція повертає об'єкт, що містить суму двох комплексних чисел Complex operator+(Complex c) { Complex c2; // тимчасовий об'єкт // додавання комплексних чисел c2.real = real + c.real; c2.imag = imag + c.imag; return c2; } // оголошення операторної функції, яка перевантажує унарний '+' // функція повертає модуль комплексного числа float operator+(void) { float res; res = std::sqrt(real*real+imag*imag); return res; } // оголошення операторної функції operator+() // функція додає до комплексного числа деяке число, яке є вхідним параметром Complex operator+(float real) { Complex c2; // результуючий об'єкт c2.real = this->real + real; c2.imag = this->imag; return c2; } };
Нижче демонструється використання класу Complex та перевантажених операторних функцій в деякому іншому методі
Complex c1(1,5); Complex c2(3,-8); Complex c3; // результуючий об'єкт double d; // перевірка c3 = c1 + c2; d = c3.GetR(); // d = 1 + 3 = 4 d = c3.GetI(); // d = 5 + (-8) = -3 // перевантажений унарний оператор '+' d = +c1; // d = |1 + 5j| = 5.09902 - модуль числа d = +c2; // d = |3 + (-8)j| = 8.544 // виклик перевантаженого бінарного '+', // додати до комплексного числа число c3 = c1 + 5.0; d = c3.GetR(); // d = 1 + 5 = 6
⇑
11. Чи можна змінювати значення операндів в оператоній функції?
Так, можна. Однак такі дії не є корисними з точки зору здорового глузду. Так, наприклад, операція множення
6 * 9
не змінює значення своїх операндів 6 та 9. Результат рівний 54. Якщо операторна функція operator*() буде змінювати значення своїх операндів, то це може призвести до невидимих помилок в програмах, оскільки програміст за звичкою, буде вважати, що значення операндів є незмінні.
⇑
12. Чи можна реалізувати операторні функції в класі, які перевантажують однаковий оператор, отримують однакові параметри але повертають різні значення?
Ні не можна. Операторна функція не може мати декілька реалізацій в класі з однаковою сигнатурою параметрів (коли типи та кількість параметрів співпадають). У випадку порушення цього правила компілятор видає помилку:
cannot overload functions distinguished by return type alone
що означає
не можна перевантажувати функції, що відрізняються тільки типом повернення
Наприклад. Не можна в класі перевантажувати оператор ‘+’ так як показано нижче
class SomeClass { // ... SomeClass operator+(SomeClass c1) { // ... } double operator+(SomeClass c1) // це є помилка! { // ... } // ... }
Це правило стосується будь-яких функцій класу.
⇑
13. Чи можна реалізувати дві і більше операторних функцій в класі, які перевантажують однаковий оператор і отримують різні (відмінні між собою) параметри?
Так, можна. У п. 10 реалізовано клас Complex, в якому реалізовано дві операторні функції, що перевантажують оператор ‘+’ різними способами. Ці функції відрізняються вхідними параметрами.
⇑
Зв’язані теми
- Перевантаження функцій у класах. Перевантаження конструкторів. Доступ до перевантаженої функції за покажчиком. Приклади
- “Дружні” операторні функції: відмінності, реалізація особливості застосування. Перевантаження операторів +, –, *, / з допомогою “дружніх” операторних функцій
⇑