Лекція 5
Перевантаження операторів (операцій)
Перевантаження операторів. Загальні положення
Мета перевантаження операторів – поліпшити розуміння і наочність операцій, які виконуються над
об’єктами класів. Перевантаження операторів (operator overloading) - це можливість застосовувати
вбудовані оператори мови до різних типів, у тому числі і для типів користувача.
Кожна мова програмування визначає множину операцій над базовими типами даних. Звичайно число
цих типів невелике, однак і в цьому випадку часто виявляється, що не всі необхідні для деякого додатка
операції реалізовані. Наприклад, у С++ відсутня операція зведення в ступінь для арифметичних типів.
Нестача операцій заповнюється розробкою функцій, що включаються у відповідні бібліотеки.
Наприклад, відсутність операцій із рядками в мові С привело до необхідності створення бібліотеки
рядкових функцій. Однак використання функцій не дозволяє створити такий же компактний і наочний
запис, як це можна зробити за допомогою операцій.
Особливо відчувається потреба в нових операціях при формуванні таких класів, як “комплексне число”,
“рядок”, “матриця” , множина та інші.
Обмеження. У мові С++ є спеціальні засоби, що дозволяють перевизначити вже існуючі операції.
Приписувати новим операціям нові лексеми неприпустимо. Це визначається тим, що для існуючих
операцій вже встановлений синтаксис (одномісна, двомісна операція), пріоритет і порядок виконання
при однаковому пріоритеті із сусідніми операціями (зліва направо або справа наліво). Введення
нового позначення для операції зажадало б громіздких описів.
Відповідно до правил С++ не можна перевантажувати операції “. ”, “::”, “? ”
Перевантаження операторів. Основні оператори мови С++
Операції поділяються на:
унарні або одномісні — &, *, -, +, ~, !, ++, –-, sizeof;
бінарні або двомісні — +, -, *, /, %, <<, >>, &, :, ^, <, >,
<=, ==, >=, !=, &&, ||,
=,*=, /=, %=, +=, -=, <<=, >>=, &=, |=, ^=, ., ->, ,, (), [];
умовну триарну або тримісну операцію — ?: .
Проблеми введення нової операції (операція
зведення у ступінь).
Приклад: y=a*b‡c‡d;
• Який у ‡ пріоритет?
• Скільки у ‡ операндів (місць)?
• Яка у ‡ асоциативність (порядок виконання)?
№ Операції Порядок виконання
1 () , {} -> . Л -> П
2 ! ~ ++ — & * (type) П -> Л
3 sizeof П -> Л
4 * / % Л -> П
5 + – Л -> П
6 << >> Л -> П
7 < <= > >= Л -> П
8 == != Л -> П
9 & Л -> П
10 ^ Л -> П
11 | Л -> П
12 && Л -> П
13 || Л -> П
14 ?: П -> Л
15 = += *= -= /= %= П -> Л
16 , Л -> П
Загальні правила при виборі оператора, що перевантажується
При перевантаженні операторів треба намагатися, щоб зміст перевантаженого оператора був очевидним для
користувача. Добрим прикладом перевантаження в цьому сенсі є використання операторів + і += для конкатенації
екземплярів std::basic_string<>.
Оригінальне рішення використовується у класі std::filesystem::path (C++17). У цьому класі оператори / та /=
перевантажені для конкатенації елементів шляху. Цей символ оператора збігається з традиційним роздільником
елементів шляху.
Необхідно враховувати пріоритет та асоціативність операторів, вони при перевантаженні не змінюються і повинні
відповідати очікуванням користувача. Характерним прикладом є використання оператора << для виведення даних у
потік. На жаль, пріоритет цього оператора досить високий, тому дужками доводиться користуватися частіше, ніж
хотілося б. Наприклад
std::сout<<c?x:y;
буде прийнято як (std::сout<<c)?x:y;
Тому потрібно писати std::сout<<(c?x:y);
Бажано, щоб перевантажені оператори максимально близько відтворювали інтерфейс та семантику відповідних
вбудованих операторів. У цьому випадку поведінка коду, що використовує перевантажені оператори, була б
максимально схожою на поведінку коду, що використовує вбудовані оператори. Наприклад, оператор присвоювання
повинен повертати посилання на лівий операнд, яке може бути використане як правий операнд в іншому
присвоюванні. І тут стають допустимими звичні висловлювання типу a=b=c. Оператори порівняння повинні повертати
bool та не змінювати операнди. Унарні оператори +, -, ~ повинні повертати модифіковане значення та не змінювати
операнд. Якщо реалізація оператора повертає об'єкт за значенням, його часто оголошують константним. Це запобігає
модифікації значення, що повертається, що дозволяє запобігти ряду синтаксичних непорозумінь, яких немає при
використанні вбудованих операторів.
Оператори, які не рекомендовані для перевантаження
Не рекомендується перевантажувати наступні три бінарних оператори: , (кома), &&(кон’юнкція, «і»), ||
(діз’юнкція, «або»). Справа в тому, що операнд «кома» викликає послідовність операцій, які мають
бути виконані. Значення всього виразу коми є значенням останнього виразу списку, розділеного
комами. У багатьох випадках це незручно.
Для операторів && і || стандарт передбачає так звану семантику швидких обчислень (short-circuit
evaluation), але для перевантажених операторів це не гарантується або просто безглуздо, що може
виявитися дуже неприємною несподіванкою для програміста. Семантика швидких обчислень, звана
ще закорочуванням, полягає в тому, для оператора && другий операнд не обчислюється, якщо перший
дорівнює false, а для оператора ||другий операнд не обчислюється, якщо перший дорівнює true.)
a && b && c a || b || c
Також не рекомендується перевантажувати унарний оператор & (взяття адреси). Тип з
перевантаженим оператором небезпечно використовувати з шаблонами, оскільки вони можуть
використовувати стандартну семантику цього оператора. Правда в С++11 з'явилася стандартна функція
(точніше шаблон функції) std:: addressof (), яка вміє отримувати адресу без оператора і правильно
написані шаблони повинні використовувати саме цю функцію замість вбудованого оператора.
Перевантаження унарних і бінарних операторів
Оператори можна перевантажувати у двох варіантах: як функцію-член класу и як вільну (не-член класу)
функцію.
Перевантаження унарних операцій. Префіксна або постфіксна унарна операція може бути перевизначена
двома способами:
- нестатичною функцією - членом класу без аргументів;
- функцією – не членом класу з одним аргументом (часто дружньою функцією).
Якщо # - унарна операція, у класі Х, то #X інтерпетується як
X. operator#(), якщо функція належить до класу Х,
або operator#(X), якщо функція не належить до класу Х.
Для перевантаження постфіксних операцій “++” і “- -“ слід записати операторну функцію - член класу з одним
аргументом типу int: operator++(int) і operator- -(int). Без вказівки аргументу операція інтепретується як
префиксна.
Первантаження бінарних операцій. Бінарна операція може бути перевизначена двома способами:
- описом нестатичної функції - члена класу з одним аргументом;
- описом функції – не члена класу (звичайно дружня функція) із двома аргументами.
Наприклад, якщо # - бінарна операція класу Х, то X#Y можна інтепретувати як
X. operator#(Y), або operator#(X,Y)
Перевантаження операцій. Власні функції класу
Розглянемо перевантаження арифметичних операцій для
роботи з комплексними числами (дійсна та уявна частини) із
використанням власних функцій класу.
Код, що представляє дії над об'єктами, став значно
наочнішим.
void main ( )
{Сomplex a ( 1.5, 2.9), b (4, 3), c ( 5), d, e;
d=a+b;
d. print ( );//re=5.5 im=5.9
e=d*c;
e. print ( );//re=27.5 im=29.5
d=( a+b)*c;
d. print ( );//re=27.5 im=29.5
e=(a-b)*(c-d);
e.print( );
}
Перевантаження операцій. Дружні функції класу
Розглянемо перевантаження арифметичних операцій для
роботи з комплексними числами (дійсна та уявна частини) із
використанням дружніх функцій класу.
class Сomplex
{double re, im;//уявлення комплексного числа
public:
Сomplex (double r, double i=0) {re=r; im=i;}
Сomplex ( int r, int i=0) {re=r; im=i;}
Сomplex ( ) {re=im=0;}
friend Сmplex operator+(const Сomplex&, const Сomplex&);
friend Сmplex operator+(const Сomplex&, double);
friend Сomplex operator+(const Сomplex&, int);
friend Сomplex operator-(const Сomplex&, const Сomplex&);
friend Сomplex operator*(const Сomplex&, const Сomplex&);
friend Сomplex operator/(const Сomplex&, const Сomplex&);
void print ( ) {cout<<”re=”<<re<<” im=”<<im;}};
Сomplex operator+(const Сomplex&c1, const Сomplex&c2)
{return Сomplex ( c1. re+c2. re, c1. im+c2. im);}
Сomplex operator+(const Сomplex&c1, double r)
{return Сomplex ( c1. re+r, c1. im);}
Дружні функції забезпечили симетрію при
використанні операцій.
Сomplex operator+(const Сomplex&c1, int r)
{return Сomplex (c1. re+r, c1. im )}
Сomplex operator-(const Сomplex& c1, const Сomplex&c2)
{return Сomplex (c1. re-c2. re, c1. im-c2. im);}
Сomplex operator*(const Сomplex&c1, const Сomplex&c2)
{return complex ( c1. re*c2. re - c1. im*c2. im,
c1. re*c2. im + c1. im*c2. re);}
Сomplex operator/(const Сomplex&c1, const Сomplex&c2)
{. . . }
void main ( )
{Сomplex a ( 1.5, 2.9), b (4, 3), c ( 5), d, e;
d=a+b;
d. print ( );//re=5.5 im=5.9
e=d*c;
e. print ( );//re=27.5 im=29.5
d=( a+b)*c;
d. print ( );//re=27.5 im=29.5
e=(a-b)*(c-d);
e.print( );
}
Перевантаження оператора присвоєння
Конструктор копіювання і оператор присвоювання виконують майже ідентичну роботу: обидва копіюють значення з
одного об'єкта до іншого об'єкта. Однак конструктор копіювання використовується при ініціалізації нових об'єктів, тоді як
оператор присвоєння замінює вміст існуючих об'єктів. При перевантаженні оператора присвоєння необхідно керуватися
такими правилами:
•аргументом перевантаженого оператора присвоювання має бути незмінне посилання на екземпляр даного класу (щоб
випадково не зіпсувати екземпляр);
•перед здійсненням присвоєння необхідно здійснити перевірку на присвоєння самому собі (щоб не виконувати зайвих
дій);
•оператор повинен здійснювати поелементне присвоєння (тобто має бути виконане послідовне надання значень для
кожної змінної);
•оператор повинен повертати посилання на самого себе (щоб воно було схоже на присвоєння вбудованих типів даних,
тоді буде можливим запис x=y=z=1).
Під час роботи з бінарними операторами,
які змінюють лівий операнд (наприклад,
operator+=), зазвичай використовується
перевизначення через методи класу.
У таких випадках лівим операндом
завжди є об'єкт класу, який вказує
прихований покажчик *this
Перевантаження операторів +=, *= …
Перевантаження операторів із присвоєнням (+=, *= і т.і.) здійснюється за такими правилами:
• аргументом перевантаженого оператора з присвоєнням має бути незмінне посилання на екземпляр
даного класу (щоб випадково не зіпсувати екземпляр);
• перевантажений оператор із присвоєнням повинен повертати посилання на самого себе.
Перевантаження оператора ! і унарного-
Такі оператори застосовуються тільки до одного об'єкта і їх перевантаження слід виконувати через методи класу.
#include <iostream>
class Something
{private:
double m_a, m_b, m_c;
public:
Something (double a = 0.0, double b = 0.0, double c = 0.0) :
m_a(a), m_b(b), m_c(c) { }
Something operator- () const
{return Something(-m_a, -m_b, -m_c); }
bool operator! () const
{return (m_a == 0.0 && m_b == 0.0 && m_c == 0.0); }
double getA() { return m_a; }
double getB() { return m_b; }
double getC() { return m_c; }
};
int main()
{
Something s1; // використовуєм конструктор за замовчанням ( 0.0, 0.0, 0.0)
if (!s1) std::cout << "Something is null.n";
else std::cout << "Something is not null.n";
Something s2(1.1,2.2,3.3);
if (!s2) std::cout << "Something is null.n";
else std::cout << "Something is not null.n";
return 0;
}
Перевантаження операцій відношення
Операції відношення рекомендовано перевантажувати функціями не членами класу. Ряд операцій
перевантажують парами. Наприклад, якщо перевантажують оператор ==, то необхідно також
перевантажити і операнд !=. Аналогічно перевантаження оператора < вимагае перевантаження
оператора >. Приклад:
Приклад застосування операцій відношення
class Equipment
{String name;
String firm;
double cost;
public:
Equipment(…){…}
~Equipment(){…}
String getFirm(){…}
String getName(){…}
…
friend bool operator<=(const Equipment& e1, const Equipment& e2);
…
};
//З однотипного обладнання вибираємо те, що дешевше
bool operator<=(const Equipment& e1, const Equipment& e2)
{return ( e1.name==e2.name)&&(e1.cost<=e2.cost);}
…..
void main()
{Equipment equip1(…);
Equipment equip2(…);
if(equip1<= equip2) std::cout<<“Обладнання ”<< equip1. getName()<<“фірми ”
<< equip1. getFirm()<<“дешевше ніж у фірмі ” <<equip2. getFirm();
…
return 0;}
Оператор assert
Оператор assert у мові C++ — це макрос препроцесора, який обробляє умовний вираз під час виконання.
Якщо умовний вираз істинний, то оператор assert нічого не робить. Якщо - помилковий, то виводиться
повідомлення про помилку, і програма завершується. Це повідомлення про помилку містить помилковий
умовний вираз, а також ім'я файлу з кодом та номером рядка з assert. Таким чином, можна легко знайти та
ідентифікувати проблему, що дуже допомагає при налагодженні програм.
Сам assert реалізований в заголовному файлі cassert і часто використовується як для перевірки коректності
переданих параметрів функції, так і для перевірки значення функції, що повертається:
Якщо у наведеній вище програмі викликати
getArrayValue(array, -3);
то програма виведе наступне повідомлення:
Assertion failed: index >= 0 && index <=8, file C:VCProjectsProgram.cpp, line 5
Перевантаження оператора індексації
Оператор [] вважається бінарним оператором. Оператор індексу має бути нестатичною функцією-членом класу, що
приймає один аргумент. Цей аргумент визначає потрібний індекс масиву.
#include <cassert>
#include <iostream>
using namespace std;
class IntVector{
private:
int *arr;
int n;
public:
IntVector(int n1)
{arr=new int[n=n1];}
~Intvector(){delete[] arr;}
int & operator[] (int index)
{assert(index >= 0 && index < n);
return arr[index];}
Не все можна перевантажити методами класу
Потрібно зазначити, що через метод класу перевантажити оператор <<
ми не зможемо. Тому що при перевантаженні через метод класу як лівий
операнд використовується поточний об'єкт. А тут лівим операндом є
об'єкт типу std::ostream. std::ostream є частиною стандартної бібліотеки
C++. Ми не можемо використовувати std::ostream як лівий неявний
параметр, на який би вказував прихований покажчик *this, оскільки
покажчик *this може вказувати тільки на поточний об'єкт поточного
класу, члени якого ми можемо змінити, тому перевантаження
оператора << має здійснюватися через дружню (може вільну) функцію.
Перевантаження операторів через методи класу не використовується,
якщо лівий операнд не є класом, наприклад, operator+(int, Dollars) ,
або клас, який ми не можемо змінити, наприклад, std::ostream).
Перевантаження операцій читання-запису. Оператори виведення <<
Оператори введення >> та виведення << перевизначені і чудово працюють для примітивних типів даних, таких
як int або double. Для використання їх із об'єктами класів користувача їх потрібно перевантажити. Ці оператори
не повинні бути членами класу, а визначаються поза класом як звичайні чи дружні функції.
Перевантаження оператора <<. Зазвичай перший параметр оператора << подає посилання на неконстантний
об'єкт ostream. Цей об'єкт не повинен представляти константу, оскільки запис у потік змінює його стан. Причому
параметр представляє саме посилання, тому що не можна копіювати об'єкт класу ostream. Другий параметр
оператора визначає посилання на константу об'єкта класу, який треба вивести в потік. Для сумісності з іншими
операторами оператор, що перевантажується, повинен повертати значення параметра ostream.
#include <iostream>
#include <string>
struct Person
{ std::string name;
int age;
};
std::ostream& operator << (std::ostream &os, const Person &p)
{ return os << p.name << " " << p.age;}
int main()
{ Person tom;
tom.name = "Tom";
tom.age = 31;
std::cout << tom << std::endl;
return 0;
}
Перевантаження операторів введення >>
Перший параметр оператора >>, як правило, представляє посилання на об'єкт istream, з якого здійснюється читання.
Другий параметр представляє посилання на неконстантний об'єкт, в який треба записати дані. Зазвичай як результат
оператори повертають посилання на потік введення istream з першого параметра.
#include <iostream>
#include <string>
struct Person
{ std::string name;
int year;
};
std::istream& operator >> (std::istream& in, Person& p)
{ in >> p.name >> p.year; return in;}
int main()
{ Person bob;
std::cout << "Input name and age: ";
std::cin >> bob;
std::cout << "Name: " << bob.name << "tYear: " << bob.year << std::endl;
return 0;
}
Якщо потрібно введення /виведення закритих членів класу, то потрібно застосувати дружні функції класу.
Завдання для практики
Створити клас «Годинник». Перевизначити операції відношення
для вибору годинника за атрибутами Гарантія та Ціна.

asddsadasddsadasddsadasddsadasddsadasddsadasddsadasddsadasddsad

  • 1.
  • 2.
    Перевантаження операторів. Загальніположення Мета перевантаження операторів – поліпшити розуміння і наочність операцій, які виконуються над об’єктами класів. Перевантаження операторів (operator overloading) - це можливість застосовувати вбудовані оператори мови до різних типів, у тому числі і для типів користувача. Кожна мова програмування визначає множину операцій над базовими типами даних. Звичайно число цих типів невелике, однак і в цьому випадку часто виявляється, що не всі необхідні для деякого додатка операції реалізовані. Наприклад, у С++ відсутня операція зведення в ступінь для арифметичних типів. Нестача операцій заповнюється розробкою функцій, що включаються у відповідні бібліотеки. Наприклад, відсутність операцій із рядками в мові С привело до необхідності створення бібліотеки рядкових функцій. Однак використання функцій не дозволяє створити такий же компактний і наочний запис, як це можна зробити за допомогою операцій. Особливо відчувається потреба в нових операціях при формуванні таких класів, як “комплексне число”, “рядок”, “матриця” , множина та інші. Обмеження. У мові С++ є спеціальні засоби, що дозволяють перевизначити вже існуючі операції. Приписувати новим операціям нові лексеми неприпустимо. Це визначається тим, що для існуючих операцій вже встановлений синтаксис (одномісна, двомісна операція), пріоритет і порядок виконання при однаковому пріоритеті із сусідніми операціями (зліва направо або справа наліво). Введення нового позначення для операції зажадало б громіздких описів. Відповідно до правил С++ не можна перевантажувати операції “. ”, “::”, “? ”
  • 3.
    Перевантаження операторів. Основніоператори мови С++ Операції поділяються на: унарні або одномісні — &, *, -, +, ~, !, ++, –-, sizeof; бінарні або двомісні — +, -, *, /, %, <<, >>, &, :, ^, <, >, <=, ==, >=, !=, &&, ||, =,*=, /=, %=, +=, -=, <<=, >>=, &=, |=, ^=, ., ->, ,, (), []; умовну триарну або тримісну операцію — ?: . Проблеми введення нової операції (операція зведення у ступінь). Приклад: y=a*b‡c‡d; • Який у ‡ пріоритет? • Скільки у ‡ операндів (місць)? • Яка у ‡ асоциативність (порядок виконання)? № Операції Порядок виконання 1 () , {} -> . Л -> П 2 ! ~ ++ — & * (type) П -> Л 3 sizeof П -> Л 4 * / % Л -> П 5 + – Л -> П 6 << >> Л -> П 7 < <= > >= Л -> П 8 == != Л -> П 9 & Л -> П 10 ^ Л -> П 11 | Л -> П 12 && Л -> П 13 || Л -> П 14 ?: П -> Л 15 = += *= -= /= %= П -> Л 16 , Л -> П
  • 4.
    Загальні правила привиборі оператора, що перевантажується При перевантаженні операторів треба намагатися, щоб зміст перевантаженого оператора був очевидним для користувача. Добрим прикладом перевантаження в цьому сенсі є використання операторів + і += для конкатенації екземплярів std::basic_string<>. Оригінальне рішення використовується у класі std::filesystem::path (C++17). У цьому класі оператори / та /= перевантажені для конкатенації елементів шляху. Цей символ оператора збігається з традиційним роздільником елементів шляху. Необхідно враховувати пріоритет та асоціативність операторів, вони при перевантаженні не змінюються і повинні відповідати очікуванням користувача. Характерним прикладом є використання оператора << для виведення даних у потік. На жаль, пріоритет цього оператора досить високий, тому дужками доводиться користуватися частіше, ніж хотілося б. Наприклад std::сout<<c?x:y; буде прийнято як (std::сout<<c)?x:y; Тому потрібно писати std::сout<<(c?x:y); Бажано, щоб перевантажені оператори максимально близько відтворювали інтерфейс та семантику відповідних вбудованих операторів. У цьому випадку поведінка коду, що використовує перевантажені оператори, була б максимально схожою на поведінку коду, що використовує вбудовані оператори. Наприклад, оператор присвоювання повинен повертати посилання на лівий операнд, яке може бути використане як правий операнд в іншому присвоюванні. І тут стають допустимими звичні висловлювання типу a=b=c. Оператори порівняння повинні повертати bool та не змінювати операнди. Унарні оператори +, -, ~ повинні повертати модифіковане значення та не змінювати операнд. Якщо реалізація оператора повертає об'єкт за значенням, його часто оголошують константним. Це запобігає модифікації значення, що повертається, що дозволяє запобігти ряду синтаксичних непорозумінь, яких немає при використанні вбудованих операторів.
  • 5.
    Оператори, які нерекомендовані для перевантаження Не рекомендується перевантажувати наступні три бінарних оператори: , (кома), &&(кон’юнкція, «і»), || (діз’юнкція, «або»). Справа в тому, що операнд «кома» викликає послідовність операцій, які мають бути виконані. Значення всього виразу коми є значенням останнього виразу списку, розділеного комами. У багатьох випадках це незручно. Для операторів && і || стандарт передбачає так звану семантику швидких обчислень (short-circuit evaluation), але для перевантажених операторів це не гарантується або просто безглуздо, що може виявитися дуже неприємною несподіванкою для програміста. Семантика швидких обчислень, звана ще закорочуванням, полягає в тому, для оператора && другий операнд не обчислюється, якщо перший дорівнює false, а для оператора ||другий операнд не обчислюється, якщо перший дорівнює true.) a && b && c a || b || c Також не рекомендується перевантажувати унарний оператор & (взяття адреси). Тип з перевантаженим оператором небезпечно використовувати з шаблонами, оскільки вони можуть використовувати стандартну семантику цього оператора. Правда в С++11 з'явилася стандартна функція (точніше шаблон функції) std:: addressof (), яка вміє отримувати адресу без оператора і правильно написані шаблони повинні використовувати саме цю функцію замість вбудованого оператора.
  • 6.
    Перевантаження унарних ібінарних операторів Оператори можна перевантажувати у двох варіантах: як функцію-член класу и як вільну (не-член класу) функцію. Перевантаження унарних операцій. Префіксна або постфіксна унарна операція може бути перевизначена двома способами: - нестатичною функцією - членом класу без аргументів; - функцією – не членом класу з одним аргументом (часто дружньою функцією). Якщо # - унарна операція, у класі Х, то #X інтерпетується як X. operator#(), якщо функція належить до класу Х, або operator#(X), якщо функція не належить до класу Х. Для перевантаження постфіксних операцій “++” і “- -“ слід записати операторну функцію - член класу з одним аргументом типу int: operator++(int) і operator- -(int). Без вказівки аргументу операція інтепретується як префиксна. Первантаження бінарних операцій. Бінарна операція може бути перевизначена двома способами: - описом нестатичної функції - члена класу з одним аргументом; - описом функції – не члена класу (звичайно дружня функція) із двома аргументами. Наприклад, якщо # - бінарна операція класу Х, то X#Y можна інтепретувати як X. operator#(Y), або operator#(X,Y)
  • 7.
    Перевантаження операцій. Власніфункції класу Розглянемо перевантаження арифметичних операцій для роботи з комплексними числами (дійсна та уявна частини) із використанням власних функцій класу. Код, що представляє дії над об'єктами, став значно наочнішим. void main ( ) {Сomplex a ( 1.5, 2.9), b (4, 3), c ( 5), d, e; d=a+b; d. print ( );//re=5.5 im=5.9 e=d*c; e. print ( );//re=27.5 im=29.5 d=( a+b)*c; d. print ( );//re=27.5 im=29.5 e=(a-b)*(c-d); e.print( ); }
  • 8.
    Перевантаження операцій. Дружніфункції класу Розглянемо перевантаження арифметичних операцій для роботи з комплексними числами (дійсна та уявна частини) із використанням дружніх функцій класу. class Сomplex {double re, im;//уявлення комплексного числа public: Сomplex (double r, double i=0) {re=r; im=i;} Сomplex ( int r, int i=0) {re=r; im=i;} Сomplex ( ) {re=im=0;} friend Сmplex operator+(const Сomplex&, const Сomplex&); friend Сmplex operator+(const Сomplex&, double); friend Сomplex operator+(const Сomplex&, int); friend Сomplex operator-(const Сomplex&, const Сomplex&); friend Сomplex operator*(const Сomplex&, const Сomplex&); friend Сomplex operator/(const Сomplex&, const Сomplex&); void print ( ) {cout<<”re=”<<re<<” im=”<<im;}}; Сomplex operator+(const Сomplex&c1, const Сomplex&c2) {return Сomplex ( c1. re+c2. re, c1. im+c2. im);} Сomplex operator+(const Сomplex&c1, double r) {return Сomplex ( c1. re+r, c1. im);} Дружні функції забезпечили симетрію при використанні операцій. Сomplex operator+(const Сomplex&c1, int r) {return Сomplex (c1. re+r, c1. im )} Сomplex operator-(const Сomplex& c1, const Сomplex&c2) {return Сomplex (c1. re-c2. re, c1. im-c2. im);} Сomplex operator*(const Сomplex&c1, const Сomplex&c2) {return complex ( c1. re*c2. re - c1. im*c2. im, c1. re*c2. im + c1. im*c2. re);} Сomplex operator/(const Сomplex&c1, const Сomplex&c2) {. . . } void main ( ) {Сomplex a ( 1.5, 2.9), b (4, 3), c ( 5), d, e; d=a+b; d. print ( );//re=5.5 im=5.9 e=d*c; e. print ( );//re=27.5 im=29.5 d=( a+b)*c; d. print ( );//re=27.5 im=29.5 e=(a-b)*(c-d); e.print( ); }
  • 9.
    Перевантаження оператора присвоєння Конструкторкопіювання і оператор присвоювання виконують майже ідентичну роботу: обидва копіюють значення з одного об'єкта до іншого об'єкта. Однак конструктор копіювання використовується при ініціалізації нових об'єктів, тоді як оператор присвоєння замінює вміст існуючих об'єктів. При перевантаженні оператора присвоєння необхідно керуватися такими правилами: •аргументом перевантаженого оператора присвоювання має бути незмінне посилання на екземпляр даного класу (щоб випадково не зіпсувати екземпляр); •перед здійсненням присвоєння необхідно здійснити перевірку на присвоєння самому собі (щоб не виконувати зайвих дій); •оператор повинен здійснювати поелементне присвоєння (тобто має бути виконане послідовне надання значень для кожної змінної); •оператор повинен повертати посилання на самого себе (щоб воно було схоже на присвоєння вбудованих типів даних, тоді буде можливим запис x=y=z=1). Під час роботи з бінарними операторами, які змінюють лівий операнд (наприклад, operator+=), зазвичай використовується перевизначення через методи класу. У таких випадках лівим операндом завжди є об'єкт класу, який вказує прихований покажчик *this
  • 10.
    Перевантаження операторів +=,*= … Перевантаження операторів із присвоєнням (+=, *= і т.і.) здійснюється за такими правилами: • аргументом перевантаженого оператора з присвоєнням має бути незмінне посилання на екземпляр даного класу (щоб випадково не зіпсувати екземпляр); • перевантажений оператор із присвоєнням повинен повертати посилання на самого себе.
  • 11.
    Перевантаження оператора !і унарного- Такі оператори застосовуються тільки до одного об'єкта і їх перевантаження слід виконувати через методи класу. #include <iostream> class Something {private: double m_a, m_b, m_c; public: Something (double a = 0.0, double b = 0.0, double c = 0.0) : m_a(a), m_b(b), m_c(c) { } Something operator- () const {return Something(-m_a, -m_b, -m_c); } bool operator! () const {return (m_a == 0.0 && m_b == 0.0 && m_c == 0.0); } double getA() { return m_a; } double getB() { return m_b; } double getC() { return m_c; } }; int main() { Something s1; // використовуєм конструктор за замовчанням ( 0.0, 0.0, 0.0) if (!s1) std::cout << "Something is null.n"; else std::cout << "Something is not null.n"; Something s2(1.1,2.2,3.3); if (!s2) std::cout << "Something is null.n"; else std::cout << "Something is not null.n"; return 0; }
  • 12.
    Перевантаження операцій відношення Операціївідношення рекомендовано перевантажувати функціями не членами класу. Ряд операцій перевантажують парами. Наприклад, якщо перевантажують оператор ==, то необхідно також перевантажити і операнд !=. Аналогічно перевантаження оператора < вимагае перевантаження оператора >. Приклад:
  • 13.
    Приклад застосування операційвідношення class Equipment {String name; String firm; double cost; public: Equipment(…){…} ~Equipment(){…} String getFirm(){…} String getName(){…} … friend bool operator<=(const Equipment& e1, const Equipment& e2); … }; //З однотипного обладнання вибираємо те, що дешевше bool operator<=(const Equipment& e1, const Equipment& e2) {return ( e1.name==e2.name)&&(e1.cost<=e2.cost);} ….. void main() {Equipment equip1(…); Equipment equip2(…); if(equip1<= equip2) std::cout<<“Обладнання ”<< equip1. getName()<<“фірми ” << equip1. getFirm()<<“дешевше ніж у фірмі ” <<equip2. getFirm(); … return 0;}
  • 14.
    Оператор assert Оператор assertу мові C++ — це макрос препроцесора, який обробляє умовний вираз під час виконання. Якщо умовний вираз істинний, то оператор assert нічого не робить. Якщо - помилковий, то виводиться повідомлення про помилку, і програма завершується. Це повідомлення про помилку містить помилковий умовний вираз, а також ім'я файлу з кодом та номером рядка з assert. Таким чином, можна легко знайти та ідентифікувати проблему, що дуже допомагає при налагодженні програм. Сам assert реалізований в заголовному файлі cassert і часто використовується як для перевірки коректності переданих параметрів функції, так і для перевірки значення функції, що повертається: Якщо у наведеній вище програмі викликати getArrayValue(array, -3); то програма виведе наступне повідомлення: Assertion failed: index >= 0 && index <=8, file C:VCProjectsProgram.cpp, line 5
  • 15.
    Перевантаження оператора індексації Оператор[] вважається бінарним оператором. Оператор індексу має бути нестатичною функцією-членом класу, що приймає один аргумент. Цей аргумент визначає потрібний індекс масиву. #include <cassert> #include <iostream> using namespace std; class IntVector{ private: int *arr; int n; public: IntVector(int n1) {arr=new int[n=n1];} ~Intvector(){delete[] arr;} int & operator[] (int index) {assert(index >= 0 && index < n); return arr[index];}
  • 16.
    Не все можнаперевантажити методами класу Потрібно зазначити, що через метод класу перевантажити оператор << ми не зможемо. Тому що при перевантаженні через метод класу як лівий операнд використовується поточний об'єкт. А тут лівим операндом є об'єкт типу std::ostream. std::ostream є частиною стандартної бібліотеки C++. Ми не можемо використовувати std::ostream як лівий неявний параметр, на який би вказував прихований покажчик *this, оскільки покажчик *this може вказувати тільки на поточний об'єкт поточного класу, члени якого ми можемо змінити, тому перевантаження оператора << має здійснюватися через дружню (може вільну) функцію. Перевантаження операторів через методи класу не використовується, якщо лівий операнд не є класом, наприклад, operator+(int, Dollars) , або клас, який ми не можемо змінити, наприклад, std::ostream).
  • 17.
    Перевантаження операцій читання-запису.Оператори виведення << Оператори введення >> та виведення << перевизначені і чудово працюють для примітивних типів даних, таких як int або double. Для використання їх із об'єктами класів користувача їх потрібно перевантажити. Ці оператори не повинні бути членами класу, а визначаються поза класом як звичайні чи дружні функції. Перевантаження оператора <<. Зазвичай перший параметр оператора << подає посилання на неконстантний об'єкт ostream. Цей об'єкт не повинен представляти константу, оскільки запис у потік змінює його стан. Причому параметр представляє саме посилання, тому що не можна копіювати об'єкт класу ostream. Другий параметр оператора визначає посилання на константу об'єкта класу, який треба вивести в потік. Для сумісності з іншими операторами оператор, що перевантажується, повинен повертати значення параметра ostream. #include <iostream> #include <string> struct Person { std::string name; int age; }; std::ostream& operator << (std::ostream &os, const Person &p) { return os << p.name << " " << p.age;} int main() { Person tom; tom.name = "Tom"; tom.age = 31; std::cout << tom << std::endl; return 0; }
  • 18.
    Перевантаження операторів введення>> Перший параметр оператора >>, як правило, представляє посилання на об'єкт istream, з якого здійснюється читання. Другий параметр представляє посилання на неконстантний об'єкт, в який треба записати дані. Зазвичай як результат оператори повертають посилання на потік введення istream з першого параметра. #include <iostream> #include <string> struct Person { std::string name; int year; }; std::istream& operator >> (std::istream& in, Person& p) { in >> p.name >> p.year; return in;} int main() { Person bob; std::cout << "Input name and age: "; std::cin >> bob; std::cout << "Name: " << bob.name << "tYear: " << bob.year << std::endl; return 0; } Якщо потрібно введення /виведення закритих членів класу, то потрібно застосувати дружні функції класу.
  • 19.
    Завдання для практики Створитиклас «Годинник». Перевизначити операції відношення для вибору годинника за атрибутами Гарантія та Ціна.