Освой самостоятельно С++ за 21 день.
Шрифт:
В предыдущей версии программы мы столкнулись с проблемой из-за того, что в классе Dog доступен только один замещенный метод Move, в котором не задаются параметры. Проблема была разрешена явным обращением к методу Move(int) базового класса в строке 38.
Рекомендуется:Повышайте функциональные возможности класса путем создания новых производных классов. Изменяйте выполнение отдельных функций в производных классах с помощью замещения методов.
Не
Виртуальные методы
В этой главе неоднократно подчеркивалось, что объекты класса Dog одновременно являются объектами класса Mammal. До сих пор под этим подразумевалось, что объекты класса Dog наследуют все атрибуты (данные) и возможности (методы) базового класса. Но в языке C++ принципы иерархического построения классов несут в себе еще более глубинный смысл.
Полиморфизм в C++ развит настолько, что допускается присвоение указателям на базовый класс адресов объектов производных классов, как в следующем примере:
Mammal* pMammal = new Dog;
Данное выражение создает в области динамической памяти новый объект класса Dog и возвращает указатель на этот объект, который является указателем класса Mammal. Это вполне логично, так как собака — представитель млекопитающих.
Примечание:В этом суть полиморфизма. Например, можно объявить множество окон разных типов, включая диалоговые, прокручиваемые окна и поля списков, после чего создавать их в программе с помощью единственного виртуального метода 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 step\n"; }
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 Speakconst { cout << "Woof!\n"; }
24: void Moveconst { 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 Speakconst { cout << "Woof!\n"; }
19: };
20:
21:
22: class Cat : public Mammal
23: {
24: public:
25: void Speakconst { cout << "Meow!\n"; }
26: };
27:
28:
29: class Horse : public Mammal
30: {
31: public:
32: void Speakconst { cout << "Whinny!\n"; }
33: };
34:
35: class Pig : public Mammal
36: {
37: public:
38: void Speakconst < cout << "Oink!\n"; }