Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
+ООП_Навч_посібник.doc
Скачиваний:
7
Добавлен:
01.07.2025
Размер:
6.58 Mб
Скачать

14.2.3. Перевантаження операторів відношення та логічних операторів

Оператори відношення (наприклад, "==", "<", ">", "=<", "<=", ">=") і логічні оператори (наприклад, "&&" або " ") також можна перевантажувати, причому механізм їх реалізації не представляє жодних проблем. Як правило, перевантажена операторна функція відношення повертає об'єкт класу, для якого вона перевантажується. А перевантажений оператор відношення або логічний оператор повертає одне з двох можливих значень: true або false. Це відповідає звичайному застосуванню цих операторів і дає змогу використовувати їх в умовних виразах.

Розглянемо приклад перевантаження операторної функції дорівнює "==" для вже розглянутого вище класу kooClass:

// Перевантаження операторної функції дорівнює "=="

bool kooClass::operator==(kooClass op2)

{

if((x == op2.x) && (y == op2.y) && (z == op2.z))

return true;

else

return false;

}

Якщо вважати, що операторна функція operator==() вже реалізована, то такий код програми є абсолютно коректним:

kooClass A_ob, B_ob;

//...

if(A_ob == B_ob) cout << "A_ob = B_ob\n";

else cout << "A_ob не дорівнює B_ob\n";

Оскільки операторна функція operator==() повертає результат типу bool, то її можна використовувати для керування настановою if. Як вправу рекомендуємо самостійно реалізувати і інші оператори відношення та логічні оператори для класу kooClass.

14.3. Особливості реалізації оператора присвоєння

У попередньому розділі ми розглянули потенційну проблему, пов'язану з передачею об'єктів функціям і поверненням об'єктів з функцій. У обох випадках проблема була викликана використанням конструктора за замовчуванням, який створює побітову копію об'єкта. Згадаймо, що вирішення цього питання полягає у створенні власного конструктора копії, який точно визначає, як повинна бути створена копія об'єкта.

Подібна проблема може виникати і під час присвоєння одного об'єкта іншому. За замовчуванням об'єкт, який знаходиться з лівого боку від оператора присвоєння, отримує побітову копію об'єкта, що знаходиться справа. До негативних наслідків це може призвести у випадках, коли під час створення об'єкт виділяє певний ресурс (наприклад, пам'ять), а потім змінює його або звільняє. Якщо після виконання операції присвоєння об'єкт змінює або звільняє цей ресурс, то другий об'єкт також змінюється, оскільки він все ще використовує його. Вирішення цього питання полягає в перевантаженні оператора присвоєння.

Щоб до кінця зрозуміти суть описаної проблеми, розглянемо таку (некоректну) програму.

Код програми 14.7. Демонстрація виявлення помилки, що генерується під час повернення об'єкта з функції

#include <iostream> // Для потокового введення-виведення

#include <cstring> // Для роботи з рядковими типами даних

#include <cstdlib> // Для використання бібліотечних функцій

using namespace std; // Використання стандартного простору імен

class strClass { // Оголошення класового типу

char *s;

public:

strClass() { s = 0;}

strClass(const strClass &obj); // Оголошення конструктора копії

~strClass() {if(s) delete[]s; cout << "Звільнення s-пам'яті.\n";}

void showRez(char *s) { cout << "s= " << s << "\n";}

void setStr(char *str);

};

// Визначення конструктора копії.

strClass::strClass(const strClass &obj)

{

s = new char[strlen(obj.s)+1];

strcpy(s, obj.s);

}

// Завантаження рядка.

void strClass::setStr(char *str)

{

s = new char[strlen(str)+1];

strcpy(s, str);

}

// Ця функція повертає об'єкт типу strClass.

strClass input()

{

char strMas[80];

strClass s_ob;

cout << "Введіть рядок: "; cin >> strMas;

s_ob.setStr(strMas);

return s_ob;

}

int main()

{

strClass S_ob; // Створення об'єкта класу

//Присвоюємо об'єкт, повернутий функцією input(), об'єкту S_ob.

S_ob = input(); // Ця настанова генерує помилку!!!!

S_ob.showRez();

getch(); return 0;

}

Можливі результати виконання цієї програми мають такий вигляд:

Введіть рядок: Привіт

Звільнення s-пам'яті.

Звільнення s-пам'яті.

s= тут "сміття"

Звільнення s-пам'яті.

Залежно від використовуваного компілятора, Ви можете побачити "сміття" або ні. Програма може також згенерувати помилку тривалості виконання. У будь-якому випадку помилки не минути. І ось чому.

У цій програмі конструктор копії коректно обробляє повернення об'єкта функцією input(). Згадаймо, що у разі, коли функція повертає об'єкт, для зберігання значення, що повертається нею, створюється тимчасовий об'єкт. Оскільки під час створення об'єкта-копії конструктор копії виділяє нову область пам'яті, то член s початкового об'єкта і член s об'єкта-копії вказуватимуть на різні області пам'яті, які, як наслідок, не стануть псувати один одного.

Проте помилки не минути, якщо об'єкт, який повертається функцією, присвоюється об'єкту S_ob, оскільки у процесі виконання операції присвоєння за замовчуванням створюється побітова його копія. У цьому випадку тимчасовий об'єкт, який повертається функцією input(), копіюється в об'єкт S_ob. Як наслідок, член obj.s вказує на ту ж саму область пам'яті, що і член s тимчасового об'єкта. Але після виконання операції присвоєння в процесі руйнування тимчасового об'єкта ця пам'ять звільняється. Отже, член obj.s тепер вказуватиме на вже звільнену пам'ять! Понад це, пам'ять, яка адресується членом obj.s, повинна бути звільнена і після завершення роботи програми, тобто удруге. Щоб запобігти цьому, необхідно перевантажити оператор присвоєння так, щоб об'єкт, який розташовується зліва від оператора присвоєння, виділяв власну область пам'яті.

Реалізацію цього рішення покажемо у такій відкоректованій програмі.

Код програми 14.8. Демонстрація коректної роботи програми

#include <vcl>

#include <iostream> // Для потокового введення-виведення

#include <cctype> // Для роботи з символьними аргументами

#include <cstring> // Для роботи з рядковими типами даних

#include <conio> // Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

class strClass { // Оголошення класового типу

char *s;

public:

strClass(); // Оголошення звичайного конструктора

strClass(const strClass &obj); // Оголошення конструктора копії

~strClass() { if(s) delete[]s; cout << "Звільнення s-пам'яті.\n";}

void showRez(char *s) { cout << "s= " << s << "\n";}

void setStr(char *str);

strClass operator=(const strClass &obj); // Перевантажений

// оператор присвоєння

};

// Визначення звичайного конструктора.

strClass::strClass()

{

s = new char ('\0'); // Член s вказує на NULL-рядок.

}

// Визначення конструктора копії.

strClass::strClass(const strClass &obj)

{

s = new char[strlen(obj.s)+1];

strcpy(s, obj.s);

}

// Завантаження рядка.

void strClass::setStr(char *str)

{

s = new char[strlen(str)+1];

strcpy(s, str);

}

// Перевантаження оператора присвоєння "=".

strClass strClass::operator=(const strClass &obj)

{

/* Якщо виділена область пам'яті має недостатній

розмір, виділяється нова область пам'яті. */

if(strlen(obj.s) > strlen(s)) {

delete[]s;

s = new char[strlen(obj.s)+1];

}

strcpy(s, obj.s);

// Повернення модифіков. об'єкта операнда, адресованого покажчиком

return *this;

}

// Ця функція повертає об'єкт типу strClass.

strClass input()

{

char strMas[80];

strClass s_ob;

cout << "Введіть рядок: "; cin >> strMas;

s_ob.setStr(strMas);

return s_ob;

}

int main()

{

strClass S_ob; // Створення об'єкта класу

// Присвоюємо об'єкт, повернутий функцією input(), об'єкту S_ob

S_ob = input(); // Тепер тут все гаразд!

S_ob.showRez();

getch(); return 0;

}

Ця програма тепер відображає такі результати (у припущенні, що на пропозицію "Введіть рядок: " Ви введете "Привіт").

Введіть рядок: Привіт

Звільнення s-пам'яті.

Звільнення s-пам'яті.

Звільнення s-пам'яті.

Привіт

Звільнення s-пам'яті.

Як бачимо, ця програма тепер працює коректно. Спробуйте детально проаналізувати програму і зрозуміти, чому виводиться кожне з повідомлень "Звільнення s-пам'яті.".1