7.4. Возврат значения
7.4. Возврат значения
В теле функции может встретиться инструкция return. Она завершает выполнение функции. После этого управление возвращается той функции, из которой была вызвана данная. Инструкция return может употребляться в двух формах:
return;
return expression;
Первая форма используется в функциях, для которых типом возвращаемого значения является void. Использовать return в таких случаях обязательно, если нужно принудительно завершить работу. (Такое применение return напоминает инструкцию break, представленную в разделе 5.8.) После конечной инструкции функции подразумевается наличие return. Например:
void d_copy( double "src, double *dst, int sz )
{
/* копируем массив "src" в "dst"
* для простоты предполагаем, что они одного размера
*/
// завершение, если хотя бы один из указателей равен 0
if ( !src || !dst )
return;
// завершение,
// если указатели адресуют один и тот же массив
if ( src == dst )
return;
// копировать нечего
if ( sz == 0 )
return;
// все еще не закончили?
// тогда самое время что-то сделать
for ( int ix = 0; ix sz; ++ix )
dst[ix] = src[ix];
// явного завершения не требуется
}
Во второй форме инструкции return указывается то значение, которое функция должна вернуть. Это значение может быть сколь угодно сложным выражением, даже содержать вызов функции. В реализации функции factorial(), которую мы рассмотрим в следующем разделе, используется return следующего вида:
return val * factorial(val-1);
В функции, не объявленная с void в качестве типа возвращаемого значения, обязательно использовать вторую форму return, иначе произойдет ошибка компиляции. Хотя компилятор не отвечает за правильность результата, он сможет гарантировать его наличие. Следующая программа не компилируется из-за двух мест, где программа завершается без возврата значения:
// определение интерфейса класса Matrix
#include "Matrix.h"
bool is_equa1( const Matrix ml, const Matrix m2 )
{
/* Если содержимое двух объектов Matrix одинаково,
* возвращаем true;
* в противном случае - false
*/
// сравним количество столбцов
if ( ml.colSize() != m2.co1Size() )
// ошибка: нет возвращаемого значения
return;
// сравним количество строк
if ( ml.rowSize() != m2.rowSize() )
// ошибка: нет возвращаемого значения
return;
// пробежимся по обеим матрицам, пока
// не найдем неравные элементы
for ( int row = 0; row ml.rowSize(); ++row )
for ( int col = 0; co1 ml.colSize(); ++co1 )
if ( ml[row][col] != m2[row][col] )
return false;
// ошибка: нет возвращаемого значения
// для случая равенства
}
Если тип возвращаемого значения не точно соответствует указанному в объявлении функции, то применяется неявное преобразование типов. Если же стандартное приведение невозможно, происходит ошибка компиляции. (Преобразования типов рассматривались в разделе 4.1.4.)
По умолчанию возвращаемое значение передается по значению, т.е. вызывающая функция получает копию результата вычисления выражения, указанного в инструкции return. Например:
Matrix grow( Matrix* p ) {
Matrix val;
// ...
return val;
}
grow() возвращает вызывающей функции копию значения, хранящегося в переменной val.
Такое поведение можно изменить, если объявить, что возвращается указатель или ссылка. При возврате ссылки вызывающая функция получает l-значение для val и потому может модифицировать val или взять ее адрес. Вот как можно объявить, что grow() возвращает ссылку:
Matrix grow( Matrix* p ) {
Matrix *res;
// выделим память для объекта Matrix
// большого размера
// res адресует этот новый объект
// скопируем содержимое *p в *res
return *res;
}
Если возвращается большой объект, то гораздо эффективнее перейти от возврата по значению к использованию ссылки или указателя. В некоторых случаях компилятор может сделать это автоматически. Такая оптимизация получила название именованное возвращаемое значение. (Она описывается в разделе 14.8.)
Объявляя функцию как возвращающую ссылку, программист должен помнить о двух возможных ошибках:
• возврат ссылки на локальный объект, время жизни которого ограничено временем выполнения функции. (О времени жизни локальных объектов речь пойдет в разделе 8.3.) По завершении функции такой ссылке соответствует область памяти, содержащая неопределенное значение. Например:
// ошибка: возврат ссылки на локальный объект
Matrix add( Matrix m1, Matrix m2 )
{
Matrix result:
if ( m1.isZero() )
return m2;
if ( m2.isZero() )
return m1;
// сложим содержимое двух матриц
// ошибка: ссылка на сомнительную область памяти
// после возврата
return result;
}
В таком случае тип возврата не должен быть ссылкой. Тогда локальная переменная может быть скопирована до окончания времени своей жизни:
Matrix add( ... )
• функция возвращает l-значение. Любая его модификация затрагивает сам объект. Например:
#include vector
int get_val( vectorint vi, int ix ) {
return vi [ix];
}
int ai[4] = { 0, 1, 2, 3 };
vectorint vec( ai, ai+4 ); // копируем 4 элемента ai в vec
int main() {
// увеличивает vec[0] на 1
get_val( vec.0 )++;
// ...
}
Для предотвращения нечаянной модификации возвращенного объекта нужно объявить тип возврата как const:
const int get_val( ... )
Примером ситуации, когда l-значение возвращается намеренно, чтобы позволить модифицировать реальный объект, может служить перегруженный оператор взятия индекса для класса IntArray из раздела 2.3.
7.4.1. Передача данных через параметры и через глобальные объекты
Различные функции программы могут общаться между собой с помощью двух механизмов. (Под словом “общаться” мы подразумеваем обмен данными.) В одном случае используются глобальные объекты, в другом – передача параметров и возврат значений.
Глобальный объект определен вне функции. Например:
int glob;
int main() {
// что угодно
}
Объект glob является глобальным. (В главе 8 рассмотрение глобальных объектов и глобальной области видимости будет продолжено.) Главное достоинство и одновременно один из наиболее заметных недостатков такого объекта – доступность из любого места программы, поэтому его обычно используют для общения между разными модулями. Обратная сторона медали такова:
* функции, использующие глобальные объекты, зависят от этих объектов и их типов. Использовать такую функцию в другом контексте затруднительно;
* при модификации такой программы повышается вероятность ошибок. Даже для внесения локальных изменений необходимо понимание всей программы в целом;
* если глобальный объект получает неверное значение, ошибку нужно искать по всей программе. Отсутствует локализация;
* используя глобальные объекты, труднее писать рекурсивные функции (Рекурсия возникает тогда, когда функция вызывает сама себя. Мы рассмотрим это в разделе 7.5.);
* если используются потоки (threads), то для синхронизации доступа к глобальным объектам требуется писать дополнительный код. Отсутствие синхронизации – одна из распространенных ошибок при использовании потоков. (Пример использования потоков при программировании на С++ см. в статье “Distributing Object Computing in C++” (Steve Vinoski and Doug Schmidt) в [LIPPMAN96b].)
Можно сделать вывод, что для передачи информации между функциями предпочтительнее пользоваться параметрами и возвращаемыми значениями.
Вероятность ошибок при таком подходе возрастает с увеличением списка. Считается, что восемь параметров – это приемлемый максимум. В качестве альтернативы длинному списку можно использовать в качестве параметра класс, массив или контейнер. Он способен содержать группу значений.
Аналогично программа может возвращать только одно значение. Если же логика требует нескольких, некоторые параметры объявляются ссылками, чтобы функция могла непосредственно модифицировать значения соответствующих фактических аргументов и использовать эти параметры для возврата дополнительных значений, либо некоторый класс или контейнер, содержащий группу значений, объявляется типом, возвращаемым функцией.
Упражнение 7.9
Каковы две формы инструкции return? Объясните, в каких случаях следует использовать первую, а в каких вторую форму.
Упражнение 7.10
Найдите в данной функции потенциальную ошибку времени выполнения:
vectorstring readText( ) {
vectorstring text;
string word;
while ( cin word ) {
text.push_back( word );
// ...
}
// ....
return text;
}
Упражнение 7.11
Каким способом вы вернули бы из функции несколько значений? Опишите достоинства и недостатки вашего подхода
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Выполнение процедуры завершения и возврат из функции дежурного ожидания
Выполнение процедуры завершения и возврат из функции дежурного ожидания По окончании выполнения операции расширенного ввода/вывода связанная с ней процедура завершения со своими аргументами, определяющими структуру OVERLAPPED, счетчик байтов и код ошибки, помещается в
Переход в приостановленное состояние и возврат к выполнению
Переход в приостановленное состояние и возврат к выполнению Приостановленное состояние задачи (состояние ожидания, заблокированное состояние, sleeping, blocked) представляет собой специальное состояние задачи, в котором задание не выполняется. Это является очень важным, так
Возврат результата потока
Возврат результата потока Выше отмечено, что вызов pthread_exit(), завершающий ожидаемый поток, может передать результат выполнения потока. То же действие может быть выполнено и оператором return потоковой функции, которая из прототипа ее определения должна возвращать значение
Возврат товара
Возврат товара Очень интересный момент, который обязательно надо обговорить. Во всех интернет-магазинах свои правила возврата товара, но российское законодательство все равно прописывает общие правила для всех. Если вы получили товар ненадлежащего качества, то мнения
Возврат товара
Возврат товара Перед тем, как завершить оформление заказа, было бы неплохо прочитать условия возврата. Некоторые магазины пишут, что возврат из-за рубежа невозможен, другие относятся к этому нормально, и вместе с заказом присылают лист возврата, если вы вдруг что-то
ГЛАВА 4. ВОЗВРАТ И ОТСЕЧЕНИЕ
ГЛАВА 4. ВОЗВРАТ И ОТСЕЧЕНИЕ Давайте подытожим всю информацию, которую мы почерпнули в гл. 1 и 2 о том, что может произойти с целевым утверждением (целью).1. Может иметь место попытка доказать согласованность целевого утверждения с базой данных. В процессе доказательства
Урок № 48. Возврат ТМЦ поставщику
Урок № 48. Возврат ТМЦ поставщику Возврат товарно-материальных ценностей поставщику – операция, встречающаяся довольно часто. Кто-то возвращает товары по причине невозможности рассчитаться за них, кто-то – из-за снижения покупательского спроса, кто-то хочет вернуть
Урок № 55. Возврат товаров от покупателей
Урок № 55. Возврат товаров от покупателей Для оформления возврата товарно-материальных ценностей от покупателей в программе предназначен документ "Возврат товаров от покупателя". Для перехода в режим работы с этими документами нужно выполнить команду главного меню
Урок № 69. Возврат спецодежды, спецоснастки и инвентаря из эксплуатации
Урок № 69. Возврат спецодежды, спецоснастки и инвентаря из эксплуатации Переданные в эксплуатацию ценности могут возвращаться обратно на склад, с которого они ранее были выданы. Для отражения в учете этой операции в программе "1С:Бухгалтерия 8" предназначен документ,
1. Пустые значения (Empty-значения)
1. Пустые значения (Empty-значения) Пустое значение – это просто одно из множества возможных значений какого-то вполне определенного типа данных.Перечислим наиболее «естественные», непосредственные пустые значения (т. е. пустые значения, которые мы могли бы выделить
2. Неопределенные значения ( Null-значения)
2. Неопределенные значения (Null-значения) Слово Null используется для обозначения неопределенных значений в базах данных.Чтобы лучше понять, какие значения понимаются под неопределенными, рассмотрим таблицу, являющуюся фрагментом базы данных: Итак, неопределенное
4.6.4 Возврат Значения
4.6.4 Возврат Значения Из функции, которая не описана как void, можно (и долно) возвращать значение. Возвращаемое значение задается опратором return. Например:int fac(int n) (*return (n»1) ? n*fac(n-1) : 1; *)В функции может быть больше одного оператора return: int fac(int n) (* if (n » 1) return n*fac(n-1); else return 1; *)Как и
19.4. Возврат значения функции
19.4. Возврат значения функции После естественного завершения выполнения функции либо в том случае, когда она завершается в результате выполнения какого?либо условия, можно выбрать один из двух возможных вариантов: 1. Дождаться, пока функция естественным образом не