《第8部分继承与派生.ppt》由会员分享,可在线阅读,更多相关《第8部分继承与派生.ppt(26页珍藏版)》请在三一办公上搜索。
1、1,第8章 继承与派生,2,本章学习要求:,继承的概念 单继承多继承,3,8.1 继承与派生的概念,由于种种原因(如技术和资源等方面的限制),对已有软件不加修改地直接使用往往是很困难的。因为,已有软件的功能与新软件所需要的功能总是有差别的,如果要复用就必须解决这个差别。解决这个差别的一种途径是修改已有软件的源代码,这不仅需要读懂源代码,而且修改过程可能引入新的错误,花费大量的调试时间。继承机制是面向对象技术提供的另一种解决软件复用问题的途径。通过利用已有的类来定义新的类,即在定义一个新类时,先把一个或多个已有类的属性和功能全部包含进来,然后再给出新功能和属性的定义或对已有类的属性或功能重新定义
2、。,在继承关系中存在两个类:一个是已经存在的类,称为基类(或称父类);另一个是将要被创建的新类,称为派生类(或称子类)。派生类拥有基类的所有成员,并可以定义新的成员或对基类的一些成员进行重新定义,从而使派生类呈现与基类不同的特征。类之间的继承关系可表示为如图所示。,4,8.2 单继承,5,8.2.1 单继承的定义,在定义单继承时,派生类只能有一个直接父类,其定义如下:class:其中,为所新定义的派生类的名字,是派生类的直接基类的名字,继承方式可以是public(公有)、private(私有)和protected(保护),它用于影响基类成员到派生类后访问权限,继承方式可以省略,默认为priva
3、te。用于给出在派生类中新增的成员。如:,class Employeestring name;/姓名string offertel;/办公电话string dept;/部门string no;/工号double string sal;/基本工资double raiseSalary()/计算工资;,class Programmer:public Employee string specialty;/技术特长 void Programming();,6,8.2.2 继承方式以及派生类成员的访问,从基类继承的成员也可能会在两个地方被访问:在派生类内部,派生类新增的函数可能会访问从基类继承的成员。在派
4、生类的外部,通过派生类对象来访问从基类继承的成员。对从基类中继承的成员的访问,受到其在派生类中的访问控制方式的限制,而基类的成员在派生类中的访问权限由该成员在基类中的访问控制方式和派生类的继承方式共同决定的。继承方式在定义派生类时指出,如未指出,则默认为private。继承方式对基类成员在派生类中的访问控制方式的影响如表所示。,7,表 继承方式对基类成员在派生类中的访问控制方式的影响,8,基类成员被继承到派生类后,其访问控制方式决定于其在基类中的访问控制方式和继承方式,如表8-1所示。在派生类外只能访问在派生类中访问控制为public的基类成员。即基类的pubic成员被public方式继承到派
5、生类后,可以在派生类外部访问,其他都不可以。在派生类中自动拥有基类的所有成员,但并不是所有的从基类继承的成员都能够被派生类中新增的成员访问。派生类的新成员只能访问基类中的public和protected成员,与继承方式无关。,9,8.2.3 派生类对象的初始化和撤销,1派生类对象的初始化在派生类对象中包含一个匿名的基类对象,在创建和初始化派生类对象时,系统按照一定的顺序进行:先初始化该匿名的基类对象,然后再初始化派生类自己的新增成员。匿名基类对象的初始化由基类的构造函数来完成,派生类的构造函数会调用基类的构造函数来完成匿名基类对象的初始化,默认情况是调用基类无参数的构造函数,如果要调用其他带参
6、数的构造函数,则必须在派生类构造函数的成员初始化列表中指出。派生类构造函数的一般格式为:():(),().;,10,例 派生类对象的初始化。class parentpublic:parent(int i)x=i;prent()x=0;private:int x;class derived:public parentpublic:derived()/默认调用基类的无参数构造函数y=0;derived(int i)y=i;/默认调用基类的无参数构造函数/parent(a)调用基类的构造函数初始化匿名基类对象derived(int a,int b):parent(a),y(b)private:int
7、 y;int main()derived d1;/调用derived:derived()和parent:parent()derived d2(10);/调用derived:derived(int)和parent:parent()derived d3(10,20);/用derived:derived(int,int)和parent:parent(int),11,2派生类对象的撤销当派生类对象消亡时,析构的顺序与初始化顺序刚好相反:先调用该类的析构函数体中的代码,然后按照当初初始化时的相反顺序调用成员的析构函数,最后调用匿名基类对象的析构函数。在析构函数函数中,不需要显式调用成员对象以及基类的析构
8、函数,编译器会自动添加代码来调用。,12,3类的多级继承一个派生类也可以作为基类来派生出另一个新的类。如图8-5所示的类继承层次结构:A为基类,类B是类A的派生类,类C是类B的派生类,则类C也是类A的派生类。类B称为A的直接派生类,类C称为类A的间接派生类。类A是类B的直接基类,是类C的间接基类。,13,例多级继承的访问权限。实现右图中的ABC 多级继承关系 解:定义类层次结构中的类。class Apublic:void afun1()protected:void afun2();private:int a;class B:public Apublic:void bfun1()protecte
9、d:void bfun2()private:double b;class C:protected Bpublic:void cfun1()protected:void cfun2()private:char c;,14,8.3 多重继承,15,8.3.1 多重继承的定义,在定义多重继承的派生类时,需要给出两个或两个以上的直接基类,其格式为:class:,.,设有两个类A和B,定义如下:class Apublic:void fa();private:int m;float f;class Bpublic:void fb();private:int n;double d;,现在需要定义第三个类C,
10、要求C类包含A类和B类的所有成员,它还要有自己新定义成员r和fc(),如何定义呢?用多重继承实现的代码为:,class C:public A,public Bpublic:void fc();private:int r;char c;,16,对于多重继承,需要说明以下几点:(1)对每个基类都要指定继承方式,默认为private继承,各基类之间使用逗号分割。(2)派生类拥有所有基类的所有成员。(3)继承方式对每个基类成员到派生类后的访问控制的规定同单继承。(4)基类的声明次序决定对基类构造函数/析构函数的调用次序,以及对基类成员的存储安排。,例如,上面用多继承定义的派生类C,定义C类的对象mo:
11、C mo;则对象mo的内存空间布局如图所示。,17,8.3.2 成员名二义性,在多重继承中,当多个基类中包含同名的成员时,它们在派生类中就会出现成员名二义性问题。例如,对于下面的类CC,由于它的两个基类中都有函数f,这样,在类CC中访问函数f就会出现二义性:class CApublic:void f()void g();class CBpublic:void f()void g();class CC:public CA,public CBpublic:void func()f();/错误,是CA的f,还是CB的f?;int main()CC c;c.f();/错误,是CA的f,还是CB的f?,
12、18,在C+中,解决上面问题的办法是采用基类名受限访问:class CC:public CA,public CB public:void func()CA:f();/正确,调用CA的f。CA作为类名进行限定CB:f();/正确,调用CB的f。CB作为类名进行限定;int main()CC c;c.A:f();/正确,调用CA的f。c.B:f();/正确,调用CB的f。,19,8.3.3 重复继承虚基类,在多重继承中,如果多个直接基类有公共的基类,则会出现重复继承。造成派生类对象中同时有两个相同的基类对象.造成二义性.可以使用虚基类来解决.继承时,在基类前加virtual关键字就可以实现虚继承。
13、如:class B:virtual public A;类B虚继承类A,类A称为类B的虚基类。对于虚继承,有下面两点需要注意:编译器在编译任何一个派生类P时,若P以及P的多个基类(直接基类或间接基类)有共同的虚基类X,则在类P中只会保留一份X的成员。若派生类P的基类(直接基类或间接基类)有虚基类,则必须在P的构造函数中调用该虚基类的构造函数,以对唯一的拷贝进行初始化。系统保证这个唯一的拷贝只会被初始化一次。,20,例 虚继承演示。#include using namespace std;class Apublic:A(int a)cout A:A(int)n;x=a;int GetX()retu
14、rn x;private:int x;class B:virtual public Apublic:B(int a):A(a)cout B:B(int)n;class C:virtual public Apublic:C(int a):A(a)cout C:C(int)n;class D:public B,public Cpublic:D(int a):A(a),B(a),C(a)cout D:D(int)n;int main()D d(10);cout d.GetX();,21,8.4 继承与组合,1组合前面我们介绍了可以通过继承从一个已有类创建出新类。继承描述的是派生类和基类之间的“is-
15、a”的关系,如,程序员是一个职员。因此,可以在定义Programmer时从Employee类继承。另外,我们还可以通过组合创建新类,即把已有类的对象作为新类的成员对象,为实现新类的功能提供服务。,22,/已有的可重用类class Common public:X()m=0;void Set(int i)m=i;int Read()constreturn m;int Permute(int scale)return m=m*scale;private:int m;/新类class Custom public:Y()void SetMember(int i)c.Set(i);void ReadMem
16、ber()const return c.m;void Permute(int scale=1)if(scale=0)scale=0.1;return c.Permute(scale);private:Common c;/c为Custom类提供功能支持;,在Custom类中,包含了一个Common类对象c,Custom类的数据以及功能都由c来提供。Custom中对c提供的Permulate功能进行了适当的约束,即:scale不能为0,默认值为1等。新类也可定义自己的新成员。同时也可由多个可重用的已有类的对象来提供数据和功能上的服务,此时称为多个已有类对象成员组合生成新类。组合描述的是新类和已有类
17、之间的一种包含的关系。它们之间不是继承所描述的“is-a”的关系,而是一种“has-a”的关系。即:可以描述成“新类 has-a 旧类”。,23,2组合和继承的选择无论是组合还是继承都是把可重用类的对象放在新类中,两者都是用构造函数的初始化列表去初始化这些子对象,那么这两者之间有什么不同呢?一般一个或多个已有类和新类之间具有“has-a”的关系时,通常采用组合。已有类和新类之间具有“is-a”的关系时,通常采用继承。(2)组合通常在希望新类内部具有已有类的功能以及性能,但却不希望已有类的接口作为新类的接口时来使用。(3)实际上,在很多情况下,不管是采用继承还是组合都可以从已有类创建我们所需要的
18、新类。在选择实现方式时,我们需要从派生类的功能实现、代码的可理解性、派生类的用户的容易使用等各方面来考虑,选择一种合适的方式。,24,8.5 子类型,有一个特定的类型S,当且仅当它至少提供了类型T的行为,则称类型S是类型T的子类型。子类型是类型间的一般和特殊的关系。public继承时,派生类具有基类的所有功能和行为。只有public继承可以实现子类型,只有public派生类才是基类真正的子类型,它完整地继承了基类的功能。称派生类为基类的子类型,它是基类的特殊化。称基类为父类型,它是所有派生类的一般化。在使用基类对象的时候可以使用派生类对象代替,具体表现在以下几个方面:基类指针可以指向派生类的对象。,25,#include using namespace std;class parentpublic:int mp;class derived:public parentpublic:int md;int main()parent*pp=new parent;pp=new derived;/将派生类对象地址赋值给基类对象的指针,26,(2)基类的引用类型变量可以引用派生类对象。parent p;parent/输出200,