Параметры-ссылки
Использование ссылок в качестве параметров модифицирует стандартный механизм передачи по значению. При такой передаче функция манипулирует локальными копиями аргументов. Используя параметры-ссылки, она получает 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