Рейтинговые книги
Читем онлайн Освой самостоятельно С++ за 21 день. - Джесс Либерти

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 66 67 68 69 70 71 72 73 74 ... 170

Примечание:В этом суть полиморфизма. Например, можно объявить множество окон разных типов, включая диалоговые, прокручиваемые окна и поля списков, после чего создавать их в программе с помощью единственного виртуального метода draw(). Создав указатель на базовое окно и присваивая этому указателю адреса объектов производных классов, можно обращаться к методу draw() независимо от того, с каким из объектов в данный момент связан указатель. Причем всегда будет вызываться вариант метода, специфичный для класса выбранного объекта.

Затем этот указатель можно использовать для вызова любого метода класса Mammal. Причем если метод был замещен, скажем, в классе Dog, то при обращении к методу через указатель будет вызываться именно вариант, указанный в данном производном классе. В этом суть использования виртуальных функций. Листинг 11.8 показывает, как работает виртуальная функция и что происходит с не виртуальной функцией.

Листинг 11.8. Использование виртуальных методов

1: //Листинг 11.8. Использование виртуальных методов

2:

3: #include<iostream.h>

4:

5: class Mammal

6: {

7:    public:

8:       Mammal():itsAge(1) { cout << "Mammal constructor...n"; }

9:       virtual ~Mammal() { cout << "Mammal destructor...n"; }

10:      void Move() const { cout << "Mammal move one stepn"; }

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

12:   protected:

13:      int itsAge;

14:

15: };

16:

17: class Dog : public Mammal

18: {

19:    public:

20:       Dog() { cout << "Dog Constructor...n"; }

21:       virtual ~Dog() { cout << "Dog destructor...n"; }

22:       void WagTail() { cout << "Wagging Tail...n"; }

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

24:       void Move()const { cout << "Dog moves 5 steps...n"; }

25: };

26:

27: int main()

28: {

29:

30:   Mammal *pDog = new Dog;

31:   pDog->Move();

32:   pDog->Speak();

33:

34:   return 0;

35: }

Результат:

Mammal constructor...

Dog Constructor...

Mammal move one step

Woof!

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

В строке 30 создается указатель класса Mammal (pDog), но ему присваивается адрес нового объекта производного класса Dog. Поскольку собака является млекопитающим, это вполне логично. Данный указатель затем используется для вызова функции Move(). Поскольку pDog известен компилятору как указатель класса Mammal, результат получается таким же, как при обычном вызове метода Move() из объекта класса Mammal.

В строке 32 через указатель pDog делается обращение к методу Speak(). В данном случае метод Speak() объявлен как виртуальный, поэтому вызывается вариант функции Speak(), замещенный в классе Dog.

Это кажется каким-то волшебством. Хотя компилятор знает, что указатель pDog принадлежит классу Mammal, тем не менее происходит вызов версии функции, объявленной в другом производном классе. Если создать массив указателей базового класса, каждый из которых указывал бы на объект своего производного класса, то, обращаясь попеременно к указателям данного массива, можно управлять выполнением всех вариантов замещенного метода. Эта идея реализована в листинге 11.9.

Листинг 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() в предыдущем примере выполнялась та функция, которая была задана в соответствующем производном классе.

1 ... 66 67 68 69 70 71 72 73 74 ... 170
На этой странице вы можете бесплатно читать книгу Освой самостоятельно С++ за 21 день. - Джесс Либерти бесплатно.

Оставить комментарий