Sizeof value 11 страница

Координати об'єкта <A>:

x= 1, y= 2, z= 3

Координати об'єкта <B>:

x= 10, y= 10, z= 10

Координати об'єкта <C=A*B>:

x= 10, y= 20, z= 30

Координати об'єкта <C=A*B*C>:

x= 100, y= 400, z= 900

Координати об'єкта <C=B>:

x= 1, y= 2, z= 3

Координати об'єкта <B=A>:

x= 1, y= 2, z= 3

Координати об'єкта <++С>:

x= 2, y= 3, z= 4

Координати об'єкта <С++>:

x= 3, y= 4, z= 5

Координати об'єкта <A=++С>:

x= 4, y= 5, z= 6

Координати об'єкта <С>:

x= 4, y= 5, z= 6

Координати об'єкта <A=C++>:

x= 4, y= 5, z= 6

Координати об'єкта <С>:

x= 5, y= 6, z= 7

Координати об'єкта <-С>:

x= -5, y= -6, z= -7

Як підтверджують останні чотири рядки результатів виконання програми, під час префіксного інкрементування значення об'єкта C_ob збільшується до виконання операції присвоєння об'єкту A_ob, при постфіксному інкрементуванні – після виконання операції присвоєння.

Необхідно пам'ятати! Якщо символ "++" знаходиться перед операндом, то викликається операторна функція operator++(), а якщо після операнда – то операторна функція operator++(int notused). Аналогічний підхід використовується і для перевантаження префіксної та постфіксної форм оператора декремента для будь-якого класу. Для домашньої вправи спробуйте визначити операторну функцію декремента для класу kooClass.

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

14.1.3. Особливості реалізації механізму перевантаження операторів

Дія перевантаженого оператора стосовно класу, для якого він визначається, не обов'язково повинно мати відношення до стандартної дії цього оператора стосовно вбудованих С++-типів. Наприклад, оператори "<<" і ">>", що вживаються до об'єктів cout і cin, мають мало спільного з аналогічними операторами, що застосовуються до значень цілочисельного типу. Але для поліпшення структурованості та читабельності коду програми створюваний програмістом перевантажений оператор повинен за змогою відображати традиційне призначення того або іншого оператора. Наприклад, оператор додавання "+", перевантажений для класу kooClass, концептуально подібний до операції "+", визначеної для цілочисельних типів. Адже навряд чи є логіка у визначенні для класу, наприклад, оператора додавання "+", який за своєю дією більше нагадуватиме оператор ділення "/". Таким чином, основна ідея створення програмістом перевантажених операторів – наділити їх новими (потрібними для нього) можливостями, які, проте, пов'язані з їх первинним призначенням.

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

:: .* ?

Оператор ".*" – це оператор спеціального призначення, який буде розглядатися нижче у цьому навчальному посібнику.

14.1.4. Значення порядку слідування операндів

Перевантажуючи бінарні оператори, потрібно пам'ятати, що у багатьох випадках порядок слідування операндів має значення. Наприклад, вираз А + B комутативний, а вираз А – В – ні[58]. Отже, реалізуючи перевантажені версії не комутативних операторів, потрібно пам'ятати, який операнд знаходиться зліва від символу операції, а який – праворуч від нього. Наприклад, у наведеному нижче коді програми продемонстровано перевантаження оператора ділення для класу kooClass:

// Перевантаження бінарного оператора ділення "/".

kooClass kooClass::operator-(kooClass op2)

{

kooClass tmp; // Створення тимчасового об'єкта

 

tmp.x = x / op2.x;

tmp.y = y / op2.y;

tmp.z = z / op2.z;

 

return tmp; // Повертає модифікований тимчасовий об'єкт

}

Необхідно пам'ятати! Саме лівий операнд викликає операторну функцію. Правий операнд передається в безпосередньому вигляді.

Ось чому для коректного виконання операції віднімання використовується саме такий порядок слідування операндів:

х / op2.х.

14.2. Перевантаження операторів з використанням
функцій-не членів класу

Бінарні операторні функції, які не є членами класу, мають два параметри, а унарні (теж не члени) – один.

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

=, (), [], –>.

14.2.1. Використання функцій-"друзів" класу для перевантаження
бінарних операторів

У наведеному нижче коді програми для перевантаження бінарного оператора додавання "+" замість функції-члена класу використовується функція-"друг" класу.

Код програми 14.4. Демонстрація перевантаження бінарного оператора додавання "+" за допомогою функції-"друга" класу

#include <vcl>

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

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

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

 

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

int x, y, z; // Тривимірні координати

public:

kooClass() {x = y = z = 0;}

kooClass(int izm, int jzm, int kzm) { x = izm; y = jzm; z = kzm;}

friend kooClass operator+(kooClass op1, kooClass op2);

kooClass operator=(kooClass op2); // Операнд ор1 передається неявно.

void showRez(char *s);

};

 

// Тепер це функція-"друг" класу.

// Перевантаження бінарного оператора додавання "+".

kooClass operator+(kooClass op1, kooClass op2)

{

kooClass tmp; // Створення тимчасового об'єкта

 

tmp.x = op1.x + op2.x;

tmp.y = op1.y + op2.y;

tmp.z = op1.z + op2.z;

 

return tmp; // Повертає модифікований тимчасовий об'єкт

}

 

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

kooClass kooClass::operator=(kooClass op2)

{

x = op2.x;

y = op2.y;

z = op2.z;

 

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

return *this;

}

 

// Відображення тривимірних координат x, y, z.

void kooClass::showRez(char *s)

{

cout << "Координати об'єкта <" << s << ">:\n";

cout << "\tx= " << x << ", y= " << y << ", z= " << z << "\n";

}

 

int main()

{

kooClass A_ob(1, 2, 3), B_ob(10, 10, 10), C_ob;

 

A_ob.showRez("A");

B_ob.showRez("B");

 

C_ob = A_ob + B_ob; // Додавання об'єктів A_ob і B_ob

C_ob.showRez("C=A+B");

 

C_ob = A_ob + B_ob + C_ob; // Додавання об'єктів A_ob, B_ob і C_ob

C_ob.showRez("C=A+B+C");

 

C_ob = B_ob = A_ob ; // Множинне присвоєння об'єктів

C_ob.showRez("C=B");

B_ob.showRez("B=A");

 

getch(); return 0;

}

У процесі виконання ця програма відображає на екрані такі результати:

Координати об'єкта <A>:

x= 1, y= 2, z= 3

Координати об'єкта <B>:

x= 10, y= 10, z= 10

Координати об'єкта <C=A+B>:

x= 11, y= 12, z= 13

Координати об'єкта <C=A+B+C>:

x= 22, y= 24, z= 26

Координати об'єкта <C=B>:

x= 1, y= 2, z= 3

Координати об'єкта <B=A>:

x= 1, y= 2, z= 3

Як бачимо, операторній функції operator+() тепер передаються два операнди. Лівий операнд передається параметру op1, а правий – параметру op2.

У багатьох випадках під час перевантаження операторів за допомогою функцій-"друзів" класу немає ніякої переваги порівняно з використанням функцій-членів класу. Проте можлива ситуація (коли потрібно, щоб зліва від бінарного оператора знаходився об'єкт вбудованого типу), у якій функція-"друг" класу виявляється надзвичайно корисною. Щоб зрозуміти це твердження, розглянемо такий випадок. Як уже зазначалося вище, покажчик на об'єкт, який викликає операторну функцію-члена класу, передається за допомогою ключового слова this. Під час використання бінарного оператора функцію викликає об'єкт, який розташований зліва від нього. І це чудово за умови, що лівий об'єкт визначає задану операцію. Наприклад, припустимо, що у нас є певний об'єкт tmp.obj, для якого визначено операцію додавання з цілочисельним значенням, тоді такий запис є цілком допустимим виразом:

tmp.obj + 10; // працюватиме

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

10 + tmp.obj; // не працюватиме

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

Вирішення такої проблеми базується на перевантаженні оператора додавання "+" з використанням двох функцій-"друзів" класу. У цьому випадку операторній функції безпосередньо передаються обидва операнди, і вона викликається подібно до будь-якої іншої перевантаженої функції, тобто на основі типів її аргументів. Одна версія операторної функції operator+() оброблятиме аргументи об'єкт + int-значення, а інша – аргументи int-значення + об'єкт. Перевантаження бінарного оператора додавання "+" (або будь-якого іншого бінарного оператора) з використанням функцій-"друзів" класу дає змогу розташовувати значення вбудованого типу як справа, так і зліва від операції. Реалізацію такого механізму перевантаження операторної функції показано у наведеному нижче коді програми.

Код програми 14.5. Демонстрація механізму перевантаження бінарних операторів множення "*" і ділення "/" з використанням функцій-"друзів" класу

#include <vcl>

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

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

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

 

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

int x, y, z; // Тривимірні координати

public:

kooClass() {x = y = z = 0;}

kooClass(int izm, int jzm, int kzm) { x = izm; y = jzm; z = kzm;}

friend kooClass operator*(kooClass op1, int izm);

friend kooClass operator*(int izm, kooClass op1);

friend kooClass operator/(kooClass op1, int izm);

friend kooClass operator/(int izm, kooClass op1);

void showRez(char *s);

};

 

// Тепер це функція-"друг" класу.

// Перевантаження бінарного оператора множення "*".

kooClass operator*(kooClass op1, int izm)

{

kooClass tmp; // Створення тимчасового об'єкта

 

tmp.x = op1.x * izm;

tmp.y = op1.y * izm;

tmp.z = op1.z * izm;

 

return tmp; // Повертає модифікований тимчасовий об'єкт

}

 

// Тепер це функція-"друг" класу.

// Перевантаження бінарного оператора множення "*".

kooClass operator*(int izm, kooClass op1)

{

kooClass tmp; // Створення тимчасового об'єкта

 

tmp.x = izm * op1.x;

tmp.y = izm * op1.y;

tmp.z = izm * op1.z;

 

return tmp; // Повертає модифікований тимчасовий об'єкт

}

 

// Перевантаження бінарного оператора ділення "/".

kooClass operator/(kooClass op1, int izm)

{

kooClass tmp; // Створення тимчасового об'єкта

 

tmp.x = op1.x / izm;

tmp.y = op1.y / izm;

tmp.z = op1.z / izm;

 

return tmp; // Повертає модифікований тимчасовий об'єкт

}

 

// Перевантаження бінарного оператора ділення "/".

kooClass operator/(int izm, kooClass op1)

{

kooClass tmp; // Створення тимчасового об'єкта

 

tmp.x = izm / op1.x;

tmp.y = izm / op1.y;

tmp.z = izm / op1.z;

 

return tmp; // Повертає модифікований тимчасовий об'єкт

}

 

// Відображення тривимірних координат x, y, z.

void kooClass::showRez(char *s)

{

cout << "Координати об'єкта <" << s << ">:\n";

cout << "\tx= " << x << ", y= " << y << ", z= " << z << "\n";

}

 

int main()

{

kooClass A_ob(1, 2, 3), B_ob(10, 10, 10), C_ob;

int a = 10, b = 5;

 

A_ob.showRez("A");

B_ob.showRez("B");

 

C_ob = A_ob * a; // Множення об'єкта A_ob на число a

C_ob.showRez("C=A*a");

 

C_ob = a * A_ob; // Множення числа a на об'єкт A_ob

C_ob.showRez("C=a*A");

 

C_ob = B_ob / b; // Ділення об'єкта B_ob на число b

C_ob.showRez("C=B*b");

 

C_ob = a / B_ob; // Ділення числа a на об'єкт B_ob

C_ob.showRez("C=a/B");

 

getch(); return 0;

}

У процесі виконання ця програма відображає на екрані такі результати:

Координати об'єкта <A>:

x= 1, y= 2, z= 3

Координати об'єкта <B>:

x= 10, y= 10, z= 10

Координати об'єкта <C=A*a>:

x= 10, y= 20, z= 30

Координати об'єкта <C=a*A>:

x= 10, y= 20, z= 30

Координати об'єкта <C=B/b>:

x= 2, y= 2, z= 2

Координати об'єкта <С=a/B>:

x= 1, y= 1, z= 1

Як бачимо, операторна функція operator*() перевантажується двічі, даючи змогу тим самим передбачити два можливі випадки участі цілого числа і об'єкта типу kooClass в операції додавання. Аналогічно перевантажується двічі операторна функція operator/().

14.2.2. Використання функцій-"друзів" класу для перевантаження унарних операторів

За допомогою функцій-"друзів" класу можна перевантажувати й унарні оператори. Але усвідомлення механізму реалізації такого перевантаження вимагатиме від програміста деяких додаткових зусиль. Спершу подумки повернемося до початкової форми перевантаження унарного оператора інкремента "++", визначеної для класу kooClass і реалізованої у вигляді функції-члена класу. Для зручності аналізу наведемо код цієї операторної функції:

// Перевантаження префіксної форми унарного оператора інкремента "++"

kooClass kooClass::operator++()

{

x++;

y++;

z++;

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

return *this;

}

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

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

// Цей варіант перевантаження операторної функції працювати не буде

kooClass operator++(kooClass op1)

{

op1.x++;

op1.y++;

op1.z++;

return op1;

}

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

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

таким чином, якщо для перевантаження операторів інкремента або декремента використовується функція-"друг" класу, то її префіксна форма приймає один параметр (який і є операндом), а постфіксна форма – два параметри (другим є цілочисельне значення, яке не використовується).

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

Код програми 14.6. Демонстрація механізму використання операторної функції-"друга" класу operator++() для перевантаження префіксної та постфіксної форми операторів інкремента

#include <vcl>

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

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

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

 

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

int x, y, z; // Тривимірні координати

public:

kooClass() {x = y = z = 0;}

kooClass(int izm, int jzm, int kzm) {x = izm; y = jzm; z = kzm;}

friend kooClass operator*(kooClass op1, kooClass op2);

kooClass operator=(kooClass op2);

// Ці функції для перевантаження оператора інкремента "++"

// використовують посилальні параметри.

friend kooClass operator++(kooClass &op1);

friend kooClass operator++(kooClass &op1, int notused);

void showRez(char *s);

};

 

// Тепер це операторна функція-"друг" класу.

kooClass operator*(kooClass op1, kooClass op2)

{

kooClass tmp; // Створення тимчасового об'єкта

 

tmp.x = op1.x * op2.x;

tmp.y = op1.y * op2.y;

tmp.z = op1.z * op2.z;

 

return tmp; // Повертає модифікований тимчасовий об'єкт

}

 

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

kooClass kooClass::operator=(kooClass op2)

{

x = op2.x;

y = op2.y;

z = op2.z;

 

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

return *this;

}

 

/* Перевантаження префіксної форми унарного оператора інкремента "++"

з використанням функції-"друга" класу. Для цього необхідне

використання посилального параметра. */

kooClass operator++(kooClass &op1)

{

op1.x++;

op1.y++;

op1.z++;

 

return op1;

}

 

/* Перевантаження постфіксної форми унарного оператора інкремента "++"

з використанням функції-"друга" класу. Для цього необхідне використання

посилального параметра. */

kooClass operator++(kooClass &op1, int notused)

{

kooClass tmp = op1;

 

op1.x++;

op1.y++;

op1.z++;

 

return tmp; // Повертає модифікований тимчасовий об'єкт

}

 

// Відображення тривимірних координат x, y, z.

void kooClass::showRez(char *s)

{

cout << "Координати об'єкта <" << s << ">:\n";

cout << "\tx= " << x << ", y= " << y << ", z= " << z << "\n";

}

 

int main()

{

kooClass A_ob(1, 2, 3), B_ob(10, 10, 10), C_ob;

 

A_ob.showRez("A");

B_ob.showRez("B");

 

C_ob = A_ob * B_ob; // Множення об'єктів A_ob і B_ob

C_ob.showRez("C=A*B");

 

C_ob = A_ob * B_ob * C_ob; // Множення об'єктів A_ob, B_ob і C_ob

C_ob.showRez("c");

 

C_ob = B_ob = A_ob ; // Множинне присвоєння об'єктів

C_ob.showRez("C=B");

B_ob.showRez("B=A");

 

++C_ob; // Префіксна форма інкремента

C_ob.showRez("++C");

 

C_ob++; // Постфіксна версія інкремента

C_ob.showRez("C++");

 

A_ob = ++C_ob; // Об'єкт A_ob набуває значення об'єкта C_ob

// після інкрементування.

A_ob.showRez("A=++C"); // У цьому випадку об'єкти A_ob і C_ob

C_ob.showRez("C"); // мають однакові значення координат.

 

A_ob = C_ob++; // Об'єкт A_ob набуває значення об'єкта C_ob

// до інкрементування.

A_ob.showRez("A=C++"); // У цьому випадку об'єкти A_ob і C_ob

C_ob.showRez("C"); // мають різні значення координат.

 

getch(); return 0;

}

У процесі виконання ця програма відображає на екрані такі результати:

Координати об'єкта <A>:

x= 1, y= 2, z= 3

Координати об'єкта <B>:

x= 10, y= 10, z= 10

Координати об'єкта <C=A*B>:

x= 10, y= 20, z= 30

Координати об'єкта <C=A*B*C>:

x= 100, y= 400, z= 900

Координати об'єкта <C=B>:

x= 1, y= 2, z= 3

Координати об'єкта <B=A>:

x= 1, y= 2, z= 3

Координати об'єкта <++С>:

x= 2, y= 3, z= 4

Координати об'єкта <С++>:

x= 3, y= 4, z= 5

Координати об'єкта <A=++С>:

x= 4, y= 5, z= 6

Координати об'єкта <С>:

x= 4, y= 5, z= 6

Координати об'єкта <A=C++>:

x= 4, y= 5, z= 6

Координати об'єкта <С>:

x= 5, y= 6, z= 7

Необхідно пам'ятати!Для реалізації механізму перевантаження операторів потрібно використовувати функції-члени класу. Функції-"друзі" класу використовуються у мові програмування C++ в основному для оброблення спеціальних ситуацій.

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

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

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

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

bool kooClass::operator==(kooClass op2)

{

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

return true;