Урок 100. Возврат значений по ссылке, по адресу и по значению

   ⁄ 

 Обновлено 20 Дек 2017  ⁄ 

⁄   1152

В трех предыдущих уроках мы узнали о передаче аргументов в функции по значению, по ссылке и по адресу. В этом уроке мы рассмотрим возврат значений обратно из функции в вызывающий объект всеми этими тремя способами.

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

Возврат по значению

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

Еще одним преимуществом является то, что вы можете возвращать переменные (или выражения), в вычислении которых задействованы и локальные переменные, объявленные в теле самой функции, без необходимости беспокоиться о проблемах, которые могут возникнуть с областью видимости. Поскольку переменные вычисляются до того, как функция производит возврат и копия значения возвращается в caller, то здесь не должно быть никаких проблем с областью видимости этих переменных, когда заканчивается блок, в которым они объявлены.

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

Когда использовать возврат по значению:

  при возврате переменных, которые были объявлены внутри функции;



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

Когда не использовать возврат по значению:

  при возврате стандартных массивов или указателей (используйте возврат по адресу);

  при возврате больших структур или классов (используйте возврат по ссылке).

Возврат по адресу

Возврат по адресу – это возврат адреса переменной обратно в 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 (11 оценок, среднее: 4,64 из 5)
Загрузка...
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО