Лабораторна робота № 5
Перевантаження операторів
Мета роботи: познайомитися із перевантаженням операторів.
Короткі теоретичні відомості
Перевантаження операторів
Кожному оператору мова С++ ставить у відповідність ім'я функції, що складається з ключового слова operator, власне оператору та аргументів відповідних типів:
тип operator [символ оператору] (списокПараметрів)
{
//тіло метода
}


Щоб використовувати операцію над об'єктами класів, ця операція повинна бути перевантажена, але є два виключення. Операція присвоювання (=) може бути використана з кожним класом без явного перевантаження. За замовчуванням операція присвоювання зводиться до побітового копіювання даних-елементів класу. Проте таке побітове копіювання небезпечне для класів з елементами, що вказують на динамічно виділені області пам'яті; для таких класів слід явно перевантажувати операцію присвоювання. Операція адресації (&) також може бути використана з об'єктами будь-яких класів без перевантаження; вона просто повертає адресу об'єкта в пам'яті. Але операцію адресації можна також і перевантажувати.
Перевантаження операцій підпорядковується наступним правилам:
При перевантаженні зберігаються кількість аргументів, пріоритети операцій та правила асоціації, що використовуються у стандартних типах даних;
Для стандартних типів даних операції не підлягають перевизначенню;
Перевантажена функція-оператор не може мати параметрів по замовчуванню, не успадковується та не може бути визначеною як static;
Функція-оператор може бути визначена трьома способами – метод класу, дружня функція або звичайна функція. В останніх двох випадках вона повинна приймати хоча б один аргумент, що має тип класу, покажчика або посилання на клас.
При перевантаженні операцій ( ), [], -> або = функція перевантаження операції повинна бути оголошена як елемент класу. Для інших операцій функції перевантаження операцій можуть не бути функціями-елементами.
Коли функція-операція реалізована як функція-елемент, крайній лівий (або єдиний) операнд повинен бути об'єктом того класу (або посиланням на об'єкт того класу), елементом якого є функція. Якщо лівий операнд повинен бути об'єктом іншого класу або убудованого типу, така функція-операція не може бути реалізована як функція-елемент. Функція-операція, реалізована не як функція-елемент, повинна бути другом, якщо ця функція повинна мати прямий доступ до закритих або захищених елементів цього класу. Щоб оголосити функцію як друга (frіend) класу, перед її прототипом в описі класу ставиться ключове слово frіend. Дружні функції класу визначаються поза областю дії класу, але мають право доступу до закритих елементів класу.
Перевантажена операція << повинна мати лівий операнд типу ostream & (такий, як cout), так що вона не може бути функцією-елементом. Аналогічно, перевантажена операція >> повинна мати лівий операнд типу іstream & (такий, як cіn), так що вона теж не може бути функцією-елементом. До того ж кожна з цих перевантажених функцій-операцій може забажати доступу до закритих елементів-даних об'єкта класу, так що ці перевантажені функції-операції роблять функціями-друзями класу.
Будь-яку бінарну операцію можна перевантажувати як нестатичну функцію-елемент з одним аргументом, або як функцію, що не є елементом, із двома аргументами (один з цих аргументів повинен бути або об'єктом класу, або посиланням на об'єкт класу).
Унарну операцію класу можна перевантажувати як функцію-елемент без аргументів, або як функцію, що не є елементом, з одним аргументом; цей аргумент повинен бути або об'єктом класу, або посиланням на об'єкт класу. Функції-елементи, що реалізують перевантажені операції, повинні бути нестатичними, щоб вони могли мати доступ до даних класу. Нагадаємо, що статичні функції-елементи можуть мати доступ тільки до статичних даних-елементів класу.
При перевантаженні унарних операцій переважно створюють функції-операції, що є елементами класу, замість дружніх функцій, що не є елементами. Дружніх функцій краще уникати доти, поки вони не стануть абсолютно необхідними. Використання друзів порушує інкапсуляцію класу.
Щоб перевантажити операцію інкремента та декремента для одержання можливості використання і префіксної, і постфіксної форм, кожна з цих двох перевантажених функцій-операцій повинна мати різну сигнатуру, щоб компілятор мав можливість визначити, яка версія мається на увазі в кожному конкретному випадку. Префіксний варіант перевантажується як будь-яка інша префіксна унарна операція. Для постфіксної форми вводиться додатковий параметр цілого типу у список аргументів, щоб зробити функцію для постфіксного варіанту відмінною від функції для префіксної форми.
Зауваження щодо перевантаження операцій:
Існують обмеження на перевантаження: не підлягають цій процедурі селектор елемента структури (.), оператор доступу до елементу за покажчиком (*), операція дозволу видимості (::), символи препроцесору (#, ##) та sizeof(). Неможливим є введення власних операторів.
Компілятор С++ не розуміє семантики перевантаженого оператору, а отже, не нав'язує жодних математичних концепцій. Можна перевантажити, скажімо, оператор інкременту в якості зменшення аргументу, проте навряд чи в цьому є сенс.
Не існує виведення складних операторів з простих: якщо ви перевантажили оператори operator+ та operator=, це зовсім не означає, що С++ обчислить вираз a += b, оскільки ви не перевантажили operator +=.
Перевантаження бінарних операторів не тотожньо відносно перестановки аргументів місцями, тим більше, якщо вони різного типу.
// Array.h: interface for the Array class.
#include<iostream.h>
#include<stdlib.h>
#include<time.h>
class Array
{
friend ostream& operator<< (ostream& output, Array& arr); int* ptr;
int size;
public:
Array(int s = 10);
Array(Array& arr);
virtual ~Array();
void Rindomize(int num = 10);
Array& operator= (Array& arr);
Array& operator+= (Array& arr);
Array operator+ (Array& arr) ;
int operator!= (Array& arr);
Array operator++ ();
Array operator++ (int);
};
// Array.cpp: implementation of the Array class.
#include "Array.h"
Array::Array(int s)
{
size = s;
ptr = new int[size];
for(int i = 0; i < size; i++)
{
ptr[i] = 0;
}
cout << "Constructor" << endl;
}
//--------------------------------------------------------------------------------
Array::Array(Array& arr)
{
size = arr.size;
ptr = new int[size];
for(int i = 0; i < size; i++)
{
ptr[i] = arr.ptr[i];
}
cout << "Copy Constructor" << endl;
}
//--------------------------------------------------------------------------------
Array::~Array()
{
delete[] ptr;
cout << "Destructor" << endl;
}
//--------------------------------------------------------------------------------
void Array::Rindomize(int num)
{
for(int i = 0; i < size; i++)
{
ptr[i] = rand() % num;
}
}
//--------------------------------------------------------------------------------
ostream& operator<< (ostream& output, Array& arr)
{
for(int i = 0; i < arr.size; i++)
{
output << arr.ptr[i] << " ";
}
output << endl;
return output;
}
//--------------------------------------------------------------------------------
Array& Array::operator= (Array& arr)
{
if(this != &arr)
{
delete[] ptr;
size = arr.size;
ptr = new int[size];
for(int i = 0; i < size; i++)
{
ptr[i] = arr.ptr[i];
}
}
cout << "Operator =" << endl;
return *this;
}
//--------------------------------------------------------------------------------
int Array::operator!= (Array& arr)
{
if(size != arr.size)
return 1;
for(int i = 0; i < size; i++)
{
if(ptr[i] != arr.ptr[i])
return 1;
}
return 0;
}
//--------------------------------------------------------------------------------
Array Array::operator+ (Array& arr)
{
int mins = (size < arr.size) ? size : arr.size;
Array temp;
if(mins == arr.size)
{
temp = *this;
for(int i = 0; i < mins; i++)
{
temp.ptr[i] += arr.ptr[i];
}
}
else
{
temp = arr;
for(int i = 0; i < mins; i++)
{
temp.ptr[i] += ptr[i];
}
}
cout << "Operator +" << endl;
return temp;
}
//--------------------------------------------------------------------------------
Array& Array::operator+= (Array& arr)
{
Array temp;
temp = *this + arr;
*this = temp;
return *this;
}
//--------------------------------------------------------------------------------
Array Array::operator++ ()
{
for(int i = 0; i < size; i++)
ptr[i] += 1;
return *this;
}
//--------------------------------------------------------------------------------
Array Array::operator++ (int)
{
Array temp = *this;
for(int i = 0; i < size; i++)
ptr[i] += 1;
return temp;
}
// Main.cpp : main program
#include"Array.h"
int main()
{
srand(time(NULL));
Array a(5);
a.Rindomize(5);
Array b(7);
b.Rindomize(5);
Array c;
cout << "Array a:" << endl << a;
cout << a++;
cout << a;

cout << "Array b:" << endl << b;
c = a + b;
cout << "Array c:" << endl << c;
return 0;
}
Результати виконання:
Constructor
Constructor
Constructor
Array a:
0 1 4 0 3
Copy Constructor
Copy Constructor
Destructor
0 1 4 0 3
Destructor
1 2 5 1 4
Array b:
2 0 1 3 2 2 4
Constructor
Operator =
Operator +
Copy Constructor
Destructor
Operator =
Destructor
Array c:
3 2 6 4 6 2 4
Destructor
Destructor
Destructor
Press any key to continue


Завдання
Описати клас, що реалізовує вказаний нижче тип даних. Клас повинен містити множину конструкторів для створення об'єктів певного типу (конструктор по замочуванню та з параметрами, конструктор копії) та подані у таблиці операції над об'єктами класу (плюс обов'язково операцію присвоювання) з використанням механізму перевантаження операцій.
Написати програму, яка демонструє роботу з об'єктами цього класу. Організувати виведення та введення даних за допомогою класів-потоків сin та cout.
Варіант
Завдання

1
Клас Matrix (матриця квадратна)
Операції: - =, * =.

2
Клас Complex (комплексні числа)
Операції: - =, * =.

3
Клас Fraction (дроби)
Операції: - =, * =.

4
Клас HugeInt (цілі числа)
Операції: - =, порівняння(<, >).

5
Клас Fraction (дроби)
Операції: + =, / =.

6
Клас Set (множини)
Операції: об’єднання множин(+ =), перетин множин (- =).

7
Клас HugeInt (цілі числа)
Операції: * =, + =.