Литмир - Электронная Библиотека
A
A

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Итак, правильный ответ формулируется так: выбор зависит от природы функции.

• Для маленьких объектов предпочтительнее передача по значению.

• Для функций, допускающих в качестве своего аргумента “нулевой объект” (представленный значением

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. Пример: списки

Наиболее распространенными и полезными структурами данных являются списки. Как правило, список создается с помощью узлов, каждый из которых содержит определенную информацию и указатель на другие узлы. Это — классический пример использования указателей. Например, короткий список норвежских богов можно представить в следующем виде.

Программирование. Принципы и практика использования C++ Исправленное издание - _194.png

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Такой список называют двусвязным (doubly-linked list), поскольку в нем существуют предшествующий и последующий узлы. Список, в котором существуют только последующие узлы, называют односвязным (singly-linked list). Мы используем двусвязные узлы, когда хотим облегчить удаление элемента. Узлы списка определяются следующим образом:

struct Link {

  string value;

  Link* prev;

  Link* succ;

  Link(const string& v,Link* p = 0,Link* s = 0)

      :value(v),prev(p),succ(s) { }

};

Иначе говоря, имея объект типа

Link
, мы можем получить доступ к последующему элементу, используя указатель
succ
, а к предыдущему элементу — используя указатель
prev
. Нулевой указатель позволяет указать, что узел не имеет предшествующего или последующего узла. Список норвежских богов можно закодировать так:

Link* norse_gods = new Link("Thor",0,0);

norse_gods = new Link("Odin",0,norse_gods);

norse_gods–>succ–>prev = norse_gods;

norse_gods = new Link("Freia",0,norse_gods);

norse_gods–>succ–>prev = norse_gods;

Мы создали этот список с помощью структуры

Link
: во главе списка находится
Тор
, за ним следует Один, являющийся предшественником Тора, а завершает список Фрея — предшественница Одина. Следуя за указателями. можете убедиться, что мы правы и каждый указатель
succ
и
prev
ссылается на правильного бога. Однако этот код мало понятен, так как мы не определили явно и не присвоили имя операции вставки.

Link* insert(Link* p, Link* n) // вставка n перед p ( фрагмент )

{

  n–>succ = p;       // p следует после n

  p–>prev–>succ = n; // n следует после предшественника p

  n–>prev = p–>prev; // предшественник p становится

                     // предшественником n

  p–>prev = n;       // n становится предшественником p

  return n;

}

Этот фрагмент программы работает, если указатель

p
действительно ссылается на объект типа
Link
и этот объект действительно имеет предшественника. Убедитесь, что это именно так. Размышляя об указателях и связанных структурах, таких как список, состоящий из объектов типа
Link
, мы практически всегда рисуем на бумаге диаграммы, состоящие из прямоугольников и стрелок, чтобы проверить программу на небольших примерах. Пожалуйста, не пренебрегайте этим эффективным средством.

Приведенная версия функции

insert()
неполна, поскольку в ней не предусмотрен случай, когда указатели
n
,
p
или
p–>prev
равны
0
. Добавив соответствующую проверку, мы получим немного более сложный, но зато правильный вариант функции
insert
.

Link* insert(Link* p, Link* n) // вставляет n перед p; возвращает n

{

  if (n==0) return p;

  if (p==0) return n;

  n–>succ = p;         // p следует после n

  if (p–>prev) p–>prev–>succ = n;

  n–>prev = p–>prev;   // предшественник p становится

                       // предшественником n

233
{"b":"847443","o":1}