1、 第10章 继承和派生类 目的与要求10.1 继承与派生10.2 冲突、支配规则和赋值兼容性10.3 虚基类10.4 静态数据成员本章小结目的与要求 通过本章学习,应理解继承与派生的概念,掌握派生类的定义语句格式及使用方法。知道基类成员经公有或私有派生后在派生类中的访问权限。初步掌握冲突、支配规则、赋值兼容性的概念。了解虚基类的概念、虚基类的语句定义格式及使用方法。了解静态成员的定义格式、初始化方式及作用域。10.1 继承与派生 继承:从已有类出发建立新的类,使新类部分或全部地继承已有类的成员。派生:通过继承已有的一个或多个类产生一个新类称为派生。10.1.1 继承与派生的基本概念1继承与派生
2、的定义 在定义类B时,若使用类A的部分或全部成员,则称类B继承了类A,并称类A为基类或父类,称类B为派生类或子类。基类与派生类或父类与子类的关系可以用图表示。基类(父类)A派生类(子类)B继承与派生2单一继承 若派生类是由一个基类派生出来的,称为单一继承,如派生类Score是由一个基类Student派生出的,所以为单一继承。如图所示。基类(父类)Student派生类(子类)Score单一继承3多重继承 若派生类由多个基类派生出,称为多重继承。如图所示。多重继承基类(父类)Student基类(父类)Score派生类(子类)Information10.1.2 派生类的定义 1.定义派生类的格式 c
3、lass:,;说明:(1)派生类由n个基类多重派生,当n=1时为单一继承。(2)访问权限access=public、private与protected2公有派生 若在定义派生类时,access为public,则定义公有派生。公有派生时,基类中所有成员在派生类中保持各个成员的访问权限。具体访问权限如下:(1)基类中public成员在派生类仍保持为public成员,所以在派生类内、外都可直接使用这些成员。(2)基类中private成员属于基类私有成员,所以在派生类内、外都不能直接使用这些成员。只能通过该基类公有或保护成员函数间接使用基类中的私有成员。公有派生(3)基类中protected成员可在派
4、生类中直接使用,但在派生类外不可直接访问这类成员,必须通过派生类的公有或保护成员函数或基类的成员函数才能访问。【例10.1】用学生档案类Student派生出学生成绩类Score。讨论基类中公有、私有与保护数据成员在派生类中访问权限的变化。例程3私有派生 若在定义派生类时,access为private则定义了私有派生。经过私有派生后:(1)基类中公有成员在派生类变为私有成员,在派生类内可以使用,而在派生类外不能直接使用。(2)基类中保护成员在派生类变为私有成员,在派生类内可以使用,而在派生类外不能直接使用。(3)基类中私有成员在派生类内、外都不能直接使用,必须通过基类公有函数使用。10.1.3
5、派生类的构造函数与基类成员的初始化 1.派生类构造函数格式 派生类的构造函数由初始化基类数据成员构造函数与初始化派生类新增的数据成员构造函数组成。派生类构造函数的格式为::(形参表):(实参表1),(实参表n)派生类构造函数体 派生类构造函数格式说明:(1)基类构造函数实参可以是表达式或派生类构造函数的形参。(2)实参只与形参名有关,而与参数顺序无关。(3)冒号后基类构造函数列表称为初始化成员列表。派生类构造函数举例【例10.2】多重派生实例。定义描述圆的类Circle,定义描述高的类High,用描述圆的类与描述高的类作为基类,多重派生出圆柱体类Cylinder。讨论多重继承中基类成员的初始化
6、问题。例程例程2建立对象时,构造函数的调用顺序 先调用基类构造函数,再调用派生类构造函数。注意:(1)在派生类中并不能直接对基类的私有数据成赋初始值,必须通过公有的构造函数进行初始化工作。(2)基类构造函数的调用顺序取决于它们在派生类中说明顺序,而与它们在派生类构造函数中的顺序无关。3撤消对象时,析构函数的调用顺序 在撤消派生类的对象时,析构函数的调用顺序正好与构造函数的顺序相反。即:先调用派生类的析构函数,再调用基类析构函数。【例10.3】定义两个基类Base1与Base2,并由Base1与Base2派生出派生类Derive。编写程序,输出派生类中构造函数与析构函数的调用关系。例程例程4派生
7、类中包含对象成员的构造函数 若派生类中包含对象成员,则在派生类构造函数的初始化成员列表中不仅要列举要调用的基类构造函数,而且要列举调用的对象成员构造函数。【例10.4】将例10.3的派生类改为包含对象成员的派生类。例程例程 首先要调用基类的构造函数,再调用对象成员的构造函数,最后执行派生类的构造函数。在有多个对象成员的情况下,调用这些对象成员的构造函数的顺序取决于它们在派生类中说明顺序。10.2 冲突、支配规则和赋值兼容性 10.2.1冲突 引例:【例10.5】在例10.2中将描述高的类改为描述矩形的类Rectangle,在Rectangle类中用矩形中心坐标(x,y)、高(High)与宽(W
8、idth)作为类的数据成员。例程例程 冲突:派生类使用基类中同名成员时出现不惟一称为冲突。解决方法:使用作用域运算符“:”指明同名成员属于哪一个基类,即::冲突结论:若一个公有派生类是由两个或多个基类派生,当基类中成员的访问权限为public或protected且不同基类中的成员同名时,派生类使用到基类中的同名成员时出现不惟一性称为冲突。解决方法是使用作用域运算符指明发生冲突的成员属于哪个基类。10.2.2 支配规则 所谓支配规则是指,当派生类中成员与基类中成员同名时,在不用作用域运算符时,派生类成员名优于基类成员名。【例10.6】在【例10.5】的派生类Cylinder中,新增数据成员(x,
9、y,z)表示圆柱体中心坐标。并从基类Rectangle中删除(x,y)。例程例程 注意:当派生的成员名与基类的成员名相同时,在派生类中或派生类外要使用基类中的这种成员时,仍要使用作用域运算符。10.2.3 赋值兼容规则 公有派生类的对象可赋给其基类对象,基类对象不能赋给派生类对象的规则,称为赋值兼容规则。10.2.4 基类和对象成员的几点说明(1)任一基类在派生类中只能继承一次。(2)基类成员与对象成员在使用上的差别 派生类中可直接使用基类的成员(访问权限 允许的话);使用对象成员时,必须在对象名后加上成员运算符“.”和成员名。10.3 虚基类 10.3.1 多重派生的基类拷贝 若类B与类C由
10、类A公有派生,而类D由类B与类C公有派生,则类D中将包含类A的两个拷贝(如图所示)。这种同一个基类在派生类中产生多个拷贝不仅多占用了存储空间,而且可能会造成多个拷贝数据的不一致。基类A基类A基类B基类C派生类D派生类中包含同一基类的两个拷贝多重派生的基类拷贝【例10.7】一个公共基类在派生类中产生两个拷贝。例程例程 如果在多条继承路径上有一个公共的基类,则该基类会在这些路径中的某几条路径的汇合处产生几个拷贝。为使这样的公共基类只产生一个拷贝,须将该基类说明为虚基类。10.3.2 虚基类 在多重派生的过程中,欲使公共的基类在派生中只有一个拷贝,可将此基类说明成虚基类。虚基类的定义格式为:clas
11、s:virtual ;或 class:virtual ;【例10.8】定义虚基类,使派生类中只有基类的一个拷贝。例程例程10.4 静态数据成员 将类的某一个数据成员的存储类型定义为静态类型时,则由该类所产生的所有对象均共享为静态成员所分配的一个存储空间。(1)静态数据成员的定义与引用方法在类中对静态数据成员作引用性说明 static ;在类外文件作用域对静态数据成员作定义性说明 必须在类外的文件作用域中,且只能作一次定义性说明,并分配 内存空间。:=初值;定义性说明时,静态数据成员缺省初值为0。静态数据成员引用格式 :静态数据成员(2)同类不同对象的静态数据成员占用相同的存储空间。(3)静态数
12、据成员置初值不受访问权限的限制。(4)为了保持静态数据成员取值的一致性,通常在构造函数中不给静态数据置初值,而是在静态数据成员的定义性说明时指定初值。本章小结 1.继承 继承继承:从已有类出发建立新的类,使新类部分或全部地继承已有类的成员称为继承。派生派生:通过继承已有的一个或多个类产生一个新类称为派生。派生类的定义格式如下:class:,派生类体;基类成员在派生类中访问权限基类成员访问权限 公有派生后的访问权限 私有派生后的访问权限publicpublicprivateprivate不可直接访问不可直接访问protectedprivateprivate 公有或私有派生后基类成员在派生类中访问
13、权限 派生类构造函数和析构函数 因为派生类成员由基类成员与派生类中新增加的成员组成,所以初始化工作应分为对派生类中新增成员的初始化与基类成员的初始化。由于初始化工作是由构造函数完成的,所以初始化基类成员的工作是由派生类的构造函数来完成的。派生类构造函数的格式如下::(形参表):(实参表1)(实参表n);构造函数的调用顺序是先基类后派生类,析构函数的调用顺序是先派生类后基类。2冲突、支配和赋值兼容性 冲突冲突:派生类使用基类中同名成员时出现不唯一称为冲突。冲突的解决方法::支配规则支配规则:使用派生类中与基类中同名成员时,派生类成员优于基类同名成员的规则称为支配规则。赋值的兼容性赋值的兼容性:派
14、生类对象可赋值给基类对象,基类对象不能赋给派生类对象称为赋值的兼容性。3虚基类 在多重派生的过程中,欲使公共的基类在派生类中只有一个拷贝,可将此基类说明成虚基类。虚基类的定义格式为:class:virtual ;或 class:virtual;4静态数据成员 静态数据成员的定义必须分两步完成,在类内作引用性说明,在类外作定义性说明。说明与引用格式为:类内作引用性说明的格式:static ;类外作定义性说明的格式::=初值;引用格式::例10.1(1)#include class Student private:int No;/定义No为私有数据成员 protected:int Age;/定义A
15、ge为保护的数据成员 public:char Sex;/定义Sex为公有数据成员 Student(int no,int age,char sex)/定义类Student的构造函数 No=no;Age=age;Sex=sex;int GetNo()return No;/返回No的公有成员函数 int GetAge()return Age;/返回Age的公有成员函数 void ShowS()/显示No、Age、Sex的公有成员函数 coutNo=NotAge=AgetSex=Sexendl;例10.1(2)class Score:public Student /由基类Student公有派生出子类S
16、core private:int Phi,Math;/定义类Score的私有数据成员 public:Score(int n,int a,char s,int p,int m):Student(n,a,s)/类Score的构造函数 Phi=p;Math=m;void Show(void)/显示类Score与其父类Student的数据成员值 coutNo=GetNo()tAge=AgetSex=Sex tPhi=PhitMath=Mathendl;例10.1(3)void main(void)Score s(101,20,M,90,80);/用类Score定义一个对象s s.ShowS();/类S
17、core的对象s调用基类公有函数 ShowS()s.Show();/类Score的对象调用公有函数Show()coutNo=s.GetNo()tAge=s.GetAge()tSex=s.Sexendl;程序执行后输出:No=101 Age=20 Sex=M No=101 Age=20 Sex=M Phi=90 Math=80 No=101 Age=20 Sex=M返回例10.2(1)#include class Circle /定义描述圆的类,其中(x,y)为圆心,r为半径 protected:float x,y,r;public:Circle(float a,float b,float c)
18、x=a;y=b;r=c;class High /定义描述高的类 private:float h;public:High(float a)h=a;float Geth()return h;例10.2(2)class Cylinder:public Circle,private High /由圆与高派生出圆柱体类 private:float Volume;public:Cylinder(float a,float b,float c,float d):Circle(a,b,c),High(d)/D Volume=r*r*3.1415*Geth();/E void Show()coutx=xty=y
19、tr=rt h=Geth()tV=Volumeendl;例10.2(3)void main(void)Cylinder cy(3,3,2,10);cy.Show();程序执行后输出:x=3 y=3 r=2 h=10 V=125.664 返回返回例10.3(1)#include class Base1 /定义基类Base1 private:int x;/定义基类Base1的私有数据成员x public:Base1(int a)/基类Base1的构造函数 x=a;cout调用基类1的构造函数!endl;Base1()/基类Base1的析构函数 cout调用基类1的析构函数!endl;例10.3(2
20、)class Base2 /定义基类Base2 private:int y;/定义基类Base2的私有数据成员y public:Base2(int a)/基类Base2的构造函数 y=a;cout调用基类2的构造函数!endl;Base2()/基类Base2的析构函数 cout调用基类2的析构函数!endl;例10.3(3)class Derive:public Base1,public Base2 /派生类Derive private:int z;/派生类Derive新增的私有数据成员 public:Derive(int a,int b):Base1(a),Base2(20)/派生类Deri
21、ve构造函数 z=b;cout调用派生类构造函数!endl;Derive()/派生类Derive的析构函数 cout调用派生类的析构函数!endl;void main(void)Derive c(100,200);例10.3(4)程序执行后输出:调用基类1的构造函数!调用基类2的构造函数!调用派生类的构造函数!调用派生类析构函数!调用基类2的析构函数!调用基类1的析构函数!返回返回例10.4(1)class Derive:public Base1,public Base2 private:int z;Base1 b1,b2;/在派生类中定义基类对象成员b1,b2 public:Derive(i
22、nt a,int b):Base1(a),Base2(20),b1(200),b2(a+b)/定义构造函数 z=b;cout调用派生类的构造函数!endl;Derive()cout调用派生类的析构函数!endl;void main(void)Derive d(100,200);例10.4(2)执行程序后输出:调用基类1的构造函数!调用基类2的构造函数!调用基类1的构造函数!调用基类1的构造函数!调用派生类的构造函数!调用派生类的析构函数!调用基类1的析构函数!调用基类1的析构函数!调用基类2的析构函数!调用基类1的析构函数!返回返回例10.5(1)#include class Circle /
23、定义描述圆的基类 protected:float x,y,r;/(x,y)为圆心,r为半径 public:Circle(float a,float b,float c)x=a;y=b;r=c;float Area()return(r*r*3.14159);/计算圆的面积;例10.5(2)class Rectangle /定义描述矩形的基类 protected:float x,y,h,w;public:Rectangle(float a,float b,float c,float d)x=a;y=b;h=c;w=d;float Area(void)/计算矩形面积 return h*w;例10.5
24、(3)class Cylinder:public Circle,public Rectangle /描述一个圆柱体的派生类 private:float Volume;/圆柱体的体积 public:Cylinder(float a,float b,float c):Circle(a,b,c),Rectangle(10,10,c,c)/A Volume=Area()*h;/B float GetV()return Volume;void Show(void)coutx=xty=yendl;/C;例10.5(4)void main(void)Cylinder cy(3,3,2);cy.Show();
25、coutVolume=cy.GetV()endl;程序执行后输出:x=3 y=3 Volume=25.1327 返回返回例10.5(5)#include class Circle /定义描述圆的基类 protected:float x,y,r;/(x,y)为圆心,r为半径 public:Circle(float a,float b,float c)x=a;y=b;r=c;float Area()return(r*r*3.14159);/计算圆的面积;例10.5(6)class Rectangle /定义描述矩形的基类 protected:float x,y,h,w;public:Rectang
26、le(float a,float b,float c,float d)x=a;y=b;h=c;w=d;float Area(void)/计算矩形面积 return h*w;例10.5(7)class Cylinder:public Circle,public Rectangle/描述一个圆柱体的派生类 private:float Volume;/圆柱体的体积 public:Cylinder(float a,float b,float c):Circle(a,b,c),Rectangle(10,10,c,c)/D Volume=Circle:Area()*h;/E float GetV()ret
27、urn Volume;void Show(void)coutx=Circle:xty=Circle:yendl;/F;例10.5(8)void main(void)Cylinder cy(3,3,2);cy.Show();coutVolume=cy.GetV()endl;程序执行后输出:x=3 y=3 Volume=25.1327 返回返回例10.6(1)#include class Circle /定义描述圆的基类,其中(x,y)为圆心,r为半径 protected:float x,y,r;public:Circle(float a,float b,float c)x=a;y=b;r=c;f
28、loat Area()return(r*r*3.14159);/计算圆的面积;class Rectangle /定义描述矩形的基类 protected:float h,w;public:例10.6(2)Rectangle(float c,float d)h=c;w=d;float Area(void)/计算矩形面积 return h*w;class Cylinder:public Circle,public Rectangle /描述一个圆柱体的派生类 private:float x,y,z,Volume;/圆柱体的中心坐标与体积 public:Cylinder(float a,float b
29、,float c,float d):Circle(a,b,d),Rectangle(d,d)x=a;y=b;z=c;Volume=Circle:Area()*h;例10.6(3)float GetV()return Volume;void Show(void)coutx=xty=y tz=z endl;void main(void)Cylinder cy(3,3,3,2);cy.Show();coutVolume=cy.GetV()endl;程序执行后输出:x=3 y=3 z=3Volume=25.1327 返回返回例10.7(1)#include class A public:int x;A
30、(int a)x=a;class B:public A /由公共基类A派生出类B public:int y;B(int a,int b):A(b)y=a;例10.7(2)class C:public A /由公共基类A派生出类C public:int z;C(int a,int b):A(b)z=a;class D:public B,public C /由基类B、C派生出类D public:int m;D(int a,int b,int d,int e,int f):B(a,b),C(d,e)m=f;void Print()coutx=B:xty=yendl;coutx=C:xtz=ze 例1
31、0.7(3)cout”m=”mendl;void main(void)D d1(100,200,300,400,500);d1.Print();程序执行后输出:x=200 y=100 x=400 z=300 m=500 返回返回例10.8(1)#include class A public:int x;A(int a=0)x=a;class B:virtual public A /由公共基类A派生出类B public:int y;B(int a,int b):A(b)y=a;例10.8(2)class C:public virtual A /由公共基类A派生出类C public:int z;C
32、(int a,int b):A(b)z=a;class D:public B,public C /由基类B、C派生出类D public:int m;D(int a,int b,int d,int e,int f):B(a,b),C(d,e)m=f;例10.8(3)Void Print()coutx=xty=yendl;coutx=xtz=zendl;coutm=mendl;void main(void)D d1(100,200,300,400,500);d1.Print();d1.x=400;d1.Print();例10.8(4)执行程序后输出:x=0 y=100 x=0 z=300 m=500 x=400 y=100 x=400 z=300 m=500 返回返回