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

8.2.5. Поняття про незалежні посилання

Поняття посилання включено у мову програмування C++ в основному для підтримки способу передачі параметрів "за посиланням" і для використання як посилального типу значення, що повертається функцією. Без огляду на це, можна оголосити незалежну змінну посилального типу, яка і називається незалежним посиланням. Проте, заради справедливості, необхідно зазначити, що ці незалежні посилальні змінні використовуються досить рідко, оскільки вони можуть "збити з пантелику" Вашу програму. Зробивши (для очищення совісті) ці зауваження, ми все ж таки можемо приділити незалежним посиланням певну увагу.

Незалежне посилання – це просто ще одна назва для змінних дещо іншого типу.

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

Код програми 8.8. Демонстрація механізму використання незалежного посилання

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

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

int main()

{

int jzm, kzm;

int &izm = jzm; // незалежне посилання

jzm = 10;

cout << jzm << " " << izm; // Виводиться: 10 10

kzm = 121;

izm = kzm; // Копіює в змінну jzm значення змінної kzm,

// а не адресу змінної kzm.

cout << "\n" << jzm; // Виводиться значення: 121

getch(); return 0;

}

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

10 10

121

Адреса, яку містить посилальна змінна, є фіксованою і її не можна змінити. Отже, у процесі виконання настанови izm = kzm в змінну jzm (що адресується посиланням izm) копіюється значення змінної kzm, а не її адреса. Як ще один приклад зазначимо, що після виконання настанови izm++ посилальна змінна izm не стане містити нову адресу, як це можна було б припустити. У цьому випадку на 1 збільшиться вміст змінної jzm.

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

8.2.6. Врахування обмежень під час використання посилань

На застосування посилальних змінних накладаються такі обмеження:

  • не можна посилатися на посилальну змінну;

  • не можна створювати масиви посилань;

  • не можна створювати покажчик на посилання, тобто не можна до посилання застосовувати оператор "&";

  • посилання не дозволено використовувати для бітових полів структур1.

8.2.7. Перевантаження функцій

У цьому розділі ми дізнаємося про одну з найдивовижніших можливостей мови програмування C++ – перевантаження функцій. У мові C++ декілька функцій можуть мати однакові імена, але за умови, що їх параметри будуть різними. Таку обставину називають перевантаженням функцій (function overloading), а імена функцій, які в ній задіяно, перевантаженими (overloaded) функціями. Перевантаження функцій – один із способів реалізації поліморфізму у мові програмування C++.

Перевантаження функцій – це механізм, який дає змогу двом спорідненим функціям мати однакові імена.

Розглянемо простий приклад перевантаження функцій.

Код програми 8.9. Демонстрація "триразового" перевантаження функції fun()

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

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

void fun(int izm); // Один цілочисельний параметр

void fun(int izm, int jzm); // Два цілочисельні параметри

void fun(double kzm); // Один параметр типу double

int main()

{

fun(10); // Виклик функції fun(int)

fun(10, 20); // Виклик функції fun(int, int)

fun(12.23); // Виклик функції fun(double)

getch(); return 0;

}

void fun(int izm)

{

cout << "У функції fun(int) індекс izm дорівнює " << izm << "\n";

}

void fun(int izm, int jzm)

{

cout << "У функції fun(int, int) індекс izm дорівнює " << izm;

cout << ", jzm дорівнює " << jzm << "\n";

}

void fun(double kzm)

{

cout << "У функції fun(double) індекс kzm дорівнює " << kzm << "\n";

}

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

У функції fun(int) індекс izm дорівнює 10

У функції fun(int, int) індекс izm дорівнює 10, jzm дорівнює 20

У функції fun(double) індекс kzm дорівнює 12.23

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

Для визначення того, яку версію перевантаженої функції викликати, компілятор використовує тип і/або кількість аргументів. Таким чином, перевантажені функції повинні відрізнятися типами і/або кількістю параметрів. Хоча перевантажені методи можуть відрізнятися і типами значень, що повертаються, цього виду інформації недостатньо для мови C++, щоб в усіх випадках компілятор міг вирішити, яку саме функцію потрібно викликати.

Щоб краще зрозуміти виграш від перевантаження функцій, розглянемо три функції із стандартної бібліотеки: abs(), labs() і fabs(). Вони були вперше визначені у мові С, а потім заради сумісності включено у стандарт мови C++. Функція abs() повертає абсолютне значення (модуль) цілого числа, функція labs() повертає модуль довгого цілочисельного значення (типу long), а fabs() – модуль значення з плинною крапкою (типу double). Оскільки мова C не підтримує перевантаження функцій, то кожна функція повинна мати власне ім'я, хоча всі три функції виконують, по суті, одну і ту саму дію. Це робить ситуацію складнішою, ніж вона є насправді. Іншими словами, при одних і тих самих діях програмісту необхідно пам'ятати імена всіх трьох (у цьому випадку) функцій замість одного. Але у мові програмування C++, як це показано в наведеному нижче прикладі, можна використовувати тільки одне ім'я для всіх трьох функцій.

Код програми 8.10. Демонстрація створення функцій myAbs() – перевантаженої версії функції abs()

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

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

// Функція myAbs() перевантажується трьома способами

int myAbs(int izm);

double myAbs(double dzm);

long myAbs(long lzm);

int main()

{

cout << myAbs(-10) << "\n";

cout << myAbs(-11.0) << "\n";

cout << myAbs(-9L) << "\n";

getch(); return 0;

}

int myAbs(int izm)

{

cout << "Використання int-функції myAbs(): ";

if(izm<0) return -izm;

else return izm;

}

double myAbs(double dzm)

{

cout << "Використання double-функції myAbs(): ";

if(dzm<0.0) return -dzm;

else return dzm;

}

long myAbs(long lzm)

{

cout << "Використання long-функції myAbs(): ";

if(lzm<0) return -lzm;

else return lzm;

}

Результати виконання цієї програми є такими:

Використання int-функції myAbs(): 10

Використання double-функції myAbs(): 11

Використання long-функції myAbs(): 9

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

Кожна версія перевантаженої функції може виконувати будь-які дії. Іншими словами, не існує правила, яке б зобов'язувало програміста пов'язувати перевантажені функції загальними діями. Проте з погляду стилістики перевантаження функцій все-таки передбачає певну "спорідненість" його версій. Таким чином, незважаючи на те, що одне і те саме ім'я функції можна використовувати для перевантаження не взаємопов'язаних між собою загальними діями функцій, цього робити не варто. Наприклад, у принципі можна використовувати ім'я sqr для створення функції, яка повертає квадрат цілого числа, і функції, яка повертає значення квадратного кореня з дійсного числа (типу double). Але, оскільки ці операції фундаментально різні між собою, застосування механізму перевантаження методів у цьому випадку зводить нанівець його первинну мету1. На практиці перевантажувати функції має сенс тільки для тісно пов'язаних операцій.