Язык программирования C++. Вводный курс

Возврат значения


В теле функции может встретиться инструкция 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.


Содержание раздела