Програмування С, С++теорія та практика (частина 2)
2.8 Перевантаження операцій
Ключове слово орегаґог використовується для того, аби визначити нову, перевантажену дію конкретного оператора мови. Як і у випадку з перевантаженими функціями, компілятор відрізнятиме різні функції за контекстом звертання числом і типом параметрів та операндів. Перевантаження операторів не вносить нічого нового у мові, з чим би ми не зустрічалися раніше, якщо лише згадати перевантаження функцій (див. розділ 2.2.5 "Перевантаження функцій"). Це дає можливість використовувати об’єкти у виразах замість того, щоб передавати їх як параметри у функції. Кожному оператору мова Сі++ ставить у відповідність ім’я функції, що складається з ключового слова орегаґог, власне оператору та аргументів відповідних типів:
<тип повертання> орегаЬг<символ оператору> (<вхідні параметри>);
Приміром, оголошення функції орегаґог+ , що приймає два аргументи типу Т та повертає значення у вигляді сумування двох значень матиме наступний вигляд:
Т орега£ог+(Т а, Т а);
Семантика такого запису повністю аналогічна функції Т айй (Т а, Т Ь), яка, можливо, і більш звична для сприйняття, проте спосіб перевантаження оператора має певну вигоду. Як перевантажений оператор, функція орегаґог+() може викликатися у виразах скрізь, де використовується знак + та мають місце представники класу Т:
Т а1, а2;
а1+а2; // аналогічно орегагог + (а1,а2); або
агігі(а1,а2);
Перевантаження операцій підпорядковується наступним правилам:
• при перевантаженні зберігаються кількість аргументів, пріоритети операцій та правила асоціації, що використовуються у стандартних типах даних;
• для стандартних типів даних операції не підлягають перевизначенню;
• перевантажена функція-оператор не може мати параметрів по замовчуванню, не успадковується та не може бути визначеною як ііаііс.
• функція-оператор може бути визначена трьома способами - метод класу, дружня функція або звичайна функція. В останніх двох випадках вона повинна приймати хоча б один аргумент, що має тип класу, покажчика або посилання на клас.
Як перший приклад, що використовує перевантажені зовнішні Угіеп^-функції, розглянемо перевантаження бінарних операцій "+" та "-":
Приклад 1.
// клас приймає рядок як константу, перетворюючи його в // еквівалент типу Іопд #іпс1ийе <іоз^геат.Ь>
#іпс1ийе <з^гіпд.Ь>
#іпс1ийе <5'Ьгі1іЬ.Ь> сіазз Т {
рг^а^е:
сЬаг Vа1ие[20]; риЬііс:
Т() ^а1ие[0]=0; }
Т(сопз£ сЬаг *з) ;
1опд де^а1ие ^оій.)
{
ге^игп а£о1^а1ие) ;
}
// функції оголошуються як друзі, отже мають // доступ до усіх членів об'єкту Т £гіепй 1опд орега^ог +(Т а, Т Ь);
£гіепй 1опд орега^ог -(Т а, Т Ь);
};
Т::Т(сопз£ сЬаг *з)
{
з^гпсру(Vа1ие,з,11);
Vа1ие[11]=0;
1опд орега£ог+( Т а, Т Ь)
{
ге'Ьигп (а£о1 (а.Vа1ие) +а£о1 (Ь.Vа1ие) ) ;
}
1опд орега'Ьог- ( Т а, Т Ь)
{
ге'Ьигп (а£о1 (а.Vа1ие) -а£о1 (Ь^а1ие) ) ;
}
таіп () {
Т а="23";
// ініціалізація об'єктів символьними рядками Т Ь="22";
сои^<< "Уа1ие о£ а="<< а.де^а1ие(); сои^<< "\пУа1ие о£ Ь="<< Ь.де^а1ие(); сои^<< "\п а+Ь=="<<(а+Ь);
// використання перевантажених операторів сои'Ь<< "\п а-Ь=="<<(а-Ь)<<"\п"; ге'Ьигп 0;
}
Таким чином можливе вільне оперування значеннями за допомогою звичайних операторів (+ та -), не застосовуючи при цьому операції перетворення до рядків, якщо тільки вони будуть вищевказаного типу Т. Перевантажені функції-оператори були зовнішніми, а тому обов'язково визначалися з ключовим словом /гіепсі, аби мати доступ до елементів-даних з протокольної частини класу.
Перевантажені функції-операції можуть бути і членами класу, причому обов'язково нестатичними (згадаємо, що такі функції мають прихований аргумент ґкіз). За рахунок цього і з'являється відмінність в реалізації такої функції перевантаження на відміну від подібної зовнішньої. Саме про це і йтиметься нижче. Розглянемо клас Сигг, що використовується для представлення грошової форми національної валюти:
Приклад 2.
с1азз Сигг{ рго'ЬесЬей:
ипзідпей іп£ дгіупуа; // значення у гривнях ипзідпей іп£ сор; // значення у копійках риЬ1іс:
Сигг(ипзідпей іп£ й, ипзідпей іп£ с)
{
дгіупуа=гі.; сор=с;
мЬі1е(сор>=100){
// приведення до коректного грошового вигляду
дгіупуа++; сор-=100;
}
}
Сигг орегагог +( Сигг& з2);
};
Звичайним є бажання застосувати операції додавання екземплярів такого класу, коли результатом буде також змінна типу Сигг. Перевантаження оператору додавання суттєво покращить зовнішній значень у грошовому виразі:
// визначення класової функції орегаЬог +
Сигг Сигг::орегаЬог +( Сигг& з2)
{
//оператор + додає "поточний" екземпляр до з2, зберігаючи // результат в новій змінній сор=сор+з2.сор; дгіупуа=дгіупуа+з2 . дгіупуа;
Сигг Vа1 (гі,с); ге'Ьигп Vа1;
}
Для кращого порівняння наведемо паралельний виклад аналогічної зовнішньої Уг/еий-функції, що не є членом класу, який теж міг бути застосованим у даному випадку:
// додає з1 до з2, зберігаючи результат в новій змінній £гіепгі Сигг орегагог +(Сигг& з1, Сигг& з2)
{
іп£ с=з1.сор+з2.сор;
іп£ Й=з1.дгіупуа+з2.дгіупуа;
Сигг Vа1 (Й,с); ге'Ьигп Vа1;
}
Функції практично ідентичні, за винятком хіба що кількості параметрів. Оператор не змінює значення жодного із своїх аргументів, створюючи новий екземпляр та повертаючи його значення. Але якщо у зовнішній версії складаються екземпляри зі та з2, то у випадку функції-елемента класу першим виступатиме "поточний" екземпляр, для якого викликатиметься функція (саме той, на який і посилається покажчик ґкіз), а вже другим йтиме з2. На відміну від реалізації використання таких функцій для обох випадків не відрізнятиметься:
Сигг зит1(5,50);
Сигг зит2(7,55);
Сигг зит(0,0);
8ит=зит1+зит2 ;
Отже, у функції-елементі, що реалізує перевантажений оператор, завжди на один аргумент менше, аніж в аналогічної зовнішньої функції, оскільки лівий аргумент у такому випадку завжди передається неявно. Ця відмінність явно проглядається і у перевантаженні унарних операцій:
• як дружніх функцій:
£гіепй 1опд орега^ог-(Та)
{
ге^игп -а^о^а^а^е);
}
• як функцій-елементів:
1опд Т: : орега^ог - ^оігі.)
{
ге^игп -а£о1(Vа1ие);
} // або -а'Ьо1('ЬЬіз-^а1ие);
Зауваження щодо перевантаження операцій.
1. Існують обмеження на перевантаження: не підлягають цій процедурі селектор елемента структури (.), оператор доступу до елементу за покажчиком (*), операція дозволу видимості (::), символи препроцесору (#, ##) та шео/(). Неможливим є введення власних операторів та зміна їх пріоритетів. Крім того, неможливо перевантажувати оператори для вбудованих типів.
2. Компілятор С++ не розуміє семантики перевантаженого оператору, а отже, не нав’язує ніяких математичних концепцій. Ніщо вам не завадить перевантажити, скажімо, оператор інкременту в якості зменшення аргументу, проте навряд чи в цьому є хоч трохи здорового глузду. Якщо не слідувати традиційному осмисленню обчислень, результати роботи вашої програми для стороннього спостерігача можуть бути збентежуючими.
3. Не існує виведення складних операторів з простих: якщо ви перевантажили оператори орегаІог+ та орегаґог=, це зовсім не
означає, що С++ обчислить вираз а+=Ь, оскільки ви не перевантажили орегаґог +=.
4. Перевантаження бінарних операторів не тотожньо відносно перестановки аргументів місцями, тим більше, якщо вони різного типу. Оскільки, скажімо, орегаІог*(гїоиЬ1е, Т&) та орегаІог*(Т&, гїоиЬІе) володіють різними параметрами, їх необхідно
перевизначати окремо. Зручно це зробити, не створюючи новий код у другому операторі, а послатися у ньому на визначений перший оператор, змінивши лише порядок слідування аргументів:
Т орега'Ьог*(йоиЬ1е £, Т &з) {
//реалізація методу
}
Т орега'Ьог*(йоиЬ1е £, Т &з)
{
ге'Ьигп £*з;
}
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
