cout << xx << endl; // вывод: 1; функция f изменяет
// значение xx
int yy = 7;
cout << f(yy) << endl; // вывод: 8
cout << yy << endl; // вывод: 8; функция f изменяет
// значение yy
}
Передачу аргументов по ссылке можно проиллюстрировать
следующим образом.
Сравните этот пример с соответствующим примером из раздела 8.5.3.
Совершенно очевидно, что передача по ссылке — очень мощный механизм: функции могут непосредственно оперировать с любым объектом, передаваемым по ссылке. Например, во многих алгоритмах сортировки перестановка двух значений — весьма важная операция. Используя ссылки, можем написать функцию, меняющую местами два числа типа
double
.
void swap(double& d1, double& d2)
{
double temp = d1; // копируем значение d1 в переменную temp
d1 = d2; // копируем значение d2 в переменную d1
d2 = temp; // копируем старое значение d1 в переменную d2
}
int main
{
double x = 1;
double y = 2;
cout << "x == " << x << " y== " << y << '\n'; // вывод: x==1 y==2
swap(x,y);
cout << "x == " << x << " y== " << y << '\n'; // вывод: x==2 y==1
}
В стандартной библиотеке предусмотрена функция
swap
для любого типа, который можно скопировать, поэтому его можно применять к любому типу.
8.5.6. Сравнение механизмов передачи параметров по значению и по ссылке
Зачем нужны передачи по значению, по ссылке и по константной ссылке. Для начала рассмотрим один формальный пример.
void f(int a, int& r, const int& cr)
{
++a; // изменяем локальную переменную a
++r; // изменяем объект, с которым связана ссылка r
++cr; // ошибка: cr — константная ссылка
}
Если хотите изменить значение передаваемого объекта, то должны использовать неконстантную ссылку: передача по значению создаст копию, а передача по константной ссылке предотвратит изменение передаваемого объекта. Итак, можно написать следующий код:
void g(int a, int& r, const int& cr)
{
++a; // изменяем локальную переменную a
++r; // изменяем объект, с которым связана ссылка r
int x = cr; // считываем объект, с которым связана ссылка cr
}
int main
{
int x = 0;
int y = 0;
int z = 0;
g(x,y,z); // x==0; y==1; z==0
g(1,2,3); //
ошибка: ссылочный аргумент r должен быть переменным
g(1,y,3); // OK: поскольку ссылка cr является константной,
// можно передавать литерал
}
Итак, если хотите изменить значение объекта, передаваемого по ссылке, следует передать объект. С формальной точки зрения целочисленный литерал
2
— это значение (а точнее, r-значение, т.е. значение в правой части оператора присваивания), а не объект, хранящий значение. Для аргумента
r
функции
f
требуется l-значение (т.е. значение, стоящее в левой части оператора присваивания).
Обратите внимание на то, что для константной ссылки l-значение не требуется. С ней можно выполнять преобразования точно так же, как при инициализации или при передаче по значению. При последнем вызове
g(1,y,3)
компилятор зарезервирует переменную типа
int
для аргумента
cr
функции
g
g(1,y,3); // означает: int __compiler_generated = 3;
// g(1,y,__compiler_generated)
Такой объект, создаваемый компилятором, называется временным объектом (temporary object).
Правило формулируется следующим образом.
1. Для передачи очень маленьких объектов следует использовать передачу аргументов по значению.
2. Для передачи больших объектов, которые нельзя изменять, следует использовать передачу аргументов по константной ссылке.
3. Следует возвращать результат, а не модифицированный объект, передаваемый по ссылке.
4. Передачу по ссылке следует использовать только в необходимых случаях.
Эти правила позволяют создавать очень простой, устойчивый к ошибкам и очень эффективный код. Под очень маленькими объектами подразумеваются одна или две переменных типа
int
, одна или две переменных типа double или соразмерные им объекты. Если вы видите аргумент, передаваемый по обычной ссылке, то должны предполагать существование функции, которая его модифицирует. Третье правило отражает ситуацию, в которой требуется функция, изменяющая значение переменной. Рассмотрим пример.
int incr1(int a) { return a+1; } // возвращает в качестве результата
// новое значение
void incr2(int& a) { ++a; } // модифицирует объект,
// передаваемый по ссылке
int x = 7;
x = incr1(x); // совершенно очевидно
incr2(x); // совершенно непонятно
Почему же мы все-таки используем передачу аргументов по ссылке? Иногда это оказывается важным в следующих ситуациях.