Урок №106. Повернення значень по посиланню, по адресу і по значенню

  Юрій  | 

  Оновл. 18 Січ 2021  | 

 84

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

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

Повернення по значенню

Повернення по значенню — це найпростіший і найбезпечніший тип повернення. При поверненні по значенню, копія значення, що повертається, передається назад в caller. Як і у випадку з передачею по значенню, ви можете повертати літерали (наприклад, 7), змінні (наприклад, x) або вирази (наприклад, x + 2), що робить цей спосіб дуже гнучким.

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

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

Коли використовувати повернення по значенню:

   при поверненні змінних, які були оголошені всередині функції;

   при поверненні аргументів функції, які були передані в функцію по значенню.

Коли не використовувати повернення по значенню:

   при поверненні стандартних масивів або вказівників (використовуйте повернення по адресу);

   при поверненні великих структур або класів (використовуйте повернення по посиланню).

Повернення по адресу

Повернення по адресу — це повернення адреси змінної назад в caller. Подібно передачі по адресу, повернення по адресу може повертати тільки адрес змінної. Літерали і вирази повертати не можна, так як вони не мають адрес. Оскільки при поверненні по адресу просто копіюється адреса з функції в caller, то цей процес також дуже швидкий.

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

Як ви можете бачити, value знищується відразу після того, як його адрес повертається в caller. Кінцевим результатом буде те, що caller отримає адресу звільненої пам’яті (“висячий” вказівник), що, безсумнівно, викличе проблеми. Це одна з найпоширеніших помилок, яку роблять початківці. Більшість сучасних компіляторів видадуть попередження (а не помилку), якщо програміст спробує повернути локальну змінну по адресу. Однак є кілька способів обдурити компілятор, щоб зробити щось “погане”, не генеруючи при цьому попередження, тому вся відповідальність лежить на програмістові, який повинен гарантувати, що адреса, що повертається, буде коректною.

Повернення по адресу часто використовується для повернення динамічно виділеної пам’яті назад в caller:

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

Коли використовувати повернення по адресу:

   при поверненні динамічно виділеної пам’яті;

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

Коли не використовувати повернення по адресу:

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

   при поверненні великої структури або класу, які були передані по посиланню (використовуйте повернення по посиланню).

Повернення по посиланню

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

Однак, як і при поверненні по адресу, ви не повинні повертати локальні змінні по посиланню. Розглянемо наступний фрагмент коду:

Тут повертається посилання на змінну value, яка знищиться, коли функція завершить своє виконання. Це означає, що caller отримає посилання на сміття. На щастя, ваш компілятор, найімовірніше, видасть попередження або помилку, якщо ви спробуєте зробити подібне.

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

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

7

Коли ми викликаємо getElement(array, 15), то getElement() повертає посилання на елемент масиву під індексом 15, а потім main() використовує це посилання для присвоювання значення 7 цьому елементу.

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

Коли використовувати повернення по посиланню:

   при поверненні посилання-параметру;

   при поверненні елементу масиву, який був переданий у функцію;

   при поверненні великої структури або класу, який не знищується в кінці функції (наприклад, той, який був переданий у функцію).

Коли не використовувати повернення по посиланню:

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

   при поверненні стандартного масиву або значення вказівника (використовуйте повернення по адресу).

Змішування значень, що повертаються, і посилань

Хоча функція може повертати як значення, так і посилання, caller може неправильно це інтерпретувати. Подивимося, що станеться при змішуванні значень, що повертаються, і посилань на значення:

У випадку A ми присвоюємо посилання на значення, що повертається, змінній, яка сама не є посиланням. Оскільки value не є посиланням, то значення, що повертається, просто копіюється в value так, наче returnByReference() був поверненням по значенню.

У випадку B ми намагаємося ініціалізувати посилання ref копією значення, що повертається, функції returnByValue(). Однак, оскільки значення, що повертається, не має адреси (це r-value), ми отримаємо помилку компіляції.

У випадку C ми намагаємося ініціалізувати константне посилання cref копією значення, що повертається, функції returnByValue(). Оскільки константні посилання можуть бути ініціалізовані за допомогою r-values, то тут не повинно виникати ніяких проблем. Зазвичай r-values знищуються в кінці виразу, в якому вони створені, однак, при прив’язці до константного посилання, час життя r-value (в даному випадку, значення, що повертається, функції) продовжується відповідно до часу життя посилання (в даному випадку, cref) .

Висновки

У більшості випадків ідеальним варіантом для використання є повернення по значенню. Це також найгнучкіший і найбезпечніший спосіб повернення даних у викликаючий об’єкт. Однак повернення по посиланню або по адресу також може бути корисним при роботі з динамічно виділеною пам’яттю. При використанні повернення по посиланню або по адресу переконайтеся, що ви не повертаєте посилання або адресу локальної змінної, яка вийде з області видимості, коли функція завершить своє виконання!

Тест

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

Завдання №1

Функція sumTo(), яка приймає цілочисельний параметр, а повертає суму всіх чисел між 1 і числом, яке ввів користувач.

Відповідь №1

Завдання №2

Функція printAnimalName(), яка приймає структуру Animal в якості параметру.

Відповідь №2

Завдання №3

Функція minmax(), яка приймає два цілих числа в якості вхідних даних, а повертає найменше та найбільше числа в якості окремих параметрів.

Підказка: Використовуйте параметри виводу.

Відповідь №3

Завдання №4

Функція getIndexOfLargestValue(), яка приймає цілочисельний масив (як вказівник) і його розмір, а повертає індекс найбільшого елементу масиву.

Відповідь №4

Завдання №5

Функція getElement(), яка приймає цілочисельний масив (як вказівник) і індекс і повертає елемент масиву по цьому індексу (НЕ копію елементу). Припускається, що індекс коректний, а значення, що повертається, — константне.

Відповідь №5

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

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

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

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