Ссылки
В C++ имеется модифицированная форма указателей, называемая ссылкой (reference). Ссылка — это указатель, который автоматически разыменовывается при любом обращении к нему. Поэтому ссылке, как таковой, нельзя присвоить значение, как это можно сделать с обычным указателем. Ссылку можно только инициализировать.
Ссылки как псевдонимы переменных
Переменная-ссылка объявляется со знаком “&”, предшествующим ее имени; инициализирующим выражением должно быть имя переменной. Рассмотрите такой пример:
///////////////////////////////////////////////////////////////////
// Alias.cpp: Ссылка как псевдоним.
//
#include <stdio.h>
int main(void)
{
int iVar = 7777;
// Инициализация целой переменной.
int *iPtr = siVar;
// Инициализация указателя адресом
// iVar.
int &iRef = iVar;
// Инициализация ссылки как
// псевдонима iVar.
int *irPtr = &iRef;
// Инициализация указателя "адресом"
// ссылки.
printf("iVar = %d\n", iVar) ;
printf("*iPtr = %d\n", *iPtr);
printff'iRef = %d\n", iRef);
printf("*irPtr = %d\n", *irPtr) ;
printff'iPtr = %p, irPtr = %p\n", iPtr, irPtr);
return 0;
}
Первые четыре оператора printf выводят одно и то же число 7777. последний оператор печатает значения двух указателей, первый из которых инициализирован адресом переменной, а второй адресом ссылки. Эти адреса также оказываются одинаковыми. Как видите, после инициализации ссылки ее имя используется в точности как имя ассоциированной с ней переменной, т. е. как псевдоним.
Еще раз подчеркнем, что ссылку после инициализации нельзя изменить; все обращения к ссылке будут относиться на самом деле к переменной, именем которой она была инициализирована.
Ссылки в качестве параметров функции
Все сказанное в предыдущем параграфе не имеет существенного практического значения; к чему, в самом деле, вводить ссылку на переменную, если с тем же успехом можно обращаться к ней самой? По-иному дело обстоит в случае, когда ссылкой объявляется параметр функции.
Параметры функции мало чем отличаются от ее локальных автоматических переменных. Разница только в том, что они располагаются на стеке выше адреса в регистре ЕВР, а локальные переменные — ниже. Передача параметров при вызове функции эквивалентна созданию локальных переменных и инициализации их значениями соответствующих аргументов.
Это означает, что если объявить формальный параметр как ссылку, то при вызове он будет инициализирован в качестве псевдонима переменной-аргумента. Если говорить о конкретной реализации этого механизма, функции передается указатель на переменную, автоматически разыменуемый при обращении к нему в теле функции.
Тем самым параметры-ссылки делают возможной настоящую “передачу по ссылке” без использования явных указателей и адресов. Вот сравнение различных видов передачи параметров:
//////////////////////////////////////////////////
// RefPar.cpp: Передача параметров по ссылке.
//
#include <stdio.h>
void SwapVal(int x, int y)
// Передача значений.
{
int temp;
temp = x;
x = y;
у = temp;
}
void SwapPtr(int *x, int *y)
// Передача указателей.
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
void SwapRef(int &x, int &y)
// Передача ссылок.
{
int temp;
temp = x;
x = y;
y = temp;
}
int main(void)
(
int i = 11, i = 22;
printf("Initial values: i=%d,j=%d.\n", i,j ) ;
SwapVal(i, j);
printf ("After SwapVal():i = %d,j =%d.\n",i,j);
SwapPtr (&i, &j) ;
printf("After SwapPtr():i =%d,j=%d.\n",i,j);
SwapRef(i, j) ;
printf("After SwapRef():i =%d,j=%d.\n",i,j);
return 0;
Программа напечатает:
Initial values: i = 11, j = 22.
After SwapValO i = 11, j = 22.
After SwapPtr():i = 22, j = 11.
After SwapRef () i = 11, j =22.
Как и должно быть, функция SwapVal () не влияет на значения передаваемых ей переменных, поскольку она обменивает лишь локальные их копии. Функция SwapPtr () , принимающая указатели, обменивает значия исходных переменных, но требует явного указания адресов при вызове и явного их разыменования в теле переменной. Наконец, SwapRef () , с параметрами-ссылками, также обменивает значения переданных ей переменных. Причем она отличается от функции, принимающей параметры-значения, только заголовком. Тело SwapRef () и ее вызов выглядят совершенно аналогично функции SwapVal () .
Помимо случаев, когда функция должна возвращать значения в параметрах, ссылки могут быть полезны при передаче больших структур, поскольку функции тогда передается не сама структура, а ее адрес.
Передавая параметры по ссылке, нужно всегда помнить о возможных побочных эффектах. Если только функция не должна явно изменять значение своего аргумента, лучше объявить параметр как ссылку на константу (например, const int SiVal). Тогда при попытке присвоить параметру значение в теле функции компилятор выдаст сообщение об ошибке.
Ссылка в качестве возвращаемого значения
Возвращаемое функцией значение также может быть объявлено ссылкой. Это позволяет использовать функцию в левой части присваивания. Рассмотрите такой пример:
////////////////////////////////////////////////////////
// массива.
//
#include <stdio.h>
#include <assert.h>
const int arrSize = 8;
int &refItem(int indx)
// Возвращает ссылку на элемент
//
iArray[indx] {
static int iArray[arrSize];
//
// Проверка диапазона:
//
assert(indx >= 0 && indx< arrSize);
return iArray[indx];
}
int main(void) {
int i;
for (i=0; .KarrSise; i++)
refltem(i) = 1 << i;
// Присваивает значение элементу
// iArray[i].
for (i=O; i<arrSize; i++)
printf("iArray[%02d] = %4d\n'\ i, refltem(i));
refltem(i) = 0; // Попытка выхода за пределы
// массива.
return 0;
}
В первом из операторов for функция refltem() вызывается в левой части присваивания. Во втором for она возвращает значение, которое передается функции printf (). Обратите внимание, что, во-первых, массив iArray[] объявлен как статический локальный в refltem(), благодаря чему непосредственное обращение к нему вне этой функции невозможно. Во-вторых, refltem() попутно проверяет допустимость переданного ей индекса. Ссылка позволяет на элементарном уровне организовать механизм, который мы в следующей главе назовем сокрытием или абстрагированием данных.