750 likes | 949 Views
C++ 面向对象程序设计 第五章. 本章导读. 继承是面向对象程序设计的一个重要特性,通过继承实现了数据抽象基础上的代码重用。继承性反映了类的层次结构,并支持对事物从一般到特殊的描述。继承性使得程序员可以以一个已有的较一般的类为基础建立一个新类,而不必从零开始设计。从而可以从一个或多个先前定义的类中继承数据成员和成员函数,而且可以重新定义或加进新的数据成员和成员函数,从而建立了类的层次或等级。. 本章要点. 继承与派生 派生类的定义 类的继承方式 派生类的构造函数和析构函数 派生类对基类成员的继承. 第五章 目录. 5.1 继承与派生
E N D
C++面向对象程序设计 第五章
本章导读 • 继承是面向对象程序设计的一个重要特性,通过继承实现了数据抽象基础上的代码重用。继承性反映了类的层次结构,并支持对事物从一般到特殊的描述。继承性使得程序员可以以一个已有的较一般的类为基础建立一个新类,而不必从零开始设计。从而可以从一个或多个先前定义的类中继承数据成员和成员函数,而且可以重新定义或加进新的数据成员和成员函数,从而建立了类的层次或等级。
本章要点 • 继承与派生 • 派生类的定义 • 类的继承方式 • 派生类的构造函数和析构函数 • 派生类对基类成员的继承
第五章 目录 • 5.1 继承与派生 • 5.2 派生类的构造函数和析构函数 • 5.3 多继承 • 5.4 赋值兼容规则 • 5.5 程序举例 • 5.6本章小结 • 习题五
5.1 继承与派生 • 5.1.1 继承与代码重用 • 5.1.2 派生类的声明 • 5.1.3 派生类对基类成员的访问 • 5.1.4 派生类对基类成员的访问规则
5.1.1 继承与代码重用 • 类的继承是新的类从已有类那里得到已有的特性。从已有的类产生新类的过程就是类的派生。在继承过程中,原有的类或已经存在的用来派生新类的类称为基类或父类,而由已经存在的类派生出的新类则称为派生类或子类。 • 在应用程序设计中,经常要用到一些有相同或部分相同的程序和类,继承可以实现这些类的代码的重用。 • 例如:表一定义的person类和student类
从上面两个类的声明中,我们可以看出来,两者的数据成员和成员函数有很多相同的地方,后者只是在前者的基础上添加了一些成员,因此,可以使用继承来派生另一个类,实现代码的重用,提高程序的效率。从上面两个类的声明中,我们可以看出来,两者的数据成员和成员函数有很多相同的地方,后者只是在前者的基础上添加了一些成员,因此,可以使用继承来派生另一个类,实现代码的重用,提高程序的效率。 • 根据派生类所拥有的基类数目不同,可以分为单继承和多继承。一个类只有一个直接基类时,称为单继承;而一个类同时有多个直接基类时,则称为多继承,如图5-1所示。
派生类具有如下特点: • (1)新的类可在基类的基础上包含新的成员; • (2)在新的类中可隐藏基类的成员函数; • (3)可为新类重新定义成员函数。
基类与派生类之间的关系如下: • (1) 基类是对派生类的抽象,派生类是对基类的具体化,是基类定义的延续。 • (2) 派生类是基类的组合。多继承可以看作是多个单继承的简单组合。 • (3)公有派生类的对象可以作为基类的对象处理。
5.1.2 派生类的声明 • 派生类声明的一般格式如下: • class <派生类名> : <继承方式1> <基类名1>, • <继承方式2> <基类名2>, • …, • <继承方式n> <基类名n> • { • <派生类新定义成员> • };
其中的“基类名”是已有的类的名称。“派生类名”是继承原有类的特性而生成的新类的名称。单继承时,只需定义一个基类;多继承时,需同时定义多个基类。 • “继承方式”即派生类的访问控制方式,用于控制基类中声明的成员在多大的范围内能被派生类的用户访问。每一个继承方式,只对紧随其后的基类进行限定。
继承方式包括3种: • (1)公有继承public • (2)私有继承private • (3)保护继承protected • 若不显式的给出继承方式关键字,系统则默认为私有继承(private)。类的继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限。
从已有类派生出的新类,除了能从基类继承所有成员之外,还可以在派生类内完成以下几种功能:从已有类派生出的新类,除了能从基类继承所有成员之外,还可以在派生类内完成以下几种功能: • (1)可以增加新的数据成员; • (2)可以增加新的成员函数; • (3)可以重新定义基类中已有的成员函数; • (4)可以改变现有成员的属性。
5.1.3 派生类对基类成员的访问 • 派生类对基类成员的访问能力如表5.1所示 表5.1 派生类对基类成员的访问能力
从上表可以看出: • (1)基类中的私有成员在派生类中是隐藏的,只能在基类内部访问。 • (2)派生类中的成员不能访问基类中的私有成员,可以访问基类中的公有成员和保护成 • 员。
5.1.4 派生类对基类成员的访问规则 • 派生类对基类成员的访问形式主要有以下两种: • (1)内部访问: 由派生类中新增成员对基类继承来的成员的访问。 • (2)对象访问: 在派生类外部,通过派生类的对象对从基类继承来的成员的访问。
1.私有继承的访问规则 • 当类的继承方式为私有继承时,基类的public成员和protected成员被继承后作为派生类的private成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。 • 基类的private成员在私有派生类中是不可直接访问的,所以无论是派生类成员还是通过派生类的对象,都无法直接访问从基类继承来的private成员,但是可以通过基类提供的public成员函数间接访问。
2.公有继承的访问规则 • 当类的继承方式为公有继承时,基类的public成员和protected成员被继承到派生类中仍作为派生类的public成员和protected成员,派生类的其他成员可以直接访问它们。但是,类的外部使用者只能通过派生类的对象访问继承来的public成员。 • 基类的private成员在私有派生类中是不可直接访问的,所以无论是派生类成员还是通过派生类的对象,都无法直接访问从基类继承来的private成员,但是可以通过基类提供的public成员函数间接访问它们。
3.保护继承的访问规则 • 当类的继承方式为保护继承时,基类的public成员和protected成员被继承到派生类中都作为派生类的protected成员,派生类的其他成员可以直接访问它们,但是类的外部使用者不能通过派生类的对象来访问它们。 • 基类的private成员在私有派生类中是不可直接访问的,所以无论是派生类成员还是通过派生类的对象,都无法直接访问基类的private成员。
5.2 派生类的构造函数和析构函数 • 5.2.1 派生类构造函数和析构函数的执行顺序 • 5.2.2 派生类构造函数和析构函数的构造规则
5.2.1 派生类构造函数和析构函数的执行顺序 • 在这里,我们先用一个例子来说明派生类构造函数和析构函数的执行顺序。 • 【例5.4】 • #include <iostream> • using namespace std; • class Foundation
{ • public: • Foundation() • { • cout<<"基类的构造函数"<<endl; • } • ~Foundation() • { • cout<<"基类的析构函数"<<endl; • } • };
class Derivative:public Foundation • { • public: • Derivative() • { • cout<<"派生类的构造函数"<<endl; • } • ~Derivative() • { • cout<<"派生类的析构函数"<<endl; • } • };
void main() • { • Derivative exam; • } • 说明:此程序的输出结果为 • 基类的构造函数 • 派生类的构造函数 • 派生类的析构函数 • 基类的析构函数
从结果中,我们可以得出这样一个结论:通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。从结果中,我们可以得出这样一个结论:通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
5.2.2 派生类构造函数和析构函数的构造规则 • 我们应该注意一个问题,派生类不能继承基类的构造函数和析构函数。但基类的构造函数中没有参数或者没有显式定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数,但是当基类中含有带参数的构造函数时,派生类必须定义构造函数,提供把参数传递给基类构造函数的途径。
在C++中,派生类构造函数的一般格式为: • 派生类名(参数总表):基类名(参数表) • { • // 派生类新增成员的初始化语句 • }
说明:基类构造函数的参数,一般来源于派生类构造函数的参数总表。说明:基类构造函数的参数,一般来源于派生类构造函数的参数总表。 • 当派生类中含有内嵌对象成员时,其构造函数的一般形式为: • 派生类名(参数总表):基类名(参数表1),内嵌对象名1(内嵌对象参数表1),…,内嵌对象名n(内嵌对象参数表n) • { • // 派生类新增成员的初始化语句 • }
在定义派生类对象时,构造函数的执行顺序如下:在定义派生类对象时,构造函数的执行顺序如下: • 1.调用基类的构造函数; • 2.调用内嵌对象成员的构造函数(有多个对象成员时,调用顺序由它们在类中声明的顺序 • 确定); • 3.派生类的构造函数体中的内容
撤消对象时,析构函数的调用顺序与构造函数的调用顺序正好相反。 • 这里要特别强调的是:当基类构造函数不带参数时,派生类不一定要定义构造函数,然而当基类的构造函数哪怕是只带有一个参数,由它所派生出来的所有派生类都必须定义其构造函数。即使是它的构造函数什么功能都不实现,函数体为空,但他起到了函数传递作用。
5.3 多继承 • 5.3.1 多继承的声明 • 5.3.2 多继承的构造函数和析构函数 • 5.3.3 虚基类
5.3.1 多继承的声明 • 有两个以上基类的派生类声明的一般形式如下: • class 派生类名:继承方,1 基类名1,…,继承方式n 基类名n{ • // 派生类新增的数据成员和成员函数 • }; • 其中,<继承方式1>、<继承方式2>、…是三种继承方式:public,private和protected之一。冒号后面的部分称为基类表,各基类之间用逗号分隔,缺省的继承方式是private。
例如: • class person{ … }; • class student { … }; • class school:public person,public student{ … }; • 其中,派生类shool具有两个基类(类person和类stdent),因此,类school是多继承的。派生类school的成员包含了基类person中成员、基类student中成员和该类本身的成员。
5.3.2 多继承的构造函数和析构函数 • 多继承下派生类的构造函数与单继承下派生类构造函数相似,它必须同时负责该派生类所有基类构造函数的调用。同时,派生类的参数个数必须包含完成所有基类初始化所需的参数个数。多继承构造函数定义的一般形式如下:
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n)派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n) • { • // 派生类新增成员的初始化语句 • }
说明: • (1)对基类成员和子对象成员的初始化必须在成员初始化列表中进行,新增成员的初始 • 化既可以在成员初始化列表中进行,也可以在构造函数体中进行。 • (2)派生类构造函数必须对这三类成员进行初始化,其执行顺序如下所述。 • 调用基类构造函数; • 调用子对象的构造函数; • 派生类的构造函数体;
(3)当派生类有多个基类时,处于同一层次的各个基类的构造函数的调用顺序取决于定(3)当派生类有多个基类时,处于同一层次的各个基类的构造函数的调用顺序取决于定 • 义派生类时声明的顺序(自左向右),而与在派生类构造函数的成员初始化列表中给出的顺序无关。 • (4)如果派生类的基类也是一个派生类,则每个派生类只需负责其直接基类的构造,依 • 次上溯。
(5)当派生类中有多个子对象时,各个子对象构造函数的调用顺序也取决于在派生类中(5)当派生类中有多个子对象时,各个子对象构造函数的调用顺序也取决于在派生类中 • 定义的顺序(自前至后),而与在派生类构造函数的成员初始化列表中给出的顺序无关。 • (6)派生类构造函数提供了将参数传递给基类构造函数的途径,以保证在基类进行初始 • 化时能够获得必要的数据。因此,如果基类的构造函数定义了一个或多个参数时,派生类必须定义构造函数。
(7)如果基类中定义了缺省构造函数或根本没有定义任何一个构造函数(此时,由编译(7)如果基类中定义了缺省构造函数或根本没有定义任何一个构造函数(此时,由编译 • 器自动生成缺省构造函数)时,在派生类构造函数的定义中可以省略对基类构造函数的调用,即省略“<基类名>(<参数表>)”。 • (8)子对象的情况与基类相同。
(9)当所有的基类和子对象的构造函数都可以省略时,可以省略派生类构造函数的成员(9)当所有的基类和子对象的构造函数都可以省略时,可以省略派生类构造函数的成员 • 初始化列表。 • (10)如果所有的基类和子对象构造函数都不需要参数,派生类也不需要参数时,派生 • 类构造函数可以不定义。
5.3.3 虚基类 • 前面讲到实现二义性的一种方法就是使用作用域标志符来唯一标志它们。此外另外两种解决二义性的方法是: • (1) 在类中定义同名成员。定义同名成员可以解决二义性问题的主要原因是支配规则在 • 起作用。支配规则如下: 类X中的成员N支配类Y中同名的成员N是指类X以类Y为它的一个基类。如果一个名字支配另一个名字,则二者之间不存在二义性,当使用该成员时,使用支配者中的成员。
(2) 虚基类 • 在解决二义性问题时要注意: • (1) 一个类不能从同一个类中直接继承一次以上。 • (2) 二义性检查在访问控制权限或类型检查之前进行,访问控制权限不同或类型不同不能解决二义性问题。
下面我们就来看一下解决二义性其中的一种方法:虚基类。下面我们就来看一下解决二义性其中的一种方法:虚基类。 • 1.虚基类的概念 • 如果基类中只存在一个拷贝,那么就不会存在二义性,如果想使公共的基类只产生一个拷贝,则可以把它声明为虚基类。 • 虚基类的声明是在派生类的声明过程,其语法形式如下: • class 派生类名:virtual 继承方式 类名{ • // … • }
2.虚基类的初始化 • 在使用虚基类机制时应该注意以下几点: • (1)如果在虚基类中定义有带形参的构造函数,并且没有定义缺省形式的构造函数,则 • 整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员。