《C++程序设计课程介绍-第12章 组合与继承.ppt》由会员分享,可在线阅读,更多相关《C++程序设计课程介绍-第12章 组合与继承.ppt(113页珍藏版)》请在三一办公上搜索。
1、第12章 组合与继承,组合 继承 虚函数与多态性纯虚函数与抽象类 多继承,组合,组合就是把用户定义类的对象作为新类的数据成员 组合表示一种聚集关系,是一种部分和整体(is a part of)的关系 必须用初始化列表去初始化对象成员,组合实例,定义一个复数类,而复数的虚部和实部都用有理数表示,类定义,class Complexfriend Complex operator+(Complex x,Complex y);friend istream,成员函数的实现,Complex operator+(Complex x,Complex y)Complex tmp;/利用Rational类的加法重载
2、函数完成实部和虚部的相加 tmp.real=x.real+y.real;tmp.imag=x.imag+y.imag;return tmp;,istream,复数类的使用,int main()Complex x1,x2,x3;cout x1;cout x2;x3=x1+x2;cout x1+x2=x3 endl;return 0;,第12章 组合与继承,组合 继承 虚函数与多态性纯虚函数与抽象类 多继承,派生类的概念,继承是面向对象程序设计的一个重要特征,它允许在已有类的基础上创建新的类基类、父类派生类、导出类或子类继承可以让程序员在已有类的基础上通过增加或修改少量代码的方法得到新的类,从而较
3、好地解决代码重用的问题。,派生类,单继承的格式基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象,派生类的定义,一般格式:class 派生类名:派生方法 基类名/派生类新增的数据成员和成员函数;派生方法:公有派生:public私有派生:private保护派生:protected,派生实例,class base int x;public:void setx(int k);class derived1:public base int y;public:void sety(int k);,Derived1有两个数据成员:x
4、,y。有两个成员函数:setx和sety,派生类对基类成员的访问,派生类的成员函数不能访问基类的私有数据成员 protected访问特性 protected成员是一类特殊的私有成员,它不可以被全局函数或其他类的成员函数访问,但能被派生类的成员函数访问 protected成员破坏了类的封装,基类的protected成员改变时,所有派生类都要修改,派生类,单继承的格式基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象,派生类对基类成员的访问性,class base int x;public:void setx(int k
5、);class derived1:public base int y;public:void sety(int k);,继承实例,定义一个二维平面上的点类型,可以设置点的位置,获取点的位置。在此基础上,扩展出一个三维空间上的点类型。,point_2d的定义,class point_2d private:int x,y;public:void setpoint2(int a,int b)x=a;y=b;int getx()return x;int gety()return y;,point_3d的定义,class point_3d:public point_2dint z;public:void
6、 setpoint3(int a,int b,int c)setpoint2(a,b);z=c;int getz()return z;,point_3d的讨论,point_3d的组成:有三个数据成员:x,y和z,有五个公有的成员函数:setpoint2,setpoint3,getx,gety和getz。point_3d的成员函数无法直接访问基类的x和y,因此在setpoint3函数中必须调用在point_2d的公有成员函数setpoint2实现。point_3d类的使用和普通类完全一样,用户不用去管这个类是用继承方式从另外一个类扩展而来,还是完全直接定义的。,Point_3d的使用,int m
7、ain()point_2d p1;point_3d p2;p1.setpoint2(1,2);cout p1:(p1.getx(),p1.gety()endl;p2.setpoint3(1,2,3);cout p2:(p2.getx(),p2.gety(),p2.getz()endl;return 0;,P1:(1,2)P2:(1,2,3),派生类,单继承的格式基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象,派生类的构造函数和析构函数,由于派生类继承了其基类的成员,所以在建立派生类的实例对象时,必须初始化基类继承
8、的数据成员。派生类对象析构时,也必须析构基类对象。派生类不继承基类的构造函数、析构函数和赋值运算符,但是派生类的构造函数、析构函数和赋值运算符能调用基类的构造函数、析构函数和赋值运算符。,派生类的构造函数,基类成员的初始化由基类的构造函数完成。派生类的构造函数调用基类的构造函数完成基类成员的初始化。派生类构造函数可以隐式调用基类缺省的构造函数,也可以在派生类的构造函数的初始化列表显式地调用基类的构造函数。,构造函数的格式,派生类构造函数的格式:派生类构造函数名(参数表):基类构造函数名(参数表)。基类构造函数中的参数表通常来源于派生类构造函数的参数表,也可以用常数值。如果构造派生类对象时调用的
9、是基类的缺省构造函数,则可以不要初始化列表。如果派生类新增的数据成员中含有对象成员,则在创建对象时,先执行基类的构造函数,再执行成员对象的构造函数,最后执行自己的构造函数体。,派生类构造实例,定义一个二维平面上的点类,并从它派生出一个圆类,point2.h,/Definition of class Point#ifndef POINT2_H#define POINT2_Hclass Point public:Point(int=0,int=0);/default constructor Point();/destructorprotected:/accessible by derived cl
10、asses int x,y;/x and y coordinates of Point;#endif,point2.cpp,#include point2.h/Constructor for class PointPoint:Point(int a,int b)x=a;y=b;cout Point constructor:x,y endl;/Destructor for class PointPoint:Point()cout Point destructor:x,y endl;,circle2.h,#ifndef CIRCLE2_H#define CIRCLE2_H#include poin
11、t2.hclass Circle:public Point public:/default constructor Circle(double r=0.0,int x=0,int y=0);Circle();private:double radius;#endif,circle2.cpp,#include circle2.h/Constructor for Circle calls constructor for PointCircle:Circle(double r,int a,int b):Point(a,b)/call base-class Constructor radius=r;/s
12、hould validatecout Circle constructor:radius is radius x,y endl;/Destructor roi class CircleCircle:Circle()cout Circle destructor:radius is radius x,y endl;,Circle类的应用,#include point2.h“#include circle2.h int main()/Show constructor and destructor calls for Point Point p(11,22);cout endl;Circle circ
13、le1(4.5,72,29);cout endl;Circle circle2(10,5,5);cout endl;return 0;,Point constructor:11,22 Point destructor:11,22 Point constructor:72,29 Circle constructor:radius is 4.5 72,29Point constructor:5,5 Circle constructor:radius is 10 5,5 Circle destructor:radius is 10 5,5 Point destructor:5,5 Circle de
14、structor:radius is 4.5 72,29 Point destructor:72,29,派生类构造函数的构造规则,若基类使用缺省或不带参数的构造函数,则在派生类定义构造函数是可略去:基类构造函数名(参数表)。此时若派生类也不需要构造函数,则可不定义构造函数。当基类构造函数需要参数,而派生类本身并不需要构造函数时,派生类还必须定义构造函数。该函数只是起了一个参数传递作用。如果省略了派生类的构造函数,那么就由派生类的默认构造函数调用基类的默认构造函数。,派生类实例,定义一个图书馆系统中的读者类,每个读者的信息包括:卡号、姓名、单位、允许借书的数量以及已借书记录。学生最多允许借5本书
15、,教师最多允许借10本书。,设计过程,系统中有两类读者:学生读者和教师读者。这两类读者有一部分内容是相同的:卡号、姓名和单位。可将两类读者的共同部分内容设计成一个基类。学生读者和教师读者从基类派生,增加已借书的数量以及已借书记录两个数据成员,并将允许借书的数量定义为整个类共享的常量,基类的设计,class readerint no;char name10;char dept20;public:reader(int n,char*nm,char*d)no=n;strcpy(name,nm);strcpy(dept,d);,教师读者类的设计,class readerTeacher:public r
16、eaderenum MAX=10;int borrowed;int recordMAX;public:readerTeacher(int n,char*nm,char*d):reader(n,nm,d)borrowed=0;bool bookBorrow(int bookNo);bool bookReturn(int bookNo);void show();/显示已借书信息;,学生读者类的设计,class readerStudent:public reader enum MAX=5;int borrowed;int recordMAX;public:readerStudent(int n,ch
17、ar*nm,char*d):reader(n,nm,d)borrowed=0;bool bookBorrow(int bookNo);bool bookReturn(int bookNo);void show();/显示已借书信息;,派生类对象的析构,派生类的析构函数值析构自己新增的数据成员,基类成员的析构由基类的析构函数析构派生类析构函数会自动调用基类的析构函数派生类对象析构时,先执行派生类的析构函数,再执行基类的析构函数,派生类构造函数和析构函数的构造规则实例,class base public:base()coutconstructing basen;base()coutdestruct
18、int basen;class derive1:public basepublic:derive1()coutconstructing derive1n;derive1()coutdestructing derive1n;main()derive1 op;return 0;,执行结果,constructing base constructing derive1 destructing derive1 destructing base,派生类,单继承的格式基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象,重定义基类的
19、函数,派生类是基类的扩展,可以是保存的数据内容的扩展,也可以是功能的扩展。当派生类对基类的某个功能进行扩展时,他定义的成员函数名可能会和基类的成员函数名重复。如果只是函数名相同,而原型不同时,系统认为派生类中有两个重载函数。如果原型完全相同,则派生类的函数会覆盖基类的函数。这称为重定义基类的成员函数。,实例,定义一个圆类型,用于保存圆以及输出圆的面积和周长。在此类型的基础上派生出一个球类型,可以计算球的表面积和体积。,圆类的设计,数据成员:圆的半径成员函数:由于需要提供圆的面积和周长,需要提供两个公有的成员函数。除了这些之外,还需要一个构造函数,圆类的定义,class circle prote
20、cted:double radius;public:circle(double r=0)radius=r;double getr()return radius;double area()return 3.14*radius*radius;double circum()return 2*3.14*radius;,球类的定义,class ball:public circle public:ball(double r=0):circle(r)double area()return 4*3.14*radius*radius;double volumn()return 4*3.14*radius*rad
21、ius*radius/3;,Ball类的构造函数,Ball类没有新增加数据成员,因而不需要构造函数。但基类的构造函数需要参数,所以ball类必须写构造函数Ball类构造函数的作用是为circle类的构造函数传递参数,Ball类的area函数,Ball类包含了两个原型完全一样的area函数。一个是自己定义的,一个是从circle类继承来的当对ball类的对象调用area函数时,调用的是ball类自己定义的area函数,派生类引用基类的同名函数,派生类中重新定义基类的成员函数时,它的功能往往是基类功能的扩展。为完成扩展的工作,派生类版本通常要调用基类中的该函数版本。引用基类的同名函数必须使用作用域
22、运算符,否则会由于派生类成员函数实际上调用了自身而引起无穷递归。这样会使系统用光内存,是致命的运行时错误。,实例,在circle类的基础上定义一个cylinder类。可以计算圆柱体的表面积和体积 设计考虑:存储圆柱体可以在圆的基础上增加一个高度。圆柱体的表面积是上下两个圆的面积加上它的侧面积圆柱体的体积是底面积乘上高度。而求圆的面积的函数在circle类中已经存在。,Cylinder类的定义,class cylinder:public circle double height;public:cylinder(double r=0,double h=0):circle(r)height=h;do
23、uble geth()return height;double area()return 2*circle:area()+circum()*height;double volumn()return circle:area()*height;,Ball和cylinder类的使用,int main()circle c(3);ball b(2);cylinder cy(1,2);cout circle:r=c.getr()endl;cout area=c.area()tcircum=c.circum()endl;cout ball:r=b.getr()endl;cout area=b.area()t
24、volumn=b.volumn()endl;cout cylinder:r=cy.getr()th=cy.geth()endl;cout area=cy.area()tvolumn=cy.volumn()endl;return 0;,执行结果,circle:r=3area=28.26 circum=18.84ball:r=2area=50.24 volumn=33.4933cylinder:r=1area=18.84 volumn=6.28,派生类,单继承的格式基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象,派生
25、类作为基类,基类本身可以是一个派生类,如:class base class d1:public base class d2:public d1 每个派生类继承他的直接基类的所有成员。如果派生类的基类是一个派生类,则每个派生类只负责他的直接基类的构造,依次上溯。当构造d2类的对象时,会先调用d1的构造函数,而d1的构造函数执行时又会先调用base的构造函数。因此,构造d2类的对象时,最先初始化的是base的数据成员,再初始化d1新增的成员,最后初始化d2新增的成员。析构的过程正好相反。,实例,#include using namespace std;class base int x;public
26、:base(int xx)x=xx;coutconstructing basen;base()coutdestructint basen;class derive1:public base int y;public:derive1(int xx,int yy):base(xx)y=yy;coutconstructing derive1n;derive1()coutdestructing derive1n;,class derive2:public derive1int z;public:derive2(int xx,int yy,int zz):derive1(xx,yy)z=zz;coutc
27、onstructing derive2n;derive2()coutdestructing derive2n;main()derive2 op(1,2,3);return 0;,constructing baseconstructing derive1constructing derive2 destructing derive2destructing derive1destructint base,派生类,单继承的格式基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象,将派生类对象隐式转换为基类对象,将派生类对象赋
28、给基类对象基类指针指向派生类对象基类的对象引用派生类的对象,将派生类对象赋给基类对象,派生类中的基类部分赋给此基类对象,派生类新增加的成员就舍弃了。赋值后,基类对象和派生类对象再无任何关系。,class base public:int a;class d1:public base public:int b;,d1 d;d.a=1;d.b=2;base bb=d;cout bb.a;/输出1bb.a=3;cout d.a;/输出1,基类指针指向派生类对象,尽管该指针指向的对象是一个派生类对象,但由于它本身是一个基类的指针,它只能解释基类的成员,而不能解释派生类新增的成员。因此,只能访问派生类中的
29、基类部分。通过指针修改基类对象时,派生类对象也被修改。,d1 d;d.a=1;d.b=2;base*bp=/输出3,基类的对象引用派生类的对象,给派生类中的基类部分取个别名。基类对象改变时,派生类对象也被修改。,d1 d;d.a=1;d.b=2;base/输出3,class Shape public:void printShapeName()cout“Shape”endl;class Point:public Shape public:void printShapeName()cout“Point”endl;class Circle:public Point public:void print
30、ShapeName()cout“Circle”endl;class Cylinder:public Circle public:void printShapeName()cout“Cylinder”endl;,实例,将派生类对象赋给基类对象,int i;Point aPoint;Circle aCircle;Cylinder aCylinder;Shape shapes3=aPoint,aCircle,aCylinder;for(i=0;i3;i+)shapesi.printShapeName();,ShapeShapeShape,基类指针指向派生类对象,int i;Point aPoint;
31、Circle aCircle;Cylinder aCylinder;Shape*pShape3=,ShapeShapeShape,基类的对象引用派生类的对象,int i;Point aPoint;Circle aCircle;Cylinder aCylinder;Shape,shape,注意,不能将基类对象赋给派生类对象,除非在基类中定义了向派生类的类型转换函数不能将基类对象地址赋给指向派生类对象的指针也不能将指向基类对象的指针赋给指向派生类对象的指针。如果程序员能确保这个基类指针指向的是一个派生类的对象,则可以用reinterpret_cast类型的强制类型转换。表示程序员知道这个风险,第1
32、2章 组合与继承,组合 继承 虚函数与多态性纯虚函数与抽象类 多继承,虚函数与多态性,多态性虚函数虚析构函数,多态性,多态性:不同对象收到相同的消息时产生不同的动作。多态性的作用:便于系统功能的扩展,多态性的实现,静态联编:编译时已决定用哪一个函数实现某一动作。动态联编:直到运行时才决定用哪一个函数来实现动作,静态联编,函数重载:用同一名字实现访问一组相关的函数运算符重载重载函数是通过“名字压延”方法来实现。即在编译时将函数名和参数结合起来创造一个新的函数名,用新的名字替换原有名字。,运行时多态性,运行时多态性是指必须等到程序动态运行时才可确定的多态性,主要通过继承结合动态绑定获得。这与类的继
33、承密切相关。因为存在类型的兼容性,所以有些函数只有在运行时才能确定是调用父类的还是子类的函数。在C+中,使用虚函数(Virtual Functions)来实现。,虚函数与多态性,多态性虚函数虚析构函数,虚函数,虚函数提供动态重载方式,允许函数调用与函数体之间的联系在运行时才建立。虚函数的定义:在基类中用关键词virtual说明,并在派生类中重新定义的函数称为虚函数。在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数与参数类型的顺序都必须与基类中的原型完全相同。当把一个函数定义为虚函数时,等于告诉编译器,这个成员函数在派生类中可能有不同的实现。必须在执行时根据传递的参数来决定调用哪
34、一个函数,虚函数的使用,虚函数是与基类指针指向派生类对象,或基类对象引用派生类对象结合起来实现多态性。当基类指针指向派生类对象或基类对象引用派生类对象时,对基类指针或对象调用基类的虚函数,系统会到相应的派生类中寻找此虚函数的重定义。如找到,则执行派生类中的函数。如没有找到,则执行基类的虚函数。,虚函数,class Shape public:virtual void printShapeName()cout“Shape”endl;class Point:public Shape public:virtual void printShapeName()cout“Point”endl;class C
35、ircle:public Point public:virtual void printShapeName()cout“Circle”endl;class Cylinder:public Circle public:virtual void printShapeName()cout“Cylinder”endl;,将派生类对象赋给基类对象,int i;Point aPoint;Circle aCircle;Cylinder aCylinder;Shape shapes3=aPoint,aCircle,aCylinder;for(i=0;i3;i+)shapesi.printShapeName()
36、;,ShapeShapeShape,基类指针指向派生类对象,int i;Point aPoint;Circle aCircle;Cylinder aCylinder;Shape*pShape3=,PointCircleCylinder,基类的对象引用派生类的对象,int i;Point aPoint;Circle aCircle;Cylinder aCylinder;/Shape*pShape3=,Point,使用虚函数的注意事项,在派生类中重新定义虚函数时,它的原型必须与基类中的虚函数完全相同。否则编译器会把它认为是重载函数,而不是虚函数的重定义。派生类在对基类的虚函数重定义时,关键字vir
37、tual可以写也可以不写。不管virtual写或者不写,该函数都被认为是虚函数。但最好是在重定义时写上virtual。,例,正方形是一类特殊的矩形,因此,可以从rectangle类派生一个square类。在这两个类中,都有一个显示形状的函数。,class rectangle int w,h;public:rectangle(int ww,int hh):w(ww),h(hh)virtual void display()cout“this is a rectanglen”;class square:public rectangle public:square(int ss):rectangle(
38、ss,ss)void display()/虚函数 cout“this is a squaren”;,虚函数与多态性,多态性虚函数虚析构函数,为什么需要虚析构函数,构造函数不能是虚函数,但析构函数可以是虚函数,而且最好是虚函数 如果派生类新增加的数据成员中含有指针,指向动态申请的内存,那么派生类必须定义析构函数释放这部分空间。但如果派生类的对象是通过基类的指针操作的,则delete基类指针指向的对象就会造成内存泄漏。,解决方案,将基类的析构函数定义为虚函数。当析构基类指向的派生类的对象时,找到基类的析构函数。由于基类的析构函数是虚函数,又会找到派生类的析构函数,执行派生类的析构函数。,虚析构函数
39、的继承性,和其他的虚函数一样,析构函数的虚函数的性质将被继承。如果继承层次树中的根类的析构函数是虚函数的话,所有派生类的析构函数都将是虚函数。,第12章 组合与继承,组合 继承 虚函数与多态性纯虚函数与抽象类 多继承,纯虚函数,纯虚函数:是一个在基类中说明的虚函数,它在该基类中没有定义,但要在它的派生类里定义自己的版本,或重新说明为纯虚函数纯虚函数的一般形式 virtual 类型 函数名(参数表)=0,纯虚函数实例,class shape protected:double x,y;public:shape(double xx,double yy)x=xx;y=yy;virtual double
40、 area()=0;virtual void display()cout This is a shape.The position is(x,y)n;,抽象类,抽象类:如果一个类中至少有一个纯虚函数,则该类被称为抽象类抽象类使用说明:抽象类只能作为其他类的基类,不能建立抽象类的对象。可以声明指向抽象类的指针或引用,此指针可指向它的派生类,进而实现多态性 抽象类不能用作参数类型、函数返回类型或显式转换类型 如果派生类中给除了基类所有纯虚函数的实现,则该派生类不再是抽象类,否则仍为抽象类,抽象类的意义,保证进入继承层次的每个类都具有纯虚函数所要求的行为,这保证了围绕这个继承层次所建立起来的软件系统
41、能正常运行,避免了这个继承层次的用户由于偶尔的失误(忘了为它所建立的派生类提供继承层次所要求的行为)而影响系统正常运行,抽象类实例,下面程序用于计算各类形状的总面积#include class shape public:virtual void area()=0;class rectangle:public shapefloat w,h;public:rectangle(float ww,float hh)w=ww;h=hh;void area()coutarea();ptr=,第12章 组合与继承,组合 继承 虚函数与多态性纯虚函数与抽象类 多继承,多重继承,一个派生类有多个基类时称为多重继
42、承多继承时,派生类包含所有基类的成员,多重继承的定义格式,class 派生类名:基类表 新增派生类的数据成员和成员函数;基类表为:派生方法1 基类名1,派生方法2 基类名2,派生方法n 基类名n,多继承的访问特性,与单继承相同取决于每个基类的派生方法,和成员在基类中的访问特性,多重继承的访问特性,与单继承规则相同,例:#include class Xint a;public:void setX(int x)a=x;void showX()couta;class Yint b;public:void setY(int x)b=x;void showY()coutb;class Z:public
43、X,private Y int c;public:void setZ(int x,int y)c=x;setY(y);void showZ()coutc;main()Z obj;obj.setX(3);obj.showX();obj.setY(4);obj.showY();/非法 obj.setZ(5,6);obj.showZ();,类z有三个数据成员和六个成员函数,多重继承的构造函数和析构函数,构造函数的定义形式 派生类构造函数名(参数表):基类1构造函数名(参数表),基类2构造函数名(参数表),。基类n构造函数名(参数表)执行顺序:先执行基类(按照继承的次序,而不是构造函数的初始化列表的次
44、序),再执行派生类。析构的次序和构造相反,多重继承的主要问题,二义性多个基类有相同的的成员名有两个以上的基类有共同的基类,二义性,如果多个基类中有相同的成员名,则派生类在引用时就具有二义性。例:class A public:void f();class B public:void f();void g();class C:public A,public B public:void g();void h();如有:C x;则 x.f()有二义性。,x.f()的解决方法,方法一:C类的成员在引用f 时说明是A的f 还是B的f。如:x.A:f();或 x.B:f();其缺陷是对C的用户不利。C用户必
45、须知道自己是从哪些基类派生出来的。方法二:在C类声明中指出基类名。如在C中可声明:void ha()A:f();void hb()B:f();,多重继承实例,#includeclass A int i;public:A(int ii=0)i=ii;coutA.i=iendl;void show()coutA:show()i=iendl;class B int i;public:B(int ii=0)i=ii;coutB.i=iendl;void show()coutB:show()i=iendl;class C:public A,public B int i;public:C(int i1=0
46、,int i2=0,int i3=0):A(i1),B(i2)i=i3;coutC.i=iendl;void show()coutC:show()i=iendl;void main()C c(1,2,3);c.A:show();c.show();,执行结果,A.i=1 B.i=2 C.i=3 A:show()i=1 C:show()i=3,二义性-cont.,如果一个派生类从多个基类派生,而这些基类又有公共的基类,则对该基类中声明的名字进行访问时,可能会产生二义性。这个问题由虚基类来解决。,二义性-cont.,如:Class B public:int b;Class B1:public B p
47、rivate:int b1;Class B2:public B private:int b2;Class C:public B1,public B2 public:int f();private:int d;定义:C c;,下面对b的访问是有二义性的:c.bc.B:b下面对b的访问是正确的:c.B1:bb.B2:b,二义性实例,class Apublic:void fun()coutA:fun()endl;class B1:public A public:void fun1()coutB1:fun1()endl;class B2:public A public:void fun1()coutB
48、2:fun1()endl;class D:public 1,public 2;void main()D obj;/obj.fun1();/不可以执行各有fun1()函数)obj.B1:fun1();obj.B2:fun1();/obj.fun();/不可以执行/obj.A:fun();/不可以执行:二义性 obj.B1:fun();/无二义性:可以执行,虚基类,用途:当一个派生类是从多个基类派生,而这些基类又有一个公共的基类。则在这个派生类中访问公共基类中的成员时会有二义性问题。如上例中的B,B1,B2和C,他们的派生关系是:,如果B只有一个拷贝的话,那么在C中对B成员的访问就不会有二义性。,
49、虚基类的概念,使公共的基类只产生一个拷贝。虚基类的定义用关键词virtual。如:Class B public:int b;Class B1:virtual public B private:int b1;Class B2:virtual public B private:int b2;Class C:public B1,public B2 public:int f();private:int d;这样B1,B2公用了一个B的拷贝,对B成员的引用就不会产生二义性。,虚基类的初始化,保证虚基类对象只被初始化一次。虚基类的构造由最终的类的构造函数负责。例如,在构造C的对象时,由C的构造函数负责调用
50、B的构造函数,而不是由B1、B2来调用。构造次序:先执行B的构造函数,再执行B1、B2的构造函数,最后执行C的构造函数。析构次序与构造次序相反。,虚基类的初始化实例,#include class B int a;public:B(int sa)a=sa;coutconstructing Bn;class B1:virtual public B int b;public:B1(int sa,int sb):B(sa)b=sb;coutconstructing B1n;class B2:virtual public B int c;public:B2(int sa,int sb):B(sa)c=s