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


Параметры-ссылки


Использование ссылок в качестве параметров модифицирует стандартный механизм передачи по значению. При такой передаче функция манипулирует локальными копиями аргументов. Используя параметры-ссылки, она получает l-значения своих аргументов и может изменять их.

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

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

#include <vector>

// параметр-ссылка 'occurs'

// содержит второе возвращаемое значение

vector<int>::const_iterator look_up(

    const vector<int> &vec,

    int value,     // искомое значение

    int &occurs )  // количество вхождений

{

    // res_iter инициализируется значением

    // следующего за конечным элемента



    vector<int>::const_iterator res_iter = vec.end();

    occurs = 0;

    for ( vector<int>::const_iterator iter = vec.begin();

             iter != vec.end();

             ++iter )

        if ( *iter == value )

        {

            if ( res_iter == vec.end() )

                res_iter = iter;

            ++occurs;

        }

    return res_iter;

}

Третий случай, когда использование параметра-ссылки может быть полезно, – это большой объект типа класса в качестве аргумента. При передаче по значению объект будет копироваться целиком при каждом вызове функции, что для больших объектов может привести к потере эффективности. Используя параметр-ссылку, функция получает доступ к той области памяти, где размещен сам объект, без создания дополнительной копии. Например:


class Huge { public: double stuff[1000]; };

extern int calc( const Huge & );

int main() {

    Huge table[ 1000 ];

    // ... инициализация table

    int sum = 0;

    for ( int ix=0; ix < 1000; ++ix )

        // calc() ссылается на элемент массива

        // типа Huge

        sum += calc( tab1e[ix] );

    // ...

}

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

В следующем примере нарушается константность параметра xx функции foo(). Поскольку параметр функции foo_bar() не является ссылкой на константу, то нет гарантии, что вызов foo_bar() не изменит значения аргумента. Компилятор сигнализирует об ошибке:

class X;

extern int foo_bar( X& );

int foo( const X& xx ) {

    // ошибка: константа передается

    // функции с параметром неконстантного типа

    return foo_bar( xx );

}

Для того чтобы программа компилировалась, мы должны изменить тип параметра foo_bar(). Подойдет любой из следующих двух вариантов:

extern int foo_bar( const X& );

extern int foo_bar( X ); // передача по значению

Вместо этого можно передать копию xx, которую позволено менять:

int foo( const X &xx ) {

    // ...

    X x2 = xx; // создать копию значения

    // foo_bar() может поменять x2,

    // xx останется нетронутым

    return foo_bar( x2 ); // правильно

}

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

void ptrswap( int *&vl, int *&v2 ) {



    int *trnp = v2;

    v2 = vl;

    vl = tmp;

}

Объявление

int *&v1;

должно читаться справа налево: v1 является ссылкой на указатель на объект типа int. Модифицируем функцию main(), которая вызывала rswap(), для проверки работы ptrswap():

#include <iostream>

void ptrswap( int *&vl, int *&v2 );

int main() {

    int i = 10;

    int j = 20;

    int *pi = &i;

    int *pj = &j;

    cout << "Перед ptrswap():\tpi: "

         << *pi << "\tpj: " << *pj << endl;

    ptrswap( pi, pj );

    cout << "После ptrswap():\tpi: "

         << *pi << "\tpj: " << pj << endl;

    return 0;

}

Вот результат работы программы:

Перед ptrswap():    pi: 10    pj: 20

После ptrswap():    pi: 20    pj: 10


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