Программирование. Принципы и практика использования C++ Исправленное издание, стр. 232
• После инициализации ссылку невозможно установить на другой объект.
• Присвоение ссылок основано на глубоком копировании (новое значение присваивается объекту, на который указывает ссылка); присвоение указателей не использует глубокое копирование (новое значение присваивается указателю, а не объекту).
• Нулевые указатели представляют опасность.
Рассмотрим пример.
int x = 10;int* p = &x; // для получения указателя нужен оператор &*p = 7; // для присвоения значения переменной x // через указатель p используется *int x2 = *p; // считываем переменную x с помощью указателя pint* p2 = &x2; // получаем указатель на другую переменную // типа intp2 = p; // указатели p2 и p ссылаются на переменную xp = &x2; // указатель p ссылается на другой объектСоответствующий пример, касающийся ссылок, приведен ниже.
int y = 10;int& r = y; // символ & означает тип, а не инициализаторr = 7; // присвоение значения переменной y // с помощью ссылки r (оператор * не нужен)int y2 = r; // считываем переменную y с помощью ссылки r // (оператор * не нужен)int& r2 = y2; // ссылка на другую переменную типа intr2 = r; // значение переменной y присваивается // переменной y2r = &y2; // ошибка: нельзя изменить значение ссылки // (нельзя присвоить переменную int* ссылке int&)Обратите внимание на последний пример; это значит не только то, что эта конструкция неработоспособна, — после инициализации невозможно связать ссылку с другим объектом. Если вам нужно указать на другой объект, используйте указатель. Использование указателей описано в разделе 17.9.3.
Как ссылка, так и указатель основаны на адресации памяти, но предоставляют программисту разные возможности.
17.9.1. Указатели и ссылки как параметры функций
Если хотите изменить значение переменной на значение, вычисленное функцией, у вас есть три варианта. Рассмотрим пример.
int incr_v(int x) { return x+1; } // вычисляет и возвращает новое // значениеvoid incr_p(int* p) { ++*p; } // передает указатель // (разыменовывает его // и увеличивает значение // на единицу)void incr_r(int& r) { ++r; } // передает ссылкуКакой выбор вы сделаете? Скорее всего, выберете возвращение значения (которое наиболее уязвимо к ошибкам).
int x = 2;x = incr_v(x); // копируем x в incr_v(); затем копируем результат // и присваиваем его вновьЭтот стиль предпочтительнее для небольших объектов, таких как переменные типа
intintКак сделать выбор между передачей аргумента по ссылке и с помощью указателя? К сожалению, каждый из этих вариантов имеет свои преимущества и недостатки, поэтому ответ на это вопрос не ясен. Каждый программист должен принимать решение в зависимости от ситуации.
Использование передачи аргумента с помощью ссылок предостерегает программиста о том, что значение может измениться. Рассмотрим пример.
int x = 7;incr_p(&x); // здесь необходим оператор &incr_r(x);Необходимость использования оператора
&incr_p(&x)xincr_r(x)
incr_p(0); // крах: функция incr_p() пытается разыменовать нульint* p = 0;incr_p(p); // крах: функция incr_p() пытается разыменовать нульСовершенно очевидно, что это ужасно. Человек, написавший функцию,
incr_p()void incr_p(int* p){ if (p==0) error("Функции incr_p() передан нулевой указатель"); ++*p; // разыменовываем указатель и увеличиваем на единицу // объект, на который он установлен}Теперь функция
incr_p()incr_r()p==0