Листинг 11.9. Произвольное обращение к набору виртуальных функций



1: //Листинг 11.9. Произвольное обращение к набору виртуальных функций

2:

3: #include <iostream.h>

4:

5: class Mammal

6: {

7: public:

8: Mammal():itsAge(1) { }

9: virtual ~Mammal() { }

10: virtual void Speak() const { cout << "Mammal speak!\n"; }

11: protected:

12: int itsAge;

13: };

14:

15: class Dog : public Mammal

16: {

17: public:

18: void Speak()const { cout << "Woof!\n"; }

19: };

20:

21:

22: class Cat : public Mammal

23: {

24: public:

25: void Speak()const { cout << "Meow!\n"; }

26: };

27:

28:

29: class Horse : public Mammal

30: {

31: public:

32: void Speak()const { cout << "Whinny!\n"; }

33: };

34:

35: class Pig : public Mammal

36: {

37: public:

38: void Speak()const < cout << "Oink!\n"; }

39: };

40:

41: int main()

42: {

43: Mammal* theArray[5];

44: Mammal* ptr;

45: int choice, i;

46: for ( i = 0; i<5; i++)

47: {

48: cout << "(1)dog (2)cat (3)horse (4)pig: ";

49: cin >> choice;

50: switch (choice)

51: {

52: case 1: ptr = new Dog;

53: break;

54: case 2; ptr = new Cat;

55: break;

56: case 3: ptr = new Horse;

57: break;

58: case 4: ptr = new Pig;

59: break;

60: default: ptr = new Mammal;

61: break;

62: }

63: theArray[i] = ptr;

64: }

65: for (i=0;i<5;i++)

66: theArray[i]->Speak();

67: return 0;

68: }

 

Результат:

(1)dog (2)cat (3)horse (4)pig: 1

(1)dog (2)cat (3)horse (4)pig: 2

(1)dog (2)cat (3)horse (4)pig: 3

(1)dog (2)cat (3)horse (4)pig; 4

(1)dog (2)cat (3)horse (4)pjg: 5

Woof!

Meow!

Whinny!

0ink!

Mammal speak!

 

Анализ: Чтобы идея использования виртуальных функций была понятнее, в данной программе этот метод раскрыт наиболее явно и четко. Сначала определяется четыре класса — Dog, Cat, Horse и Pig, которые являются производными от базового класса Mammal.

В строке 10 объявляется виртуальная функция Speak() класса Mammal. В строках 18, 25, 32 и 38 указанная функция замещается во всех соответствующих производных классах.

Пользователю предоставляется возможность выбрать объект любого производного класса, и в строках 46—64 создается и добавляется в массив указатель класса Mammal на вновь созданный объект.

 

Вопросы и ответы

Если функция-член была объявлена как виртуальная в базовом классе, следует ли повторно указывать виртуальность при объявлении этого метода в произ- водном классе?

Нет. Если метод уже был объявлен как виртуальный, то он будет оставаться таким, несмотря на замещение его в производном классе. В то же время для повышения читабельности программы имеет смысл (но не требуется) и в производных классах продолжать указывать на виртуальность данного метода с помощью ключевого слова virtual.

 

Примечание: Во время компиляции неизвестно, объект какого класса захочет создать пользователь и какой именно вариант метода Speak() будет использоваться. Указатель ptr связывается со своим объектом только во время выполнения программы. Такое связывание указателя с объектом называется динамическим, в отличие от статического связывания, происходящего во время компиляции программы.

Как работают виртуальные методы

 

При создании объекта в производном классе, например в классе Dog, сначала вызывается конструктор базового, а затем — производного класса. Схематично объект класса Dog показан на рис. 11.2. Обратите внимание, что объект производного класса состоит как бы из двух частей, одна из которых создается конструктором базового класса, а другая — конструктором производного класса.

 

Рис. 11.2. Созданный объект класса Dog

 

 

Рис. 11.3. Таблица виртуальных функций класса Mammal

 

Если в каком-то из объектов создается обычная не виртуальная функция, то всю полноту ответственности за эту функцию берет на себя объект. Большинство компиляторов создают таблицы виртуальных функций, называемые также v-таблицами. Такие таблицы создаются для каждого типа данных, и каждый объект любого класса содержит указатель на таблицу виртуальных функций (vptr, или v-указатель).

Хотя детали реализации выполнения виртуальных функций меняются в разных компиляторах, сами виртуальные функции будут работать совершенно одинаково, независимо от компилятора.

 

 

Рис. 11.4. Таблица виртуальных функций класса Dog

 

Итак, в каждом объекте есть указатель vptr, который ссылается на таблицу виртуальных функций, содержащую, в свою очередь, указатели на все виртуальные функции. (Более подробно указатели на функции рассматриваются на занятии 14.) Указатель vptr для объекта класса Dog инициализируется при создании части объекта, принадлежащей базовому классу Mammal, как показано на рис. 11.3.

После вызова конструктора класса Dog указатель vptr настраивается таким образом, чтобы указывать на замещенный вариант виртуальной функции (если такой есть), существующий для класса Dog (рис. 11.4).

В результате при использовании указателя на класс Mammal указатель vptr по- прежнему ссылается на тот вариант виртуальной функции, который соответствует реальному типу объекта. Поэтому при обращении к методу Speak() в предыдущем примере выполнялась та функция, которая была задана в соответствующем производном классе.

 


Дата добавления: 2019-02-12; просмотров: 215; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!