第7章虚函数

上传人:m**** 文档编号:592285490 上传时间:2024-09-20 格式:PPT 页数:35 大小:244.50KB
返回 下载 相关 举报
第7章虚函数_第1页
第1页 / 共35页
第7章虚函数_第2页
第2页 / 共35页
第7章虚函数_第3页
第3页 / 共35页
第7章虚函数_第4页
第4页 / 共35页
第7章虚函数_第5页
第5页 / 共35页
点击查看更多>>
资源描述

《第7章虚函数》由会员分享,可在线阅读,更多相关《第7章虚函数(35页珍藏版)》请在金锄头文库上搜索。

1、第第7章章 虚函数虚函数v本章内容:本章内容:F7.1 虚函数F7.2 虚析构函数F7.3 抽象类F7.4 友元、绑定F7.5 类的存储空间17.1 虚函数虚函数v虚虚函函数数:即用virtual定义的成员函数。Java几乎所有函数都为虚函数。F当基类对象指针指向不同类型派生类对象时,通过虚函数到基类或派生类中同名函数的映射实现多态。v动动态态多多态态:重载函数表现的是静态多态性,虚函数表现的是动态多态性:F重载函数是静态多态函数,通过静态绑定完成重载函数的调用;虚函数是动态多态函数,通过动态绑定完成虚函数的调用。动态绑定是程序自己完成的,静态绑定是编译或操作系统完成的。F虚函数的动态绑定通过

2、存储在对象中的一个指针完成。(该指针指向虚函数入口地址表VFT)v定义:virtual 函数原型 【例7.1】2v虚函数使用说明虚函数使用说明:虚函数必须是类的成员函数,非成员函数不能说明为虚函数,普通函数如main不能说明为虚函数(与编译器有关)。虚函数一般在基类的public或protected部分。在派生类中重新定义成员函数时,函数原型必须完全相同;虚函数只有在具有继承关系的类层次结构中定义才有意义,否则引起额外开销 (需要通过VFT访问);一般用父类指针(或引用)访问虚函数。根据父类指针所指对象类型的不同,动态绑定相应对象的虚函数;(虚函数的动态多态性) 【例7.1】 7.1 虚函数虚

3、函数3v虚函数使用说明(续)虚函数使用说明(续):虚函数有隐含的this参数,参数表后可出现const和volatile,静态函数成员没有this参数,不能定义为虚函数:即不能有virtual static之类的说明;构造函数构造对象的类型是确定的,不需根据类型表现出多态性,故不能定义为虚函数;析构函数可通过父类指针(引用)或delete调用,父类指针指向的对象类型可能是不确定的,因此析构函数可定义为虚函数。一 旦 父 类 (基 类 )定 义 了 虚 函 数 , 即 使 没 有“virtual”声明,所有派生类中原型相同的非静态成员函数自动成为虚函数;(虚函数的无限传递性) 【例7.2】7.1

4、 虚函数虚函数4v虚函数使用说明(续)虚函数使用说明(续):虚函数只能定义为其他类的友元,而不能定义为当前类的友元(友元非当前类的成员)。即不能同时用virtual 和friend 定义函数。虚函数同普通函数成员一样,可声明为或自动成为inline函数,也可重载、缺省和省略参数。【例7.3】虚函数能根据对象类型适当地绑定函数成员,且绑定函数成员的效率非常之高,因此,最好将普通函数成员全部定义为虚函数。注注意意:虚函数主要通过基类和派生类表现出多态特性,由于union既不能定义基类又不能定义派生类,故不能在union中定义虚函数。7.1 虚函数虚函数57.1 虚函数虚函数v虚函数与重载成员函数的

5、区别虚函数与重载成员函数的区别:F重载函数使用静态联编(绑定)机制;虚函数采用动态联编机制;F对于父类A中声明的虚函数f( ),若在子类B中重定义f( ),必须确保子类B:f( )与父类A:f( )具有完全相同的函数原型,才能覆盖原虚函数f( )而产生虚特性,执行动态联编机制。否则,只要有一个参数不同,编译系统就认为它是一个全新的(函数名相同时重载)函数,而不实现动态联编。67.2 虚析构函数虚析构函数 v虚析构函数虚析构函数:F如果基类的析构函数定义为虚析构函数,则派生类的析构函数就会自动成为虚析构函数。F说明虚析构函数的目的在于在使用delete运算符删除一个对象时,能够保证所执行的析构函

6、数就是该对象的析构函数;最好将所有的析构函数都定义为虚析构函数。注注意意:如果为基类和派生类的对象分配了动态内存,或者为派生类的对象成员分配了动态内存,则一定要将基类和派生类的析构函数定义为虚析构函数,否则便可能造成内存泄漏,导致系统出现内存保护错误。77.2 虚析构函数虚析构函数 【例例7.4】输输入入职职员员的的花花名名册册,如如果果职职员员的的姓姓名名、编编号号和和年年龄龄等等信信息息齐齐全全,则则登登记记该该职职员员的的个个人人信信息息,否否则则只只登登记记职职员的姓名。员的姓名。#include #include class STRING char *str;public: STRI

7、NG(char *s); virtual STRING( ) if (str)delete str; str=0; ;STRING:STRING(char *s) str=new charstrlen(s)+1; strcpy(str, s);class CLERK: public STRING STRING clkid; int age;public: CLERK(char *n, char *i, int a); CLERK( ) /自动成为虚函数自动成为虚函数 /自动调用自动调用clkid.STRING( ) /和和STRING:STRING( );CLERK:CLERK(char *n

8、, char *i, int a):STRING(n), clkid(i) age=a;87.2 虚析构函数虚析构函数 const int max=10;void main(void) STRING *smax; int a, k, m; char n12, i12, t256; printf(Please input name, number and age:n); for(k=0; kmax; k+) gets(t); m=sscanf(t,“%8s %8s %d”,n,i,&a)!=3; /m记录信息是否齐全记录信息是否齐全 sk=m?new STRING(n):new CLERK(n,

9、i,a); for(k=0; kSTRING( )析构析构 /若若sk指向指向CLERK的对象,则用的对象,则用sk-CLERK( )析构析构 若若STRING类没有定义虚析构函数,结果如何类没有定义虚析构函数,结果如何? 97.2 虚析构函数虚析构函数补充说明补充说明:F用父类引用实现动态多态性时需要注意,若被(new产生)引用对象自身不能析构,则必须用delete &析构: STRING &z=*new CLERK(zang,982021,23); delete &z; /析构对象z并释放对象z占用的内存F上述delete &z完成了两个任务:调用该对象析构函数,释放其基类和对象成员各自为

10、字符指针str分配的空间;释放CLERK对象自身占用的存储空间。F如果将上述delete &z改为z.CLERK( ),则只完成任务而没完成任务;如果改为free(&z),则只完成任务而没完成任务。造成内存泄露。为什么?为什么? 107.3 抽象类抽象类v纯纯虚虚函函数数:不必定义函数体的虚函数,也可以重载、缺省参数、省略参数、内联等,相当于Java的interface。F定义格式:virtual 函数原型=0。 (即函数体为空)F纯虚函数有this参数,不能同时定义为静态函数成员。F构造函数不能定义为虚函数,同样也不能定义为纯虚函数。F析构函数可以定义为虚函数,也可定义为纯虚函数。F可以定义

11、为其它类的友员函数,但通常不必要(无函数体)。117.3 抽象类抽象类v抽象类抽象类:含有纯虚函数的类。F抽象类常用作派生类的基类,不应该有对象或类实例(相当于Java的interface)。F如果派生类继承了抽象类的纯虚函数,却没有重新定义该原型虚函数,或者派生类定义了基类所没有的纯虚函数,则派生类就会自动成为抽象类。F在多级派生的过程中,如果到某个派生类为止,所有纯虚函数都已全部重新定义,则该派生类就会成为非抽象类。127.3 抽象类抽象类【例【例7.5】多级派生中的抽象类与非抽象类用法】多级派生中的抽象类与非抽象类用法#include struct A /A被定义为抽象类被定义为抽象类

12、virtual void f1( )=0; virtual void f2( )=0;void A:f1( ) coutA1; void A:f2( ) coutA:f2( ); coutB2; ;class C: public B/ f1和和f2均重新定义,均重新定义,C为非抽象类为非抽象类 void f1( ) coutf1( ); /调用调用C:f1( ) p-f2( ); /调用调用B:f2( )输出:输出:C1A2B2137.3 抽象类抽象类v抽象类使用说明抽象类使用说明:抽象类不能定义或产生任何对象,包括用new创建的对象,故不能用作函数参数的类型和函数的返回类型(调用前后要产生该

13、类型的对象)。抽象类可作派生类的基类(父类),若定义相应的基类引用和指针,就可引用或指向非抽象派生类对象。通过抽象类指针或引用可调用抽象类的纯虚函数,根据多态性,实际调用的应是该类的非抽象派生类的虚函数。如果该派生类没有重新定义被调虚函数,则会导致程序出现不可意料的运行错误。调用抽象类的普通函数成员不会出现该问题。147.3 抽象类抽象类【例【例7.6】本例说明抽象类不能产生对象】本例说明抽象类不能产生对象#include struct A/定义类定义类A为抽象类为抽象类 virtual void f1( )=0; void f2( ) ;struct B: A/定义抽象类定义抽象类A的非抽象

14、子类的非抽象子类 void f1( ) ;A f( ); /,返回类返回类A意味着抽意味着抽 /象类要产生一个对象象类要产生一个对象int g(A x); / ,调用时要传递调用时要传递 /一个类一个类A的对象的对象A &h(A &y); /,可以引用非可以引用非 /抽象子类抽象子类B的对象的对象void main(void) A a; /,抽象类不能产生抽象类不能产生 /对象对象a A *p; /,可以指向非抽象可以指向非抽象 /子类子类B的对象的对象 p-f1( ); /,运行时无运行时无A:f1( ) /如如p指向指向B类类对象正确对象正确 p-f2( ); /,调用调用A:f2( )1

15、57.3 抽象类抽象类v抽象类使用说明(续)抽象类使用说明(续):内存管理函数malloc可以为抽象类分配空间,但不调用构造函数构造抽象类的对象,因此,内存管理函数malloc实质上不产生抽象类对象。只有成功地产生了某个类的对象,才能通过抽象类指针或引用调用这个类的虚函数。抽象类作为抽象级别最高的类,主要用于定义派生类共有的数据和函数成员。抽象类的纯虚函数没有函数体,意味着目前尚无法描述该函数的功能。例如,如果图形是点、线和圆等类的抽象类,那么抽象类的绘图函数就无法绘出具体的图形。 【例7.7】 167.4 友元、绑定友元、绑定v纯虚函数和虚函数都能定义成另一个类的成员友元。由于纯虚函数一般不

16、会定义函数体,故纯虚函数一般不要定义为其他类的成员友元。v如果类A的函数成员f定义为类B的友元,那么f就可以访问类B的所有成员,但是,f并不能访问从类B派生的类C的所有成员,除非f也定义为类C的友元或者类A就是类C。(即友元对派生不具备传递性) 177.4 友元、绑定友元、绑定【例【例7.8】说明纯虚函数和虚函数定义为友元的用法】说明纯虚函数和虚函数定义为友元的用法#include class C;struct A virtual void f1(C &c)=0; virtual void f2(C &c);class B: Apublic: void f1(C &c); /f1自动成为虚函自

17、动成为虚函数数;class C char c; /允许但无意义,允许但无意义,A:f1无函数体无函数体 friend void A:f1(C &c); friend void A:f2(C &c);public: C(char c) C:c=c; ;void A:f1(C &c) coutB outputs ; void A:f2(C &c) coutA outputs ; void B:f1(C &c) coutf1(c);/调用调用B:f1( ) p-f2(c);/调用调用A:f2( )187.4 友元、绑定友元、绑定v虚函数动态绑定虚函数动态绑定:FC+使用虚函数地址表(VFT)来实现虚

18、函数的动态绑定。VFT是一个函数指针列表,存放对象的所有虚函数的入口地址。F编译程序为有虚函数的类创建一个VFT,其首地址通常存放在对象的起始单元中。调用虚函数的对象通过起始单元VFT动态绑定相应的函数成员,从而使虚函数随调用对象的不同而表现多态特性。动态绑定比静态绑定多一次地址访问,在一定程度上降低了程序的执行效率,但同虚函数的多态特性带来的优点相比,效率降低所产生的影响是微不足道的。197.4 友元、绑定友元、绑定v虚虚函函数数动动态态绑绑定定过过程程:设基类A和派生类B对应的虚函数表分别为VFTA和VFTB。则派生类对象b的虚函数动态绑定过程如下:对对象象构构造造:先将VFTA的首地址存

19、放到b的起始单元,在基类构造函数的函数体执行前,使基类对象调用的虚函数与VFTA绑定,基类构造函数执行A的虚函数;在派生类构造函数的函数体执行前(甚至初试化时),将VFTB的首地址存放到b的起始单元,使派生类构造函数调用的虚函数与VFTB绑定,执行B的虚函数。对象使用对象使用:b的起始单元指向VFTB,不需要变动。对对象象析析构构:由于当前首地址已指向VFTB,故析构函数调用的是B的虚函数;然后将VFTA的首地址存放到b的起始单元,使基类析构函数调用的虚函数与VFTA绑定,基类析构函数调用基类A的虚函数。207.4 友元、绑定友元、绑定【例【例7.9】虚函数的动态绑定】虚函数的动态绑定#inc

20、lude class A virtual void c( ) coutConstruct An; virtual void d( ) coutDeconstruct An;public: A( )c( ); virtual A( )d( );class B:A virtual void c( ) coutConstruct Bn; virtual void d( ) coutDeconstruct Bn;public: B( )c( ); virtual B( )d( );void main(void) B b; 输出结果:输出结果:Construct AConstruct BDeconstr

21、uct BDeconstruct AB:c的地址B:d的地址VFTBB()地址A:c的地址A:d的地址VFTAA()地址BAVFT首地址217.5 类的存储空间类的存储空间v单继承派生类的存储空间由基类和派生类的非静态数据成员构成。当基类或派生类包含虚函数或纯虚函数时,派生类的存储空间还包括虚函数地址表首址所占的存储单元通常(见以下说明)VFT指针占起始单元。F如果基类定义了虚函数或者纯虚函数,则派生类对象将其起始单元作为共享VFT指针,用于存放基类和派生类的虚函数地址表首址。 【例7.10】F如果基类没有定义虚函数,而派生类定义了,则派生类的存储空间由三个部分组成:第一部分为基类存储空间,第

22、二部分为派生类VFT指针,第三部分为该派生类新定义的数据成员。 【例7.11】22【例【例【例【例7.107.10】计算单继承派生类存储空间的方法计算单继承派生类存储空间的方法计算单继承派生类存储空间的方法计算单继承派生类存储空间的方法 #include class A static int b; int a; virtual int f( ); virtual int g( ); virtual int h( );class B: A static int y; int x; int f( ); virtual int u( ); virtual int v( );void main(voi

23、d) coutSize of int=sizeof(int)n; coutSize of pointer=sizeof(void *)n; coutSize of A=sizeof(A)n; coutSize of B=sizeof(B)n;sizeof(A)sizeof(a)sizeof(void *),sizeof(B)sizeof(A)sizeof(x) 23【例【例【例【例7.117.11】当基类没有虚函数时计算派生类存储空间的方法当基类没有虚函数时计算派生类存储空间的方法当基类没有虚函数时计算派生类存储空间的方法当基类没有虚函数时计算派生类存储空间的方法 #include class

24、 A static int b;int a;class B: A static int y; int x;public: virtual void u( ) ; virtual void v( ) ;void main(void) coutSize of int=sizeof(int)n; coutSize of pointer=sizeof(void *)n; coutSize of A=sizeof(A)n; coutSize of B=sizeof(B)n;sizeof(A)sizeof(a),sizeof(B)sizeof(A)sizeof(void *)sizeof(x) 24【例【

25、例【例【例7.17.1】定义父类定义父类定义父类定义父类POINT POINT 和子类和子类和子类和子类CIRCLE CIRCLE 的绘图函数成员的绘图函数成员的绘图函数成员的绘图函数成员showshow#include class POINT int x, y;public: int getx( ) return x; int gety( ) return y; virtual void show( ) coutShow a pointn; POINT(int x, int y) POINT:x=x; POINT:y=y; ;class CIRCLE: public POINT int r;

26、public: int getr( ) return r; void show( ) coutShow a circlen; CIRCLE(int x, int y, int r):POINT(x, y) CIRCLE:r=r; ; 25void main(void) CIRCLE c(3, 7, 8); POINT *p=&c; coutThe circle with radius c.getr( ); cout is at (getx( ), gety( )show( ); /p-show( ) 动态绑定,并调用相应的虚函数动态绑定,并调用相应的虚函数 输出:输出:The circle w

27、ith radius 8 is at (3, 7)Show a circle考虑:若去掉考虑:若去掉POINT的的virtual,结果如何?结果如何?26【例【例【例【例7.27.2】虚函数的使用方法虚函数的使用方法虚函数的使用方法虚函数的使用方法#include struct A virtual void f1( ) coutA:f1n; virtual void f2( ) coutA:f2n; virtual void f3( ) coutA:f3n; virtual void f4( ) coutA:f4n; ;class B: A virtual void f1( )/virtual

28、可省略可省略 coutB:f1n; void f2( ) /f2自动成为虚函数自动成为虚函数 coutB:f2n; ;class C: B void f4( ) /f4自动成为虚函数自动成为虚函数coutf1( ); p-f2( ); p-f3( ); p-f4( ); p-A:f2( ); /调用调用B:f1( )/调用调用B:f2( )/调用调用A:f3( )/调用调用C:f4( )/调用调用A:f2( )输出:输出:B:f1 B:f2 A:f3 C:f4 A:f2语法检查静语法检查静态进行,考态进行,考虑虑(对否对否): c.f1( ); /? c.f2( ); /?27【例【例【例【例

29、7.37.3】虚函数的重载方法虚函数的重载方法虚函数的重载方法虚函数的重载方法#include struct A virtual void f1( ) coutA0; virtual void f1(char c) coutA1; void f1(int x) cout“A2”; /自动内联自动内联;class B: A void f1( ) coutB0; void f1(int x) coutB2; ;class C: B void f1( ) coutC0; void f1(char c) coutC1; public: void f1(long x) coutf1(X); p-f1(

30、); c.f1(23L); p-f1(3) /自动成为虚函数自动成为虚函数/普通函数成员普通函数成员/普通函数成员普通函数成员,可加可加virtual成虚函数成虚函数/自动成为虚函数自动成为虚函数/自动成为虚函数自动成为虚函数/调用调用C:f1(char) /调用调用C:f1( ) /调用调用C:f1(long) 输出:输出:C1C0C3A2/调用调用A:f1(int) 考虑:考虑: 1. C.f( );c.f1(3); / 对对否否2. 若去掉若去掉A:f1(int) p-f1(3) /输出?输出?28例例例例 7.7 7.7 关于图形显示的多态函数。关于图形显示的多态函数。关于图形显示的多

31、态函数。关于图形显示的多态函数。#include class GRAPH double x, y;public: /. virtual void draw( )const=0; virtual void hide( )const=0; GRAPH(double x1, double y1) x=x1; y=y1; ;class POINT: public GRAPHpublic:29例例例例 7.7 7.7 关于图形显示的多态函数。关于图形显示的多态函数。关于图形显示的多态函数。关于图形显示的多态函数。 /. void hide( )const coutHide a pointn; void

32、draw( )const coutDraw a pointn; POINT(double x, double y):GRAPH(x, y) ;class CIRCLE: public POINT double r;public: /. void hide( )const coutHide a circlen; void draw( )const coutgraph-draw( ); p=p-next; 32例例例例 7.7 7.7 关于图形显示的多态函数。关于图形显示的多态函数。关于图形显示的多态函数。关于图形显示的多态函数。void main(void) GRAPHS graphs; gra

33、phs.push(new POINT(3,5);/最内层图形最后画出 graphs.push(new POINT(7,8); graphs.push(new CIRCLE(2,3,9); graphs.push(new CIRCLE(5,6,7); graphs.push(new CIRCLE(4,7,3);/最外层图最先画 graphs.draw( );/从内到外输出所有图形33#include class A int x;public: A(int a, A*&p) x=a; p=this; p-f( ); /输出A:f virtual void f( ) coutf(); ;34class C: public A A *p; B b;public: B(int a):A(3, p), b(p) /输出C:f void f( ) coutC:fn; ;void main(int argc, char* argv) B b(3); 构造B类对象,对象瞬间为A类型A类对象构造完毕,此时当作B类对象35

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 商业/管理/HR > 营销创新

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号