Объектно-ориентированное программирование, страница 5

{

Node n1("First"), n2("Second");

n1.GetData();     n2.GetData();

n2.SetNextTo(n1);

n1.GetNext()->GetData();

}

Конструктор класса Node дважды вызывается в функции main при объявлении объектов n1 и n2. Каждый раз он запрашивает память из области heap и размещает там строку символов (содержимое узла списка), которая передается конструктору в качестве параметра. Метод класса GetData выводит в поток stdout (стандартный выходной поток) поле data того объекта, которому передается сообщение. Здесь мы специально вставили выбор с помощью указателя (this->), чтобы проиллюстрировать то, что обычно делает компилятор. Его можно оставить, а можно и убрать.

Любое обращение к data в любом методе класса Node компилятор расширяет до this->data. Оператор программы n1.GetData(); выведет строку "First", а оператор n2.GetData(); — строку "Second". Внутри конструктора мы обращаемся к переменной data, не указывая явно какому объекту принадлежит это поле данных. Компилятор C++ сам расширяет это обращение до this->data. Метод SetNextTo получает в качестве параметра ссылку на объект класса Node. Поскольку этот метод принадлежит тому же классу, что и параметр, то внутренняя компонента next объекта n прямо доступна в данном методе. Ей присваивается адрес объекта, который инициировал метод (n.next=this;). Адрес содержится в переменной this. Так как в нашем случае объекту n2 послано сообщение SetNextTo(n1), то указатель this содержит адрес объекта n2, и он присваивается полю next объекта n1. Получается, что объект n1 имеет в качестве следующего узла списка объект n2 или, можно сказать, что n2 зацеплен за n1.

Метод GetNext позволяет получить адрес следующего элемента списка, то есть поле next объекта, которому послано сообщение. В последней строке функции main он используется для того, чтобы добыть объект, стоящий в списке за объектом n1. Выражение n1.GetNext() имеет результатом адрес объекта n2, которому посылается сообщение GetData. Следовательно, результатом всего выражения будет вывод поля data объекта n2, то есть строка "Second". Подумайте, можно ли заменить тело метода GetNext на return this->next;?

Теперь рассмотрим другие возможности вложения. Прежде всего объекты какого-либо класса могут быть вложены в число данных другого класса. Другой возможный способ вложения — это когда описание какого-то класса вложено в описание другого класса. Например, в библиотеке классов OWL (Object Windows Library), в классы семейства TDialog вложено объявление класса TData, который играет вспомогательную, но очень важную роль. Рассмотрим сначала первый случай вложения.

class One    // Класс One, объекты которого вложены в другой класс Two

{

int i1;     // Private-данное класса One

public:

One (int i) { i1=i; }   // Конструктор с параметром

void p() { printf ("\n i1=%d",i1); } // Вывод private-компоненты i1

};

class Two  // Класс Two содержит объекты класса One

{

int i2;           // Private-данное класса Two

One c1, d1;       // Объекты другого класса

public:            // Особенность конструктора

Two (int j, int k, int l) : c1(k), d1(l)    // Инициализация в заголовке

{ i2=j; }         // Присвоение в теле конструктора

void p() { printf ("\n i2=%d",i2); } // Методы вывода

void p1() { c1.p(); }

void p2() { d1.One::p(); }

};

void main()  //==== Использование объектов классов

{

One man(1);

Two men (2, 3, 4);

man.p();  men.p();

men.p1(); men.p2();

}

Анализируя этот пример, обратите внимание на конструктор класса Two. Он содержит в себе кроме собственного тела еще список инициализации двух вложенных объектов: c1(k), d1(l) класса One. Инициализаторы внутренних объектов могут следовать в произвольном порядке, но они должны быть расположены в заголовке конструктора (за двоеточием и разделяться запятыми). Выполняются они до исполнения тела конструктора объемлющего класса. При выходе из области действия объекта внешнего класса деструкторы классов вложенных объектов также выполняются до деструктора внешнего класса.

Отметьте, что оба класса в нашем примере имеют в своем составе метод с именем p(). Cпецификатор One в теле метода p2() не обязателен, но он помогает читающему текст программы. Компилятор распознает какой из двух методов следует вызвать по принадлежности объекта к конкретному классу. Значения переменных, которые будут выведены на экран, равны: i1=1, i2=2, i1=3, i1=4. Отметим следующие различия:

¨  i1=1 — это значение внутренней переменной i1 объекта man класса One;

¨  i2=2 — это значение внутренней переменной i2 объекта men класса Two;

¨  i1=3 — это значение внутренней переменной i1 объекта c1 класса One (объект c1 в свою очередь вложен в класс Two и, следовательно, является внутренним данным объекта men класса Two);

¨  i1=4 — это значение внутренней переменной i1 объекта d1 класса One, который также вложен в класс Two.

Описание класса или структуры является по сути описанием нового типа данных. При описании типа возможно сложное вложение классов, так же, как и для структур языка С. Например,

class A      // Какай-то класс

{

public:

int i;      // Его переменная

class B     // Вложенное описание класса B

{          

public:

  int j;     // Данное класса B

  B (int i=0) { j=i; } // Конструктор вложенного класса

  void p() { printf("\nj=%d",j); }  // Метод вложенного класса

}

 b, *pb;         // Объект и указатель вложенного класса

A (int ii, int jj) { i=ii; b.j=jj; pb=&b; }  // Конструктор объемлющего класса Инициализация объекта и указателя

void Show () { printf("\ni=%d b.j=%d",i,b.j); } // Метод объемлющего класса

};

void main()            // Иллюстрация использования

{

A a(1,2);             // Объект объемлющего класса

A *pa = new A(3,4);   // Указатель объемлющего класса

A::B bb(5);           // Объект вложенного класса

A::B *pbb = new A::B(6);  // Указатель вложенного класса

a.Show();       pa->Show();// Разнообразие обращений к методам

a.b.p();        a.pb->p();