好文档就是一把金锄头!
欢迎来到金锄头文库![会员中心]
电子文档交易市场
安卓APP | ios版本
电子文档交易市场
安卓APP | ios版本

虚基类与虚函数.ppt

51页
  • 卖家[上传人]:pu****.1
  • 文档编号:587974349
  • 上传时间:2024-09-07
  • 文档格式:PPT
  • 文档大小:1.11MB
  • / 51 举报 版权申诉 马上下载
  • 文本预览
  • 下载提示
  • 常见问题
    • 5.2.2 虚基类 1.虚基类的概念 在C++语言中,一个类不能被多次说明为一个派生类的直接基类,但可以不止一次地成为间接基类这就导致了一些问题为了方便 说明,先介绍多继承的“类格”表示法 派生类及其基类可用一有向无环图(DAG)表示,其中的箭头表示“由派生而来”类的DAG常称为一个“类格”复杂类格画出来通常更容易理解例如:2021/5/231 例 5-19 class L { public: int next; … }; class A : public L { }; class B : public L { }; class C : public A, public B { public : void f() { next=0; } };C类自己数据成员B对象数据成员L对象数据成员A对象数据成员L对象数据成员 C的对象LALBC 这时,next有两个赋值语句next=0; 具有二义性,它是将A::next置为零,还是将B::next置为零,或者将两者都置为0,需要在函数f()中被显式的说明.2021/5/232 如果希望间接基类L与其派生类的关系是如下图C++语言提供了这种描述手段。

      它将L说明为A和B的虚基类 LABC2021/5/233 当在多条继承路径上有一个公共的基类,在这些路径中的某几条路经汇合处,这个公共基类就会产生多个实例 如果只想保存这个基类的一个实例,可以将这个公共基类说明为虚拟基类或称虚基类 它仅是简单地将关键字virtual加到基类的描述上,例如改写上述例子为例5-202021/5/234 例 5-20 class L { public: int next; }; class A : virtual public L { … }; class B : virtual public L { }; class C : public A, public B { public : void f() { next=0; } }; 这时C类对象中只有L的一个复制,因而函数C::f()中的语句next=0; 没有二义性。

      对于类C而言,L类是B类的虚基类,而是类A的真基类;但对于类B而言,L类还是B类的真基类 例 5-21或class A : public virtual L或class A : public virtual 2021/5/235 class 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的顺序变了2021/5/236 一个派生类的对象的地址可以直接赋给虚基类的指针,例如: C obj; L * ptr=&obj; 这时不需要强制类型转换,并且,一个虚基类的引用可以引用一个派生类的对象,例如: C obj2; L &ref=obj2; 反之则不行,无论在强制类型转换中指定什么路径,一个虚基类的指针或引用不能转换为派生类的指针或引用例如: C * P=(C*)(A*)ptr; 将产生编译错误2021/5/237 2. 虚基类对象的初始化 虚基类的初始化与多继承的初始化在语法上是一样的,但隐含的构造函数的调用次序有点差别 虚基类构造函数的调用次序是这样规定的: 1. 虚基类的构造函数在非虚基类之前调用 2. 若同一层次中包含多个虚基类,虚基类构造函数按它们说明的次序调用 3. 若虚基类由非虚基类派生,则遵守先调用基类构造函数,再调用派生类构造函数的规则。

      2021/5/238 例如 : class X : public Y, virtual public Z { } X one; 将产生如下调用次序: Z() Y() X() 这里Z是X的虚基类,故先调用Z的构造函数,再调用Y的构造函数,最后才调用派生类X自己的构造函数 例 5-222021/5/239 # include "iostream.h" class base{ public: base(){cout<<"Base"<

      2021/5/2312 class V { public : int V; }; class A { public : int a;}; class B : public A, virtual public V {…}; class C : public A, Virtual public V {…}; class D : public B, public C { public : void f(); }; void D::f( ){v++; a++;} 例 5-25 虚基类和非虚基类的不同 AVABCD在D中仅仅一个v错误,具有二义性,在D中有两个a调用次序:B( ): V( ) A( ) B( )C( ): A( ) C( )D( ): D( )2021/5/2313 5.3 虚函数与多态性对于普通成员函数的重载,可表达为下面的方式: 1) 在同一个类中重载 2) 在不同类中重载 3) 基类的成员函数在派生类中重载 因此,重载函数的访问是在编译时区分的,有以下三种方法:2021/5/2314 1.根据参数的特征加以区分,例如: Show(int , char)与Show(char *, float) 不是同一函数,编译能区分。

      2. 使用“::”加以区分,例如: Circle :: Show有别于Point :: Show 3. 根据类对象加以区分 ACircle.Show() 调用Circle::Show() APoint.Show() 调用Point :: Show() 这里ACircle和APoint分别是Circle和Point的对象2021/5/2315 例 5-26 #include class A { public: void fun(){cout<<“In A”<fun(); }派生类对象B::funA::funC::fun2021/5/2316 5.3.1 基类对象的指针指向派生类对象 指向基类和派生类的指针变量是相关的,假设B_class是基类,D_class是从B_class公有派生出来的派生类,任何被说明为指向B_class的指针也可以指向D_class。

      例如: 利用p,可以访问从基类B_class继承的成员,但D_class自己定义的成员,p不能访问例如:例5-27指向类型B_class的对象的指针类型B_class的对象类型D_class的对象p指向类型D_class的对象,它是 B_class 的派生类p指向类型B_class的对象B_class *p; B_class B_ob; D_class D_ob; p=&B_ob; p=&D_ob;派生类对象派生类成员基类成员2021/5/2317 #include #include class B_class{char name[80]; public : void put_name(char * s) { strcpy(name, s); } void show_name( ) {cout<put_name("Thomas Edison");p=&D_ob;p->put_name("Albert Einstein");B_ob.show_name( );D_ob.show_name( );dp=&D_ob;dp->put_phone("555555_1234");dp->show_phone( );p->show_pone( ); ((D_class*)p)->show_phone ( ); p->show_phone( ); } 错误错误 该程序输出: Thomas Edison Albert Einstein 555555_1234 555555_1234class D_class: public B_class { char phone_num[80]; public: void put_phone(char * num){ strcpy(phone_num, num); } void show_phone( ) cout<

      但是相反却不正确,即不能用指向派生类的指针指向一个基类的对象 2.希望用基类指针访问其公有派生类的特定成员,必须将基类指针用显式类型转换为派生类指针例如: ((D_class*)p)->show_phone( );2021/5/2319 5.3.2 虚函数 例 5-28 #include class Base { protected: int x; public: Base(int a) {x=a;} void who() {cout<<“base ”<who(); p=&first_obj; p->who(); p=&second_obj; p->who(); first_obj.who(); second_obj.who(); }该程序输出:base 1base 2base 3First derivation 2Second derivation 3 second_obj x3Base::who()Second_d::who()first_obj x2Base::who()first_d::who()base_obj x1Base::who()pp->who() 指向基类的指针p,不管是指向基类的对象base_obj还是指向派生的对象first_obj和second_obj, p->who()调用的都是基类定义的 who()的版本.必须显式地用 first_obj.who(); 和 second_obj.who(); 才能调用类first_d和类second_d中定义的who()的版本。

      其本质的原因在于普通成员函数的调用是在编译时静态区分2021/5/2321 如果随着p所指向的对象的不同p—>who()能调用不同类中who()版本,这样就可以用一个界面p->who()访问多个实现版本:Base中的who(), First_d 中的 who(),以及Second_d中的who(),这在编程时非常有用 实际上,这表达了一种动态的性质,函数调用p->who()依赖于运行时p所指向的对象虚函数提供的就是这种解释机制如果在base中将成员函数who()说明为虚函数,则修改上述程序为例5-29 虚函数是在基类中被冠以virtual的成员函数,它提供了一种接口界面虚函数可以在一个或多个派生类中被重新定义,但要求在派生类中 重新定义时,虚函数的函数原型,包括返回类型,函数名,参数个数,参数类型的顺序,必须完全相同2021/5/2322 class First_d : public Base { public: First_d(int a ):Base(a) { } void who(){“First derivation “< class Base { protected: int x; public: Base(int a) {x=a;} virtual void who() {cout<<“base ”<who(); p=&first_obj; p->who(); p=&second_obj; p->who(); first_obj.who(); second_obj.who(); } 程序输出: base 1 First derivation 2 Second derivation 3 First derivation 2 Second derivation 32021/5/2323 second_obj x3Base::who()Second_d::who()first_obj x2Base::who()first_d::who()pbase_obj x1virtual Base::who() 基类的虚函数who()定义了一种接口,在派生类中此接口定义了不同的实现版本,由于虚函数的解释机制,实现了“单界面,多实现版本”的思想。

      这种在运行时刻将函数界面与函数的不同实现版本进行匹配的过程,称为晚期匹配,也称为运行时的多态性p->who()2021/5/2324 基类函数f具有虚特性的条件是: 1) 在基类中,将该函数说明为virtual函数 2)定义基类的公有派生类 3) 在基类的公有派生类中原型一致地重载该虚函数 4) 定义指向基类的指针变量,它指向基类的公有派生类的对象 例 5-302021/5/2325 void main( ) { derived d; base * bp=&d; bp->vf1( ); bp->vf2( ); bp->f( ); } class base{ public: virtual void vf1( ); virtual void vf2( ); virtual void vf3( ); void f( ); }; class derived : public base{ public : void vf1( ); void vf2(int); char vf3( ); void f( ); };错误,仅返回类型不同具有虚特性一般函数重载,参数不同,虚特性丢失一般的函数重载,非虚函数的重载2021/5/2326 例 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() { cout<<“No area computation defined”; cout<<“for this class.\n”; }}; class triangle : public figure{ public: void show_area() { cout<<“Triangle with high”; cout<set_dim(10.0, 5.0); p->show_area(); p=&s; p->set_dim(10.0, 5.0); p->show_area(); p=&c; p->set_dim(9.0); p->show_area();}程序输出:Triangle with high 10 and base 5 has an area of 25.0Square with dimension 10*5 has an area of 50.0Circle with radius 9 has an area of 254.34 X y Figure::show_area()triangle ::show_area() X y Figure::show_area()square ::show_area() X y Figure::show_area()circle ::show_area()p2021/5/2328 2.可以使用成员名限定可以强制使用静态联编 例 5-32 #include class A{ public:virtual void fun() {cout<<“In A”<fun(); B &Bref=Cobj; Bref.fun(); Bref.B::fun(); }调用B::fun() 不是C::fun() 使用成员名限定可以强制使用静态联编2021/5/2329 3.在成员函数中调用虚函数 在一个基类或派生类的成员函数中,可以直接调用等级中的虚函数。

      此时,需要根据成员函数中this指针和它所指向的对象来判断调 用的是哪个函数例 5-332021/5/2330 #include class A{ public : virtual void fun1(){cout<<“A1-2”< fun1(); Apointer2-> fun1();delete Apointer1;delete Apointer1;}程序输出 : A 1-2 A 2-3 A 3-4 A 4-5 A end B 1-2 B 2-3 B 3-4 B 4-5 B endfun2( )相当于fun2(const A *this ) this即为Apointer2,因此仍然调用所指向对象中的函数2021/5/2331 例 5-34 #include class A { public : void fun1(){cout<<“A1-2”<fun1(); delete Apointer;}程序输出:A 1-2B 2-3B 3-4B 4-5B endfun1( )不是虚函数,故基类的指针变量,指向派生类时只能访问基类中定义的成员。

      fun2,fun3,fun4,fun5是虚函数,故基类的指针变量,指向派生类时访问的是派生类中定义的成员2021/5/2332 例 5-35 #include class A { public : virtual void fun1(){cout<<“A1-2”<fun1(); delete Apointer; }程序输出: A 1-2 A 2-3 B 3-4 B 4-5 A end基类虽然将fun1,fun2定义为虚函数,但在派生类中并没有原型一致的重载它们,所以要调用基类中的函数。

      2021/5/2333 4 . 在构造函数和析构函数中调用虚函数 在构造函数和析构函数中调用虚函数时,采用静态联编即它们所调用的虚函数是自己的类或者它的基类中的虚函数,但不是任何在派生类中定义的虚函数 例 5-362021/5/2334 #include class A { public: A(){cout<<“A is Creating”<

      2021/5/2335 5 . 析构函数可以定义为虚函数 构造函数不能为虚函数,而析构函数可以定义为虚函数 若析构函数为虚函数,那么当使用delete释放基类指针指向的派生类对象时,先调用派生类的析构函数,再调用基类的析构函数2021/5/2336 class Deriver: public Base{ int d; public: Deriver(int num1,int num2):Base(num1) {d=num2;cout<<“Deriver create\n”;} ~Deriver() {cout<<“Deriver destory\n”;} }; 例 5-37#include class Base{ int b; public: Base(int num) {b=num;cout<<“Base create\n”;}~Base(){cout<<“Base destroy\n”;}}; void main() { Base * pb1,*pb2; pb1=new Base(1); pb2=new Deriver(2,3); delete pb1; delete pb2; cout<<“**********\n”; Base Bobj(4); Deriver Dobj(5,6); }程序输出: Base create Base create Deriver create Base destroy Base destory ********** Base create Base create Deriver create Deriver destroy Base destroy Base destory 基类对象指向派生类对象时,释放时不调用派生类的析构函数2021/5/2337 例 5-38 #include < iostream.h> class Base { int b; public: Base(int num) {b=num;cout<<“Base create\n”;} virtual ~Base() {cout<<“Base destroy\n”;}}; class Deriver:public Base {int d;public: Deriver(int num1,int num2):Base(num1) { d=num2;cout<<“Deriver create\n”;} ~Deriver() {cout<<“Deriver destory\n”; };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 需要先调用派生类的析构函数,再调用基类的析构函数 2021/5/2338 6. 多继承和虚基类 在多继承中,由于能从多条路径继承,如何确定函数调用时应该激活哪一个函数版本呢? 下述例子说明了一个问题 如: 例 5-39 2021/5/2339 struct A1 { int f( ); }; struct A2 : virtual A1 //缺省为公有派生方式 { int f( ); }; struct A3 : A2 { }; struct A4 : A3, virtual A1 { }; void f(A2 * pa2, A4 *pa4) { pa2->f( ); //调用 A2:f( ) pa4->f( ); //调用 A2:f( ) } 继承路径如图:A1A2A3A4A1():A1(): A1()A3():A3(): A2() A3()A4():A4(): A4()创建A4的对象时,构造函数的调用次序为:2021/5/2340 由于A2是A1的派生类,A2中重新定义的函数f覆盖了类A中定义的函数f,而且A4和A2都将A1说明为虚基类,因此,pa4->f( )调用的是 A2 :: f( )虚基类也能应用这个规则。

      有人可能会想,A1::f( )离A4更近,因为A1是A4的直接基类, 而A2不是pa4->f()应该调用A1:f( ),而不是调用A2::f( )情况并非如此,由DAG图可见,根据继承路径pa4->f( )应有两种调用选择: A2::f( ); 和 A1::f( ) 2021/5/2341 5.3.3 纯虚函数及抽象类 基类表示抽象的概念,如figure是一个基类表示有型的东西,可以派生出封闭图形和非封闭图形两类Shape体现了一个抽象的概念,在figure中定义一个求面积的函数显然是无意义的,但可以将其声明为一个虚函数,提供一个派生的公共界面,并由各派生类提供求面积的各自版本因此基类的有些虚函数没有定义是很正常的,但是要求派生类必须重新定义这些虚函数 为此 C++引入了纯虚函数的概念2021/5/2342 纯虚函数是一个在基类中说明的虚函数它在基类中没有定义,要求任何派生类必须定义自己的版本 纯虚函数具有以下的形式: virtual type func_name(参数表)=0; 在构造函数和析构函数中调用虚函数使用静态编联,因此在这两个函数中不能调用纯虚函数。

      但其它函数可以调用纯虚函数2021/5/2343 如果一个类至少有一个纯虚函数,就称这个类为抽象类 抽象类可以定义一种接口,由派生类提供各种实现 Ø 抽象类只能用作其它类的基类.Ø 可以用作声明抽象类的指针和引用 Ø 不能创建对象Ø 不能用作参数Ø 不能用作函数返回类型或显式转换的类型2021/5/2344 例 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 x; shape * p;shape fun( ); void g(shape);x= shape(23);shape & h(shape &); 错误,抽象类不能建立对象可以声明抽象类的指针错误,抽象类不能作为返回类型错误,抽象类不能作为参数类型可以声明抽象类的引用不能用作显式类型转换2021/5/2345 如果派生类没有原型一致地重载该纯虚函数。

      从基类继承来的纯虚函数,在派生类中仍是纯虚函数 例如: class ab_circle : public shape { int radius ; public : void rotate(int){…}; }; 由于 shape :: draw( )是一个纯虚函数,缺省的ab_cricle :: draw也是一个纯虚函数,这时ab_circle仍为抽象类2021/5/2346 要使ab_circle类为非抽象的,必须如下说明: class ab_circle : public shape { int radius ; public : void rotate(int){…}; void draw( ){…} };2021/5/2347 1. 下列选项中正确的是下列选项中正确的是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) static2021/5/2348 6. 类型转换函数的作用是———————— 7.赋值重载函数与赋值构造函数应用中的区别是———————8 C++中局部变量和全局变量重名时,欲访问全局变量,应该——————————9 下列程序运行时会出现严重错误,为什么?#include void main( ) {char* p,* q; p=new char[10]; q=new char[10]; strcpy(p,"ABCD"); q=p; delete []p; delete []q; }10类中的保护段数据与共有段数据的主要区别是什么?2021/5/2349 10. C++建立类族的实现是通过 [ ]A) 类的嵌套 B)虚函数 C)类的继承 D) 抽象类11. 不能被派生类继承的有 [ ]A)构造函数 B) 虚函数 C) 静态成员函数 D) 赋值操作函数1. 纯虚函数是 A) virtual int vf(int); B) void vf(int)=0;C) virtual void vf()=0; D) virtual void vf(int){}2.设置虚基类的目的是 A)简化程序 B)消除二义性C)提高运行效率 D)减少目标代码3.设置虚函数的目的是实现动态编联,用关键字 标识虚函数。

      4.类的派生有主要有三种方式,分别用描述符public, protected 和 表示2021/5/2350 部分资料从网络收集整理而来,供大家参考,感谢您的关注! 。

      点击阅读更多内容
      相关文档
      2025国开山东开大《土质学与土力学》形成性考核123答案+终结性考核答案.docx 中学综合素质知识点梳理【中学教师资格证】.docx 2025国开山东开大《特许经营概论》形成性考核123答案+终结性考核答案.doc 2025年高考英语全国一卷真题(含答案).docx 2025国开山东《农民专业合作社创建与管理》形成性考核123答案+终结性考核答案.docx 2025国开山东开大《自然现象探秘》形成性考核123答案+终结性考核答案.docx 2025国开山东《消费心理学》形成性考核123答案+终结性考核答案.doc 2025国开山东《小微企业管理》形成性考核123答案+终结性考核答案.doc 2025国开山东开大《资本经营》形成性考核123答案+终结性考试答案.docx 2025国开山东《小学生心理健康教育》形考123答案+终结性考试答案.docx 2025国开《视频策划与制作》形考任务1-4答案.docx 2025国开《亲子关系与亲子沟通》形考任务234答案+期末大作业答案.docx 2025国开电大《煤矿地质》形成性考核123答案.docx 2025国开电大《冶金原理》形考任务1234答案.docx 2025国开《在线学习项目运营与管理》形考任务1234答案.doc 2025国开电大《在线教育的理论与实践》阶段测验1-4答案.docx 2024 年注册环保工程师《专业基础考试》真题及答案解析【完整版】.docx 环保工程师---2023 年注册环保工程师《专业基础考试》真题及答案解析【完整版】.docx 2025国开《液压与气压传动》形考任务一参考答案.docx 2025年春江苏开放大学教育研究方法060616计分:形成性作业2、3答案.docx
      关于金锄头网 - 版权申诉 - 免责声明 - 诚邀英才 - 联系我们
      手机版 | 川公网安备 51140202000112号 | 经营许可证(蜀ICP备13022795号)
      ©2008-2016 by Sichuan Goldhoe Inc. All Rights Reserved.