// ival получает значение min_val,
// а не refVal меняет значение на min_val
refVal = min_val;
Все операции со ссылками реально воздействуют на адресуемые ими объекты. В том числе и операция взятия адреса. Например:
refVal += 2;
прибавляет 2 к ival – переменной, на которую ссылается refVal. Аналогично
int ii = refVal;
присваивает ii текущее значение ival,
int *pi = refVal;
инициализирует pi адресом ival.
Если мы определяем ссылки в одной инструкции через запятую, перед каждым объектом типа ссылки должен стоять амперсанд () – оператор взятия адреса (точно так же, как и для указателей). Например:
// определено два объекта типа int
int ival = 1024, ival2 = 2048;
// определена одна ссылка и один объект
int rval = ival, rval2 = ival2;
// определен один объект, один указатель и одна ссылка
int inal3 = 1024, *pi = ival3, ri = ival3;
// определены две ссылки
int rval3 = ival3, rval4 = ival2;
Константная ссылка может быть инициализирована объектом другого типа (если, конечно, существует возможность преобразования одного типа в другой), а также безадресной величиной – такой, как литеральная константа. Например:
double dval = 3.14159;
// верно только для константных ссылок
const int ir = 1024;
const int ir2 = dval;
const double dr = dval + 1.0;
Если бы мы не указали спецификатор const, все три определения ссылок вызвали бы ошибку компиляции. Однако, причина, по которой компилятор не пропускает таких определений, неясна. Попробуем разобраться.
Для литералов это более или менее понятно: у нас не должно быть возможности косвенно поменять значение литерала, используя указатели или ссылки. Что касается объектов другого типа, то компилятор преобразует исходный объект в некоторый вспомогательный. Например, если мы пишем:
double dval = 1024;
const int ri = dval;
то компилятор преобразует это примерно так:
int temp = dval;
const int ri = temp;
Если бы мы могли присвоить новое значение ссылке ri, мы бы реально изменили не dval, а temp. Значение dval осталось бы тем же, что совершенно неочевидно для программиста. Поэтому компилятор запрещает такие действия, и единственная возможность проинициализировать ссылку объектом другого типа – объявить ее как const.
Вот еще один пример ссылки, который трудно понять с первого раза. Мы хотим определить ссылку на адрес константного объекта, но наш первый вариант вызывает ошибку компиляции:
const int ival = 1024;
// ошибка: нужна константная ссылка
int *pi_ref = ival;
Попытка исправить дело добавлением спецификатора const тоже не проходит:
const int ival = 1024;
// все равно ошибка
const int *pi_ref = ival;
В чем причина? Внимательно прочитав определение, мы увидим, что pi_ref является ссылкой на константный указатель на объект типа int. А нам нужен неконстантный указатель на константный объект, поэтому правильной будет следующая запись:
const int ival = 1024;
// правильно
int *const piref = ival;
Между ссылкой и указателем существуют два основных отличия. Во-первых, ссылка обязательно должна быть инициализирована в месте своего определения. Во-вторых, всякое изменение ссылки преобразует не ее, а тот объект, на который она ссылается. Рассмотрим на примерах. Если мы пишем:
int *pi = 0;
мы инициализируем указатель pi нулевым значением, а это значит, что pi не указывает ни на какой объект. В то же время запись
const int ri = 0;
означает примерно следующее:
int temp = 0;
const int ri = temp;
Что касается операции присваивания, то в следующем примере:
int ival = 1024, ival2 = 2048;
int *pi = ival, *pi2 = ival2;
pi = pi2;
переменная ival, на которую указывает pi, остается неизменной, а pi получает значение адреса переменной ival2. И pi, и pi2 и теперь указывают на один и тот же объект ival2.
Если же мы работаем со ссылками:
int ri = ival, ri2 = ival2;
ri = ri2;
то само значение ival меняется, но ссылка ri по-прежнему адресует ival.