Возврат значения
В теле функции может встретиться инструкция 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( vector<int> &vi, int ix ) { return vi [ix]; } int ai[4] = { 0, 1, 2, 3 }; vector<int> 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.