《【精品】c++_继承和派生类(可编辑.ppt》由会员分享,可在线阅读,更多相关《【精品】c++_继承和派生类(可编辑.ppt(84页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、C+_继承和派生类13.1 继承与派生的概念继承与派生的概念 在在C+中中,所所谓谓“继继承承”就就是是在在一一个个已已存存在在的的类类的的基础上建立一个新的类。基础上建立一个新的类。已已存存在在的的类类称称为为“基基类类”或或“父父类类”;新新建建立立的的类类称为称为“派生类派生类”或或“子类。子类。新新产产生生的的类类不不仅仅有有自自己己特特有有的的成成员员数数据据和和成成员员函函数数,而且而且有被继承类的全部成员数据和成员函数有被继承类的全部成员数据和成员函数。一一个个基基类类可可以以派派生生出出许许多多个个派派生生类类,每每一一个个派派生生类类又又可可以以作作为为基基类类再再派派生生出
2、出新新的的派派生生类类,因因此此基基类类和和派生类是相对而言的。派生类是相对而言的。一一代代一一代代地地派派生生下下去去,就就形形成成了了类类的的继继承承的的层层次次结结构。构。继承常用来表示继承常用来表示类属类属关系,不能将继承理解关系,不能将继承理解为构成关系。当从现存类中派生出新类时,为构成关系。当从现存类中派生出新类时,可以对派生类做如下几种变化:可以对派生类做如下几种变化:全部或部分地继承基类的成员数据或成员函数;全部或部分地继承基类的成员数据或成员函数;可以增加新的数据成员;可以增加新的数据成员;可以增加新的成员函数;可以增加新的成员函数;可以重新定义已有的成员函数;可以重新定义已
3、有的成员函数;可以改变现有成员的属性。可以改变现有成员的属性。基类基类派生类派生类1派生类派生类2(a a)单一继承)单一继承 一个基类一个基类可以直接可以直接派生出多派生出多个派生类个派生类 单一继承与多重继承单一继承与多重继承 一个派生类只有一个直接基类的情况称为单一继承一个派生类只有一个直接基类的情况称为单一继承(single-inheritance)。如果一个派生类可以同时有多个)。如果一个派生类可以同时有多个基类,称为多重继承(基类,称为多重继承(multiple-inheritance),这时的派),这时的派生类同时得到了多个已有类的特征。生类同时得到了多个已有类的特征。派生出来派
4、生出来的新类同样的新类同样可以作为基可以作为基类再继续派类再继续派生出更新的生出更新的类,依此类类,依此类推形成一个推形成一个层次结构层次结构。基类基类1基类基类2基类基类n派生类派生类1派生类派生类2(b)多重继承)多重继承 class Tri double x,y,z;/表示三角形三边表示三角形三边public:Tri(double,double,double);/构造函数构造函数 double Peri();/求三角形的周长求三角形的周长 double Area();/求三角形的面积求三角形的面积;class Col:public Tri /公有继承Tri类 double h;/表示三角
5、柱体的高度public:Col(double,double,double,double);double Volu();/求三角柱体的体积;在派生类的类体中,只需要声明新增的成员数据和在派生类的类体中,只需要声明新增的成员数据和成员函数即可。成员函数即可。多继承的派生类多继承的派生类定义格式如下:定义格式如下:class:继承方式继承方式1,继承方继承方式式2,继承方式继承方式n;继承方式的解释与单继承一致。继承方式的解释与单继承一致。class Stu int stu_id;/代表学生学号代表学生学号 double score;/代表学生成绩代表学生成绩public:Stu(int,doubl
6、e);/学生类的构造学生类的构造函数函数 void Show();/输出学生的信息输出学生的信息;class Emp int sta_id;/代表职工工号代表职工工号 double salary;/代表职工工资代表职工工资public:Emp(int,double);/职工类构造函数职工类构造函数 void Show();/输出职工的信息输出职工的信息;class Stu_Emp:public Stu,public Emppublic:Stu_Emp(int,double,int,double);/新类型的构造函数新类型的构造函数 void Show();/输出新类型的信息输出新类型的信息;1
7、3.1.3 派生类的三种继承方式派生类的三种继承方式 派生类中的成员由两部分组成,一部分是从基类派生类中的成员由两部分组成,一部分是从基类继承来的,一部分是派生类新增的。继承来的,一部分是派生类新增的。从基类继承来的成员在派生类中的访问权限与其从基类继承来的成员在派生类中的访问权限与其继承方式是密切相关的,并且除了继承方式是密切相关的,并且除了public,protected和和private之外,还有一种访问权限称之外,还有一种访问权限称为为“不可访问不可访问”,即无论何种继承方式,派生类,即无论何种继承方式,派生类中从基类继承过来的基类的私有成员在派生类中中从基类继承过来的基类的私有成员在
8、派生类中都是不可访问的。都是不可访问的。基类的私有成员在派生类中并没有成为派生类中基类的私有成员在派生类中并没有成为派生类中的私有成员,只有基类的成员函数可以引用它,的私有成员,只有基类的成员函数可以引用它,而不能被派生类的成员函数引用。而不能被派生类的成员函数引用。公有继承公有继承 当派生类以当派生类以公有公有(publicpublic)方式)方式继承继承基类时,基基类时,基类的类的公有成员和保护成员公有成员和保护成员在派生类中的在派生类中的访问权限访问权限不变不变,仍为公有成员和保护成员;而基类的,仍为公有成员和保护成员;而基类的私有私有成员成员则如前所述,则如前所述,在派生类中在派生类中
9、的访问权限成为的访问权限成为“不可访问不可访问”。class A int x;/私有私有protected:int y;/保护保护public:int z;/公有公有 A(int a,int b,int c)x=a;y=b;z=c;void ShowA()coutxtytzendl;class B:public A/公有继承公有继承 int t;/私有私有public:B(int a,int b,int c,int d):A(a,b,c)t=d;void ShowB()couttendl;void main()B ob(1,2,3,4);/定义派生类对象定义派生类对象 ob.ShowA();/
10、调用派生类中继承基类的公有成员函数调用派生类中继承基类的公有成员函数 ob.ShowB();/调用派生类中新定义的公有成员函数调用派生类中新定义的公有成员函数私有继承私有继承 当派生类以当派生类以私有私有(private)方式)方式继承继承基类时,基基类时,基类的类的公有成员和保护成员公有成员和保护成员在在派生类中派生类中的访问权限的访问权限全部成为私有成员全部成为私有成员;而基类的;而基类的私有成员私有成员在派生类在派生类中的访问权限仍为中的访问权限仍为“不可访问不可访问”。当定义派生类。当定义派生类时省略基类的继承方式时,时省略基类的继承方式时,默认默认的继承方式是私的继承方式是私有(有(
11、private)继承。)继承。class A int x;/私有私有protected:int y;/保护保护public:int z;/公有公有 A(int a,int b,int c)x=a;y=b;z=c;void ShowA()coutxtytzendl;class B:private A/私有继承私有继承 int t;/私有私有public:B(int a,int b,int c,int d):A(a,b,c)t=d;void ShowB()ShowA();couttendl;void main()B ob(1,2,3,4);/定义派生类对象定义派生类对象 /ob.ShowA();/
12、不能在类外调用基类成员不能在类外调用基类成员 ob.ShowB();/调用派生类中新定义的公有成员函数调用派生类中新定义的公有成员函数保护继承保护继承 当派生类以当派生类以保护(保护(protected)方式继承)方式继承基类时,基类时,基类的公有成员和保护成员基类的公有成员和保护成员在派生类中的访问权在派生类中的访问权限限全部成为保护成员全部成为保护成员;而基类的;而基类的私有成员私有成员在派生在派生类中的访问权限仍为类中的访问权限仍为“不可访问不可访问”。保护(保护(protected)继承是一种带有)继承是一种带有“血缘血缘”关系关系的继承形式,可以想象,基类无论经过多少次的继承形式,可
13、以想象,基类无论经过多少次“保护保护”继承,其公有成员和保护成员在派生类内继承,其公有成员和保护成员在派生类内部都是允许访问的,而在类外是禁止访问的。部都是允许访问的,而在类外是禁止访问的。class A int x;/私有私有protected:int y;/保护保护public:int z;/公有公有 A(int a,int b,int c)x=a;y=b;z=c;void ShowA()coutxtytzendl;class B:protected A/保护继承保护继承 int t;/私有私有public:B(int a,int b,int c,int d):A(a,b,c)t=d;vo
14、id ShowB()/公有函数公有函数 ShowA();/基类中的公有变为派生类中的保护,只能在类中调用基类中的公有变为派生类中的保护,只能在类中调用 couttendl;void main()B ob(1,2,3,4);/定义派生类对象定义派生类对象/ob.ShowA();/不能在类外调用基类成员不能在类外调用基类成员 ob.ShowB();/调用派生类中新定义的公有成员函数调用派生类中新定义的公有成员函数不同派生方式访问权限不同派生方式访问权限不可直接访问不可直接访问 不可直接访问不可直接访问 private 不可直接访问不可直接访问 private protected 不可直接访问不可直
15、接访问 private public u私有派私有派生生 不可直接访问不可直接访问 不可直接访问不可直接访问 private 不可直接访问不可直接访问 protected protected 可直接访问可直接访问 public public u公有派公有派生生 外部函数外部函数void main()在派生类中对在派生类中对基类成员的访基类成员的访问限定问限定 基类中的访基类中的访问限定问限定 派生方式派生方式 不可直接访问不可直接访问 不可直接访问不可直接访问 private 不可直接访问不可直接访问 protected protected 不可直接访问不可直接访问 protected pub
16、lic u保护派保护派生生 13.2.1 单继承派生类的构造函数单继承派生类的构造函数 派生类中包含有继承基类的成员和派生类中的新增成派生类中包含有继承基类的成员和派生类中的新增成员,在创建派生类的对象时,不仅要给派生类中的数据员,在创建派生类的对象时,不仅要给派生类中的数据成员初始化,还要给它从基类中继承过来的数据成员初成员初始化,还要给它从基类中继承过来的数据成员初始化。这样,在派生类的对象构造时,必须首先正确地始化。这样,在派生类的对象构造时,必须首先正确地构造这个对象中基类的成员。构造这个对象中基类的成员。派生类构造函数的一般形式为:派生类构造函数的一般形式为:派生类构造函数名派生类构
17、造函数名(总参数列表总参数列表):):基类构造函数名基类构造函数名(参参数列表数列表)派生类中新增数据成员初始化语句派生类中新增数据成员初始化语句 创建派生类对象时首先调用基类的构造函数,然后才创建派生类对象时首先调用基类的构造函数,然后才执行派生类构造函数体内的语句。执行派生类构造函数体内的语句。class Tri /三角形类三角形类 double a,b,c;public:Tri(double x,double y,double z)a=x;b=y;c=z;cout调用基类的构造函数调用基类的构造函数endl;double Peri(void);/周长作为函数值返回周长作为函数值返回 do
18、uble Area(void);/面积作为函数值返回面积作为函数值返回;class Col:public Tri /公有继承公有继承Tri类类 double h;/表示三角柱体的高度表示三角柱体的高度public:Col(double x,double y,double z,double t):Tri(x,y,z)/三角柱体的构造函数,利用参数初始化三角形三边和高度三角柱体的构造函数,利用参数初始化三角形三边和高度 h=t;cout调用派生类的构造函数调用派生类的构造函数endl;double Volu()return Area()*h;void main()Col col(3,4,5,2);
19、/创建三角柱体对象创建三角柱体对象 coutcol的体积的体积:col.Volu()endl;/求求col的体积的体积程序运行后的结果为:程序运行后的结果为:调用基类的构造函数调用基类的构造函数调用派生类的构造函数调用派生类的构造函数col的体积的体积:12派生类对象释放时执行析构函数的顺序派生类对象释放时执行析构函数的顺序与构造函数相反,先执行派生类的析构与构造函数相反,先执行派生类的析构函数函数Col(),再执行其基类的析构函数,再执行其基类的析构函数Tri()13.2.2 多继承派生类的构造函数多继承派生类的构造函数 在多继承的情况下,由于派生类具有多个基类,在在多继承的情况下,由于派生
20、类具有多个基类,在创建派生类的对象时,同样首先构造派生类对象中基类创建派生类的对象时,同样首先构造派生类对象中基类的成员,的成员,调用基类构造函数的顺序按照它们继承时说明调用基类构造函数的顺序按照它们继承时说明的顺序的顺序,而不是派生类构造函数中列举的顺序。,而不是派生类构造函数中列举的顺序。多继承派生类构造函数的一般形式为:多继承派生类构造函数的一般形式为:派生类构造函数名派生类构造函数名(总参数列表总参数列表):基类基类1构造函数名构造函数名(参数参数列表列表1),基类基类2构造函数名构造函数名(参数列表参数列表2),基类基类n构造构造函数名函数名(参数列表参数列表n)派生类中新增数据成员
21、初始化语句派生类中新增数据成员初始化语句class Stu /学生类学生类 int stu_id;/代表学生学号代表学生学号 double score;/代表学生成绩代表学生成绩public:Stu(int n,double sc)/学生类的构造函数学生类的构造函数 stu_id=n;score=sc;cout调用学生类构造函数调用学生类构造函数endl;void ShowA()cout学号学号:stu_idt成绩成绩:scoreendl;class Emp/职工类职工类 int sta_id;/代表职工工号代表职工工号 double salary;/代表职工工资代表职工工资public:Em
22、p(int n,double sa)/职工类构造函数职工类构造函数 sta_id=n;salary=sa;cout调用职工类构造函数调用职工类构造函数endl;void ShowB()cout工号工号:sta_idt工资工资:salaryendl;class Stu_Emp:public Emp,public Stu/在职的学生类在职的学生类public:Stu_Emp(int n1,double sc,int n2,double sa):Stu(n1,sc),Emp(n2,sa)cout调用派生类构造函数调用派生类构造函数endl;void main()Stu_Emp s(10001,98,
23、20001,2000.0);s.ShowA();s.ShowB();程序运行后的结果为:程序运行后的结果为:调用职工类构造函数调用职工类构造函数调用学生类构造函数调用学生类构造函数调用派生类构造函数调用派生类构造函数学号学号:10001 成绩成绩:98工号工号:20001 工资工资:200013.2.3 有子对象的派生类的构造函数有子对象的派生类的构造函数 在类的数据成员中,还可以包括基类或其他类在类的数据成员中,还可以包括基类或其他类的对象,称为的对象,称为子对象子对象,即对象中的对象。子对象中,即对象中的对象。子对象中的数据成员在创建时同样需要初始化,所以,在派的数据成员在创建时同样需要初
24、始化,所以,在派生类构造函数的初始化列表中,不仅要列举所调用生类构造函数的初始化列表中,不仅要列举所调用的基类的构造函数,而且要列举所包含的子对象成的基类的构造函数,而且要列举所包含的子对象成员的构造函数。员的构造函数。其构造函数的一般形式为:其构造函数的一般形式为:派生类构造函数名派生类构造函数名(总参数列表总参数列表):):基类构造函数名基类构造函数名(参参数列表数列表),),子对象名子对象名(参数列表参数列表)派生类中新增数据成员初始化语句派生类中新增数据成员初始化语句 13.2.3 有子对象的派生类的构造函数有子对象的派生类的构造函数 执行派生类构造函数的顺序是:执行派生类构造函数的顺
25、序是:调用基类构造函数调用基类构造函数,调用顺序按照它们,调用顺序按照它们继承继承时时说明的顺序说明的顺序。调用子对象类的构造函数调用子对象类的构造函数,调用顺序按照它,调用顺序按照它们在们在类中说明的顺序类中说明的顺序。执行派生类构造函数体中的内容执行派生类构造函数体中的内容。派生类对象释放时派生类对象释放时执行析构函数的顺序执行析构函数的顺序正好与正好与构造函数构造函数相反相反。class Base1 int x;public:Base1(int a)x=a;cout调用基类调用基类1的构造函数的构造函数!n;Base1()cout调用基类调用基类1的析构函数的析构函数!n;class B
26、ase2 int y;public:Base2(int a)y=a;cout调用基类调用基类2的构造函数的构造函数!n;Base2()cout调用基类调用基类2的析构函数的析构函数!n;class Derived:public Base2,public Base1int z;Base1 b1,b2;public:Derived(int a,int b):Base1(a),Base2(20),b1(200),b2(a+b)z=b;cout调用派生类的构造函数调用派生类的构造函数!n;Derived()cout调用派生类的析构函数调用派生类的析构函数!n;void main(void)Derive
27、d c(100,200);调用基类调用基类2的构造函数的构造函数调用基类调用基类1的构造函数的构造函数调用派生类的构造函数调用派生类的构造函数调用派生类的析构函数调用派生类的析构函数调用基类调用基类1的析构函数的析构函数调用基类调用基类2的析构函数的析构函数调用基类调用基类1的构造函数的构造函数调用基类调用基类1的构造函数的构造函数调用基类调用基类1的析构函数的析构函数调用基类调用基类1的析构函数的析构函数说明基类说明基类1的对象的对象b1,b2在实际应用中,使用派生类构造函数时应注意在实际应用中,使用派生类构造函数时应注意如下几个问题:如下几个问题:当基类中有默认的构造函数或者没有定义构当基
28、类中有默认的构造函数或者没有定义构造函数时,派生类构造函数的定义中就可以造函数时,派生类构造函数的定义中就可以省略对基类构造函数的调用。省略对基类构造函数的调用。当基类的构造函数使用一个或多个参数时,当基类的构造函数使用一个或多个参数时,则在派生类必须定义构造函数,提供将参数则在派生类必须定义构造函数,提供将参数传递给基类构造函数的途径。传递给基类构造函数的途径。13.3 继承的冲突与支配继承的冲突与支配13.3.1 冲突冲突 若一个公有的派生类是由两个或多个基若一个公有的派生类是由两个或多个基类派生,当基类中成员的访问权限为类派生,当基类中成员的访问权限为publicpublic,且不同基类
29、中的成员具有相同的名字时,且不同基类中的成员具有相同的名字时,出现了重名的情况。这时在派生类使用到基出现了重名的情况。这时在派生类使用到基类中的同名的成员时,出现了不唯一性,这类中的同名的成员时,出现了不唯一性,这种情况称为冲突。种情况称为冲突。在职学生类在职学生类Stu_Emp中一共继承了两个名为中一共继承了两个名为id的成员变量的成员变量和两个名为和两个名为Show()的成员函数的成员函数 13.3.1 冲突冲突13.3.1 冲突冲突 当多个基类存在同名成员的时候,使用类当多个基类存在同名成员的时候,使用类限定符进行区分,表示方法如下:限定符进行区分,表示方法如下:类名类名:成员名成员名;
30、在上例中,如果要输出学生类的信息,表示为:在上例中,如果要输出学生类的信息,表示为:s.Stu:Show();要输出职工类的信息,表示为:要输出职工类的信息,表示为:s.Emp:Show();class Apublic:int x;void Show()cout x=xn;A(int a=0)x=a;class Bpublic:int x;void Show()cout x=xn;B(int a=0)x=a;class C:public A,public Bint y;public:void Setx(int a)x=a;/c1对象中有两个对象中有两个x成员成员 void Sety(int b
31、)y=b;int Gety()return y;void main(void)C c1;c1.Show();/c1对象中有两个对象中有两个Show()函数函数这时,可以利用这时,可以利用类作用类作用域符域符:来指明数据或函来指明数据或函数的来源。数的来源。如:如:A:x=a;c1.B:Show();在在C+中,允许派生类中新增加的成员名与中,允许派生类中新增加的成员名与其基类的成员名相同,这种同名并不产生冲突。其基类的成员名相同,这种同名并不产生冲突。当没有使用作用域运算符时,则当没有使用作用域运算符时,则派生类中定派生类中定义的成员名优先于基类中的成员名义的成员名优先于基类中的成员名,这种优
32、先关,这种优先关系称为系称为支配规则。支配规则。13.3.2 支配支配13.3.2 支配支配 如果基类和派生类中出现了同名成员,那么,在派生类中所定义的成员名具有支配地位,这就是继承中的支配规则,也称同名覆盖。class Apublic:int x;void Show()cout x=xn;class Bpublic:int y;void Show()cout y=yn;class C:public A,public Bpublic:int y;/类类B和类和类C均有均有y的成员的成员;void main(void)C c1;c1.x=100;c1.y=200;/给派生类中的给派生类中的y赋值
33、赋值 c1.B:y=300;/给基类给基类B中的中的y赋值赋值 c1.A:Show();c1.B:Show();/用作用域运算符限定调用的函数用作用域运算符限定调用的函数 cout y=c1.yn;/输出派生类中的输出派生类中的y值值 cout y=c1.B:yn;/输出基类输出基类B中的中的y值值当派生类中新增加的当派生类中新增加的数据或函数与基类中数据或函数与基类中原有的同名时,若不原有的同名时,若不加限制,则加限制,则优先调用优先调用派生类中的成员派生类中的成员。13.3.3 赋值兼容规则赋值兼容规则 赋值的兼容规则指的是在公有派生的情况下,派生类赋值的兼容规则指的是在公有派生的情况下,
34、派生类对象和基类对象间的赋值关系,有以下三种情况(假设对象和基类对象间的赋值关系,有以下三种情况(假设类类derived由类由类base派生):派生):派生类对象可以赋值给基类对象。派生类对象可以赋值给基类对象。derived d;base b;b=d;也就是说,派生类对象中从基类继承过来的那一部分也就是说,派生类对象中从基类继承过来的那一部分成员数据可以为基类对象中的对应的成员数据赋值。成员数据可以为基类对象中的对应的成员数据赋值。派生类对象可以初始化基类引用。派生类对象可以初始化基类引用。derived d;base&br=d;也就是说,派生类对象中从基类继承过来也就是说,派生类对象中从基
35、类继承过来的那一部分成员可以有一个基类的别名的那一部分成员可以有一个基类的别名br。派生类对象的地址可以赋给基类的指针。派生类对象的地址可以赋给基类的指针。derived d;base*pb=&d;当然,只能用基类指针引用派生类对象中从基当然,只能用基类指针引用派生类对象中从基类继承过来的那一部分成员。类继承过来的那一部分成员。class Apublic:int x;A(int a=0)x=a;class Bpublic:int y;B(int a=0)y=a;class C:public A,public B int z;public:C(int a,int b,int m):A(a),B(
36、b)z=m;void Show()coutx=xt;couty=yt;coutz=zn;void main(void)A a1(100);B b1(200);C c1(10,20,50);couta1.x=a1.xendl;coutb1.y=b1.yendl;c1.Show();a1=c1;b1=c1;/派生类对象向基类对象赋值派生类对象向基类对象赋值couta1.x=a1.xendl;coutb1.y=b1.yShow();coutxendl;a1.x=100b1.y=200 x=10y=20z=50a1.x=10b1.y=20错误!3813.4.1 虚基类的定义虚基类的定义 在职学生类既有
37、学生类的特性,又有职工类的特性。但是在职学生类既有学生类的特性,又有职工类的特性。但是在职学生本身是一个人,应该只有一个身份证号码,因此应该在职学生本身是一个人,应该只有一个身份证号码,因此应该只保持一份只保持一份Person类类的数据。然而,如果要是不加限制地直接的数据。然而,如果要是不加限制地直接继承,就会造成派生类中存在多个基类拷贝的情况。继承,就会造成派生类中存在多个基类拷贝的情况。13.4.1 虚基类的定义虚基类的定义 在多重派生的过程中,若欲使公共的基类在派在多重派生的过程中,若欲使公共的基类在派生类中只有一份拷贝,则可以将这种基类声明为虚生类中只有一份拷贝,则可以将这种基类声明为
38、虚基类。虚基类在派生时进行声明,其声明的形式为:基类。虚基类在派生时进行声明,其声明的形式为:class 派生类名派生类名:virtual 访问限定符访问限定符 基类类名基类类名;或:或:class派生类名派生类名:访问限定符访问限定符 virtual 基类类名基类类名;例如学生类定义:例如学生类定义:class Stu:virtual public Person 学生类新增成员学生类新增成员;同样需对职工类定义:同样需对职工类定义:class Emp:virtual public Person 职工类新增成员职工类新增成员;这样由学生类和职工类派生出的在职学生类的定义为:这样由学生类和职工类
39、派生出的在职学生类的定义为:class Stu_Emp:public Stu,public Emp 在职学生类新增成员在职学生类新增成员;为了保证虚基类在派生类中只继承一为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类次,应当在该基类的所有直接派生类中声明其为虚基类。例如必须在学生中声明其为虚基类。例如必须在学生类类Stu和职工类和职工类Emp中都将中都将Person类声类声明为虚基类,这样才能保证在派生类明为虚基类,这样才能保证在派生类中只有一份中只有一份Person类的拷贝。类的拷贝。13.4.2 虚基类的初始化虚基类的初始化 如果在虚基类中定义了带参数的构造函如果在虚基类
40、中定义了带参数的构造函数,而且没有定义默认的构造函数,则在其数,而且没有定义默认的构造函数,则在其所有派生类(包括直接派生或间接派生的派所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化列表直接生类)中,通过构造函数的初始化列表直接显式调用虚基类的构造函数对其进行初始化。显式调用虚基类的构造函数对其进行初始化。class Apublic:int x;A(int a=0)x=a;cout调用虚基类调用虚基类A构造函数构造函数endl;class B:public virtual Apublic:int y;B(int a=0,int b=0):A(a)y=b;cout调用派生类
41、调用派生类B构造函数构造函数endl;class C:public virtual Apublic:int z;C(int a=0,int c=0):A(a)z=c;class D:public B,public Cpublic:int dx;D(int a1,int b,int c,int d,int a2):B(a1,b),C(a2,c)dx=d;cout调用派生类调用派生类D构造函数构造函数endl;void main(void)D d1(10,20,30,40,50);coutd1.xendl;d1.x=400;coutd1.xendl;coutd1.yendl;没有对虚基类构造函数的
42、调用,用缺省的构造函数没有对虚基类构造函数的调用,用缺省的构造函数直接在派生类中调用虚基类的构造函数直接在派生类中调用虚基类的构造函数43对于虚基类构造函数调用的说明如下:对于虚基类构造函数调用的说明如下:如果一个派生类有一个直接或间接的虚基类,那如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始化列表中必须列出对虚么派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未被列出,则表示使用该虚基类构造函数的调用;如果未被列出,则表示使用该虚基类的默认的构造函数。基类的默认的构造函数。在一个成员初始化列表中同时出现对虚基类和非在一个成员初始化列表中同时出现对
43、虚基类和非虚基类构造函数的调用,则先调用虚基类的构造函数。虚基类构造函数的调用,则先调用虚基类的构造函数。直接或间接继承虚基类的派生类,其构造函数的直接或间接继承虚基类的派生类,其构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。成员初始化列表中都要列出对虚基类构造函数的调用。但是,只有用于创建对象的那个派生类的构造函数才真但是,只有用于创建对象的那个派生类的构造函数才真正调用虚基类的构造函数,而在初始化列表中的其他派正调用虚基类的构造函数,而在初始化列表中的其他派生类对虚基类构造函数的调用并不执行,这样保证了对生类对虚基类构造函数的调用并不执行,这样保证了对虚基类中数据成员只初始化一
44、次。虚基类中数据成员只初始化一次。第第14章章 虚函数虚函数学习目标学习目标:理解多态性的基本思想理解多态性的基本思想 ;虚函数。虚函数。14.1 多态性的概念多态性的概念 多态性是面向对象程序设计的关键技术之一。多态性是面向对象程序设计的关键技术之一。若程序设计语言若程序设计语言不支持多态性,不能称为面向对不支持多态性,不能称为面向对象的语言象的语言。利用多态性技术,可以调用同一个函。利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能。数名的函数,实现完全不同的功能。在在C+C+中中有两种有两种多态性多态性 编译时的多态性编译时的多态性 运行时的运行时的多态性多态性 指在程序执行
45、前,无法根据函数指在程序执行前,无法根据函数名和参数来确定该调用哪一个函名和参数来确定该调用哪一个函数,必须在程序执行过程中,根数,必须在程序执行过程中,根据执行的具体情况来动态地确定。据执行的具体情况来动态地确定。它是它是通过类继承关系和虚函数来实现通过类继承关系和虚函数来实现的。目的也是建立一种通用的程序。的。目的也是建立一种通用的程序。通过函数的重载和运算符通过函数的重载和运算符的重载来实现的。的重载来实现的。14.2 虚函数虚函数 多态性,调用同一个函数名的函数,但实现完多态性,调用同一个函数名的函数,但实现完全不同的功能。通过虚函数或重载技术来实现。多全不同的功能。通过虚函数或重载技
46、术来实现。多态性分为态性分为静态和动态静态和动态多态性,多态性,静态静态多态性是通过多态性是通过函函数的重载或运算符的重载数的重载或运算符的重载来实现的;动态多态性是来实现的;动态多态性是通过通过虚函数虚函数来实现的。其本质是来实现的。其本质是通过改变指针的指通过改变指针的指向调用不同的函数向调用不同的函数。编译时多态性编译时多态性函数重载(兔子逃生)函数重载(兔子逃生)class 兔子兔子public:void 逃生(老鹰逃生(老鹰a)“兔子蹬鹰兔子蹬鹰”;void 逃生(狼逃生(狼 b)“动如脱兔动如脱兔”;派生类对象的地址可以赋给基类的指针。派生类对象的地址可以赋给基类的指针。deriv
47、ed d;base*pb=&d;当然,只能用基类指针引用派生类对象中从基当然,只能用基类指针引用派生类对象中从基类继承过来的那一部分成员。类继承过来的那一部分成员。基类和派生类的同名成员基类和派生类的同名成员class 宠物宠物public:void speak()cout zzz;class 猫猫:public 宠物宠物public:void speak()coutmiao!miao!;class 狗狗:public宠物宠物public:void speak()coutspeak();虚函数的定义虚函数的定义 虚函数是基类的成员函数,定义虚函数的格式如下:虚函数是基类的成员函数,定义虚函数的
48、格式如下:virtual 函数类型函数类型 函数名(参数列表)函数名(参数列表)函数体函数体 关于虚函数的使用说明如下:关于虚函数的使用说明如下:派生类中的虚函数必须与基类中的虚函数具有相同派生类中的虚函数必须与基类中的虚函数具有相同的函数名、相同的参数列表,相同或相似的返回类的函数名、相同的参数列表,相同或相似的返回类型。型。(返回类型相似是指派生类中虚函数的返回类(返回类型相似是指派生类中虚函数的返回类型可以是基类虚函数返回类型的公有派生类型)。型可以是基类虚函数返回类型的公有派生类型)。派生类中与基类中虚函数同原型的成员函数,也一派生类中与基类中虚函数同原型的成员函数,也一定是虚函数,在
49、其定义中,关键字定是虚函数,在其定义中,关键字virtual可以被省可以被省略。换句话说,略。换句话说,虚函数是可以继承的。虚函数是可以继承的。虚函数是实现动态联编的必要条件,但不是惟一条虚函数是实现动态联编的必要条件,但不是惟一条件件。动态多态性的实现需要满足三个条件:一是类。动态多态性的实现需要满足三个条件:一是类之间满足赋值兼容规则;二是要在基类中声明虚函之间满足赋值兼容规则;二是要在基类中声明虚函数;三是要通过基类指针或基类引用调用虚函数。数;三是要通过基类指针或基类引用调用虚函数。虚函数必须是类的非静态成员函数虚函数必须是类的非静态成员函数。构造函数不能是虚函数,析构函数可以是虚函数
50、构造函数不能是虚函数,析构函数可以是虚函数。虚函数的执行速度要稍慢一些。为了实现多态性,虚函数的执行速度要稍慢一些。为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现的。函数的调用机制也是间接实现的。class Circle/表示圆类表示圆类protected:double r;/表示圆的半径表示圆的半径public:Circle(double rad)r=rad;double Peri()return 2*r*PI;virtual double Area()return PI*r*r;class Cylind