54: for (int i = 0; i 55: {
56: for (int j = 0; j 57: cout << "x ";
58:
59: cout << "\n";
60: }
61: }
62:
63: class Square : public Rectangle
64: {
65: public:
66: Square(int len);
67: Square(int len, int width);
68: ~Square { }
69: long GetPerim { return 4 * GetLength;}
70: };
71:
72: Square::Square(int len):
73: Rectangle(len,len)
74: { }
75:
76: Square::Square(int len, int width):
77: Rectangle(len,width) 78:
79: {
80: if (GetLength != GetWidth)
81: cout << "Error, not a sguare... a Rectangle??\n";
82: }
83:
84: int main
85: {
86: int choice;
87: bool fQuit = false;
88: Shape * sp;
89:
90: while ( ! fQuit )
91: {
92: cout << "(1)Circle (2)Rectangle (3)Square (0)Quit:";
93: cin >> choice;
94:
95: switch (choice)
96: {
97: case 0: fQuit = true;
98: break;
99: case 1: sp = new Circle(5);
100: break;
101: case 2: sp = new Rectangle(4,6);
102: break;
103: case 3: sp = new Square(5);
104: break;
105: default: cout << "Please enter a number between 0 and 3" << endl;
106: continue;
107: break;
108: }
109: if(! fQuit)
110: sp->Draw;
111: delete sp;
112: cout << "\n";
113: }
114: return 0;
115: }
Результат:
(1)Circle (2)Rectangle (3)Square (0)Quit: 2
x x x x x x
X X X X X X
X X X X X X
X X X X X X
(1)Circle (2)Rectangle (3)Square (0)Quit:3
X X X X X
X X X X x
X X X X X
X X X X X
X X X X X
(1)Circle (2)Rectangle (3)Square (0)Quit:0
Анализ:
В строках 6—15 объявляется класс Shape. Методы GetArea и GetPerim возвращают -1 как сообщение об ошибке, а метод Draw не выполняет никаких действий. Давайте подумаем, можно ли в принципе нарисовать форму? Можно нарисовать окружность, прямоугольник или квадрат, но форма — это абстракция, которую невозможно изобразить.Класс Circle производится от класса Shape, и в нем замещаются три виртуальных метода. Обратите внимание, что в данном случае нет необходимости использовать ключевое слово virtual, поскольку виртуальность функций наследуется в производном классе. Тем не менее для напоминания о виртуальности используемых функций не лишним будет явно указать это.
Класс Square производится от класса Rectangle и наследует от него все методы, причем метод GetPerim замещается в новом классе.
Все методы должны функционировать нормально в производных классах, но не в базовом классе Shape, поскольку невозможно создать экземпляр формы как таковой. Программа должна быть защищена от попытки пользователя создать объект этого класса. Класс Shape существует только для того, чтобы поддерживать интерфейс, общий для всех производных классов, поэтому об этом типе данных говорят как об абстрактном, или ADT (Abstract Data Туре).
Абстрактный класс данных представляет общую концепцию, такую как форма, а не отдельные объекты, такие как окружность или квадрат. В C++ ADT по отношению к другим классам всегда выступает как базовый, для которого невозможно создать функциональный объект абстрактного класса.
Чистые виртуальные функции
C++ поддерживает создание абстрактных типов данных с чистыми виртуальными функциями. Чистыми виртуальными функциями называются такие, которые инициализируются нулевым значением, например:
virtual void Draw = 0;
Класс, содержащий чистые виртуальные функции, является ADT. Невозможно создать объект для класса, который является ADT. Попытка создания объекта для такого класса вызовет сообщение об ошибке во время компиляции. Помещение в класс чистой виртуальной функции будет означать следующее:
• невозможность создания объекта этого класса;
• необходимость замещения чистой виртуальной функции в производном классе.
Любой класс, произведенный от ADT, унаследует от него чистую виртуальную функцию, которую необходимо будет заместить, чтобы получить возможность создавать объекты этого класса. Так, если класс Rectangle наследуется от класса Shape, который содержит три чистые виртуальные функции, то в классе Rectangle должны быть замещены все эти три функции, иначе он тоже будет ADT. В листинге 13.8 изменено объявление классa Shape таким образом, чтобы он стал абстрактным типом данных. Остальная часть листинга 13.7 не изменилась, поэтому не приводится. Просто замените объявление класса в строках 7—16 листинга 13.7 листингом 13.8 и запустите программу.
Листинг 13.8. Абстрактные типы данных
1: класс Shape
2: {
3: public:
4: Shape{ }
5: ~Shape{ }.
6: virtual long GetArea = 0; // ошибка
7: virtual long GetPerim= 0;
8: virtual void Draw = 0;
9: private:
10: };
Результат:
(1)Circle (2)Rectangle (3)Square (0)Quit: 2
x x x x x x
x x x x x x
x x x x x x
x x x x x x
(1)Circle (2)Rectangle (3)Square (0)Quit: 3
x x x x x
x x x x x
x x x x x
x x x x x
x x x x x
(1)Circle (2)Rectangle (3)Square (0)Quit: 0
Анализ:
Как видите, выполнение программы не изменилось. Просто теперь в программе невозможно создать объект класса Shape.Абстрактные типы данных
Чтобы объявить класс как абстрактный тип данных.достаточно добавить в него одну или несколько чистых виртуальных функций. Для этогопосле объявления функции необходимо добавить - 0, например:
сlass Shape
{
virtual void Draw = 0; // чистая виртуальная функция
}
Выполнение чистых виртуальных функций
Обычно чистые виртуальные функции объявляются в абстрактном базовом классе и не выполняются. Поскольку невозможно создать объект абстрактного базового класса, как правило, нет необходимости и ff выполнении чистой виртуальной функции. Класс ADT существует только как объявление интерфейса объектов, создаваемых в производных классах.
Тем не менее все же иногда возникает необходимость выполнения чистой виртуальной функции. Она может быть вызвана из объекта, произведенного от ADT, например чтобы обеспечить общую функциональность для всех замещенных функций. В листинге 13.9 представлен видоизмененный листинг 13.7, в котором класс Shape объявлен как ADT и в программе выполняется чистая виртуальная функция Draw. Функция замещается в классе Circle, что необходимо для создания объекта этого класса, но в объявлении замещенной функции делается вызов чистой виртуальной функции из базового класса. Это средство используется для достижения дополнительной функциональности методов класса.
В данном примере дополнительная функциональность состоит в выведении на экран простого сообщения. В реальной программе чистая виртуальная функция может содержать достаточно сложный программный код, например создание окна, в котором рисуются все фигуры, выбираемые пользователем.
Листинг 13.9. Выполнение чистых виртуальных функций
1: // Выполнение чистых виртуальных функций
2:
3: #include
4:
5: class Shape
6: {
7: public:
8: Shape{ }
9: virtual ~Shape{ }
10: virtual long GetArea = 0;
11: virtual long GetPerim= 0;
12: virtual void Draw = 0;
13: private:
14: };
15:
16: void Shape::Draw
17: {
18: cout << "Abstract drawing mechanism!\n";
19: }
20:
21: class Circle : public Shape
22: {
23: public:
24: Circle(int radius):itsRadius(radius) { }
25: virtual ~Circle { }
26: long GetArea { return 3 * itsRadius * itsRadius; }
27: long GetPerim { return 9 * itsRadius; }
28: void Draw;
29: private:
30: int itsRadius;
31: int itsCircumference;
32: };
33:
34: voidCircle::Draw
35: {
36: cout << "Circle drawing routine here!\n";
37: Shape::Draw;
38: }
39:
40:
41: class Rectangle : public Shape
42: {
43: public:
44: Rectangle(int len, int width):
45: itsLength(len), itsWidth(width){ }
46: virtual ~Rectangle{ }
47: long GetArea { return itsLength * itsWidth; }
48: long GetPerim { return 2*itsLength + 2*itsWidth;
49: virtual int GetLength { return itsLength; >
50: virtual int GetWidth { return itsWidth; }
51: void Draw;
52: private:
53: int itsWidth;
54: int itsLength;
55: };
56:
57: void Rectangle::Draw
58: {
59: for (int i = 0; i 60: {
61: for (int j = 0; j 62: cout << "x ";
63:
64: cout << "\n";
65: }
66: Shape::Draw;
67: }
68:
69:
70: class Square : public Rectangle
71: {
72: public:
73: Square(int len);
74: Square(int len, int width);
75: virtual ~Square{ }
76: long GetPerim { return 4 * GetLength;}
77: };
78:
79: Square::Square(int len):
80: Rectangle(len,len)
81: { }
82:
83: Square::Square(int len, int width):
84: Rectangle(len,width)
85:
86: {
87: if (GetLength != GetWidth)
88: cout << "Error, not a square... a Rectangle??\n";
89: }
90:
91: int main
92: {
93: int choice;
94: bool fQuit = false;
95: Shape * sp;
96:
97: while (1)
98: {
99: cout << "(1)Circle (2)Rectangle (3)Square (0)Quit: ";
100: cin >> choice;
101:
102: switch (choice)
103: {
104: case 1: sp = new Circle(5);
105: break;
106: case 2: sp = new Rectangle(4,6);
107: break;
108: case 3; sp = new Square (5);
109: break;
110: default: fQuit = true;
111: break;
112: }
113: if (fQuit)
114: break;
115:
116: sp->Draw;
117: delete sp;
118: cout << "\n";
119: }
120: return 0;
121: }
Результат:
(1)Circle (2)Rectangle (3)Square (0)Quit: 2
x x x x x x
x x x x x x
x x x x x x
X X X Х X X
Abstract drawing mechanism!
(1)Circle (2)Rectangle (3)Square (0)Quit: 3
x x x x x
X X X X X
X X X X X
X X X X X
X X X X X
Abstract drawing mechanism!
(1)Circle (2)Rectangle (3)Square (0)Quit: 0
Анализ:
В строках 5—14 объявляется класс абстрактного типа данных Shape с тремя чистыми виртуальными функциями. Впрочем, для того чтобы класс стал ADT, достаточно было объявить в нем хотя бы один из методов как чистую виртуальную функцию.Далее в программе все три функции базового класса замешаются в производных классах Circle и Rectangle, но одна из них — функция Draw — выполняется как чистая виртуальная функция, поскольку в объявлении замещенного варианта функции в производных классах есть вызов исходной функции из базового класса. В результате выполнение этой функции в обоих производных классах приводит к выведению на экран одного и того же сообщения.
Сложная иерархия абстракций
Иногда бывает необходимо произвести один класс ADT от другого класса ADT, например для того, чтобы в производном классе ADT преобразовать в обычные методы часть функций, объявленных в базовом классе как чистые виртуальные, оставив при этом другие функции чистыми.
Так, в классе Animal можно объявить методы Eat, Sleep, Move и Reproduce как чистые виртуальные функции. Затем от класса Animal производятся классы Mammal и Fish.
Исходя из соображения, что все млекопитающие размножаются практически одинаково, имеет смысл в классе Mammal преобразовать метод Reproduce в обычный, оставив при этом методы Eat, Sleep и Move чистыми виртуальными функциями.
Затем от класса Mammal производится класс Dog, в котором необходимо заместить все три оставшиеся чистые виртуальные функции, чтобы получить возможность создавать объекты класса Dog.
Таким образом, наследование одного класса ADT от другого класса ADT позволяет объявлять общие методы для всех следующих производных классов, чтобы не замещать потом эти функции по отдельности в каждом производном классе.
В листинге 13.10 показан базовый костяк программы, в котором используется объявленный выше подход.
Листинг 13.10. Наследование класса ADT от другого класса ADT
1: // Листинг 13.10.
2: // Deriving ADTs from other ADTs
3: #include
4:
5: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown };
6:
7: class Animal // Общий базовый класс для классов Mammal и Fish
8: {
9: public:
10: Animal(int);
11: virtual ~Animal { cout << "Animal destructor...\n"; }
12: virtual int GetAge const { return itsAge; }
13: virtual void SetAge(int age) { itsAge = age; }
14: virtual void Sleep const = 0;
15: virtual void Eat const = 0;
16: virtual void Reproduce const = 0;
17: virtual void Move const = 0;
18: virtual void Speak const = 0;
19: private:
20: int itsAge;
21: };
22:
23: Animal::Animal(int age):
24: itsAge(age)
25: {
26: cout << "Animal constructor...\n";
27: }
28:
29: class Mammal : public Animal
30: {
31: public:
32: Mammal(int age):Animal(age)
33: { cout << "Mammal constructor...\n";}
34: virtual ~Mammal { cout << "Mammal destructor...\n";}
35: virtual void Reproduce const
36: { cout << "Mammal reproduction depicted...\n"; }
37: };
38:
39: class Fish : public Animal
40: {
41: public:
42: Fish(int age):Animal(age)
43: { cout << "Fish constructor...\n";}
44: virtual ~Fish { cout << "Fish destructor...\n"; }
45: virtual void Sleep const { cout << "fish snoring...\n"; }
46: virtual void Eat const { cout << "fish feeding...\n"; }
47: virtual void Reproduce const
48: { cout << "fish laying eggs...\n"; }
49: virtual void Move const
50: { cout << "fish swimming...\n"; }
51: virtual void Speak const { }
52: };
53:
54: class Horse : public Mammal
55: {
56: public:
57: Horse(int age, COLOR color ):
58: Mamrnal(age), itsColor(color)
59: { cout << "Horse constructor...\n"; }
60: virtual ~Horse { cout << "Horse destructor...\n"; }
61: virtual void Speakconst { cout << "Whinny!... \n"; }
62: virtual COLOR GetItsColor const { return itsColor; }
63: virtual void Sleep const
64: { cout << "Horse snoring.,.\n"; }
65: virtual void Eat const { cout << "Horse feeding...\n"; }
66: virtual void Move const { cout << "Horse running...\n";} 67:
68: protected:
69: COLOR itsColor;
70: };
71:
72: class Dog : public Mammal
73: {
74: public:
75: Dog(int age, COLOR color ):
76: Mammal(age), itsColor(color)
77: { cout << "Dog constructor...\n"; }
78: virtual ~Dog { cout << "Dog destructor...\n"; }
79: virtual void Speakconst { cout << "Woof!... \n"; }
80: virtual void 51eep const { cout << "Dog snoring...\n"; }
81: virtual void Eat const { cout << "0og eating...\n"; }
82: virtual void Move const { cout << "Dog running...\n"; }
83: virtual void Reproduce const
84: { cout << "Dogs reproducing...\n"; }
85:
86: protected:
87: COLOR itsColor;
88: };
89:
90: int main
91: {
92: Animal *pAnimal=0;
93: int choice;
94: bool fQuit = false;
95:
96: while (1)
97: {
98: cout << "(1)Dog (2)Horse (3)Fish(0)Quit: ";
99: cin >> choice; 100:
101: switch (choice)
102: {
103: case 1: pAnimal = new Dog(5,Brown);
104: break;
105: case 2: pAnimal = new Horse(4,Black);
106: break;
107: case 3: pAnimal = new
108: break;
109: default: fQuit = true
110: break;
111: }
112: if (fQuit)
113: break;
114:
115: pAnimal->Speak;
116: pAnimal->Eat;
117: pAnimal->Reproduce;
118: pAnimal->Move;
119: pAnimal->Sleep;
120: delete pAnimal;
121: cout << "\n";
122: }
123: return 0;
124: }
Результат:
(1)Dog (2)Horse (3)Bird (0)Quit: 1
Animal constructor. . .
Mammal constructor...
Dog constructor...
Woof!...
Dog eating. . .
Dog reproducing....
Dog running...
Dog snoring...
Dog destructor...
Mammal destructor...
Animal destructor...
(1)Dog (2)Horse (3)Bird (0)Quit: 0
Анализ:
В строках 7—21 объявляется абстрактный тип данных Animal. Единственный метод этого класса, не являющийся чистой виртуальной функцией, это общий для объектов всех производных классов метод itsAge. Остальные пять методов — Sleep, Eat, Reproduce, Move и Speak — объявлены как чистые виртуальные функции.Класс Mammal производится от Animal в строках 29—37 и не содержит никаких данных. В нем замещается функция Reproduce, чтобы задать способ размножения, общий для всех млекопитающих. Класс Fish производится непосредственно от класса Animal, поэтому функция Reproduce в нем замещается иначе, чем в классе Mammal (и это соответствует реальности).
Во всех других классах, производимых от класса Mammal, теперь нет необходимости замещать общий для всех метод Reproduce, хотя при желании это можно сделать для определенного класса, как, например, в нашей программе это было сделано в строке 83 для класса Dog. Все остальные чистые виртуальные функции были замещены в классах Fish, Horse и Dog, поэтому для каждого из них можно создавать соответствующие объекты.
В теле программы используется указатель класса Animal, с помощью которого делаются ссылки на все объекты производных классов. В зависимости от того, с каким объектом связан указатель в текущий момент, вызываются соответствующие виртуальные функции.
При попытке создать объекты для классов абстрактных типов данных Animal или Mammal компилятор покажет сообщение об ошибке.
Когда следует использовать абстрактные типы данных
В одних примерах программ, рассмотренных нами ранее, класс Animal являлся абстрактным типом данных, в других — нет. В каких же случаях нужно объявлять класс как абстрактный тип данных?
Нет никаких правил, которые требовали бы объявления класса как абстрактного. Программист принимает решение о создании абстрактного типа данных, основываясь на том, какую роль играет этот класс в программе. Так, если вы хотите смоделировать виртуальную ферму или зоопарк, то имеет смысл класс Animal объявить как абстрактный и для создания объектов производить от него другие классы, такие как Dog.
Если же вы хотите смоделировать виртуальную псарню, то теперь класс Dog будет абстрактным, от которого можно производить подклассы, представляющие разные породы собак. Количество уровней абстрактных классов следует выбирать в зависимости от того, насколько детально вы хотите смоделировать реальный объект или явление.
Рекомендуется:
Не рекомендуется: