Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

Приклад перевантаження оператора



Припустимо, CMoney є клас, призначений для зберігання й обробки грошових сум:

class CMoney

{

private:

long Grivnas;

int Copecks;

public:

CMoney ()

{

Grivnas = Copecks = 0;

}

CMoney (long Grn, int Cop)

{

SetAmount (Grn, Cop);

}

void GetAmount (long *PGrn, int *PCop)

{

*PGrn = Grivnas;

*PCop = Copecks;

}

void PrintAmount ()

{

cout.fill ('0');

cout.width (1);

cout << 'Г' << Grivnas << '.';

cout.width (2);

cout << Copecks << '\n';

}

void SetAmount (long Grn, int Cop)

{

// перевірка суми копійок, яка перевищує 100

Grivnas = Grn + Cop / 100;

Copecks = Cop % 100;

}

};

В класі CMoney грошові суми зберігаються у вигляді цілочисельних величин, призначених для подальшої обробки з використанням швидкодіючих цілочисельних операцій.

Клас містить конструктор за умовчанням для установки в 0 початкових значень змінних, що містять кількості гривень і копійок, і конструктор для присвоювання цим змінним конкретних значень.

Передбачені окрема функція-член SetAmount() для завдання величини грошової суми, а також функції-члени GetAmount() і PrintAmount() для підрахунку суми та її друку, відповідно.

Можна було б зберігати значення суми в одній змінній типу long у вигляді загальної суми в копійках, що потребувало би обробки великих значень у копійках. Проте краще для зберігання величини, що позначає кількість гривень, використовувати змінну long, а для копійок — змінну типу int. В результаті можна буде оперувати кількістю гривень, що досягає максимальної величини типу long, що в системі програмування Visual C++ складає 2147483647.

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

Для перевантаження оператора визначається функціяз ім'ям operator, за яким як суфікс слідує символ оператора.

Наприклад, можна перевантажити оператор "+", додавши до оголошення класу CMoney наступну функцію-член|:

class CMoney

{

// інші оголошення

public:

CMoney operator+(CMoney Mon)

{

CMoney Temp(Grivnas + Mon.Grivnas, Copecks + Mon.Copecks);

return Temp;

}

// інші оголошення

};

Функція-оператор "+" визначена як public, щоб її можна було використовувати в інших частинах програми.

Якщо функція-оператор "+" визначена для класу, операцію складання грошових значень можна реалізувати таким чином:

CMoney Amountl (12, 95);

CMoney Amount2 (4, 38);

CMoney Total;

Total = Amountl + Amount2;

Компілятор мови C++ інтерпретує вираз:

Amountl + Amount2

як:

Amountl.operator+(Amount2)

Функція operator+() створює тимчасовий об'єкт Temp класу CMoney, що містить розмір грошової суми, одержаної в результаті складання двох об'єктів. Потім вона повертає тимчасовий об'єкт. Наведений вище фрагмент програми присвоює об'єкт, що повертається функцією operator+(), об'єкту Total класу CMoney. Такий спосіб присвоювання робиться можливим завдяки поелементному копіюванню компілятором змінних-членів об'єкту одного класу в іншій об’єкт цього ж класу.

Подібно тому, як це робиться для стандартної операції складання, вираз, що містить більше одного перевантаженого оператора "+", обчислюється зліва направо. Наприклад, наступна програма використовує перевантажений оператор для складання величин, що зберігаються в трьох об'єктах класу CMoney:

void main ()

{

CMoney Advertising (235, 42);

CMoney Rent (823, 68);

CMoney Entertainment (1024, 32);

CMoney Overhead;

Overhead = Advertising + Rent + Entertainment;

Overhead.PrintAmount();

}

Реалізацію функції operator+() можна спростити, замінивши локальний тимчасовий об'єкт класу CMoney неявним тимчасовим об'єктом:

CMoney operator+(CMoney Mon)

{

return CMoney (Grivnas + Mon.Grivnas, Copecks + Mon.Copecks);

}

При виклику конструктора класу в інструкції return компілятор створює тимчасовий об'єкт класу, після чого функціяoperator+() безпосередньо повертає вміст цього тимчасового об'єкту.

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

Наступний фрагмент програми є остаточною версією визначення функції operator+():

CMoney operator+(const CMoney &Mon)

{

return CMoney(Grivnas + Mon.Grivnas, Copecks + Mon.Copecks);

}

Використання специфікатора const при оголошенні аргументу - гарантія того, що функціяне змінить значення цього аргументу.

7.4.2 Визначення перевантажених функцій-операторів

Подібно іншим функціям мови C++, функції-оператори можуть бути також перевантажені. При цьому, як правило, є декілька комбінацій аргументів для виклику тих або інших перевантажених операторів.

Наприклад, за допомогою перевантаженого оператора "+" можна складати об'єкт класу CMoney із числами типу int або long, що позначають ціле число гривень. Для цього в клас CMoney потрібно включити наступну функцію:

CMoney operator+(long Grn)

{

return CMoney (Grivnas + Grn, Copecks);

}

Додавання цієї функції в оголошення класу дозволить використовувати оператор "+" таким чином:

CMoney Advertising (235, 42);

//...

Advertising = Advertising + 100;

Компілятор інтерпретуватиме вираз Advertising + 100 як Advertising.operator+(100) і, отже, викликатиме тільки що визначену версію функції operator+().

Відмітимо, що визначена першою функція-оператор "+" має на увазі, що обидва операнди функції є об'єктами класу CMoney.

7.4.3 Дружні функції і класи

У інструкції останнього прикладу:

Advertising = Advertising + 100;

цілочисельну константу не можна поставити першою, оскільки компілятор інтерпретуватиме вираз 100 + Advertising як:

100.operator+(Advertising),

що є безглуздим.

Щоб обійти це обмеження, можна написати функцію-оператор, перший аргумент якої має тип long і яка не є членом класу:

// визначається глобально

CMoney operator+ (long Grn, const CMoney &Mon)

{

return CMoney(Grn + Mon.Grivnas, Mon.Copecks);

}

Але при компіляції цієї функції виникнуть проблеми, оскільки вона не є функцією-членом класу CMoney і не має доступу до закритих змінних цього класу, а саме до Grivnas і Copecks.

Для отримання такого доступу функцію потрібно зробити дружньою класу CMoney, оголосивши її всередині оголошення CMoney з використанням специфікатора friend:

class CMoney

{

// інші оголошення

friend CMoney operator+ (long Grn, const CMoney &Mon);

// інші оголошення

};

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

Коли така дружня функція-оператор визначена, операцію складання можна застосувати таким чином:

CMoney Advertising (235, 42);

//...

Advertising = 100 + Advertising;

Тепер компілятор проінтерпретує вираз:

100 + Advertising

як:

operator+(100, Advertising)

і, отже, викличе дружню версію функції-оператора.

Можна було б зробити дружніми класу і перші два варіанти функції operator+(), не визначаючи їх як функції-члени класу, хоча особливої переваги це не дає:

friend CMoney operator + (const CMoney &Monl,

const CMoney &Mon2);

friend CMoney operator + (const CMoney SMon, long Grn);

В оголошенні класу також можна оголосити дружнім інший клас, наприклад:

class A

{

// …

friend class FriendOfA;

// …

};

Завдяки такому оголошенню будь-яка функція-член класу FriendOfA має доступ до закритих і захищених членів класу А.

7.4.4 Загальні принципи перевантаження операторів

Функція, що перенавантажує оператор і не є методом класу, повинна мати хоча б один аргумент - об'єкт класу. Отже, функцію - оператор не можна використовувати для зміни стандартного сенсу операторів у виразі, що містить тільки дані вбудованих типів.

Можна перевантажити ледь не кожний з існуючих бінарних і унарних операторів мови C++.

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

У табл. 7.2 перелічені оператори мови C++, що допускають перевантаження.

Таблиця 7.2

Перелік операторів мови C++, які можна перевантажувати

+ - * / % ^ & |
~ ! = < > += -= *=
/= %= ^= &= |= << >> <<=
>>= == != <= >= && || ++
-- ->* , -> [] () new delete
new[] delete[]            

У таблиці 7.3 перелічені оператори мови C++, що не допускають перевантаження:

Таблиця 7.3

Перелік операторів мови C++, що не допускають перевантаження

. .* :: ?: sizeof

Приклади перевантаження бінарних операторів детально були розглянуті раніше.

Використовуючи функцію-член| класу без аргументів, можна перевантажити й унарний оператор. Для класу CMoney це виглядатиме наступним чином:

// задається в оголошенні класу CMoney

CMoney operator-() // унарний оператор “-” (негативність)

{

return CMoney(-Grivnas, -Copecks);

}

Замість функції-члена унарний оператор можна перевантажити, використовуючи дружню функцію, яка не належить класу і має єдиний аргумент:

// визначається глобально

CMoney operator- (CMoney &Mon)

{

return CMoney (-Mon.Grivnas, -Mon.Copecks);

}

В самому класі, природно, має бути оголошення цієї функції як дружньої:

friend CMoney operator-(CMoney &Mon);

Наведені приклади ілюструють загальні принципи перевантаження операторів.

Проте, при перевантаженні деяких операторів (наприклад, "++") потрібно дотримуватись спеціальних правил.

7.4.5 Конструктори копіювання і приведення

Розглянемо спеціальні властивості конструкторів, що мають єдиний аргумент. До них відносяться також конструктори з додатковими аргументами, що мають значення за умовчанням, які, отже, можуть бути викликані з єдиним аргументом.

Якщо єдиний (або перший) аргумент конструктора є посиланням на об'єкт того класу, в якому оголошений конструктор, то конструктор називається конструктором копіювання.

Якщо ж аргумент має тип, що відрізняється від класу конструктора, то такий конструктор називається конструктором приведення.

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

Наприклад, якщо клас CTest має конструктор:

CTest(int Parm)

{

// код конструктора

}

то можна створити об'єкт, використовуючи інструкцію:

CTest Test(5);

або еквівалентну по дії інструкцію:

CTest Test = 5;

Використання знаку рівності — альтернативний спосіб передачі єдиного значення конструктору. Оскільки вона реалізується конструктором, то ця операція є операцією ініціалізації, а не присвоювання, тому перевантаження оператора "=" не зачіпає виконання даної операції.

7.4.6 Конструктори копіювання

Конструктор копіювання класу — це конструктор з єдиним аргументом, тип якого оголошений як посилання на цей же клас.

Наприклад:

class CTest

{

// …

public:

CTest(const CTest &Test)

{

// інструкції використовують члени вже наявного об'єкту Test

// класу CTest для ініціалізації нового об'єкту класу CTest

}

// …

};

Якщо конструктор копіювання класу не оголошений, то компілятор генерує його неявно.

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

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

Наприклад, навіть якщо клас CMoney, розглянутий раніше, не містить конструктор копіювання, то наступна ініціалізація все одно буде коректною:

CMoney Monl (95, 34);

CMoney Mon2 (Monl);

CMoney Mon3 = Monl;

Ініціалізація об'єкту Mon2, як і Mon3, викликає конструктор копіювання, що згенерований компілятором. В результаті даної ініціалізації обидва об'єкти Mon2 і Mon3 міститимуть ті ж значення, що й Monl (тобто змінна Grivnas буде рівна 95, а Copecks — 34).

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

Наприклад, клас CMessage, розглянутий вище в цьому розділі, не можна ініціалізувати, використовуючи просте копіювання членів класу, оскільки він містить змінні, що є вказівниками на блоки пам'яті. Для даного класу можна додати наступний конструктор копіювання:

class CMessage

{

// …

public:

CMessage (const CMessage &Message)

{

Buffer = new char [strlen (Message.Buffer) + 1];

strcpy (Buffer, Message.Buffer);

}

// …

};

Такий конструктор копіювання дозволяє ініціалізувати об'єкти, як це показано в наступному фрагменті програми:

CMessage Messagel;

Messagel.Set ("hello");

CMessage Message2(Messagel); // використовується конструктор

// копіювання

CMessage Message3 = Messagel;// використовується той же конструктор

// копіювання

Крім операцій копіювання компілятор автоматично викликає конструктор копіювання класу в двох інших випадках:

- при передачі об'єкту класу як аргументу функції;

- при поверненні функцією об'єкту класу.

Як приклад розглянемо раніше визначену функцію-оператор:

CMoney operator+(CMoney Mon)

{

return CMoney(Grivnas + Mon.Grivnas, Copecks + Mon.Copecks);

}

Аргумент Mon є об'єктом класу CMoney. При кожному виклику функції він повинен створюватися і ініціалізуватися за допомогою об'єкту, що передається у функцію. Для ініціалізації аргументу компілятор викликає конструктор копіювання, визначений явно або згенерований компілятором.

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

Оскільки тип значення, що повертається функцією, є об'єктом класу CMoney, то при виклику функції компілятор генерує тимчасовий об'єкт класу CMoney і використовує значення виразу, заданого в інструкції return, для ініціалізації цього об'єкту. Для виконання цієї ініціалізації компілятор також викликає конструктор копіювання.

Непродуктивні витрати при виклику конструктора копіювання можна виключити передачею і поверненням посилань на об'єкти замість самих об'єктів (якщо це можливо). Функція operator+() класу CMoney, розглянута вище, не може повертати посилання на тимчасовий об'єкт класу CMoney. Передача посилання на об'єкт, який перестає існувати після виходу з функції, є поганим стилем програмування.





Дата публикования: 2014-11-03; Прочитано: 2213 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.02 с)...