Download presentation
Presentation is loading. Please wait.
1
第6章 继 承 主讲人:陈南京
2
本章提要 继承的基本概念 在C++中定义继承关系 利用继承来定义“危险品货物”类 多重继承的概念 多重继承歧义问题的解决方法
3
引言 在第5章完成的程序只能对普通货物进行费用计算,也就是说,该程序不能对危险品货物进行费用计算,因为,航空公司对危险品货物除了要收取正常的运输费用以外,还要收取附加费用。 如何实现对危险品进行收费呢?依据面向对象的思想,能不能定义一个“危险品货物”类,在这个类的代码中,对危险品货物进行附加收费? 更进一步,又如何定义这个“危险品货物”类呢?。 从“货物”和“危险品货物”这两个词语中我们可以了解到:“危险品货物”是“货物”的一种特殊情况,因为“危险品货物”一定是“货物” 。既然如此,能不能依据“危险品货物”与“货物”之间的特殊的关系,有效地设计“危险品货物”类的程序代码呢?回答是:能!这就是本章要阐述的一个重要概念——C++的继承。
4
6.1 进一步剖析航空货物 托运费用计算程序
5
6.1 进一步剖析航空货物托运费用计算程序 提出问题 :如何处理对危险品货物的航空托运收费?
继续“航空货物托运费用计算程序”这个案例。从航空旅行的常识我们知道,航空公司需要对旅客所携带的危险品货物进行附加收费,因此,对于相同重量的货物而言,对危险品货物的收费当然要高于对普通货物的收费,对于危险品货物,假设每公斤需要再附加收费5元。 那么,我们所设计的费用自动计算程序如何才能客观的反应这一要求呢?
6
6.1 进一步剖析航空货物托运费用计算程序 分析问题:危险品货物是一类特殊的货物 危险品货物,顾名思义就是具有一定危险性的货物。
对于危险品货物,不管有多危险,它们仍然是“货物”,所以,它们具有一般航空货物所具有的特性。例如,它们都具有下列属性: 货物的货主 货物的重量 货物要到达的目的地 货物的内容 对于危险品货物而言,它们具有一般“货物”所不具备的其它属性: 危险级别 用途说明 由于在前面所设计的“货物”类没有包括这两项属性,因此,对于危险品货物,必须加上这两项新的属性以便更加准确的刻画危险品货物的特征。 那么,如何既高效又方便地完成这些属性的补充工作呢?
7
重新定义危险品货物类--笨拙的方法 由于“危险品货物类”是“货物类”的一个特殊情况,所以可以简单地将危险品货物的两个新属性及与之相关的新方法补充到Cargo类中,从而完成对新的货物类的定义,补充了新的属性的Cargo类的完整程序参见书中的“例6-1-1”。 从上面的设计过程可以看出,完全可以从头开始设计新的Cargo类,来完成对危险品货物的附加收费。但是,新的程序代码既累赘又冗长,所以能不能不这么做呢? 既然不想从头设计新的Cargo类,那么,应该怎样设计呢?——可以使用C++的“继承”,方便地来实现对Cargo类属性的扩展,以支持对航空危险品货物的收费。 在具体介绍如何使用“继承”来完成这个工作之前,首先介绍一下C++继承的一些基本概念。
8
6.2 继承概念的引入
9
6.2.1 危险品货物与普通货物的继承关系 因为危险品货物也是货物,所以,它首先具备了一般货物所具备的所有属性;
由于危险品货物具有一些自己独有的属性,例如:危险品货物的危险级别、危险品货物的用途,总的来说,危险品货物是货物的一个特例; 因此,可以这样表示危险品货物与货物的关系: 上图表示“危险品货物”类是“货物”类的一个特例。所以,我们可以说“危险品货物类继承了货物类的一些属性”。 货物 危险品货物
10
6.2.1 危险品货物与普通货物的继承关系 再观察一个关于“哺乳动物”、“狗”、“各种特别的狗”的例子,它们之间的属性继承关系: 动物
爬行动物 马 狗 工作型狗 牧羊犬 宠物狗 看家犬 猎犬 小哈吧狗 波斯狗
11
6.2.1 危险品货物与普通货物的继承关系 简单地说,“继承”是指某类事物具有比其父辈事物更一般性的某些特征(或称为属性),用对象和类的术语,我们可以这样表达:对象和类“继承”了另一个类的一组属性。 可以将上图中的各个方块看作是一个类,因此,例子中所涉及的这些类之间构成了一幅清晰的层次结构。在继续介绍如何在C++中表达这种继承层次关系之前,首先介绍两个术语:超类和子类。
12
6.2.2 超类与子类 C++允许从一个类“派生”其它的类,当然即将派生的新类与已有的类之间是存在一定的内在联系的,这里,引入了一个新词“派生”; 我们形象地理解“派生”:父辈类生下了“儿子”类; 例如,由于“危险品货物”类是“货物”类的一个特例,所以,可以从“货物”类派生出“危险品货物”类; 在这个派生过程中,“下一级的类”继承了“上一级的类”的属性。在C++中,为了方便表达,赋予了“下一级的类”和“上一级的类”两个更加清晰的词:子类(有时也称为派生类)、超类(有时也称为基类)。 所谓子类就是向已有的类添加了新功能后构成的类,那个类的父辈类被称为超类; 一个类可能同时既是子类又是超类,例如,狗这个类,它是哺乳动物类的子类,但是,它同时又是工作型狗类和宠物狗类的超类。
13
单元练习 远东房地产开发公司在开发普通多层住房的同时,也开发一些高档的复合式住房。复合式住房也是住房的一种,一般而言,在一栋复合式住房楼内只有上下两户住户,带车位及私家花园,在每层住户的楼层内又有两层,所以称为复合式住房。下图是关于普通多层住房与高档复式住房的继承关系: 请在上图所描述的类之间的继承关系中,指出哪个是超类、哪个是子类?同时,请指出派生类“高档复合式”类所应包括的新属性。 普通多层住房 高档复合式住房
14
6.3 在C++中声明继承性关系
15
6.3.1 在C++中使用继承的好处 通过继承可以允许代码重用,即在定义和调试了一个类之后,可以用它来创建新的子类,从而重用了基类的代码,因而节省了开发程序的时间和工作量; 代码更易于维护,因为若代码位于基类的某个地方,只需要在基类中对代码进行修改,这种修改将自动被这个类的子类继承; 通过向子类中加入新的属性和方法,可以扩充子类的功能; 在航空货物托运费用计算程序中,我们已经知道,危险品货物类——可以给它一个新的名字AirCargo——AirCargo类是Cargo类的子类,也就是说,AirCargo类继承了Cargo类的一些属性,同时它还具有自己的一些独有的属性。下面看看如何从Cargo类派生出AirCargo类。
16
6.3.1 C++中描述继承性关系的一般方法 既然AirCargo类与Cargo类存在这种继承性关系,可以采用以下形式来表示AirCargo类与Cargo类的这种关系: class AirCargo : public Cargo { ……; //AirCargo类的新属性、新函数的定义 } 上面的程序代码片段表示:AirCargo类是从Cargo类派生出来的; 正因为AirCargo类是从Cargo类派生出来的,所以可以省略AirCargo类与Cargo类中相同的程序代码,只需在AirCargo类中补充新的属性、新的函数即可。此时我们采用了省略号“……”来表示即将加入的新的属性、新的方法(也可以称为函数)等,在后续的章节中,将逐渐把其中的省略号替换为真正的程序代码; 还可以在派生类AirCargo类中对Cargo类中不能满足AirCargo类要求的函数进行修改,从而可以使派生类AirCargo类更加满足程序的要求;
17
6.3.1 C++中描述继承性关系的一般方法 一般地,在C++中,采用下面的语法格式来定义派生类,它是定义派生类的一般语法:
class 派生类的类名 :访问区分符 基类的类名 派生类的类名:任何合法的C++标识符,当然,不能使用C++的保留字; 基类的类名:任何已经定义的、或C++中已经定义的类的类名; 访问区分符:访问区分符控制对基类的成员属性及成员函数的访问控制,访问区分符可以是public、protected及private,在省略访问区分符的情况下,是private。
18
6.3.1 C++中描述继承性关系的一般方法 public访问区分符: protected访问区分符:
在public访问区分符中,基类所有的private成员仍然是基类的private成员,在派生类中不能直接访问基类中的这些private成员; 基类所有的protected成员变成了派生类的protected成员,在派生类中可以直接访问这些成员; 基类所有的public成员变成派生类的public成员,在派生类中可以直接访问这些成员; 例6-3-1 public继承 protected访问区分符: 在protected访问区分符中,基类所有的private成员仍然是基类的private成员,在派生类中不能直接访问基类中的这些private成员; 基类所有的protected成员变成派生类的protected成员,在派生类中可以直接访问这些成员; 基类所有的public成员变成派生类的protected成员,在派生类中可以直接访问这些成员; 例6-3-2 protected继承
19
6.3.1 C++中描述继承性关系的一般方法 private访问区分符:
在private访问区分符中,基类所有的private成员仍然是基类的private成员,在派生类中不能直接访问基类中的这些private成员; 基类所有的protected成员变成派生类的private成员,在派生类中可以直接访问这些成员; 基类所有的public成员变成派生类的private成员,在派生类中可以直接访问这些成员; 例6-3-3 private继承
20
6.3.1 C++中描述继承性关系的一般方法 基类成员存在形式 继承方式 子类成员存在方式 public protected private
21
6.3.1 C++中描述继承性关系的一般方法 再举一个例子6-3-4来说明如何从一个基类派生出子类,以强化对继承的认识。该例子以Person类与Student类为例,来说明C++的类之间的继承性关系。 不言而喻,Student类是Person类的子类,因为任何一个学生一定是一个人。对任何一个Person对象而言,起码应该包括如下的基本属性:姓名、性别、家庭住址;而对于任何一个Student对象而言,除了包括Person对象所包括的所有属性外,还至少包括这样的一个属性:在哪个学校念书。
22
6.3.2 通过继承方式定义的危险品货物类 解决问题:
危险品货物类AirCargo是货物类Cargo的子类,在AirCargo类中,补充定义了两个新属性dangerLevel及usage,采用继承的方式,可以这样定义AirCargo类,如例6-3-5。 哺乳动物类之间属性继承关系的例子如例6-3-6
23
6.3.3 案例:解决对危险品货物的收费问题 通过C++的继承,完整的“航空货物托运费用计算程序”的代码参见教材的例6-3-7。
24
6.3.4 构造函数及析构函数的调用顺序 在创建一个类的对象实例时(有时也称为实例化一个对象),会自动调用一个类的构造函数。可是由于AirCargo类是Cargo类的派生类,他们之间有着特殊的继承关系,在生成AirCargo类的对象时,会不会调用其基类即Cargo类的构造函数呢?如果调用的话,又是怎样调用的呢? 在具有继承关系的类中,派生类继承了基类的成员,如果基类有构造函数和析构函数,那么在这种继承关系中: 构造函数的调用顺序是:从基类到派生类; 析构函数的调用顺序是:从派生类到基类。 例6-3-8 具有继承关系的类之间构造函数的调用过程。
25
6.3.4 构造函数及析构函数的调用顺序 对于“航空货物费用计算程序”6-3-7而言,由于AirCargo类是Cargo类的派生类,所以,语句: AirCargo ac(owner, weight, destination, content, danger, use); 将首先调用Cargo类的构造函数Cargo(),从而通过执行下列语句: strcpy(this->owner, owner); this->weight = weight; strcpy(this->destination, destination); strcpy(this->content, content); 来初始化Cargo类的数据成员,接着调用AirCargo类的构造函数,通过以下语句: this->dangerLevel = dangerLevel; strcpy(this->usage, usage); 来初始化AirCargo类中新增加的数据成员。
26
6.3.4 构造函数及析构函数的调用顺序 向类的带参数的属性对象传递参数的方法: 构造函数被调用的顺序: 析构函数被调用的顺序:
为了给带参数的对象成员传递它们需要的参数,可以在类的构造函数中完成这一任务!具体方式仍然是通过一个冒号“:”,然后写上对象成员的名字再跟上参数;如果存在多个对象成员需要通过这种方式初始化,他们之间则用逗号隔开。 下面举一个说明这个方法,例6-3-9; 构造函数被调用的顺序: 如果这个类存在基类,先调用基类的构造函数; 如果这个类存在成员对象,调用成员对象的构造函数; 调用这个类的构造函数。 析构函数被调用的顺序: 首先调用这个类的析构函数; 如果这个类存在成员对象,成员对象的析构函数被调用; 如果这个类存在基类,这个类的基类的析构函数被调用。
27
单元习题 在C++中,如何表达类之间的继承关系? 在C++中,如何向带参数的基类构造函数传递参数?
28
6.4 多重继承
29
6.4.1 声明多重继承 多重继承的一般语法是: class 派生类的类名 :访问区分符 基类1的类名,…,访问区分符 基类n的类名 多重继承与已经在前面已经介绍过的单一继承的语法非常类似,只不过把子类的多个超类用逗号(,)分开,派生类对基类的成员的可访问特性与前面已经介绍过的单一继承中的派生类对基类成员的访问特性是完全一样; 例6-4-1 多重继承
30
6.4.2 多重继承中的歧义性问题及其解决 歧义性问题:当派生类的基类中有重名的成员时,那么,哪一个成员在派生类中有效呢?这就是多中继承中的所谓歧义性问题。例如6-4-2 歧义性问题的解决:使用作用域分解运算符“::”来解决歧义性问题!针对上面的例子,可以: int main( ) { Derived d; d.Base1::show( ); return 0; } 通过使用作用域分解运算符“::”来强行指定直接调用从Base1基类中继承来的show()方法,如用深色背景加重的程序代码所示,从而,可以有效地解决多重继承中的歧义性问题;
31
6.4.3 多重继承中构造函数和析构函数的调用顺序 在多重继承的情况下,基类及派生类的构造函数是按以下顺序被调用的:
按基类被列出的顺序逐一调用基类的构造函数; 如果该派生类存在成员对象,则调用成员对象的构造函数;若存在多个成员对象的情况下,按它们被列出的顺序逐一调用; 最后调用派生类的构造函数; 析构函数的调用顺序与构造函数的调用顺序正好相反! 例6-4-3 多重继承中构造函数和析构函数的调用顺序,该例子也演示了向成员对象传递参数的方法。
32
单元练习 在C++中,如何表达多重继承? 什么是多重继承的歧义性问题,如何解决?
在多重继承中,基类及派生类的构造函数及析构函数的调用顺序是怎样的?
Similar presentations