// и присваиваем его вновь
Этот стиль предпочтительнее для небольших объектов, таких как переменные типа int
. Однако передача значений туда и обратно не всегда реальна. Например, можно написать функцию, модифицирующую огромную структуру данных, такую как вектор, содержащий 10 тыс. переменных типа int
; мы не можем копировать эти 40 тыс. байтов (как минимум, вдвое) с достаточной эффективностью.
Как сделать выбор между передачей аргумента по ссылке и с помощью указателя? К сожалению, каждый из этих вариантов имеет свои преимущества и недостатки, поэтому ответ на это вопрос не ясен. Каждый программист должен принимать решение в зависимости от ситуации.
Использование передачи аргумента с помощью ссылок предостерегает программиста о том, что значение может измениться. Рассмотрим пример.
int x = 7;
incr_p(&x); // здесь необходим оператор &
incr_r(x);
Необходимость использования оператора &
в вызове функции incr_p(&x)
обусловлена тем, что пользователь должен знать о том, что переменная x
может измениться. В противоположность этому вызов функции incr_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()
выглядит проще и приятнее, чем раньше. В главе 5 было показано, как устранить проблему, связанную с некорректными аргументами. В противоположность этому пользователи, применяющие ссылки (например, в функции incr_r()
), должны предполагать, что ссылка связана с объектом. Если “передача пустоты” (когда объект на самом деле не передается) с точки зрения семантики функции вполне допустима, аргумент следует передавать с помощью указателя. p==0
в этом случае следует генерировать исключение.
• Для маленьких объектов предпочтительнее передача по значению.
• Для функций, допускающих в качестве своего аргумента “нулевой объект” (представленный значением 0
), следует использовать передачу указателя (и не забывать проверку нуля).
• В противном случае в качестве параметра следует использовать ссылку.
См. также раздел 8.5.6.
17.9.2. Указатели, ссылки и наследование
В разделе 14.3 мы видели, как можно использовать производный класс, такой как Circle
, вместо объекта его открытого базового класса Shape
. Эту идею можно выразить в терминах указателей или ссылок: указатель Circle*
можно неявно преобразовать в указатель Shape
, поскольку класс Shape
является открытым базовым классом по отношению к классу Circle
. Рассмотрим пример.
void rotate(Shape* s, int n); // поворачиваем фигуру *s на угол n
Shape* p = new Circle(Point(100,100),40);
Circle c(Point(200,200),50);
rotate(&c,45);
Это можно сделать и с помощью ссылок.
void rotate(Shape& s, int n); // поворачиваем фигуру *s на угол n
Shape& r = c;
rotate(c,75);
Этот факт является чрезвычайно важным для большинства объектно-ориентированных технологий программирования (см. разделы 14.3, 14.4).
17.9.3. Пример: списки
Наиболее распространенными и полезными структурами данных являются списки. Как правило, список создается с помощью узлов, каждый из которых содержит определенную информацию и указатель на другие узлы. Это — классический пример использования указателей. Например, короткий список норвежских богов можно представить в следующем виде.