С++ для начинающих

       

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


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


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