10.9 实例研究:继承接口和实现
下面的范例(见图10.2)要重新考察上一章中的 Point、cirele、Cylinder 类的层次结构,只不过这里类的层次结构的顶层是抽象基类Shape。类Shape中有一个纯虚函数printShapeName和print,所以它是一个抽象基类。类 shape 中还包含其他两个虚函数 area 和 volume,它们都有默认的实现(返回0值)。类Point从类shape中继承了这两个函数的实现,由于点的面积和体积是0,所以这种继承是合理的。类 circle 从类 Point 中继承了函数 volume,但 circle 本身提供了函数area的实现。Cylinder对函数area和volume提供了自己的实现。
注意,尽管 Shape 是一个抽象基类,但是仍然可以包含某些成员函数的实现,并且这些实现是可继承的。类 shape 以四个虚函数的形式提供了一个可继承的接口(类层次结构中的所有的成员都将包含这些虚函数),该类还提供了要在类层次结构头几层的派生类中使用的一些实现。
软件工程视点 10.8
一个类可以从基类继承接口和(或)实现。为实现继承而设计的层次结构倾向于在高层具有某些功能,为接口继承而设计的层次结构则倾向于在较低层具有某些功能。对于前者,每个新派生类继承基类中定义的一个或几个成员函数,新的派生类使用基类定义;对于后者,基类指定一个或几个函数,层次中每个对象都要一样调用(即有相同的签名),但各个派生类提供自己对该函数的实现方法。
1 // Fig. 10.2: shape.h
2 // Definition of abstract base class Shape
3 #ifndef SHADE_H
4 #define SHADE_H
5 #include <iostream.h>
6
7 class Shape {
8 public:
9 virtual double area() const { return 0.0; }
10 virtual double volume() const { return 0.0; }
11
12 // pure virtual functions overridden in derived classes
13 virtual void printShapeName() const = 0;
14 virtual void print() const = 0;
15 };
16
17 #endif
18 // Fig. 10.2: point1.h
19 // Definition of class Point
20 #ifndef POINT1_H
22 #include "shape.h"
24 class Point : public Shape {
25 public:
26 Point( int = 0, int = 0 ); // default constructor
27 void setPoint( int, int );
28 int getX() const { return x; }
29 int getY() const { return y; }
30 virtual void printShapeName() const { cout << "Point: "; }
31 virtual void print() const;
32 private:
33 int x, y; // x and y coordinates of Point
34 };
35
36 #endif
37 // Fig. 10.2:point1.cpp
38 // Member function definitions for class Point
39 #include "point1.h"
40
41 Point::Point( int a, int b ) { setPoint( a, b ); }
42
43 void Point::sefPoint( int a, int b }
44 {
45 x = a;
46 y = b;
47 }
48
49 void Point::print() const
5O { cout << '[' << x << ", "<< y << '] '; }
51 // Fig. 10.2: circle1.h
52 // Definition of class Circle
53 #ifndef CIRCLE1_H
54 #define CIRCLE1_H
55 #include "point1.h"
56
57 class Circle : public Point {
58 public:
59 // default constructor
60 Circle( double r = 0.0, int x = 0, int y = 0 );
61
62 void setRadius( double );
63 double getRadius() const;
64 virtual double area() const;
65 virtual void printShapeName() const { cout << "Circle: "; }
66 virtual void print() const;
67 private:
68 double radius; // radius of Circle
69 };
7O
71 #endif
72 // Fig. 10.2: circlel.cpp
73 // Member function definitions for class Circle
74 #include "circie1.h"
75
76 Circle::Circle( double r, int a, int b )
77 : Point( a, b ) // call base-class constructor
78 { setRadius( r ); }
79
80 void Circle::setRadius( double r ) { radius = r > 0 ? r : 0; }
81
82 double Circle::getRadius() const { return radius; }
83
84 double Circle::area() const
85 { return 3.14159 * radius * radius; }
86
87 void Circle::print() const
88 {
89 Point::print();
90 cout << "; Radius =" << radius;
91 }
92 // Fig. 10.2: cylindrl.h
93 // Definition of class Cylinder
94 #ifndef CYLINDR1_H
95 #define CYLINDR1_H
96 #include "circle1.h"
97
98 class Cylinder : public Circle {
99 public:
100 // default constructor
101 Cylinder( double h = 0.0, double r = 0.0,
102 int x = 0, int y = 0 );
103
104 void setHeight( double );
105 double getHeight() const;
106 virtual double area() const;
107 virtual double volume() const;
108 virtual void printShapeName() const {cout << "Cylinder: ";}
109 virtual void print() const;
110 private:
111 double height; // height of Cylinder
112 };
113
114 #endif
115 // Fig. 10.2: cylindr1.cpp
116 // Member and friend function definitions for class Cylinder
117 #include "cylindr1.h"
118
119 Cylinder::Cylinder( double h, double r, int x, int y )
120 : Circle( r, x, y ) // call base-class constructor
121 { setHeight( h ); }
122
123 void Cylinder::setHeight( double h )
124 { height = h > 0 ? h : 0; }
125
126 double Cylinder::getHeight() const { return height; }
127
128 double Cylinder::area() const
129 {
130 // surface area of Cylinder
131 return 2 * Circle::area() +
132 2 * 3.14159 * getRadiusO * height;
133 }
134
135 double Cylinder::volume() const
136 { return Circle::area() * height; }
137
138 void Cylinder::print() const
139 {
140 Circle::print();
141 cout << "; Height =" << height;
142 }
143 // Fig. 10.2: figl0_02.cpp
144 // Driver for shape, point, circle, cylinder hierarchy
145 #include <iostream.h>
146 #include <iomanip.h>
147 #include "shape.h"
148 #include "point1.h"
149 #include "circle1.h"
150 #include "cylindr1.h"
151
152 void virtualViaPointer( const Shape * );
153 void virtualViaReference( const Shape & );
154
155 int main()
156 {
157 cout << setiosflags( ios::fixed | ios::showpoint )
158 << setprecision( 2 );
159
160 Point point( 7, 11 ); // create a Point
161 Circle circle( 3.5, 22, 8 ); // create a Circle
162 Cylinder cylinder( 10, 3.3, 10, 10 ); // create a Cylinder
163
164 point.printShapeName(); // static binding
165 point.print(); // static binding
166 cout << '\n';
167
168 circle.printShapeName(); // static binding
169 circle.print(); // static binding
170 cout << '\n';
171
172 cylinder.printShapeName(); // static binding
173 cylinder.print(); // static binding
174 cout << "\n\n";
175
176 Shape *arrayOfShapes[ 3 ]; // array of base-class pointers
177
178 // aim arrayOfShapes[ 0 ] at derived-class Point object
179 arrayOfShapes[ 0 ] = &point;
180
181 // aim arrayOfShapes[ 1 ] at derived-class Circle object
182 arrayOfShapes[ 1 ] = &circle;
183
184 // aim arrayOfShapes[ 2 ] at derived-class Cylinder object
185 arrayOfShapes[ 2 ] = &cylinder;
186
187 // Loop through arrayOfShapes and call virtualViaPointer
188 // to print the shape name, attributes, area, and volume
189 // of each object using dynamic binding.
190 cout << "Virtual function calls made off"
191 << "base-class pointers\n";
192
193 for ( int i = 0; i < 3; i++ )
194 virtualViaPointer( arrayOfShape[ i ] );
195
196 // Loop through arrayOfShapes and call virtualViaReference
197 // to print the shape name, attributes, area, and volume
198 // of each object using dynamic binding.
199 cout << "Virtual function calls made off"
200 << "base-class references\n";
201
202 for (int j = 0; j < 3; j++ )
203 virtualViaReference( *arrayOfShapes[ j ] );
204
205 return 0;
206 }
207
208 // Make virtual function calls off a base-class pointer
209 // using dynamic binding.
210 void virtualViaPointer( const Shape *baseClassPtr )
211 {
212 baseClassPtr->printShapeName();
213 baseClassPtr->print();
214 cout << "\nArea = "<< baseClassPtr->area()
215 << "\nVolume =" << baseClassPtr->volume() << "\n\n";
216 }
217
218 // Make virtual function calls off a base-class reference
219 // using dynamic binding.
220 void virtualViaReference( const Shape &baseClassRef )
221 {
222 baseClassRef.printShapeName();
223 baseClassRef.print();
224 cout << "\nArea = "<< baseClassRef.area()
225 << "\nVolume "<< baseClassRef.volume() << "\n\n";
226 }
输出结果:
Point: [ 7, 11 ]
Circle: [ 22, 8 ]; Radius 3.50
Cylinder: [ 10, 10 ] ; Radius = 3.30; Height = 10.00
Virtual function calls made off base-class pointers
Point: [7, 11]
Area = 0.00
Volume = 0.00
Circle: [ 22, 8]; Radius = 3.50
Area = 38.48
Volume = 0.00
Cylider: [ 10,10 ]; Radius = 3.30; Height = 10.00
Area = 275.77
Volume = 342.12
Virtual function calls made off base-class pointers
Point: [ 7, 11]
Area = 0.00
Volume = 0.00
Circle: [ 22, 8] ; Radius = 3.50
Area = 38.48
Volume = 0.00
Cylinder:[10, 10]; Radius = 3.30; Height = 10.00
Area = 275.77
Volume = 342.12
图 10.2 定义抽象基类 Shape
基类Shape由三个public虚函数组成,不包含任何数据。函数print和printShapeName是纯虚函数,因此它们要在每个派生类中重新定义。函数area和volume都返回0.0,当派生类需要对面积(area)和(或)体积(volume)有不同的计算方法时,这些函数就需要在派生类中重新定义。注意Shape是个抽象类,包含一些“不纯”的虚函数(area和volume)。抽象类可以包含非虚函数和通过派生类继承的数据。
类Point是通过public继承从类Shape派生来的。因为Point没有面积和体积(均为0.0),所以类中没有重新定义基类成员函数area和volume,而是从类Shape中继承这两个函数。函数printShapeName和print是虚函数(在基类被定义为纯虚函数)的实现,如果不在类Point中重新定义这些函数,那么Point仍然为抽象类则不能实例化Point对象。其他成员函数包括:将新的x和y坐标值赋绐Point对象(即点)的一个”set”函数和返回Point对象的x和y坐标值的“get”函数。
类Circle是通过public继承从类Point派生来的。因为它没有体积,所以类中没有重新定义基类成员函数volume,而是从类Shape中继承。Circle是有面积的,因此要重新定义函数area。函数printShapeName和print是虚函数(在基类中被定义为纯虚函数)的实现。如果此处不重新定义该函数,则会继承类Point中该函数的版本。其他成员函数包括为Circle对象设置新的radius(半径值)的“set”函数和返回Circle对象的radius的“get”函数。
类Cylinder是通过public继承从类Circle派生来的。因为Cylinder对象的面积和体积同Circle的不同,所以需要在类中重新定义函数area和volume。函数printShapeName和print是虚函数(在基类中被定义为纯虚函数)的实现。如果此处不重新定义该函数,则会继承类Circle中该函数的版本。
类中还包括一个设置Cylinder对象height(高度)的“set”函数和一个读取Cylinder对象(圆柱体)的height的”get”函数。
驱动程序一开始就分别实例化了类Point的对象point、类Circle的对象circle和类Cylinder的对象cylinder。程序随后调用了每个对象的printShapeName和print函数,并输出每一个对象的信息以验证对象初始化的正确性。每次调用printShapeName和print(第164行到第173行)都使用静态关联,编译器在编译时知道调用printShapeName和print的每种对象类型。
接着把指针数组arrayOfShapes的每个元素声明为Shape*类型,诙数组用来指向每个派生类对象。首先把对象point的地址赋给了arrayOfShapes[O](第179行)、把对象circle的地址赋给了arrayOfShapes[1](第182行)、把对象cylinder的地址赋给了arrayOfShapes[2](第185行)。
然后用for结构(第193行)遍历arrayOfShapes数组,并对每个数组元素调用函数 virtualViaPointer(第194行):
virtualViaPointer(arrayOfShapes[ i ]);
函数virtualViaPointer用baseClassPtr(类型为constShape*)参数接收arrayOfShapes数组中存放的地址。每次执行virtualViaPointer时,调用下列4个虚函数:
baseClassPtr->printShapeName()
baseClassPtr->print()
baseClassPtr->area()
baseClassPtr->Volume()
这些调用方法对执行时 baseClassPtr 所指的对象调用一个虚函数,对象类型无法在编译时确定。输出中显示了对每个类调用的相应函数。首先,辅出字符串 Point: 和相应的 point 对象,面积和体积的计算结果都是 0.00。
然后,输出字符串 Circle: 和 circle 对象的圆心及半径,程序计算出了对象circle的面积,返回体积值为 0.00。最后,输出字符串 Cylinder: 以及相应的 cylinder 对象的底面圆心、半径和高,程序计算出了对象 cylinder 的面积和体积。所有调用函数 printShapeName、print、area 以及 volume 的虚函数都是在运行时用动态关联解决的。
最后用for结构(第202行遍历 arrayOfShapes 数组,并对每个数组元素调用函数 virtualViaReference(第203行):
virtualViaReference(*arrayofShapes[ j ]);
函数virtualViaReference用baseClassRef(类型为constShape&)参数接收对arrayOfShapes数组中存放的地址的引用(通过复引用)。每次执行virtualViaReference时,调用下列4个虚函数:
baseClassRef.printShapeName()
baseClassRef.print()
baseClassRef.area()
baseClassRef.volume()
这些调用方法对执行时 baseClassRef 所指的对象调用上述函数。输出中使用基类引用与使用基类指针时产生的结果是相同的。