и распространенный механизм. Вернемся к функции my_find() (см. раздел 8.5.1), выполняющей поиск строки в векторе строк. Передача по значению здесь была бы слишком неэффективной.
int my_find(vector<string> vs, string s); // передача по значению:
// копия
Если вектор содержит тысячи строк, то поиск занял бы заметный объем времени даже на быстром компьютере. Итак, мы можем улучшить функцию my_find(), передавая ее аргументы по константной ссылке.
// передача по ссылке: без копирования, доступ только для чтения
int my_find(const vector<string>& vs, const string& s);
8.5.5. Передача параметров по ссылке
А что делать, если мы хотим, чтобы функция модифицировала свои аргументы? Иногда это очень нужно. Например, мы можем написать функцию init(), которая должна присваивать начальные значения элементам вектора.
void init(vector<double>& v) // передача по ссылке
{
for (int i = 0; i<v.size(); ++i) v[i] = i;
}
void g(int x)
{
vector<double> vd1(10); // небольшой вектор
vector<double> vd2(1000000); // большой вектор
vector<double> vd3(x); // вектор неопределенного размера
init(vd1);
init(vd2);
init(vd3);
}
Итак, мы хотим, чтобы функция init() изменяла вектор, являющийся ее аргументом. Иначе говоря, мы хотим не копировать его (т.е. передавать по значению), не объявлять с помощью константной ссылки (т.е. передавать по константной ссылке), а просто передать обычную ссылку на вектор.
Рассмотрим ссылки более подробно. Ссылка — это конструкция, позволяющая пользователю объявлять новое имя объекта. Например, int& — это ссылка на переменную типа int. Это позволяет нам написать следующий код:
int i = 7;
int& r = i; // r — ссылка на переменную i
r = 9; // переменная i становится равной 9
i = 10;
cout << r << ' ' << i << 'n'; // вывод: 10 10
Иначе говоря, любая операция над переменной r на самом деле означает операцию над переменной i. Ссылки позволяют уменьшить размер выражений. Рассмотрим следующий пример:
vector< vector<double> > v; // вектор векторов чисел типа double
Допустим, нам необходимо сослаться на некоторый элемент v[f(x)][g(y)] несколько раз. Очевидно, что выражение v[f(x)][g(y)] выглядит слишком громоздко и повторять его несколько раз неудобно. Если бы оно было просто значением, то мы могли бы написать следующий код:
double val = v[f(x)][g(y)]; // val — значение элемента v[f(x)][g(y)]
В таком случае можно было бы повторно использовать переменную val. А что, если нам нужно и читать элемент v[f(x)][g(y)], и присваивать ему значения v[f(x)][g(y)]? В этом случае может пригодиться ссылка.
double& var = v[f(x)][g(y)]; // var — ссылка на элемент v[f(x)][g(y)]
Теперь можем как считывать, так и изменять элемент v[f(x)][g(y)] с помощью ссылки var. Рассмотрим пример.
var = var/2+sqrt(var);
Это ключевое свойство ссылок — оно может служить “аббревиатурой” объекта и использоваться как удобный аргумент. Рассмотрим пример.
// передача по ссылке (функция ссылается на полученную переменную)
int f(int& x)
{
x = x+1;
return x;
}
int main()
{
int xx = 0;
cout << f(xx) << endl; // вывод: 1
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 является константной,
// можно передавать литерал
}
Итак, если хотите изменить значение объекта, передаваемого