📄 10章 虚函数与多态性.txt
字号:
266 { hours = h >= 0 && h < 168 ? h : 0; }
267
268 // Get the HourlyWorker's pay
269 double HourlyWorker::earnings() const
270 {
271 if ( hours <= 40 ) // no overtime
272 return wage * hours;
273 else // overtime is paid at wage * 1.5
274 return 40 * wage + ( hours - 4O ) * wage * 1.5;
275 }
276
277 // Print the HourlyWorker's name
279 {
280 cout << "\n Hourly worker: ";
281 Employee::print();
282 }
283 // Fig. 10.1: figl0_01.cpp
284 // Driver for Employee hierarchy
285 #include <iostream.h>
286 #include <iomanip.h>
287 #include "employ2.h
288 #include "boss1.h"
289 #include "commis1.h"
290 #include "piece1.h"
291 #include "hourly1.h"
292
293 void virtualViaPointer( const Employee * );
294 void virtualViaReference( const Employee & );
295
296 int main()
297 {
298 // set output formatting
299 cout << setiosflags( ios::fixed | ios::showpoint )
300 << setprecision( 2 );
301
302 Boss b( "John", "Smith", 800.00 );
303 b.print(); // static binding
304 cout << "earned $" << b.earnings(); // static binding
305 virtualViaPointer( &b ); // uses dynamic binding
306 virtualViaReferenee( b ); // uses dynamic binding
307
308 CommissionWorker c( "Sue", "Jones", 200.0, 3.0, 150 );
309 c.print(); // static binding
310 cout << "earned $" << c.earnings(); // static binding
311 virtualViaPointer( &c )/ // uses dynamic binding
312 virtualViaReference( c ); // uses dynamic binding
313
314 PieceWorker p( "Bob", "Lewis", 2.5, 200 );
315 p.print(); // static binding
316 cout << "earned $" << p.earnings(); // static binding
317 virtualViaPointer( &p ); // uses dynamic binding
318 virtualViaReference( p ); // uses dynamic binding
319
320 HourlyWorker h( "Karen", "Price", 18.75, 40 );
321 h.print(); // static binding
322 cout << "earned $" << h.earnings(); // static binding
323 virtualViaPointer( &h ); // uses dynamic binding
324 virtualViaReference( h ); // uses dynamic binding
325 cout << endl;
326 return 0;
327 }
328
329 // Make virtual function calls off a base-class pointer
330 // using dynamic binding.
331 void virtualViaPointer( const Employee *baseClassPtr )
332 {
333 baseClassPtr->print();
334 cout << "earned $" << baseClassPtr->earnings();
335 }
336
337 // Make virtual function calls off a base-class reference
338 // using dynamic binding.
339 void virtualViaReference( const Employee &baseClassRef )
340 {
341 baseClassRef.print();
342 cout << " earned $ " << baseclassRef.earnings();
343 }
输出结果:
BOSS: John Smith earned $800,00
Boss: John Smith earned $800.00
Boss: John Smith earned $800.00
Commission Worker: Sue Jones earned $650.00
Commission worker: Sue Jones earned $650.00
Commission worker: Sue Jones earned $650,00
Piece worker: Bob Lewis earned $500.00
Piece worker: Bob Lewis earned $500.00
Piece worker: Bob Lewis earned $500.00
Hourly worker: Karen Price earned $550.00
Hourly worker: Karen Price earned $050.00
Hourly worker: Karen Price earned $550.00
图10. 1 Employee类层次的多态性
驱动程序main函数中的四小段代码是类似的,因此我们只讨论处理Boss对象的第一段代码。
第302行:
Boss b("John","Smith",800.OO);
实例化了类Boss的派生类对象b,并为构造函数提供了参数(即姓和名以及固定的周薪)。
第303行:
b.print(); // static binding
用圆点成员选择运算符显式地调用类Boss中的成员函数print。在编译时就可以知道被调用函数的对象类型,所以它是静态关联。使用该调用是为了和用动态关联调用函数print做一比较。
第304行:
cout << " earned $ " << b.earnings(); // static binding
用圆点成员选择运算符显式地调用类Boss中的成员函数earnings,这也是一例静态关联。使用该调用是为了和用动态关联调用函数earnings做一比较。
第305行:
virtualViaPointer(&b); // uses dynamic binding
用派生类对象b的地址调用函数virtualViaPointer(第331行)。函数在参数baseClassPtr中接收这个地址,该参数声明为constEmployee *,这正是实现多态性所必须要做的。
第333行:
baseClassPtr->print()
调用baseClassPtr所指向对象的成员函数print。由于print在基类中被声明为虚函数,因此系统调用了派生类对象的print函数(仍然是多态性行为)。该函数调用是一例动态关联,即用基类指针调用虚函数,以便在执行时才确定调用哪一个函数。
第334行:
cout<<"earned $ "<<baseClassPtr—>earnings();
调用baseClassPtr所指向对象的成员函数earnings。由于earnings在基类中被声明为虚函数,因此系统调用了派生类对象的earnings函数,这也是动态关联的一个范例。
第306行:
virtualViaReference(b); // uses dynamic binding
调用函数vitualViaRefrence(第339行)演示多态性也可以用基类引用调用虚函数来完成。该函数在参数baseClassRef中接收对象b,该参数声明为constEmployee&。这就是通过引用来影响多态行为。
第341行:
baseClassRef.print();
调用baseClassRef所引用对象的成员函数print。由于print在基类中被声明为虚函数,因此系统调用了派生类对象的print函数。该函数调用是一例动态关联,即用基类引用调用函数,以便在执行时才确定调用哪一个函数。
第342行:
cout<< "earned $ "<<baseClassRef.earnings();
调用baseClassRef所引用对象的成员函数earnings。由于earnings在基类中被声明为虚函数,因此系统调用了派生类对象的earnings函数,这也是动态关联的一个范例。
10.7 新类和动态关联
对于预先知道所有可能的类的系统来说,多态性和虚函数当然会运行得很好,但是当向系统中添加各种各样的新类时,它们同样也会运行得很好。动态关联(也叫滞后关联)允许向系统中添加新类。对于要被编译的虚函数调用来说,编译时可以不必知道对象的类型。在运行时,虚函数调用和被调用对象的成员函数相匹配。
屏幕管理程序可以不经过重新编译就可以处理添加到系统中的新的显示对象,draw函数的调用还是和原来的一样,新对象自己包含了实际的显示能力,这样就可以很容易地给系统增加功能,同时也鼓励软件复用。
动态关联可以使独立软件供应商(ISV)在不透露其秘密的情况下发行软件。发行的软件可以只包括头文件和对象文件,不必透露源代码。软件开发者可以利用继承机制从ISV提供的类中派生出新类。和ISV提供的类一起运行的软件也能够和派生类一起运行,并且能够通过动态关联使用这些派生类中重定义的虚函数。
109节将介绍综合的多态性实例研究。10。10节将深入介绍多态、虚函数与动态关联如伺在C++中实现。
10.8 虚析构函数
用多态性处理动态分配的类层次结构中的对象时存在一个问题。如果delete运算符用于指向派生类对象的基类指针,而程序中又显式地用该运算符删除每一个对象,那么,不管基类指针所指向的对象是何种类型,也不管每个类的析构函数名是不相同的这样一种情况,系统都会为这些对象调用基类的析构函数。
这种问题有一种简单的解决办法,即将基类析构函数声明为虚析构函数。这样就会使所有派生类的析构函数自动成为虚析构函数(即使它们与基类析构函数名不同)。这时,如果像上面那样使用delete运算符时,系统会调用相应类的析构函数。记住,删除派生类对象时,同时删除派生类对象的基类部分,基类析构函数在派生类析构函数之后自动执行。
编程技巧10.2
如果一个类拥有虚函数,即使该类不需要虚析构函数也给它提供一个虚析构函数,这样能够使该类的派生类包含正确调用的析构函数。
常见编程错误10.2
将构造函数声明为虚函数是语法错误。构造函数不能是虚函数。
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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -