3: Main()
4: {
5: int ivar = 1234;
// Переменной присвоено значение
6: int *iptr = &ivar;
// Указателю присвоен адрес ivar
7: int &iref = ivar;
// Ссылка ассоциирована с ivar
8: int *p = &iref;
// Указателю присвоен адрес iref
9:
10: cout « "ivar == " « ivar « '\n';
11: cout « "*iptr == " « *iptr « '\n';
12: cout « "iref == " « iref « '\n';
13: cout « "*p == " << *p « '\n';
14: Return 0;
15:}
В строках 5-8 объявляются четыре переменные. Первая — простое целое ivar, инициализированное значением 1234. Следующая, — указатель на целое с именем iptr, которой присвоен адрес ivar. Третья объявлена как ссылочная переменная. Строка
int &iref = ivar;
объявляет iref как ссылку на целое значение. Что касается iref, то ей присваивается ivar не как значение переменной, а как адрес в памяти. Следуя объявлению, iref указывает на ivar подобно указателю. Как показано в строке 12, оператор
cout « "iref == " « iref « '\n';
выводит значение ivar — 1234. Это происходит благодаря использованию iref в качестве ссылки на местоположение переменной ivar в памяти.
Четвертое объявление в строке 8 создает еще один указатель — p, которому присваивается адрес, хранящийся в iref. Строки 6 и 8 дают одинаковый результат. В обеих строках создаются указатели, ссылающиеся на ivar. Как показано в строке 13, употребление указателя в выражении *p дает доступ к значению ivar. На рис. 1.2 проиллюстрирована взаимосвязь переменных из листинга 1.15. Как видно из иллюстрации, использование iref очень похоже на использование разыменования указателя *p. В обоих выражениях осуществляется доступ к значению ivar — 1234.
Рис. 1.2. Взаимосвязь переменных в листинге 1.15, REFVALCPP
При использовании ссылок следует помнить одно правило: однажды инициализировав ссылку, ей нельзя присвоить другое значение.
В REFVAL.CPP вы не можете объявить новую целочисленную переменную и присвоить ее адрес iref:
int anotherInt;
iref = anotherInt; // ???
Это присваивание не заставит iref ссылаться на anotherInt. Это выражение присвоит значение anotherInt объекту, на который iref ссылается, другими словами — ivar. Так же вы можете присваивать новые значения объекту, к которому относится ссылка. Например, выражение
iref = 4321;
присвоит ivar значение 4321. Эти правила будет легче запомнить, если представить себе ссылки как неизменяемые альтернативные имена, похожие на указатели (но не идентичные им!).
Как следует из вышесказанного, ссылки должны быть инициализированы при их объявлении. Строка 7, к примеру, не может быть написана как
int &iref; // ???
В отличие от указателей, которые могут быть объявлены в неинициализированном состоянии или установлены в нуль (если указатель не ссылается на необходимые данные), ссылки всегда ссылаются на объект. Для ссылок не существует аналога нулевого указателя.
Ссылки нельзя инициализировать значениями в следующих четырех особых случаях: при их объявлении с ключевым словом ехtеrn, при использовании в качестве параметров функции, при использовании в качестве типа возвращаемого значения функции или в объявлениях классов.
Не существует операторов, непосредственно производящих действия над ссылками. Все операции совершаются только над соответствующими объектами.
Добавьте между строками 13 и 14 листинга 12.15 следующий оператор:
iref++;
Компилятор сгенерирует код, инкрементирующий ivar — переменную, на которую ссылается iref. Оператор не совершит никаких действий непосредственно над iref.
Ссылки также могут ссылаться на константы.
Разумеется, вы не можете присвоить значение непосредственно ссылке. Объявление
int &iref = 1234; // ???
вызовет сообщение об ошибке при компиляции, поскольку 1234 не является объектом, на который iref сможет сослаться. Вы можете, конечно, объявить константную ссылку на допустимый объект следующим образом:
int x = 1234; // Объявление и инициализация целочисленного объекта
const int &iref = x; // Объявление iref как константной ссылки на x
Этот прием очень полезен, поскольку эффективно превращает переменную в константу, которую нельзя подвергнуть изменениям. Например, вы можете записать значение x в выходной поток с помощью ссылки следующим образом:
cout << "iref = " << iref << '\n'; // Все в порядке
Вы также можете изменить значение переменной x:
x = 4321; // Тоже годится
Поскольку iref — константа, то вы, конечно, не сможете присвоить новое значение x через ссылку:
iref = 4321; // ??? Нельзя
Попытка скомпилировать последнее выражение приведет к выдаче сообщения об ошибке "Cannot modify a const object in function main()" (Нельзя изменить константный объект в функции main()).
Ссылки в качестве параметров функций
Cами по себе ссылочные переменные используются довольно редко.
В качестве параметров функции ссылки имеют более широкое применение и предлагают полезную альтернативу доступа к параметрам. Ссылки особенно полезны в функциях, возвращающих несколько объектов, например два или более вводимых значения.
Листинг 1.16 —введите сумму платежей и налогов после их запроса. Программа выведет цену и сумму налога на введенные вами значения.
Листинг 1.16. TAX.CPP (использование ссылок в качестве параметров функции)
1: #include <iostream.h>
2:
3: #include <stdlib.h>
4: #include <stdio.h>
5:
6: double GetDouble(const char *prompt);
7: void GetData(double &paid, double &rate);
8: void Calculate(const double paid, const double rate,
9: double &list, double &tax);
10:
11: main()
12: {
13: double paid, rate, list, tax; 14: GetData(paid, rate);
15: Calculate(paid, rate, list, tax);
16: printf("List pride = $%8.2f\n", list); 17: printf("Tax paid = $%8.2f\n", tax);
18: return 0;
19:} 20:
21: // Возвращает одно вещественное значение двойной точности
22: double GetDouble(const char *prompt)
23: {
24: char s[20]; // Строка для ввода
25: printf(prompt); // Отображение строки запроса
26: scanf("%20s", s); // Ввести данные как строку
27: return atof(s); // Возвратить введенные данные
28: }
29:
30: // Ввод цены и ставки налога вещественными значениями двойной точности
31: // Замечание: изменяет значение paid и rate
32: void GetData(double &paid, double &rate)
33: {
34: paid = GetDouble("Price paid?");
35: rate = GetDouble("Tax rate (ex: .06)?");
36: }
37:
38: // Вычисляет цену и налог no сумме и ставке
39: // Замечание: изменяет значение list и tax
40: void Calculate(const double paid, const double rate,
41: double &list, double &tax)
42: {
43: list = paid / (1 + rate);
44: tax = paid - list;
45:}
В программе определены три функции. Первая функция запрашивает и возвращает вещественное значение двойной точности — обычное описание функции, возвращающей некоторое значение:
double GetDouble(const char *prompt);
Вторая функция демонстрирует использование ссылочных параметров для возврата двух или более значений. Заметьте, что обоим параметрам функции предшествует символ &, означающий, что эти параметры передаются по ссылке (т.е. в GetData передаются их адреса вместо значений):
void GetData (double &paid, double &rate);
Рассмотрим тело функции GetData. В нем дважды вызывается GetDouble с аргументом-строкой, которая должна отображаться, и полученные значения присваиваются ссылочным параметрам paid и rate:
paid - GetDouble("Price paid?");
rate = GetDouble("Tax rate (ex: .06)?");
Поскольку paid и rate — ссылки, значения присваиваются объектам, переданным функции GetDouble(). Например, в функции main() следующий оператор вводит значения в две переменные, также названные paid и rate:
GetData(paid, rate);
Некоторые критикуют передачу параметров функции по ссылке, верно замечая, что такие вызовы выглядят обманчиво, поскольку аргументы в них передаются посылке неявно. Ключ к решению этой проблемы и эффективному использованию ссылочных параметров — хорошее документирование программ. Обязательно должен присутствовать комментарий, в котором указывается, что вызов GetData изменяет значения paid и rate.
Третья, и последняя, функция в примере вычисляет цену и налог к уплате, основываясь на действительной цене и ставке налога. Первых два параметра, paid и rate, — входные параметры функции. Поскольку функция не изменяет их, они объявлены константами, и передаются только их значения (точнее, копии их значений). Вторых два параметра, list и tax, — это выходные значения функции. Эти значения изменяются функцией и, следовательно, передаются по ссылке:
void Calculate(const double paid, const double rate, double &list, double &tax)
Функция Calculate присваивает новые значения list и tax в следующих операторах:
list = paid / (1 + rate);
tax = paid - list;
Поскольку параметры — ссылки, в действительности используются переменные, переданные функции Calculate(). Операция присваивания происходит по ссылке, вследствие чего изменяются действительные переменные, когда в функции main происходит следующий вызов:
Calculate(paid, rate, list, tax);
Аргументы, передающиеся по значению, невозможно отличить от аргументов, передающихся по ссылке. Хорошее документирование функции Calculate уменьшит опасность подобных ошибок.
Ссылки и указатели в качестве параметров тесно связаны. В самом деле, ссылки могут интерпретироваться компилятором С++ как указатели. Для того чтобы вникнуть в суть этой идеи, рассмотрим следующую небольшую функцию:
void f(int *ip)
*ip = 1234;
Внутри этой функции осуществляется доступ к переданному аргументу, адрес которого хранится в указателе ip, с помощью оператора вида
f(&ivar); // Передача адреса ivar
Внутри функции выражение *ip = 1234; присваивает значение 1234 переменной ivar, адрес которой передается функции f(). Теперь рассмотрим аналогичную функцию, использующую ссылочные параметры:
void f(int &ir)
{ ir = 1234;
}
Указатель ip заменен ссылкой ir, которой функция присваивает 1234. Выражение
f(ivar); // Передача ivar по ссылке
присваивает значение ссылочному объекту: передает ivar по ссылке функции f(), и поскольку ir ссылается на ivar, то ivar присваивается 1234.
Ссылки в качестве результатов функций
Функции могут возвращать ссылки на объекты при условии, что эти объекты существуют, когда функция неактивна. (Другими словами, функции не могут возвращать ссылки на локальные автоматические переменные).
Для функции, объявленной как
double &ref(double d);
необходим один вещественный аргумент двойной точности, и она возвращает ссылку на объект того же типа, предположительно объявленный где-то в другом месте.
Листинг 1.17 демонстрирует использование ссылочных функций для скрытия внутреннего представления структуры данных. Это используется при реализации больших проектов, особенно когда структуры данных могут изменяться, что, в свою очередь, может послужить причиной коренной переделки программ, написанных на обычном С. Ссылочные функции в С++ могут облегчить этот рутинный процесс, обеспечив доступ к структурам данных, подобным массивам, без привязки кода к физическим свойствам данных. Скомпилируйте и запустите программу, по приглашению введите индексное значение от 0 до 9, затем — вещественное значение. Для завершения работы программы нажмите по приглашению <Enter>.
Листинг 1.17. REFFUNC.CPP (ссылочные функции)
1 : #include <iostream.h>
2: #include <stdlib.h>
3: #include <string.h>
4:
5: #define FALSE 0
6: #define TRUE 1
7: #define SIZE 10
8: #define BUFLEN 20
9:
10: double &ref(int index);
11: void ShowArray(void); 12:
13: double array[SIZE]; 14:
15: main()
16: {
17: int done = FALSE, index;
18: char buffer[BUFLEN]; 19:
20: for (index = 0; index < SIZE; index++) 21: ref(index) = index; // Присваивание ссылочной функции!
22: while (!done) {
23: ShowArray();
24: cout « "\nEnter index from 0 to 9, Enter to quit:";
25: cin.getline(buffer, BUFLEN);
26: done = (strlen(buffer) == 0);
27: if (!done) { 28: index = atoi(buffer);
29: cout << "Enter floating-point value:";
30: cin.getline(buffer, BUFLEN); 31: ref(index) = atof(buffer); 32: } 33: }
34: return 0;
35: }
37: double &ref(int index)
38: {
39: if ((index < 0) || (index >= SIZE))
40: index = 0;