ITEEDU

9.10 将派生类对象隐式转换为基类对象

尽管派生类对象也是基类对象,但是派生类类型和基类类型是不同的。在public继承中,派生类对象能作为基类对象处理。由于派生类具有对应每个基类成员的成员(派生类的成员通常比基类的成员多),所以把派生类的对象赋给基类对象是合理的。但是,反过来赋值会使派生类中基类不具有的成员没有定义,所以这是不允许的。尽管如此,提供正确的重载赋值运算符和(或)转换构造函数可以允许这种操作(见第8章)。

常见编程错误9.4

把派生类对象赋给其基类对象,然后试图在新的基类对象中引用只在派生类中才有的成员是十语法错误。

注意,在本节后面提到指针时,也适用于引用。

在public继承中,因为派生类对象也是基类对象,所以指向派生类对象的指针可以隐式地转换为指向基类对象的指针。

基类指针和派生类指针与基类对象和派生类对象的混合和匹配有如下四种可能的方式:

1.直接用基类指针引用基类的对象。

2.直接用派生类指针引用派生类的对象。

3.用基类指针引用一个派生类的对象。由于派生类的对象也是基类的对象,所以这种引用方式是安全的,但是用这种方法只能引用基类成员。如果试图通过基类指针引用那些只在派生类中才有的成员,编译器会报告语法错误。

4.用派生类指针引用基类的对象。这种引用方式会导致语法错误。派生类指针必须先强制转换为基类指针。

常见编程错误9.5

将基类指针强制转换为派生类指针,如果用该指针引用基类对象,而基类对象中没有所要引用的派生类的成员,那么这时就会发生错误。

将派生类对象作为基类对象可能是很方便的,但使用基类指针操作这些对象容易出问题。例如,在某个计算工资单的系统中,我们希望能够遍历关于雇员的清单并计算出每人每周的工资。但是,使用基类指针使得程序只能调用基类的工资单计算例程(如果基类中确实存在该例程)。我们需要一种方法为每一个对象(不管它是派生类对象还是基类对象)调用正确的工资单计算例程,并且这种方法只需简单地使用基类指针。解决这个问题的答案是使用第10章介绍的虚函数和多态性。

9.11 关于继承的软件工程

我们可以用继承来定制现有的软件。为了把现有类定制成满足我们的需要的类,首先要继承现有类的属性和行为,然后添加和去除一些属性和行为。在C++中,派生类不必访问基类的源代码,但是需要能够连接到基类的目标代码。这种强大的功能对独立软件供应商(ISV)很有吸引力。

ISV开发出具有目标代码格式的类后,他们就拥有了这些类的所有权,因而可以销售和发放使用许可证。

用户拥有这些类后,在不必访问源代码(所有权属于ISV)的情况下,他们就能够从这些类库中派生出新的类,所有的ISV需要为目标代码提供头文件。

软件工程视点9.6

理论上,用户不需要看到所继承类的源代码。但实际上,根据发放许可证的经验,客户通常会需要源代码。程序员似乎还是不大愿意放心地把别人编写的代码放进自己的程序中。

性能提示9.1

如果性能是主要考虑,则程序员可能要浏览所继承类的源代码,以便根据性能要求调整代码。

学生们很难认识到大型软件项目的设计者和实现者所面临的问题。有过开发这种项目经验的人都知道缩短软件开发过程的关键是鼓励软件复用。面向对象的程序设计普遍鼓励软件复用,而C++尤其提倡软件复用。

正是继承了实用的类库才发挥出了软件复用的最大优势。随着人们对C++的兴趣不断增长,对类库感兴趣的人也将增加。正如个人电脑的出现带动了ISV生产的套装软件日益增长,C++也必将带动类库的建立和销售。因为应用程序设计者会用这些类库建立他们自己的应用程序,所以类库设计者也将因此而获得丰厚的报偿。当前随C++编译器分发的类库倾向于一定的通用性并限制使用范围。在世界范围内开发应用于各种领域的类库的时代正在来临。

软件工程视点9. 7

建立一个派派生类不会影响其基类的源代码和目标代码,继承这一机制保护了基类的完整性。

基类描述了共性。所有从基类派生出来的类都继承了基类的功能。在面向对象的设计过程中,设计者先寻求井提取出构成所需基类的共性,然后再通过继承从基类派生出超出基类功能的定制派生类。

软件工程视点9.8

在面向对象的系统中,类常常是紧密相关的。提取出共同的属性和行为并把它们放在一个基类中,然后再通过继承生成派生类。

正如非面向对象系统的设计者力图避免不必要的函数一样,面向对象系统的设计者也应该避免不必要的类。多余的类不仅会带来类管理上的问题,而且会阻碍软件的复用。理由很简单,因为用户难以在巨大的类集合中定位某个类权。权衡的结果还是建立较少的类,每个类都实际增加一些功能。这样的类对于某些用户来说可能功能太丰富了一点,但是他们可以屏蔽掉多余的功能,然后使之满足自己的需要。

性能提示9.2

大于功能需求的派生类可能会浪费内存和处理资源。因此应继承最接近要求的类。

注意,因为派生类中没有列出继承来的成员,所以浏览一组派生类的声明会令人迷惑,但是派生类中确实存在继承来的成员。

软件工程视点9.9

派生类除了包含其基类的属性和行为外,还能够包含附加的属性和行为。继承机制能够使基类独立于派生类编译。为了把基类与派生类中增加的属性和行为组合成派生类,编译器只需要编译派生类中增加的属性和行为。

软件工程视点9.10

只要基类的public接口不变,对基类的修改无需修改派生类,但是派生类需要重新编译。

9.12 复合与继承的比较

我们讨论了public继承所支持的"是"关系,还讨论把对象作为成员的"有"关系,并举了几个例子。"有"关系通过复合现有的类建立了新类。例如,假设有雇员类Employee、生日类BirthDate和电话号码类TelephonehNunber,说雇员(Employee)是—个生日(BirthDate)或电话号码(TelephoneNumber)是不对的,但是说雇员有生日和电话号码当然是合适的。

软件工程视点9. 11

只要成员类的public接口不变,对成员类的修改无需修改复合类,但是复合类需要重新编译。