Міністерство освіти і науки України
Національний університет „Львівська політехніка”
Кафедра ЕОМ
/
Звіт
про виконання лабораторної роботи №5
з дисципліни:
“Програмування, частина 2 (Об’єктно-орієнтоване програмування)”
на тему:“ Перевантаження операторів”
2017
Мета: познайомитися із перевантаженням операторів.
Теоретичні відомості
Перевантаження операторів
Кожному оператору мова С++ ставить у відповідність ім'я функції, що складається з ключового слова operator, власне оператору та аргументів відповідних типів:
тип operator символОператору (списокПараметрів)
{
//тіло методу
}
Щоб використовувати операцію над об'єктами класів, ця операція повинна бути перевантажена, але є два виключення. Операції присвоювання (=) і взяття адреси (&) створюються в класі автоматично за замовчуванням, тому їх можна використовувати без явного перевантаження. За замовчуванням операція присвоювання зводиться до побітового копіювання даних-елементів класу. Проте таке побітове копіювання небезпечне для класів з елементами, що вказують на динамічно виділені області пам'яті, масиви, рядки, оскільки в цьому випадку відбувається копіювання не даних (глибоке копіювання), а лише вказівників на дані (поверхневе копіювання). Для таких класів слід явно перевантажувати операцію присвоювання і здійснювати у ній глибоке копіювання. Операція адресації також може бути використана з об'єктами будь-яких класів без перевантаження. Вона просто повертає адресу об'єкта в пам'яті. Але операцію адресації можна також і перевантажувати.
Перевантаження операцій підпорядковується наступним правилам:
При перевантаженні зберігаються кількість аргументів, пріоритети операцій та правила асоціації, що використовуються у стандартних типах даних;
Для стандартних типів даних операції не підлягають перевизначенню;
Перевантажена функція-оператор не може мати параметрів по замовчуванню, не успадковується та не може бути визначеною як static;
Функція-оператор може бути визначена трьома способами – метод класу, дружня функція або звичайна функція. В останніх двох випадках вона повинна приймати хоча б один аргумент, що має тип класу, вказівника або посилання на клас.
При перевантаженні операцій ( ), [], -> та = функція перевантаження операції може бути оголошена лише як метод класу. Для інших операцій функції перевантаження операцій можуть не бути методами класу.
Оператори, які можна перевантажити:
+
-
*
/
%
^
&
|
~
!
,
=
<
>
<=
>=
++
--
<<
>>
==
!=
&&
||
+=
-=
/=
%=
^=
&=
|=
*=
<<=
>>=
[]
()
->
->*
new
new []
delete
delete []
Оператори, які не можна перевантажити:
sizeof
. (селектор елемента структури або класу)
* (оператор доступу до елементу за вказівником)
:: (оператор дозволу видимості)
?: (тернарний оператор)
typeid
const_cast
dynamic_cast
reinterpret_cast
static_cast
# і ## (символи препроцесору)
Коли операція реалізована як метод класу, тоді:
- якщо операція є унарною (передбачає один операнд, наприклад, інкременту або []), то лівим операндом вважаєтсья об’єкт, до якого застосовується операція, а правий операнд відсутній, тому метод, що реалізує даний оператор не приймає аргументів, за виключенням кількох операторів (наприклад, постфіксна форма інкременту або декременту).
- якщо операція є бінарною (передбачає 2 операнди, наприклад додавання або присвоєння) і лівий операнд є об’єктом класу у якому перевантажується операція, то крайній лівий операнд вважається об’єктом з-під якого здійснюється виклик даного методу (оператора), а правий операнд – передається як параметр, тому для нього слід вказати тип аргумента у методі; перевантажених операторів одного виду може бути кілька в залежності від типу аргументу, що передається методу;
Якщо операція є бінарною але лівий операнд не є об’єктом класу у якому перевантажується операція, то дана операція не може бути методом класу, а має бути реалізована як дружня функція, якщо ця функція повинна мати прямий доступ до закритих або захищених елементів цього класу, або звичайна функція в протилежному випадку.
Перевантажена операція << повинна мати лівий операнд типу ostream & (такий, як cout), так що вона не може бути функцією-елементом. Аналогічно, перевантажена операція >> повинна мати лівий операнд типу іstream & (такий, як cіn), так що вона теж не може бути функцією-елементом. До того ж кожна з цих перевантажених функцій-операцій може забажати доступу до закритих елементів-даних об'єкта класу, так що ці перевантажені функції-операції роблять функціями-друзями класу.
Будь-яку бінарну операцію можна перевантажувати як нестатичний метод з одним аргументом, або як функцію, що не є елементом, із двома аргументами (один з цих аргументів повинен бути або об'єктом класу, або посиланням на об'єкт класу).
Унарну операцію класу можна перевантажувати як метод без аргументів, або як функцію, з одним аргументом; цей аргумент повинен бути або об'єктом класу, або посиланням на об'єкт класу. Функції-елементи, що реалізують перевантажені операції, повинні бути нестатичними, щоб вони могли мати доступ до даних класу. Нагадаємо, що статичні методи можуть мати доступ тільки до статичних даних-елементів класу.
При перевантаженні унарних операцій переважно створюють методи класу, замість дружніх функцій, що не є членами класу. Дружніх функцій краще уникати доти, поки вони не стануть абсолютно необхідними. Використання друзів порушує інкапсуляцію класу.
Щоб перевантажити операцію інкремента та декремента для одержання можливості використання і префіксної, і постфіксної форм, кожна з цих двох перевантажених функцій-операцій повинна мати різну сигнатуру, щоб компілятор мав можливість визначити, яка версія мається на увазі в кожному конкретному випадку. Префіксний варіант перевантажується як будь-яка інша префіксна унарна операція. Для постфіксної форми вводиться додатковий параметр цілого типу у список аргументів, щоб зробити функцію для постфіксного варіанту відмінною від функції для префіксної форми.
Зауваження щодо перевантаження операцій:
Неможливим є введення власних операторів.
Компілятор С++ не розуміє семантики перевантаженого оператору, а отже, не нав'язує жодних математичних концепцій. Можна перевантажити, скажімо, оператор інкременту в якості зменшення аргументу, проте навряд чи в цьому є сенс.
Не існує виведення складних операторів з простих: якщо ви перевантажили оператори operator+ та operator=, це зовсім не означає, що С++ обчислить вираз a += b, оскільки ви не перевантажили operator +=.
Перевантаження бінарних операторів не тотожньо відносно перестановки аргументів місцями, тим більше, якщо вони різного типу.
Синтаксис виклику операторів реалізований у мові С++ таким чином, щоб програміст міг записати операцію над об’єктом класу з звичному для нього вигляді, наприклад object1 += object2. Проте для компілятора такий запис стосовно об’єктів не є звичним, бо дана операція у такому вигляді визначена лише для простих типів, а для об’єкту класу даний виклик має бути перетворений у виклик методу, що оголошений в класі, або у виклик відповідної функції. Тому при компіляції компілятор перетворює даний виклик у виклик відповідного методу або функції за наступним принципом:
- якщо оператор реалізований у вигляді методу, то лівий операнд перетворюється в об’єкт з-під якого викликається метод, що реалізує вказаний оператор, а правий операнд, якщо він присутній, передається методу в якості аргументу.
- якщо оператор реалізований у вигляді функції або дружньої функції, то воклик операції перетворюєтсья у виклик функції, яка приймає своїм лівим параметром лівий операнд операції, а правим, якщо такий присутній, - правий операнд операції.
Розглянемо приклади викликів операторів. Виклик оператору має наступний вигляд:
змінна символОператору [змінна];
Наприклад:
obj1 = obj2;
obj1++;
obj1 + 5;
2 * obj1;
Прицьому, якщо оператор визначений як метод класу, то перші 3 виклики будуть перетрансьовані компілятором в наступні виклики:
obj1.operator=(obj2);
obj1.operator++();
obj1.operator+(5);
Останній виклик реалізується виключно у вигляді дружньої або звичайної функції і після обробки компілятором буде перетворений у наступний виклик дружньої функції:
operator*(2, obj1);
Завдання:
Написати програму яка буде використовувати перевантаження операторів та продемонструвати їх використання.
В дані роботі реалізовано перевантаження наступних операторів: =, +, +=, <<.
Оператори =, +, += використовуються для додавання об’єктів класу Train.
Оператор << перевантажено для виводу об’єктів класу Train та Car на екран.
Код Train.h
#pragma once
#include <string>
#include "car.h"
class Train {
private:
string trainName;
int carCount;
int freePlaces;
vector<Car> cars;
public:
Train() {
this->trainName = "";
this->carCount = 0;
this->freePlaces = 0;
}
Train(string trainName, vector<Car> cars) {
this->trainName = trainName;
this->cars = cars;
this->carCount = cars.size();
int freePlaces = 0;
for (int i = 0; i < cars.size(); i++) {
for (int j = 0; j < cars[i].getPlaceArray().size(); j++) {
if (cars[i].getPlaceArray()[j] == true) {
freePlaces++;
}
}
}
this->freePlaces = freePlaces;
}
//Оголошуємо перевантажених операторiв
friend ostream& operator<< (ostream& output, Train& train);
Train operator+ (Train& train);
Train& operator+= (Train& train);
Train& operator= (Train& train);
//Прописуємо get i set
string getTrainName() {
return this->trainName;
}
void setTrainName(string name) {
this->trainName = name;
}
int getCarCount() {
return this->carCount;
}
void setCarCount(int name) {
this->carCount = name;
}
int getFreePlaces() {
return this->freePlaces;
}
void setFreePlaces(int name) {
this->freePlaces = name;
}
vector<Car> getCars() {
return this->cars;
}
void setCars(vector<Car> cars) {
this->cars = cars;
}
};
Код Train.cpp:
#include "Train.h"
//Перевантажуємо оператор "<<" для виведення даних про поїзд
ostream & operator<<(ostream & output, Train & train) {
output << "Назва потягу: " << train.getTrainName() << endl;
output << "Кiлькiсть вiльних мiсць: " << train.getFreePlaces() << endl;
output << "Список вагонiв: " << endl;
for (int i = 0; i < train.getCarCount(); i++) {
output << train.getCars()[i] << endl;
}
return output;
}
//Перевантажуємо оператор "+" щоб додавати поїзди
Train Train::operator+(Train & train) {
Train myTrain;
myTrain.setTrainName(this->getTrainName());
myTrain.setCarCount(this->getCarCount() + train.getCarCount());
myTrain.setFreePlaces(this->getFreePlaces() + train.getFreePlaces());
vector<Car> curr;
curr = this->getCars();
for (int i = 0; i < train.getCars().size(); i++) {
curr.push_back(train.getCars()[i]);
}
for (int i = 0; i < myTrain.getCarCount(); i++) {
curr[i].setNumber(i + 1);
}
myTrain.setCars(curr);
return myTrain;
}
//Перевантажуємо оператор "+="
Train & Train::operator+=(Train & train) {
Train curr;
curr = *this + train;
*this = curr;
return *this;
}
//Перевантажуємо оператор "="
Train & Train::operator=(Train & train) {
this->setCars(train.getCars());
this->setCarCount(train.getCarCount());
this->setFreePlaces(train.getFreePlaces());
this->setTrainName(train.getTrainName());
return *this;
}
Код Car.h:
#pragma once
#include <iostream>
#include <vector>
using namespace std;
class Car {
private:
int number;
int maxPlacesCount;
int freePlaces;
vector<bool> placeArray;
public:
Car() {
this->number = 0;
this->maxPlacesCount = 0;
}
Car(int numb, vector<bool> placePtr) {
this->number = numb;
this->maxPlacesCount = placePtr.size();
int freePlaces = 0;
for (int i = 0; i < placePtr.size(); i++) {
if (placePtr[i] == 1)
freePlaces++;
}
this->freePlaces = freePlaces;
this->placeArray = placePtr;
}
friend ostream& operator<< (ostream& output, Car& car);
//Прописуємо get i set
int getNumber() {
return this->number;
}
void setNumber(int numb) {
this->number = numb;
}
int getMaxPlacesCount() {
return this->maxPlacesCount;
}
void setMaxPlacesCount(int numb) {
this->maxPlacesCount = numb;
}
vector<bool> getPlaceArray() {
return this->placeArray;
}
void setNumber(vector<bool> numb) {
this->placeArray = numb;
}
};
Код Car.cpp:
#include "Car.h"
#include <Windows.h>
using namespace std;
enum ConsoleColor
{
Black = 0,
Blue = 1,
Green = 2,
Cyan = 3,
Red = 4,
Magenta = 5,
Brown = 6,
LightGray = 7,
DarkGray = 8,
LightBlue = 9,
LightGreen = 10,
LightCyan = 11,
LightRed = 12,
LightMagenta = 13,
Yellow = 14,
White = 15
};
void SetColor(int text, int background)
{
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hStdOut, (WORD)((background << 4) | text));
}
//Перевантажуємо оператор "<<" для виведення даних про вагон
ostream & operator<<(ostream & output, Car & car) {
output << " Вагон №" << car.getNumber() << endl;
int curr = 0;
output << " ";
for (int i = 0; i < car.getMaxPlacesCount(); i++) {
if (car.getPlaceArray()[i] == true) {
SetColor(2, 0);
output << ++curr << " ";
}
else {
SetColor(4, 0);
output << ++curr << " ";
}
}
SetColor(15, 0);
return output;
}
Код main.cpp:
#include "Train.h"
#include <Windows.h>
void main() {
setlocale(0, "ukr");
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hStdOut, (WORD)((0 << 4) | 15));
// Оголошуємо вагон 1
vector<bool> carPlaces1{ 0, 0, 0, 1, 1, 0, 1, 0, 1 };
Car Car1(1, carPlaces1);
//Оголошуємо вагон 2
vector<bool> carPlaces2{ 0, 1, 0, 0, 1, 0, 1, 0, 0 };
Car Car2(2, carPlaces2);
//Оголошуємо вагон 3
vector<bool> carPlaces3{ 1, 1, 0, 1, 1, 1, 1, 0, 1 };
Car Car3(3, carPlaces3);
//Оголошуємо вагон другого поїзда
vector<bool> carPlaces4{ 1, 0, 1, 1, 0, 1, 0, 0, 1 };
Car Car4(1, carPlaces4);
//Оголошую вектор з вагонiв для першого поїзду
vector<Car> Cars1{ Car1, Car2, Car3 };
//Оголошую вектор з вагона для другого поїзду
vector<Car> Cars2{ Car4 };
Train Train2("Lutsk-Odesa", Cars2);
Train Train1("Lviv-Odesa", Cars1);
cout << "Виводимо вiльнi мiсця першого поїзду" << endl;
cout << Train1 << endl;
cout << "Виводимо вiльнi мiсця другого поїзду" << endl;
cout << Train2 << endl;
cout << "Додаємо вагони поїздiв" << endl;
//Додаємо поїзди
Train train = Train1 + Train2;
cout << train << endl;
}
Результат виконання програми:
/
Рис.1 - Ескіз екрана виконаної програми(Вивід інформації про поїзди на екран)
Висновок: виконуючи п’яту лабораторну роботу я отримав знання та навички у вмінні перевантажувати оператори. Ці навички стануть мені в пригоді при роботі над курсовою роботою.