Программирование с классами. Производные классы. Порождение, видимость, наследование. Конструкторы и деструкторы, виртуальные функции.


Наследование — важная возможность объектно-ориентированного программирования. Эта возможность позволя­ет объявить новый класс как потомок (классы-потомки иногда называются производными классами, а родительские классы — базовыми) существующих классов . Класс-потомок уточняет признаки и действия родительского класса. Он наследует члены данных (т.е. признаки) и функции-члены (т.е. действия) родительского класса, ко­торый, в свою очередь, унаследовал члены данных и функции-члены собственного родительского класса и т.д. Класс-потомок дополнительно объявляет собственные члены данных, новые функции-члены и функции-члены, которые заменяют унаследованные. Таким образом, наследование позволяет строить иерархию классов. Корень этой иерархии назван базовым классом. Каждый класс-потомок в этой иерархии поддерживает большую специализацию, чем родительский класс.

C++ позволяет объявлять класс-потомок родительского класса, поддерживает две схемы наследования: одиночное и множественное наследование. Одиночное наследование создает такие иерархии классов, в котрых каждый класс-потомок имеет тольео один родительский класс. Множественное наследование создает такие иерархии классов, в котрых каждый класс-потомок имеет тольео один или большое количество родительских классов.

Общий синтаксис для объявления класса-потомка, ко-торый использует одиночную схему наследования:

class className : [pubIic] parentClassName

{ [publ ic:

// открытые конструкторы

// открытый деструктор

// открытые члены данных

// открытые функции-члены

[protected:

// защищенные конструкторы

// защищенный деструктор

// защищенные члены данных

// защищенные функции-члены

[private:

// закрытые конструкторы

// закрытый деструктор

// закрытые члены данных

// закрытые функции-члены

Декларация для класса-потомка начинается с ключевого слова class, за которым следует.

• Название (имя) класса.

• Символ двоеточия.

• Необязательное ключевое слово public.

• Название родительского класса.

Ключевое слово public позволяет экземплярам классов получить доступ к открытым членам родительского класса. Без этого ключевого слова только функции-члены класса-потомка могут получить доступ к членам родительских классов. C++ предписывает следующие правила видимости для открытых, защищенных и закрытых частей класса-потомка.

1. Все функции-члены могут получить доступ ко всем членам данных в классе независимо от раздела, в котором они появляются.

2. Экземпляры классов могут получить доступ только к открытым членам.

3. Функции-члены класса-потомка могут иметь доступ только к открытым и защищенным членам родительского класса. Таким образом, к закрытым членам класса нельзя получить доступ из функций-членов класса-потомка.

Конструкторы — специальные члены, которые автоматически инициализируют экземпляры класса. Когда вы создае­те экземпляр класса, программа автоматически вызывает конструктор для этого класса. Общий синтаксис для объявления конструктора в классе:

class сlass Name

{ publ ic:

//void-конструктор

classNameO;

// copy-конструктор

className (className& classNameObJect);

II дополнительный конструктор

className (parameter List);

II другие члены};

В C++ следует придерживаться следующих правил при объявлении и использовании конструкторов.

1. Название (имя) конструктора должно совпадать с названием (именем) класса.

2. Конструктор может иметь список параметров, чтобы помочь тщательно настроить процедуру создания экземпля­ров класса.

3. Класс может иметь несколько конструкторов, чтобы экземпляры можно было инициализировать по-разному.

4. Конструктор без параметров (или со списком параметров, который имеет аргументы по умолчанию для каждого параметра) называется vo/uf-конструктором или конструктором по умолчанию.

5. Конструктор с одним параметром, который имеет тип класса, называется сору-конструктором или конструктором копирования.

6. Декларация массива экземпляров требует использования конструктора по умолчанию.

7. Если в классе не объявлен конструктор, C++ создает конструктор по умолчанию для этого класса.

8. C++ вызывает конструктор, когда вы создаете экземпляр класса. Аргументы этого экземпляра выбирают соответ­ствующий конструктор, если класс объявляет несколько конструкторов.

Деструкторы — специальные функции-члены, которые уничтожают экземпляры, когда они достигают конца своей области существования. Общий синтаксис для объявления деструкторов класса:

class className {

public:

// void-конструктор

c/assNameQ;

// другие конструкторы

// деструктор ~classNa!ne();

II другие члены

};

В C++ имеются следующие правила для объявления и использования деструкторов.

1. Название (имя) деструктора должно совпадать с названием (именем) класса, но должно начинаться со знака тильда (~).

2. Деструктор не имеет списка параметров.

3. Деструктор не имеет никакого типа возвращаемой величины.

4. Класс имеет только один деструктор.

5. Если в классе не объявлен деструктор, C++ создает умалчиваемый деструктор для этого класса.

6. C++ автоматически вызывает деструктор, когда экземпляр класса достигает конца области своего существования.

Необходимо объявить деструктор, чтобы выполнить действия по уборке мусора для экземпляров классов. Приведем примеры таких действий по уборке мусора.

1. Класс использует конструктор для создания динамических данных. Деструктор освобождает память, занятую данными.

2. Конструктор класса открывает файл для ввода или вывода. Деструктор закрывает этот файл.

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

Виртуальные функции

Наследование порождает следующую проблему:

рассмотрим следующий фрагмент программы.

Class А {.

public:

int doA()

{ return doA1() * doA2(); }

int doA1()

{ return 2; }

int doA2()

{ return 10; }

};

Class В : pubIic A {

public:

int doA1() { return 20; } };

main() {

В objB;

cout « objB.doA;

return 0;

}

В этом примере объявлены класс А и его потомок-класс В. Класс А содержит функции-члены doA, doA1 и doA2. Фун Ция-член doA возвращает значение, которое является результатом вызова функций-членов doA1 и doA2. Класс В объявлял свою собственную версию функции-члена doA1 и наследует функции-члены doA и doA2. Когда функция main посыла сообщение C++ doA объекту objB (экземпляр класса В), какое значение выдает это сообщение? Поскольку класс В объявляет собственную версию функции-члена doA1, вы ожидаете, что вызов сообщения C++ doA вызовет функции-член В::doA1 и A::doA2, выдающие значение 200. Однако оператор вывода в функции main выводит 20, потому что сообщеж C++ doA в конце концов вызовет функции-члены A::doA1 и A::doA2! Другими словами, компилятор не понимает, что вы желаете вызвать функцию-член В: :doA1, когда посылаете сообщение C++ doA экземплярам класса В.

Чтобы разрешить эту проблему, в C++ существует возможность объявлять виртуальные функции. Общий синтаксис для объявления виртуальной функции-члена:

virtuaI returnType functionName{parameterList);

Применяя этот синтаксис к классам А и В, можно написать следующую программу, которая будет работать нужны образом.

class A { public:

i nt doA()

{ return doA1() * doA2(); }

virtual int doA1()

{ return 2; }

int doA2() { return 10; }

};

class В : public A {

public:

virtual int doA1() { return 20; } };

main() { В objB;

cout « objB.doA;

return 0;

}

Заметьте, что в этом фрагменте программы объявлена функция-член doA1 как виртуальная в обоих классах А и В. Когда функция main посылает сообщение C++ doA объекту ObjB, вызываются функции-члены B::doA1 и A::doA2, вы­дающие правильный результат 200.

C++ требует соблюдать следующие правила при работе с виртуальными функциями-членами.

1. Виртуальная функция-член может заменять невиртуальную функцию-член, унаследованную от родительского класса.

2. Вы можете заменить виртуальную функцию-член, только используя другую виртуальную функцию-член в классе-потомке (это правило иногда формулируют так: виртуальная всегда виртуальна). Заменяющая функция-член должна иметь те же самые список параметров и тип возвращаемой величины, что и заменяемая.

3. Вы можете перегрузить виртуальную функцию-член невиртуальной функцией-членом в классе. Однако потомки этого класса могут наследовать только виртуальную функцию-член!

Загрузка...