资源描述
5.2.2虚基类 1,虚基类的概念在C+语言中,一个类不能被多次说明为一个派生 类的直接基类,但可以不止一次地成为间接基类。这 就导致了一些问题。为了方便 说明,先介绍多继承的“类格”表示法。派生类及其基类可用一有向无环图(DAG)表示,其 中的箭头表示“由派生而来”。类的DAG常称为一个“类格”o 复杂类格画出来通常更容易理解。例如:2013-10-31例 5-19 class L public:int next;.;class A:public L(;class B:public L ;class C:public A,public:void f()next=0;;public BEx5 19.c p p1B/L对象数据成员 A对象数据成员 L对象数据成员 B对象数据成员C类自己数据成资C的对象这时,next有两个赋值语句next=0;具有二义性,它是 将A:next置为零,还是将 B:next置为零,或者将两者 都置为0,需要在函数f()中被 显式的说明.如果希望间接基类L与其派生类的关系是如下图C+语言提供了 这种描述手段。它将L说明为A和B的 虚基类。2013-10-33当在多条继承路径上有一个公共的基类,在这些 路径中的某几条路经汇合处,这个公共基类就会产生 多个实例。如果只想保存这个基类的一个实例,可以将这个 公共基类说明为虚拟基类或称虚基类。它仅是简单地将关键字virtual加到基类的描述上,例 如 改写 上述例子为例5-202013-10-34例 5-2C 或class A:public class L ci!.tual t:_.public:/int next;/class A:virtual public Lclass B:virtual public L;class C:public A,public B(public:void f()next=0;或class A:public virtual这时C类对象中只有 L的一个复制,因而函 数C:f()中的语句 next=0;没有二义性。对于类C而言,L类 是B类的虚基类,而是 类A的真基类;但对于 类B而言,L类还是B 类的真基类。例 5-21class L.public:int next;.;class A:virtual public L);class B:virtual public L);class C:public B,public A(public:void f()next=0;此例中,对于类C而言 L类是A类的虚基类,而是类B的真基类。派生时,A,B的顺序变了一个派生类的对象的 地址可以直接赋给虚基类的 指针,例如:C obj;L*ptr=&obj;这时不需要强制类型转换,并且,一个虚基类的引用可以引用一个派生类的对象,例如:C obj2;L&ref=obj2;反之则不行,无论在强制 类型转换中指定什么 路径,一个虚基类的指针或引 用 不能转换为派生类的指针或 引用。例如:C*P=(C*)(A*)ptr;将产生编译错误。2013-10-372,虚基类对象的初始化虚基类的初始化与多继承的初始化在语法上是一样 的,但隐含的构造函数的调用次序有点差别。虚基类构造函数的调用次序是这样规定的:1.虚基类的构造函数在非虚基类之前调用。2.若同一层次中包含多个虚基类,虚基类构造函数 按它们说明的次序调用。3.若虚基类由非虚基类派生,则遵守先调用基类构 造函数,再调用派生类构造函数的规则。2013-10-38例如:class X:public Y,virtual public Z()X one;将产生如下调用次序:Z()Y()X()这里Z是X的虚基类,故先调用Z的构造函数,再调 用Y的构造函数,最后才调用派生类X自己的构造函数。例 5-222013-10-39#include n iostream.hn class base public:base()coutn Base endl;class base2public:base2()coutn Base2n endl;class levell:public base2,virtual public baspublic:levell()co 所以,枸造函数的调class Ievel2:public 用顺序为:public:Ievel2()ccclass toplevel:publ public:toplevel()cbase()base2()Ievel2()base2()levell()toplevel()当建立对象view时,将产生如下调用次序Ievel2()levell()toplevel()而Ievel2()要求:base()base2()Ievel2()levell()要求 base2()levell()toplevel。要求 toplevel()-C:一例 5-23class base.;class base2.;class levell:public base2,virtual public base.;class Ievel2:public base2,virtual public base.;class toplevel:virtual public levell,public Ievel2.;toplevel view;当建立对象view 时,将产生如下 调用次序:levell():base()base2()levell()Ievel2():base2()Ievel2()toplevel():toplevel()此例中,对于toplevel的而言,base是Ievel2的虚基类toplevel 例 5-24 class B.);class X:virtual public B.;class Y:virtual public B.);class Z:public B.);class AA:public X,public Y,public Z这里AA具有两个B类的子对象:Z的B和x与Y共享的虚拟的B。2013-10-312ciass v public:1 intV;.class A public:int a;class B:public/.);class C:public i.;class D:public 1 public:void f();1;在D中仅仅一个VAvirtual public VI Virtual public V3,public CA/v-Q-5.3虚函数与多态性对于普通成员函数的重载,可表达为下面的方式:1)在同一个类中重载2)在不同类中重载3)基类的成员函数在派生类中重载因此,重载函数的访问是在编译时区分的,有以下三种方 法:2013-10-3141.根据参数的特征加以区分,例如:Show(int,char)与Show(char*5 float)不是同一函数,编译能区分。2,使用“::”加以区分,例如:Circle:Show有别于Point:Show3.根据类对象加以区分。ACircle.Show()调用Circle:Show()APoint.Show()调用Point:Show()这里ACircle和APoint分别是Circle和Point的对象。2013-10-315例 5-26#include class A public:void;z class i()uuulfun();C:fun B:fun A:fun画Ex5_26.cpp5.3.1 基类对象的指针指向派生类对象p=&D_obp指向媵量奥对蒙s的对象,B e lass的派生类它是利用p,可以访问从基类B_class继承的成员,但D_class自己 定义的成员,p不能访问。例如:例5-27#inc#inc clas chapul voic该程序输出:Thomas Edison Albert Einstein 555555_1234 555555_1234|JUL_I ICU I ID5 lai ostrcpy(name5 s);void show_name()coutnameMnM;main()BLclass*p;BLclass BLob;D_class*dp;D_class D_ob;p=&B_ob;p-put_name(Thomas Edison”);p=&D_ob;p-put_name(Albert Einstein);B_ob.show_name();D_ob.show_name();dp=&D_ob;class D_class:public B_class char phone_num80;public:void put_phone(char*num)strcpy(phone_num5 num);void show_phone()coutphone_numnnH;dp-put_phone(H555555_1234)dp-show_phone();p,show pone();VD_class*)p)-show_phone();show phope();错误 错误 Ex5 27.cpp基类指针指向派生类类对象,只能访问基类成员基类指针1.可以用一个指向基类的指针指向其公有派生类的对 象。但是相反却不正确,即不能用指向派生类的指针 指向一个基类的对象。name2.希望用基类指针访问其公有派生类的特定成员,必 须将基类指针用显式类型转换为派生类指针。例如:(D_class*)p)-show_phone();L UU5.3.2虚函数1.虚函数的概念例 5-28#include class Baseprotected:int x;public:Base(int a)x=a;void who()coutubase”vvxvv“rT;)class First_d:public Base public:First_d(int a):Base(a)()void who()coutTirst derivation 5,xwho()23base baseFrst derivation Second derivation23void main()小印吗)该程序输出:Base*p;x|base 1Base base_obj(1);First_d first_obj(2);Second_d second_obj(3);p=&base_obj;p-who();p=&first_obj;_指向基类的指针p,不管是指向基类的对象base_obj还 是向派生的对象first_ob仔口second_obj,p-who()调用 的都是基类定义的 wha)的版本.必痴显式地用first_obj.who();和second_obj.who();才能调用类first_d和类second_d中定义的who()的板本。其本质的 原 因 在于普通成员 函 数的 调用 是在编译时静态)7区分。0hx5 28.cpi)如果随着p所指向的对象的不同pwho()能调用不同 类中who()板本,这样就可以用一个界面p-who()访问多 个实现板本:Base中的who(),First_d中的 who(),以及 Second_d中的who(),这在编程时非常有用。实际上,这表达了一种动态的性质,函数调用p-who()依赖于运行时p所指向的对象。虚函数提供的就是这种解 释机制。如果在base中将成员函数who()说明为虚函数,则修改上述程序为例5-29虚函数是在基类中被足以virtual的成员函数,它提供了 一种接口界面。虚函数可以在一个或多个派生类中被重 新定义,但要求在派生类中 重新定义时,虚函数的函 数原型,包括返回类型,函数名,参数个数,参数类型 的顺序,必须完全相同。2013-10-322#includ class E prot int:public Second derivationBase(in x=a;virtual程序输出:base 1First derivation 2First derivation 2Second derivation voia wno()coutubase 5,xunn;class First_d:publi public:BaseFirst_d(int a):Basfa)-()void who()二Tirst derivation xun55;;3Second_d:public Base Dublic:ond_d(int a):Base(a)d who()uSecondwho();p=&first_obj;-p-who();p=&second_obj;who();-first_obj.who();:3)second obj.who();基类的虚函数who()定 义了一种接口,在派 生类中此接口定义了 不同的实现版本,由 于虚函数的解释机制,实现了“单界面,多 实现版本”的思想。这种在运行时刻将函 数界面与函数的不同 实现版本进行匹配的 过程,称为晚期匹配,也称为运彳亍时的多态 性。基类函数f具有虚特性的条件是:1)在基类中,将该函数说明为virtual函数。2)定义基类的公有派生类。3)在基类的公有派生类中原型一致地重载该虚函 数。4)定义指向基类的指针变量,它指向基类的公有 派生类的对象。例 5-30void main()derived d;base*bp=&d;-bp-vf1();bp-vf2();bp-f();class basepublic:virtual void vf1();virtual void vf2()#一virtual void vf3();void f();-1class derived:public base public:_void vf1();v-具有虚特性void vf2(int);一般函数重载,参数不同,虚特性丢失char vf3();-void f();错误,仅返回类型不同);一般的函:数重载.非虚函数的重载0例 5-31#include class figure protected:double x,y;public:void set_dim(double i,double j=0)x=i;y=j;virtual void show_area()coutuNo area computation defined55;coutuforthis classAn;class triangle:public figure public:void show_area()coutuTriangle with high55;coutxuand base,5y;coutu has an area of55;coutx*0.5*yAn55;class square:public figurepublic:void show_area()coutv“Square with dimension55 coutvvxvy;coutuhas an area of55;coutx*yv v“n;class circle:public figure public:void show_area()coutv“Circlewith radius55;coutx;coutuhas an aera of;coutset_dim(10.05 p-show_area();P=&s;5.0);p-set_dim(10.0,5.0);p-show_area();P=&c;p-set_dim(9.0);p-show area();0Ex._)_3i.cpp程序输出:x yFigure:show area()Figure:show area()triangle:show area()square-show areax yx ycircle:show areaQ _/Figure:show_area()Triangle with high 10 and base 5 has an area of 25.0Square with dimension 10*5 has an area of 50.0Qrcle with radius 9 has an area of 254.342,可以使用 成员名 限定可以强制 使用 静态联编例 5-32#include class A public:_virtual void fun()coutuln Ven j1;class B:叫blic A public:void fun()coutNn B,Je nd!I;class C:public B调用B:fun()不是C:fun()_使用成员名限定可以强制使用静态联编.void main()C Cobj;Cobj.fun();_Cobj.B:fun();Cobj.A:fun();A*Aref=&Cobj;Aref-fun();B&Bref=Cobj;Bref.fun();Bref.B:fun();BEx5_32.cpp3.在成员函数中调用虚函数在一个基类或派生类的成员函数中,可以直 接调用等级中的虚函数。此时,需要根据成员 函数中this指针和它所指向的对象来判断调 用 的是哪个函数。例 5-332013-10-330#include class A public:virtual void fun1()coutuA1-25,endl;fun2();virtual void fun2()coutuA2-35,endl;class B:public A public:rvoid fun1()toutuB1-2,J;fun2();Void fun2()coutvv“B2-3”;fun3();Void fun3()cout fun1();Apointer2-fun1();delete Apointerl;delete Apointerl;程序输出:A1-2A 2-3A 3-4A 4-5AondB 1-2B2-3B3-4 B4-5 B end例 5-34#include class A _ public:1void fund(显outvv“A1-25,endl;fun2();void main()A*Apointer=new B;Apointer-fun1();delete Apointer;virtual/cid filnR八1virtual void fun-fun1()不是虚函数,故基类的指针变virtual voidfun 量,指向派生类时只能访问基类virtual voidfun5(中定义的成贝。class B:public A public:void fuQ void fun void fun void fun void funfun2,fun3,fun4,fiin5是虚函数,故基 类的指针变量,指向派生类时访 问的是派生类中定义的成员。程序输出:A1-2B 2-3B 3-4B 4-5B end0()()coutuB 3-45,endl;fun4();()coutuB 4-55,endl;fun5();()coutuB end5,endl;底Ex5 34.cpp例 5-35#include class A t-public:I virtual virtual virtual virtual virtualvoid fun1()coutuA1-25,endl;fun2();void fi2()coutuA2-35,endl;fun3();void fun3()coutuA3-45,endl;fun4();void fun4()coutuA4-55,endl;fun5();VOid fUnR/cci 十.I基类虽然将fun1,fun2定义为虚函数,但在派生类中并没有原型一致的 重载它们,所以要调用基类中的class3:public ic:Pub.能 函数。voidirun3()coutbB 3-4endl;tun4();voi(Mun4()coutuB 4-55,endl;fun5();void main()A*Apointer=new B;Apointer-fun1();_ delete Apointer;c:Ex5_35.cpp多输 1-2 A 2-3 B3-4B 4-5 A end4.在构造函数和析构函数中 调用虚函数在构造函数和析构函数中调用虚函数时,采用静 态联编。即它们所调用的虚函数是自己的类或者它的 基类中的虚函数,但不是任何在派生类中定义的虚函 数。例 5-362013-10-334#include class A public:A()coutuA isCreating,5endl;-virtual void fun1()coutuA fun15,endl;virtual fun2,5 A()Destroclass、/ci/d f icO/Jccclass B:public A public:B()coutuB is Creating,5endl;fun1();void fun()fun1();B()cout|uB is程序输出:A is Creating B is Creating AfunlCis Creating Cfunl Cis destroy C fun2在构造函数和析构函数中调用虚函数时,是自己 的类或者它的基类中的虚函数,但不是任何在派生类 中定义的虚函数。public:-1C()coutuC is Creating55endl;void fun)coutC fun155endl;virtual void fun2()coutC fun255endl;C()coutuQ is Destroy55endl;fun2();void main()CCobj;Cobj.fun();c:5.析构函数可以定义为虚函数构造函数不能为虚函数,而析构函数可以定义 为虚函数。若析构函数为虚函数,那么 当使用delete释放 基类指针指向的派生类对象时,先调用派生类的析 构函数,再调用基类的析构函数。2013-10-336例 5-37#include class Base int 口putjic:BastJnt nJm)b=Bas*;cout“Base createn55;coutuBasedestroyd;void main()Base*pb1,*pb2;-pb1=new Base(1);-pb2=new Deriver(2,3);delete pb1;-delete pb2;coutu*n5,Base Bobj(4);Deriver Dobj(5,6);iEx5_37.cppclass Deriver:Puint d;public:Deriver(int盍类对象指向派生类对象时,绛放时不调用派生类的析构 函教ynum1 Jnt num2):Base(num1)d=num2;coutuDeriver createn5J;Deriver。coutuDeriver destoryn;1:5程序输出:Base create Base create Deriver create Base destroy Base destory*Base create Base create Deriver create Deriver destroy Base destroy Base destory例 5-38#include class Bapointb;|public:y/Base(int 4血)b=num;cXtuBase createn55;virtual-BaseQcouttBaA destroyn55;class Deriv(r:public Base int d;public:Deriver(int ium1,int num2):Base(ium1)d=num2;cc jtuDeriver createn55;Deriver。,coutuDeriver destoryn55;void main()Base*pb1/pb2;pb1=new Base(1);pb2=new Deriver(2,3);delete pb1;-delete pb2;Base create Base create Deriver create Base destroy Deriver destroy Base destroy画Ex5_38.cpp需要先调用派生类的析构函 数,再调用基类的析构函数2013-10-339A1A2A3A45.3.3纯虚函数及抽象类基类表示抽象的概念,如gure是一个基类表示有型的 东西,可以派生出封闭图形和非封闭图形两类。Shape体现了一个抽象的概念,在figure中定义一个求 面积的函数显然是无意义的,但可以将其声明为一个 虚函数,提供一个派生的公共界面,并由各派生类提 供求面积的各自版本。因此基类的有些虚函数没有定 义是很正常的,但是要求派生类必须重新定义这些虚 函数。为此 C+引入了纯虚函数的概念。2013-10-342纯虚函 数是一个在基类 中 说明的 虚函数 它在基类 中没有定义,要求任何派生类必须定义自己的版本。纯虚函数具有以下的形式:virtual type funjname(参数表)=0;在构造函数和析构函数中调用虚函数使用静态编联,因 此在这两个 函数中 不能调 用 纯虚 函 数。但其它函数可以调用纯虚函数。2013-10-343如果一个类至少有一个纯虚函数,就称这个 类为抽象类。抽象类可以定义一种接口,由派生类提供各 种实现。抽象类只能用作其它类的基类.可以用作声明抽象类的指针和引用。不能创建对象。不能用作参数不能用 作函数返 回 类型或显式转换的 类型。2013-10-344例 5-40class point.;class shape point center;public:point where()return center;void move(point p)center=p;draw();virtual void rotate(int)=0;virtual void draw()=0;);shape fun(Rvoid g(shapesx=shape(23);shape*shape x;错误,抽象类不能建可以声明抽象类的指 针错误,抽象类不能 作为返回类型错误,抽象类 不能作为参数 类型.shape&h(shape可以声明抽象类的引用不能用作显式类型 转换如果派生类没有原型一致地重载该纯虚函数。从基类继承来的纯虚函数,在派生类中仍是纯虚函数。例如:class ab_circle:public shape(int radius;public:void rotate(int).;由于shape:draw()是一个纯虚函数,缺省的ab_cricle:draw也是一个纯虚函数,这时ab_circle仍为抽象类。2013-10-346要使ab_circle类为非抽象的,必须如下说明:class ab_circle:public shape(int radius;public:void rotate(int).;void draw().2013-10-3471.下列选项中正确的是A)构造函数可以道载,析构函数不能道载 B)构造函数不能道载,析构函数可以重载C)构造函数可以道载,析构函数也可以重载 D)构造函数不能道载,析构函 数也不能道载2 类的析构函数的作用是A)一般成员函数 B)类的初始化C)对象的初始化 D)删除对象3.对友元函数的正确描述是A)友元函数的实现必须在类的内部定义 B)友元函数是类的成员函数C)友元函数拨坏了破坏了 类的封装性和隐藏性 D)友元函数不能访问类的私有成员4.在C+中,数据封装要解决的问题是A)数据的规范化 B)便于数据转换 C)避免数据烝失D)防止不同模块之间数据的访问5.对结构体中定义的成员,默认的访问权限为A)public B)protected C)private D)static6,类型转换函数的作用是7.赋值重载函数与赋值构造函数应用 中的 区别是8 C+中局部变量和全局变量重名时,欲访问全局变量,应 该_9 下列程序运行时会出现严重错误,为什么?#includevoid main()char*pj q;p=new char10;q=new char10;strcpy(p,ABCDM);q=p;delete p;delete q;10类中的保护段数据与共有段数据的主要区别是什么?10.C+建立类族的实现是通过 A)类的嵌套 B)虚函数 C)类的继承 D)抽象类11.不能被派生类继承的有 构造函数B)虚函数 C)静态成员函数 D)赋值操作函数1.纯虚函数是A)virtual int vf(int);B)void vf(int)=O;C)virtual void vf()=O;D)virtual void vf(int)2.设置虚基类的目的是A)简化程序 B)消除二义性C)提高运行效率 D)减少目 标代码3.设置虚函数的目的是实现动态编联,用关键字 标识虚函数。4.类的派生有主要有三种方式,分别用描述符public,protected 和 表示。
展开阅读全文