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

Вопросы и упражнения

Разместите части программы в различных файлах. Осуществите сборку программы с помощью проекта. Какому объекту принадлежит переменная top внутри метода Push? Прокомментируйте условия продолжения циклов в функции Reverse.

Частные случаи

Особое место среди всех возможных конструкторов занимает конструктор без параметров, часто называемый конструктором по умолчанию (default constructor). Если при описании класса программист явно не задает конструктор, то компилятор сам генерирует default constructor, который и используется для создания объектов класса. Для деструктора класса тоже действует правило по умолчанию, аналогичное правилу для конструктора. Если в классе не было явного объявления деструктора, то компилятор сам генерирует деструктор. Отметьте, что как и другие функции, конструктор может иметь аргументы по умолчанию. Например,

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

{

int i;     // Private-переменная класса

public:    // Конструктор с аргументом по умолчанию

A (int j=0) { i=j; }; // Инициализация переменной i

};

void main()

{

A a(10); // Число 10 передается в качестве параметра.

A b;     // По умолчанию переменной i будет присвоен нуль

}

Если имеется несколько конструкторов, то следует избегать двусмысленности, допущенной в следующем примере при вызове конструктора.

class A

{

int i;

public:

A();     // Конструктор по умолчанию

A (int j=0) { i=j; }  // Конструктор с аргументом по умолчанию

};

void main()

{

A a(10); // Здесь вызывается конструктор A (int=0), так как задан параметр 10

A b;     // Ошибка. Не ясно какой из двух конструкторов должен быть вызван

}

Рассмотрим еще один частный вид конструктора — конструктор копирования. Так называется конструктор, параметр которого имеет тип константной ссылки на объект своего класса. Например, конструктор копирования для класса A имеет прототип: A::A(const A&). Компилятор С++ сам генерирует конструктор копирования для класса, если он необходим и только если в классе не был описан никакой другой конструктор. При создании сложных типов (структур, массивов) имеет смысл разработать свой собственный конструктор копирования. Конструкторы копирования срабатывают при инициализации объекта класса другим объектом. Например,

class A

{

int i;

public:

A()           { i=0; };  // Конструктор по умолчанию

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

A(const A& a)   { i=a.i; }; // Конструктор копирования

};

void main()  // Пример использования

{

A a1;       // Работает конструктор по умолчанию

A a2 = a1;  // Вызывается конструктор копирования

A a3(a1);   // Вызывается конструктор копирования

A a4(10);   // Вызывается конструктор A::A(int)

A a5 = 7;   // Также вызывается A::A(int)

}

Если бы конструктора копирования не было в описании класса A, то при вызове сгенерированного компилятором конструктора копирования значения всех данных объекта a1 были бы почленно скопированы в соответствующие данные объектов a2 и a3. Конструктор не может иметь в качестве параметра объект своего собственного класса. Если в примере выше декларировать конструктор A (A a);, то компилятор воспримет это как ошибку. Разнообразие конструкций языка С++ иллюстрируется еще одной возможностью инициализации внутренних данных класса. Две следующие формы конструктора эквивалентны:

class A

{

private:

int i;

double d;

public:

A(int, double);     // Декларация конструктора

};

A::A (int ii, double dd) //======= Две формы тела конструктора

{

i=ii;  d=dd;      // Инициализация в теле конструктора

}

A::A (int ii, double dd) : i(ii), d(dd) // Инициализация списком в заголовке.

{

//Тело конструктора пусто

}

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

class A

{

int& i;               // Переменная i - ссылка

public:

A (int j) : i(j) { };   // i ссылается на j

};

Попытка перенести инициализацию (i=j;) в тело конструктора вызывает сообщение об ошибке.

Вопросы и упражнения

Дополните класс Stack конструкторами по умолчанию и копирования. Будет ли сгенерировано сообщение об ошибке, если убрать строку A b; в функции main примера, иллюстрирующего двусмысленность при вызове конструктора? Будет ли автоматически создан конструктор копирования, если явно задан конструктор вида A::A() { };?

Вложенность классов

Как мы видели, компонентами класса могут быть данные и методы.

Отметим, что в качестве данных класса можно задавать объекты других классов. Объекты своего класса не могут быть его членами. Тем не менее, указатели на объекты как других, так и своего собственного классов могут входить в число данных класса.

Существует специально зарезервированное ключевое слово this, определяемое как указатель на объект, которому было послано обрабатываемое сообщение. В каком-то конкретном методе указатель this содержит адрес объекта, который инициировал этот метод. Это справедливо только для методов класса, не имеющих описатель static. При любом вызове метода класса указатель this передается как скрытый (hidden) параметр, то есть передается неявно. Он является локальной переменной внутри метода и неявно используется при обращении к данным и методам класса. Иногда его используют явно. Рассмотрим эти возможности на примере инкапсуляции в классе Node такой распространенной структуры данных, как узел линейного односвязного списка. Класс Node имеет в качестве элемента данных указатель next на объект своего собственного класса.

class Node

{

char *data;       // Информационная часть узла

Node *next;       // Указатель на следующий узел

public:

Node (char *s)    // Конструктор

{

  data = strcpy (new char[strlen(s) + 1], s);

  next = 0;

}

void GetData()

{

  puts (this->data);   // Здесь указатель this может быть опущен

}

void SetNextTo (Node& n)

{

  n.next = this;   // Здесь указатель this принципиально важен

}

Node* GetNext() { return next; }

~Node()

{

  puts("\nDeleting data"); delete [] data;

}

};

void main ()       // Использование класса Node