Ссылка (неявный указатель)
Ссылка представляет собой на первый взгляд странное сочетание обычной переменной и неявного указателя:
-синтаксис определения ссылки имеет вид синтаксиса определения указателя, в котором операция "*" заменена на "&";
-в процессе работы к ссылке обращаются как к обычной, с точки зрения синтаксиса, переменной, без использования операции косвенного обращения по указателю;
-транслятор создает для ссылки неявный указатель, который может быть настроен на какую-либо переменную;
-при выполнении действий по чтению или записи значения ссылки транслятором выполняется косвенное обращение по соответствующему неявному указателю;
-при определении ссылка может быть инициализирована переменной того же типа, при этом неявный указатель получает значение адреса этой переменной. При инициализации константой создается неявная переменная со значением константы;
-размерностью ссылки является размерность указуемого типа данных, а не указателя.
.
struct dat { int day,month, year };
//--------------------------------------------------------
// Си++Эквивалент в "классическом" Си
//--------------------------------------------------------
//--------------- Инициализация константой --------------
int &a = 5; int aа = 5, *pa = &аa;
//--------------- Инициализация переменной --------------
int x; int x,*pa;
int &a = x; pa = &x;
a = 5; *pa = 5;
//-------------- Ссылка на структуру ---------------------
dat x; dat x;
dat &b = x; dat *pb = &x;
dat &c = {12,12,1990}; dat cc = {12,12,1990};
dat *pc = &cc;
b.year = 1990; pb->year= 1990;
c.day=b.day+3; pc->day = pb->day+3;
// копирование структуры
c = b; *pc = *pb;
...sizeof(b)... ...sizeof(x)...
...sizeof(*pb)... // но не sizeof(pb)
Единственный разумный вывод, который можно сделать из приведенного примера, состоит в том, что ссылка представляет собой псевдо-переменную, которая отображается в процессе работы с ней на ту переменную или константу, которой она была инициализирована.
Иначе можно сказать, что она представляет собой синтаксический синоним переменной или константы, на которую она ссылается. Как бы там ни было, но все операции над переменной-ссылкой производятся над отображенной через нее переменной.
ССЫЛКА -- псевдо-переменная, которая отображается на переменную или константу, которой она была инициализирована
В чистом виде ссылка практически не используется. Основная ее область применения -формальные параметры и результат функции. Основные свойства ссылки -синтаксис обычной переменной в операциях с ней и реализация в виде неявного указателя -сохраняются. Инициализация же заменяется назначением неявной ссылки на фактический параметр или на переменную, сохраняющую результат.
Для формального параметра -ссылки:
-формальному параметру соответствует неявный указатель;
-формальный параметр в теле функции и фактический параметр в вызове имеют синтаксис обычной переменной;
-формальному параметру в теле функции соответствует косвенное обращение по неявному указателю;
-фактический параметр в вызове функции заменяется указателем на него.
.
void INC(int &a) void INC(int *pa)
//----------------------------------------------------------
// Си++ Эквивалент в "классическом Си"
//----------------------------------------------------------
{ {
a++; (*pa)++;
} }
... ...
int b = 5; int b = 5;
INC(b); INC(&b);
При изменении значения формального параметра - ссылки происходит аналогичное изменение фактического параметра. Такой способ передачи параметров в языках программирования так и называется -ПЕРЕДАЧА ПАРАМЕТРА ПО ССЫЛКЕ.
Несколько сложнее обстоит дело со ссылкой -результатом функции:
-результат функции в операторе return и результат функции в выражении имеют синтаксис (тип) переменной;
-в качестве результата возвращается неявный указатель, назначенный на переменную (адресное выражение), стоящее в операторе return;
-если в выражении, содержащем вызов функции, производится преобразование результата в указатель на него (например, результат является фактическим параметром - ссылкой в следующем вызове), то неявный указатель передается без изменения в качестве операнда следующей операции;
- если же используется сам результат (например, сохраняется путем присваивания), то производится косвенное обращение по неявному указателю.
.
int &COPY(int *pa) int *COPY(int *pa)
//---------------------------------------------------------
// Си++ Эквивалент в "классическом Си"
//---------------------------------------------------------
{ {
return(*pa); return(pa);
} }
... ...
int a,b = 5,*pc; int a,b = 5,*pc;
//
//-- Косвенное обращение по неявному указателю - результату
a = COPY(&b); a = *COPY(&b);
//
//-- Передача неявного указателя без изменения ------------
pc = &COPY(&b); pc = COPY(&b);
pc = &COPY(&COPY(&b)); pc = COPY(COPY(&b));
Обратите внимание, что тип результата в операторе return(*pa) -не указатель, а указуемый тип. То же самое и в выражении, где находится вызов функции COPY(&b), то есть по синтаксису функция COPY возвращает не тип-указатель, а тип указуемой переменной.
Заменив в формальном параметре указатель на ссылку, получим наиболее распространенный способ "прозрачной" передачи ссылки через функцию. Он позволяет организовать цепочку вызовов функций, передающих неявный указатель с выхода функции на вход следующей:
.
int &INC(int &a ) int *INC(int *pa)
//---------------------------------------------------------
Си++ Эквивалент в "классическом Си"
//---------------------------------------------------------
{ {
a++; (*pa)++;
return(a); return(pa);
} }
... ...
int а,b = 5; int а,b = 5;
а = INC( INC( INC(b))); a = *INC( INC( INC(&b)));
Не следует забывать, что результат-ссылка всегда является неявным указателем, и он не может указывать на переменные, недействительные после выхода из функции. Поэтому нельзя возвращать таким способом автоматические переменные и выражения, которые не являются адресными.
Если рассматривать функцию как конвейер, передающий некоторую переменную со входа функции на выход, то имеется три способа такой передачи:
-значением;
-обычным указателем;
-ссылкой.
//------------------------------------------------------bk72-01.cpp
//--------------------------------------------------------
// 1. Параметр и результат - значения
//--------------------------------------------------------
struct dat { int day,month,year; };
// Эквивалент
dat Inc(dat x) // void Inc( dat x, dat *pout)
{ // {
x.day++; // x.day++;
return x; // *pout = x;}
} //
void main() //
{ //
dat a,b,*p; //
a = Inc(Inc(b)); // dat tmp1; Inc(x=b,&tmp1);
p = &Inc(b); // dat tmp2; Inc(x=tmp1; &tmp2);
a = *p; // dat tmp3; Inc(Inc(x=b,&tmp3);
} // p = &tmp3; a = *p;
//-------------------------------------------------------
// 2.Параметр и результат - указатели
//-------------------------------------------------------
struct dat { int day,month,year; };
dat* Inc(dat* x) //
{ //
x->day++; //
return x; //
} //
void main() //
{ //
dat a,b,*p; //
a = *Inc(Inc(&b)); //
p = Inc(&b); //
a = *p; //
} //
//-------------------------------------------------------
// 3. Параметр и результат - ссылки
//-------------------------------------------------------
struct dat { int day,month,year; };
dat& Inc(dat& x) // Эквивалент
{ // dat *Inc(dat *px)
x.day++; // { (*px).day++;
return x; // return px;}
} //
void main() //
{ //
dat a,b,*p; //
a = Inc(Inc(b)); // dat *pp; pp=Inc(&b); a=*Inc(pp);
p = &Inc(b); // p = Inc( &b) ;
a = *p; // a = *p;
} //
Сравнение этих примеров показывает следующее:
-примеры со ссылками и указателями идентичны по реализации, но отличаются по синтаксису вызова функции;
-примеры со значениями и ссылками отличаются по реализации, но идентичны по синтаксису вызова функции.
-после введения понятия ссылки список фактических параметров при вызове функции недостаточен для определения транслятором способа их передачи (значением или ссылкой), поэтому в Си++ для каждой внешней функции необходимо объявлять ее прототип.
Ссылка в качестве формального параметра используется всегда, когда требуется изменить значение соответствующего фактического параметра при вызове функции. Тип параметра может быть любым. В качестве примера рассмотрим ССЫЛКУ НА УКАЗАТЕЛЬ на текущий элемент списка и на текущую вершину двоичного дерева в операциях включения. В соответствии с принципом контекстного определения типа данных последовательность операций "&,*" читается от переменной p справа налево как ссылка на указатель.
//------------------------------------------------------bk72-02.cpp
//------ Включение в список с использованием ссылки на указатель и рекурсии
struct list
{
int val;
list *next;
} *head=NULL;
void InsertList(list *&ph, int n)
{
list *pnew;
if (ph==NULL || n < ph->val)
{
pnew = new list;
pnew->val=n;
pnew->next=ph;
ph=pnew;
return;
}
InsertList(ph->next, n);
}
//------ Включение в дерево с использованием ссылки на указатель и рекурсии
struct btree
{
int val;
btree *l,*r;
} *hd=NULL;
void Insert(btree *&p, int n)
{
if (p==NULL)
{
btree *pnew=new btree;
pnew->val=n;
pnew->l=pnew->r=NULL;
p=pnew;
return;
}
if (p->val < n) Insert(p->l,n);
else Insert(p->r,n);
}