《教学内容:.ppt》由会员分享,可在线阅读,更多相关《教学内容:.ppt(36页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、12 继承与派生类,2,教学内容:,12.1 继承的概念12.2 派生类 12.3 二义性问题 12.4 赋值兼容规则,3,能通过继承已有的类建立新类 掌握继承的三种方式:公有、保护、私有继承掌握派生类和基类的概念;掌握派生类的构造函数和析构函数;理解多重继承和虚基类,教学要求:,4,12.1 继承的概念,继承是面向对象程序设计的基本特征之一,它允许在原类的基础上创建新类,新类即可从一个或多个原类中继承(共享)其函数和数据,也可以重新定义原类中没有的数据和函数。故通过类的“继承”,可将原来的程序代码重复使用,从而减少了程序代码的冗余度,提高了编程的效率。,类的继承是新类从已有类那里得到已有的特
2、性。从已有类产生新类的过程就是类的派生。在继承过程中,原类称为基类或父类,而由类派生的新类则称为派生类或子类。派生新类的过程包括吸收基类的成员、调整基类成员和添加新的成员三步。,5,据派生类所拥有的基类数目不同,可以分为:单继承:一个类只有一个直接基类时,称为单继承多继承:一个类同时有多个直接基类时,则称为多继承基类与派生类之间的关系如下: 基类是对派生类的抽象,派生类是对基类的具体化 派生类是基类的组合。多继承可看作是多个单继承的简 单组合。 新类也可作为基类再派生新类,且一个基类可同时派生出 多个派生类。基类的基类甚至更高层的基类称为间接基类 派生类可对一些继承来的函数重新定义,以适应新的
3、要求 派生类包含了它所有基类中除构造和析构函数之外的所有成员。,6,12.2 派生类,派生类的定义格式:,class 派生类名:继承方式 基类名1,继承方式 基类名n 派生类新成员声明; /与前面声明一个类时完全相同 ;,例: 假设基类Base1,Base2是已声明的类,声明一个名为Dr1的 派生类,该类从基类Base1,Base2派生而来:,class Dr1:public Base1,private Base2 派生类新成员声明; ;,继承方式:指定了派生类成员以及类外对象对于从基类 继承来的成员的访问权限。有三种继承方式: public:表示公有继承; private:表示私有继承,是默
4、认方式; protected:表示保护继承。,7,下表是不同继承方式的基类在派生类中的访问权限,8,从上表可以看出:,公有继承时:基类成员的访问权限在派生类中不变。于是派 生类的其他成员可以直接访问继承来的公有和 保护成员。其他外部使用者只能通过派生类的 对象访问继承来的公有成员,保护继承时:基类成员的访问权限在派生类中全变为保护的 访问方式。于是派生类的其他成员可直接访 问从基类继承来的公有和保护成员,但在类外 部通过派生类的对象无法访问它们,9,私有继承时:基类成员的访问权限在派生类中全变为私有的访 问方式 基类中私有成员在派生类中是隐藏的,只能在基类内部访问。 派生类中的成员不能访问基类
5、中的私有成员,可以访问基类 中的公有成员和保护成员 派生类中用“类名 : 成员”访问基类成员 若派生类定义了与基类同名的成员,若要在派生类中使用基 类同名成员,可用:类名 : 成员 派生类对基类的静态成员的访问用:类名 : 成员,10,派生类的构造函数:,派生类的数据成员由所有基类的数据成员与派生类新增的数据成员共同组成,如果派生类新增成员中包括其他类的对象(子对象),派生类的数据成员中实际上还间接包括了这些对象的数据成员。因此,构造派生类的对象时,必须对基类数据成员、新增数据成员和成员对象的数据成员进行初始化。派生类的构造函数必须要以合适的初值作为参数,隐含调用基类和新增对象成员的构造函数,
6、来初始化它们各自的数据成员,然后再加入新的语句对新增普通数据成员进行初始化。,11,派生类构造函数的一般格式如下:,派生类构造函数执行的一般次序如下: 1)调用基类构造函数,调用顺序按照它们在定义派生类中声 明的顺序; 2)调用内嵌成员对象的构造函数,其调用顺序同上3)派生类的构造函数体中的内容,派生类名:派生类名(参数总表):基类名1(参数表1), 基类名n(参数表n) 子对象名1:(子对象参数表1),子对象名m(子对象参数表m) 派生类新增成员的初始化语句; ,12, 若基类中定义了缺省构造函数或根本没有定义任何一个 构造函数(此时,由编译器自动生成缺省构造函数)时, 在派生类构造函数的定
7、义中可以省略对该基类构造函数的 调用。子对象的情况也一样。 若所有基类和子对象的构造函数都不需要参数且派生类 也不需要参数时,派生类构造函数可以不定义。 若所有基类和子对象的构造函数都可省略时,可以省略 派生类构造函数的定义若基类的构造函数定义了一个或多个参数时,派生类必 须定义构造函数,注意:,13,每个派生类只需负责其直接基类的构造对基类成员和子对象成员的初始化必须在成员初始化列表 中进行,新增成员的初始化既可以在成员初始化列表中进 行,也可以在构造函数体中进行。,14,例:派生类构造函数举例,#include class B1/基类B1,构造函数有参数 public: B1(int i)
8、 coutconstructing B1 iendl; ; class B2/基类B2,构造函数有参数 public: B2(int j)coutconstructing B2 jendl; ; class B3 /基类B3,构造函数无参数 public: B3( ) coutconstructing B3 *endl; ;,15,class C:public B2,public B1,public B3 /派生新类C,注意基类名的顺序 public: /派生类的公有成员 C(int a,int b,int c,int d):B1(a),memberB2(d),memberB1(c),B2(b
9、) /注意基类名的个数与顺序及成员对象名的个数与顺序 private: /派生类的私有对象成员 B1 memberB1; B2 memberB2; B3 memberB3; ; void main( ) C obj(1,2,3,4); ,程序运行结果是: constructing B2 2constructing B1 1constructing B3 *constructing B1 3constructing B2 4 constructing B3 *,16,派生类析构造函数:,功能:是在该类对象消亡之前进行一些必要的清理工作。,定义方法:,与没有继承关系的类中析构函数的定义方法完全相同
10、,只要在函数体中负责把派生类新增的非对象成员的清理工作做好就够了,系统会自己调用基类及成员对象的析构函数来对基类及对象成员进行清理,执行次序:,和构造函数正好相反,首先对派生类新增普通成员进行清理,再新增的对象成员进行清理,最后对所有从基类继承来的成员进行清理。这些清理工作分别是执行派生类析构函数体、调用派生类对象成员所在类的析构函数和调用基类析构函数,17,例:派生类析构函数举例,#include class B1/基类B1,构造函数有参数 public: B1(int i) coutconstructing B1 iendl; B1( ) coutdestructing B1endl; ;
11、 class B2/基类B2,构造函数有参数 public: B2(int j)coutconstructing B2 jendl; B2() coutdestructing B2endl; ; class B3 /基类B3,构造函数无参数 public: B3( ) coutconstructing B3 *endl; B3( ) coutdestructing B3 endl; ;,18,class C:public B2,public B1,public B3 /派生新类C,注意基类名的顺序 public: /派生类的公有成员 C(int a,int b,int c,int d):B1(
12、a),memberB2(d),memberB1(c),B2(b) /注意基类名的个数与顺序及成员对象名的个数与顺序 private: /派生类的私有对象成员 B1 memberB1; B2 memberB2; B3 memberB3; ; void main( ) C obj(1,2,3,4); ,程序运行结果是: constructing B2 2constructing B1 1constructing B3 *constructing B1 3constructing B2 4 constructing B3 *destructing B3destructing B2destructin
13、g B1destructing B3destructing B1destructing B2,19,12.3 二义性问题,一般说来,在派生类中对基类成员的访问应该是惟一的。但是,由于多继承情况下,可能造成对基类中某个成员的访问出现了不惟一的情况(例;访问多基类中的同名成员),则称为对基类成员访问的二义性问题。,1. 同名成员的二义性,在多重继承中,如果不同基类中有同名的函数,则在派生类中就有同名的成员,这种成员会造成二义性。,20,例:,class A protected: void f(); ;class B public: void f(); void g(); ;class C: pub
14、lic A,public B public: void g(); void h(); ; C obj; obj.f( );,/无法确定访问A中或是B中的f(),obj.A:f( );/A中的f( );,注意:二义性检查在访问控制权限或类型检查之前进行, 访问控制权限不同或类型不同不能解决二义性问题。,21,使用基类名可避免这种二义:obj.A:f();/A中的f();obj.B:f();/B中的f();,例如: obj.g(); /隐含用C的g() obj.B:g();/用B的g(),以上这种用基类名来控制成员访问的规则称为支配原则。,注意:若派生类的多个基类拥有同名的成员,同时,派生类又新增
15、这样的同名成员,则派生类成员将覆盖所有基类的同名成员。若派生类的多个基类拥有同名的成员但派生类没有声明同名成员这时,这时就必须通过作用域分辨符:来标识成员,22,例:多继承同名覆盖举例,#include class B1 public: int nV; void fun() coutMember of B1endl; ; class B2 public: int nV; void fun() coutMember of B2endl; ; class D1:public B1,public B2 /声明派生类D1 public: int nV; void fun() coutMember of
16、 D1endl; void main( ) D1 d1; d1.nV=1; /对象名.成员名标识 d1.fun( ); /访问D1类成员 d1.B1:nV=2; /作用域分辨符标识 d1.B1:fun( ); /访问B1基类成员 d1.B2:nV=3; /作用域分辨符标识 d1.B2:fun( ); /访问B2基类成员,程序运行结果是: Member of D1 Member of B1 Member of B2,23,2.多重继承的二义性,从上图可以看出:派生类D3中将继承两份类B的成员,一份 由类D1派生得到,另一份由D2派生而来, 这时通过派生类D3的对象访问类D1和D2 的成员不会有问
17、题,但访问类B的成员时 编译程序不知道到底要访问哪一份的成员.,C+为此提供了虚基类,以解决这种二义性。,24,class 派生类名:virtual 继承方式 基类名 声明派生类成员 ;, 虚基类:是这样的一个基类:它虽然被一个派生类间接地 多次继承,但派生类却只继承一份该基类的成员, 这样,避免了在派生类中访问这些成员时产生二 义性。,将一个基类声明为虚基类必须在各派生类定义时,在基类的名称前面加上关键字virtual。,虚基类的派生类的定义格式如下:,25,例:虚基类举例,#include class B0 public: int nV; void fun( ) coutMember of
18、 B0endl; ; class B1: virtual public B0 /BO为虚基类,派生B1类 public: int nV1; ;class B2: virtual public B0 /BO为虚基类派生B2类 public: int nV2; ; class D1: public B1,public B2 /派生类D1声明 public: int nVd; void fund( ) coutMember of D1endl; ; void main() /程序主函数 D1 d1; /定义D1类对象d1 d1.nV=2; /使用直接基类 d1.fun( ); d1.B0:nV=2;
19、 d1.B0:fun( ); ,程序运行结果是:Member of B0 Member of B0,26, 虚基类及其派生类的构造函数:,在上例中,虚基类的使用显得非常方便、简单,这是由于该程序中的所有类使用的都是编译器自动生成的默认构造函数。若虚基类定义有非默认形式的(即带形参的)构造函数,并且没有定义默认形式的构造函数,这时,在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中列出对虚基类的初始化。,总之:对虚基类的任何派生类:其构造函数不仅负责调用 直接基类的构造函数,还需调用虚基类的构造函数,,27,在使用虚基类时要注意:,(1) 类在类族中既可用作虚基
20、类,也可被用作非虚基类(2) 派生类的构造函数的成员初始化列表中必须列出对虚基 类构造函数的调用;如果未列出,则表示使用该虚基类 的缺省构造函数。(3)从虚基类直接或间接派生的派生类中的构造函数的成员初 始化列表中都要列出对虚基类构造函数的调用。但只有用 于建立对象的最派生类的构造函数调用虚基类的构造函数, 而该派生类的所有基类中列出的对虚基类的构造函数的调 用在执行中被忽略(4)若同时出现对虚基类和非虚基类构造函数的调用时,虚基 类的构造函数先于非虚基类的构造函数执行。,28,例:虚基类及其派生类构造函数举例:,#include class B0 public: B0(int n) nV=n
21、; int nV; void fun( ) coutMember of B0endl; ;class B1:virtual public B0 /BO为虚基类,派生B1类 public: B1(int a):B0(a) int nV1; ; class B2:virtual public B0 /BO为虚基类,派生B2类 public: B2(int a):B0(a) int nV2; ;class D1:public B1,public B2 /派生类D1声明 public: D1(int a):B0(a),B1(a),B2(a) int nVd; void fund() coutMembe
22、r of D1成员名 ,31,void main( ) B0 b0; /声明B0类对象 B1 b1; D1 d1; B0 *p; /声明B0类指针 p= ,程序运行结果是: B0:display B0:display B0:display,32,程序举例,例:编写一个程序计算出球、圆柱和圆锥的表面积和体积。,分析:计算它们都需要用到圆的半径,有时还可能用到圆的面积,故可把圆定义为一个类。它包含的数据成员为半径,由于不需要作图,所以不需要定义圆心坐标。圆的半径应定义为保护属性,以便派生类能够继承和使用。圆类的公用函数是给半径赋初值的构造函数、计算圆面积的函数;也可以包含计算圆的体积的函数,但让其
23、返回0,表示圆的体积为0。定义好圆类后,再把球类、圆柱类和圆锥类定义为圆的派生类。在这些类中同样包含有新定义的构造函数、求表面积的函数和求体积的函数。另外在圆柱和圆锥类中应分别新定义一个表示其高度的数据成员。此题的完整程序如下:,33,#include #include const double PI=3.1415926; class Circle /圆类 protected: double r; /半径 public: Circle(double radius = 0) r =radius; double Area( ) /计算圆的面积 return PI*r*r ; double Volu
24、me( ) /计算圆的体积 return 0; ;,34,class Sphere: public Circle /球体类 public: Sphere(double radius = 0): Circle(radius) double Area( ) return 4*PI*r*r ; / 可用4*Circle:Area( )来代替 double Volume( ) /计算球的体积 return 4*PI*pow(r,3)/3; ; / pow(r , 3)求出r的立方值class Cylinder:public Circle /圆柱体类 double h; /高度 public: Cyli
25、nder(double radius=0, double height = 0 ) : Circle(radius) h = height; double Area( ) / 计算圆柱体的表面积 return 2*PI*r*(r+h); double Volume( ) /计算圆柱体的体积 return PI*r*r*h; /它可以用Circle: Area( ) * h来代替 ;,35,class Cone:public Circle /圆锥体类 double h; /高度 public: Cone(double radius = 0,double height = 0):Circle(ra
26、dius) h=height; double Area( ) / 计算圆锥体的表面积 double l=sqrt(h*h+r*r); /sqrt函数求出参数值的平方根 return PI*r*(r+l); double Volume( ) /计算圆锥体的体积 return PI*r*r*h/3; ;,36,void main ( ) Circle r1(2); Sphere r2(2); Cylinder r3(2,3); Cone r4(2,3); cout Circle: r1.Area( ) r1.Volume( )endl; cout Sphere: r2.Area( ) r2.Volume( )endl; cout Clinder: r3.Area( ) r3.Volume( )endl; cout Cone: r4.Area( ) r4.Volume( )endl; ,程序运行结果是:Circle: 12.5664 0 Sphere: 50.2655 33.5103 Cylinder: 62.8319 37.6991 Cone: 35.2207 12.5664,