Урок №108. Перевантаження функцій

  Юрій  | 

  Оновл. 28 Сер 2021  | 

 232

На цьому уроці ми розглянемо тему перевантаження функцій в мові C++.

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

Перевантаження функцій — це можливість визначати декілька функцій з одним і тим же ім’ям, але з різними параметрами. Наприклад:

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

Одним із способів вирішення цієї проблеми є визначення двох функцій з різними іменами і параметрами:

Але є краще рішення — перевантаження функції. Ми можемо просто оголосити ще одну функцію subtract(), яка приймає параметри типу double:

Тепер у нас є дві версії функції subtract():

Ви можете подумати, що відбудеться конфлікт імен, але це не так. Компілятор може визначити сам, яку версію subtract() слід викликати на основі аргументів, які використовуються у виклику функції. Якщо параметрами будуть змінні типу int, то мова C++ розуміє, що ми хочемо викликати subtract(int, int). Якщо ж ми надамо два значення типу з плаваючою крапкою, то мова C++ зрозуміє, що ми хочемо викликати subtract(double, double). Фактично, ми можемо визначити стільки перевантажених функцій subtract(), скільки хочемо, до тих пір, поки кожна з них матиме свої (унікальні) параметри.

Відповідно, функцію subtract() можна визначити і з більшою кількістю параметрів:

Хоча тут subtract() має 3 параметри замість 2, це не є помилкою, оскільки ці параметри відрізняються від параметрів інших версій subtract().

Типи повернення в перевантаженні функцій

Зверніть увагу, тип повернення функції НЕ враховується при перевантаженні функції. Припустимо, що ви хочете написати функцію, яка повертає рандомне число, але вам потрібна одна версія, яка повертає значення типу int, і друга — яка повертає значення типу double. У вас може виникнути спокуса зробити наступне:

Компілятор видасть помилку. Ці дві функції мають однакові параметри (точніше, їх відсутність), і другий виклик функції getRandomValue() розглядатиметься як помилкове перевизначення першого виклику. Імена функцій потрібно буде змінити.

Псевдоніми типів в перевантаженні функцій

Оскільки оголошення typedef (псевдоніма типу) не створює новий тип даних, то наступні два оголошення функції print() вважаються ідентичними:

Виклики функцій

Виконання виклику перевантаженої функції призводить до одного з трьох можливих результатів:

   Збіг знайдено. Виклик дозволений для відповідної перевантаженої функції.

   Збіг не знайдено. Аргументи не відповідають будь-якій з перевантажених функцій.

   Знайдено декілька збігів. Аргументи відповідають більше, ніж одній перевантаженій функції.

При компіляції перевантаженої функції, мова C++ виконує наступні кроки для визначення того, яку версію функції слід викликати:

Крок №1: C++ намагається знайти точний збіг. Це той випадок, коли фактичний аргумент точно відповідає типу параметра однієї з перевантажених функцій. Наприклад:

Хоча 0 може технічно відповідати і print(char *) (як нульовий вказівник), але він точно відповідає print(int). Таким чином, print(int) є кращим (точним) збігом.

Крок №2: Якщо точного збігу не знайдено, то C++ намагається знайти збіг шляхом подальшого неявного перетворення типів. На уроці про неявні конвертації ми говорили про те, як певні типи даних можуть автоматично конвертуватися в інші типи даних. Якщо коротко, то:

   char, unsigned char і short конвертуються в int;

   unsigned short може конвертуватися в int або unsigned int (в залежності від розміру int);

   float конвертується в double;

   enum конвертується в int.

Наприклад:

В цьому випадку, оскільки немає print(char), символ b конвертується в тип int, який потім вже відповідає print(int).

Крок №3: Якщо неявне перетворення неможливе, то C++ намагається знайти відповідність за допомогою стандартного перетворення. У стандартному перетворенні:

   будь-який числовий тип відповідатиме будь-якому іншому числового типу, включаючи unsigned (наприклад, int дорівнює float);

   enum відповідає формальному типу числового типу даних (наприклад, enum дорівнює float);

   нуль відповідає типу вказівника і числовому типу (наприклад, 0 як char * або 0 як float);

   вказівник відповідає вказівнику типу void.

Наприклад:

В цьому випадку, оскільки немає print(char) (точного збігу) і немає print(int) (збігу шляхом неявного перетворення), символ b конвертується в тип float і зіставляється з print(float).

Зверніть увагу, всі стандартні перетворення вважаються рівними. Жодне з них не вважається вищим за інші по пріоритету.

Крок №4: C++ намагається знайти відповідність шляхом користувацької конвертації. Хоча ми ще не розглядали класи, але вони можуть визначати конвертації в інші типи даних, які можуть бути неявно застосовані до об’єктів цих класів. Наприклад, ми можемо створити клас W і в ньому визначити для користувача конвертацію в тип int:

Хоча value відноситься до типу класу W, але, оскілький той має користувацьку конвертацію в тип int, виклик print(value) відповідає версії print(int).

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

Декілька збігів

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

У випадку з print('b') мова C++ не може знайти точний збіг. Вона намагається конвертувати b в тип int, але версії print(int) також немає. Використовуючи стандартне перетворення, мова C++ може конвертувати b як в unsigned int, так і в float. Оскільки всі стандартні перетворення вважаються рівними, то виходить два збіги.

З print(0) все аналогічно. 0 — це int, а версії print(int) немає. Шляхом стандартного перетворення ми знову отримуємо два збіги.

А ось з print(3.14159) все трохи заплутаніше: більшість програмістів віднесуть його однозначно до print(float). Однак, пам’ятайте, що за замовчуванням всі значення-літерали типу з плаваючою крапкою відносяться до типу double, якщо у них немає закінчення f. 3.14159 — це значення типу double, а версії print(double) немає. Отже, ми отримуємо ту ж ситуацію, що і в попередніх випадках — неоднозначний збіг (два варіанти).

Неоднозначний збіг вважається помилкою типу compile-time. Відповідно, помилка має бути усунута до того, як ваша програма скомпілюється. Є два рішення цієї проблеми:

Рішення №1: Просто визначте нову перевантажену функцію, яка приймає параметри саме того типу даних, який ви використовуєте у виклику функції. Тоді мова C++ зможе знайти точний збіг.

Рішення №2: Явно конвертувати за допомогою операторів явної конвертації неоднозначний параметр(и) відповідно до типу функції, яку ви хочете викликати. Наприклад, щоб виклик print(0) відповідав print(unsigned int), вам потрібно зробити наступне:

Висновки

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

Правило: Використовуйте перевантаження функцій для спрощення ваших програм.

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (3 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *