параметр функции 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
7.3.2. Параметры-ссылки и параметры-указатели
Когда же лучше использовать параметры-ссылки, а когда – параметры-указатели? В конце концов, и те и другие позволяют функции модифицировать объекты, эффективно передавать в функцию большие объекты типа класса. Что выбрать: объявить параметр ссылкой или указателем?
Как было сказано в разделе 3.6, ссылка может быть один раз инициализирована значением объекта, и впоследствии изменить ее нельзя. Указатель же в течение своей жизни способен адресовать разные объекты или не адресовать вообще.
Поскольку указатель может содержать, а может и не содержать адрес какого-либо объекта, перед его использованием функция должна проверить, не равен ли он нулю:
class X;
void manip( X *px )
{
// проверим на 0 перед использованием
if ( px != 0 )
// обратимся к объекту по адресу...
}
Параметр-ссылка не нуждается в этой проверке, так как всегда существует именуемый ею объект. Например:
class Type { };
void operate( const Type p1, const Type p2 );
int main() {
Type obj1;
// присвоим objl некоторое значение
// ошибка: ссылка не может быть равной 0
Type obj2 = operate( objl, 0 );
}
Если параметр должен ссылаться на разные объекты во время выполнения функции или принимать нулевое значение (ни на что не ссылаться), нам следует использовать указатель.
Одна из важнейших сфер применения параметров-ссылок – эффективная реализация перегруженных операций. При этом использование операций остается простым и интуитивно понятным. (Подробнее данный вопрос рассматривается в главе 15.) Разберем маленький пример. Представим себе класс Matrix (матрица). Хорошо бы реализовать операции сложения и присваивания “привычным” способом:
Matrix a, b, c;
c = a + b;
Эти операции реализуются с помощью перегруженных операторов – функций с немного необычным именем. Для оператора сложения такая функция будет называться operator+. Посмотрим, как ее определить:
Matrix // тип возврата - Matrix
operator+( // имя перегруженного оператора
Matrix m1, // тип левого операнда