虚函数和动态联编.ppt

上传人:s****8 文档编号:82719552 上传时间:2023-03-26 格式:PPT 页数:52 大小:222.50KB
返回 下载 相关 举报
虚函数和动态联编.ppt_第1页
第1页 / 共52页
虚函数和动态联编.ppt_第2页
第2页 / 共52页
点击查看更多>>
资源描述

《虚函数和动态联编.ppt》由会员分享,可在线阅读,更多相关《虚函数和动态联编.ppt(52页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、虚函数虚函数多态性是面向对象设计语言的基多态性是面向对象设计语言的基本特征。本特征。l仅仅是将数据和函数捆绑在一起,进行类的封装,使用一些简单的继承,还不能算是真正应还不能算是真正应用了面向对象的设计思想用了面向对象的设计思想。l多态性是面向对象的精髓,也是难点。l在C+中,多态性是通过虚函数来实现的。l#include l lclass vehicleclass vehicleint wheels;float weight;public:void message(void)cout Vehicle messagen;class car:public vehicleclass car:publ

2、ic vehicleint passenger_load;public:void message(void)cout Car messagen;l lclass truck:public vehicleclass truck:public vehicleint passenger_load;float payload;public:int passengers(voidpassengers(void)return passenger_load;l lclass boat:public vehicleclass boat:public vehicleint passenger_load;publ

3、ic:int passengers(voidpassengers(void)return passenger_load;void message(void)cout message();/输出Vehicle messagedelete unicycle;lunicycle=new car;unicycle=new car;unicycle-message();/输出Vehicle messagel/-lsedan=(car*)unicycle;sedan=(car*)unicycle;sedan-message();/输出Car messagedelete sedan;l/-lsemi.mes

4、sagesemi.message();/();/输出输出Vehicle messageVehicle message?sailboat.message();/输出Boat message 指针的类型决定调用那一个成员指针的类型决定调用那一个成员函数函数l因为指针的类型决定调用那一个成员函数,所以,一个vehicle*调用vehicle成员函数,即使它指向派生类的对象。l同样,一个car*也调用car 的成员函数。l我们把这称为早期联编或静态联编,因为指针要调用那一个函数是在编译时就确定的。能不能通过该指针来调用派生类能不能通过该指针来调用派生类的成员函数呢?的成员函数呢?l那么,当vehicl

5、e*指向派生类对象时,我们能不能通过该指针来调用派生类的成员函数呢?l在C+中,我们是可以作到的,这要用到C+的多态特性多态特性。l也就是说,基类指针是调用基类的成员函数,还是调用派生类的成员函数,不是由指针的类型决定的,而是由指针指向的对象的类型由指针指向的对象的类型决定的 动态联编或迟后联编动态联编或迟后联编l多态也称为动态联编或迟后联编,因为到底调用哪一个函数,在编译时不能确定,而要推迟推迟到运行时确定。到运行时确定。l也就是说,要等到程序运行时,确定了指针所指向的对象的类型时,才能够确定。l在C+中,动态联编是通过虚函数虚函数来实现的。l函数调用是通过相应的函数名来实现的。l对于源程序

6、进行编译后,存放在内存中的可执行程序,存放在内存中的可执行程序,存放在内存中的可执行程序,存放在内存中的可执行程序,函数实际上是一段机器代码函数实际上是一段机器代码函数实际上是一段机器代码函数实际上是一段机器代码,它是通过首地址进行标识和调用的。例如,假定定义一个函数:void func()/;我们可以用下面的语句调用这个函数:func();/调用func函数l这是在源程序中调用函数的方法,它是用函数名函数名函数名函数名操作的。函数执行的原理函数执行的原理l我们看看在可执行程序中函数调用是怎么操作的,我们用汇编语言来说明,因为汇编语言和机器语言(计算机可以直接执行的语言)是一一对应的。在可执行

7、程序中,函数调用使用下面的方法:call call xxxxxxxxxx xxxxx代表存放函数代码内存空间的首地址。lcall是汇编语句中的一条指令,意思是调用一个函数。l实际操作过程是:保存当前地址、保护现场,跳转到跳转到跳转到跳转到xxxxxxxxxx地址执行地址执行地址执行地址执行。l正是基于这个原因,在C/C+中的函数名是一个指针,该指针指向该函数段代码在内存中的首地址。早期或静态联编的原理早期或静态联编的原理l如何将源程序中的函数调用和函数体(也就是在内存中该函数的机器代码)联系起来呢?l这件工作是由编译器和连接程序编译器和连接程序编译器和连接程序编译器和连接程序来完成的。l在C/

8、C+语言中,函数调用在程序运行之前程序运行之前程序运行之前程序运行之前就已经和函数体(函数的首地址)联系起来。l编译器把函数体翻译成机器代码,并记录了函数的首地址。l在对函数调用的源程序段进行编译的时候,编译器知道这个函数名的首地址在那里(它可以从生成的标识符表中查到这个函数名对应的首地址),l然后将这个首地址替换函数名将这个首地址替换函数名将这个首地址替换函数名将这个首地址替换函数名,一并翻译成机器码。l这种编译方法称为早期或静态联编。动态联编动态联编l那么,当vehicle*指向派生类对象时,我们能不能通过该指针来调用派生类的成员函数呢?l从这种编译方法来看,是不可能的。因为编译器只会寻找

9、vehicle*的成员函数。l如何实现这个功能:当用基类指针调用成员函数时,是调用基类的成员函数,还是调用派生类的成员函数,不由指针的类型决定,而由指针指向的对象的类型决定呢?l也就是说,如果基类指针指向基类对象,就调用基类的成如果基类指针指向基类对象,就调用基类的成如果基类指针指向基类对象,就调用基类的成如果基类指针指向基类对象,就调用基类的成员函数,如果基类指针指向派生类对象,就调用派生类的员函数,如果基类指针指向派生类对象,就调用派生类的员函数,如果基类指针指向派生类对象,就调用派生类的员函数,如果基类指针指向派生类对象,就调用派生类的成员函数成员函数成员函数成员函数。l这就要用到另外一

10、种方法,称为动态联编或迟后联编。到底调用哪一个函数,在编译时不能确定,而要推迟到运行时确定。l在C+中,动态联编是通过虚函数虚函数虚函数虚函数来实现的。下面我们先介绍虚函数,然后讨论动态联编实现的原理。vehiclel#include/如果定义了如果定义了vehicle类的对象,编译器为该对象分配类的对象,编译器为该对象分配8个字节的个字节的存储空间存储空间class vehicleint wheels;/占用占用4个字节个字节float weight;/占用占用4个字节个字节public:void message(void)cout Vehicle messagen;/该函数不在类中分配空间

11、,编译器另外分配空间给该函数,该函数不在类中分配空间,编译器另外分配空间给该函数,并把函数首并把函数首/地址记录到一个标识符表中地址记录到一个标识符表中;carl/如果定义了如果定义了car类的对象,编译器为该对象分配类的对象,编译器为该对象分配12个字节的存储个字节的存储空间空间class car:public vehicle/基类占用基类占用8个字节个字节int passenger_load;/占用占用4个字节个字节public:void message(void)cout Car messagen;/该函数不在类中分配空间,编译器另外分配空间给该函数,该函数不在类中分配空间,编译器另外分

12、配空间给该函数,并把函数首并把函数首/地址记录到标识符表中地址记录到标识符表中;truckl/如果定义了如果定义了truck类的对象,编译器为该对象分配类的对象,编译器为该对象分配16个字节的存个字节的存储空间储空间class truck:public vehicle/基类占用基类占用8个字节个字节int passenger_load;/占用占用4个字节个字节float payload;/占用占用4个字节个字节public:int passengers(void)return passenger_load;/该函数不在类中分配空间,编译器另外分配空间给该函数,该函数不在类中分配空间,编译器另外

13、分配空间给该函数,并把函数首并把函数首/地址记录到标识符表中地址记录到标识符表中;boatl/如果定义了如果定义了boat类的对象,编译器为该对象分配类的对象,编译器为该对象分配12个字节的存个字节的存储空间储空间class boat:public vehicle/基类占用基类占用8个字节个字节int passenger_load;/占用占用4个字节个字节public:int passengers(void)return passenger_load;void message(void)cout message();/输出Vehicle message/*当编译器编译到上面语句的时候,会参照该

14、对象指针所属的当编译器编译到上面语句的时候,会参照该对象指针所属的类名类名vehicle和函数名和函数名message去标识符表中查找该成员函数的首去标识符表中查找该成员函数的首地址,查找到地址,查找到vehicle类的成员函数类的成员函数message的首地址,并使用的首地址,并使用call xxxxx语句替换该函数调用语句。语句替换该函数调用语句。*/ldelete unicycle;unicycle=new car;unicycle-message();/输出Vehicle message/*当编译器编译到上面这句的时候,会参照该对象指针所属的当编译器编译到上面这句的时候,会参照该对象指

15、针所属的类名类名vehicle和函数名和函数名message去标识符表中查找该成员函数的首去标识符表中查找该成员函数的首地址,它查到的也是地址,它查到的也是vehicle类的成员函数类的成员函数message的首地址,然的首地址,然后使用后使用call xxxxx语句替换该函数调用语句。语句替换该函数调用语句。*/lsedan=(car*)unicycle;sedan-message();/输出Car message/*当编译器编译到上面这句的时候,会参照该对象指针所属的类当编译器编译到上面这句的时候,会参照该对象指针所属的类名名car和函数名和函数名message去标识符表中查找该成员函数的

16、首地址,去标识符表中查找该成员函数的首地址,它查到的是它查到的是car类的成员函数类的成员函数message的首地址,然后使用的首地址,然后使用call xxxxx语句替换该函数调用语句。语句替换该函数调用语句。*/delete sedan;semi.message();/输出Vehicle message/*当编译器编译到上面这句的时候,会参照该对象指针所属的类当编译器编译到上面这句的时候,会参照该对象指针所属的类名名car和函数名和函数名message去标识符表中查找该成员函数的首地址,去标识符表中查找该成员函数的首地址,在基类在基类vehicle中它查到了成员函数中它查到了成员函数mes

17、sage的首地址,然后使用的首地址,然后使用call xxxxx语句替换该函数调用语句。语句替换该函数调用语句。*/sailboat.message();/输出Boat message/*当编译器编译到上面这句的时候,会参照该对象指针所属的当编译器编译到上面这句的时候,会参照该对象指针所属的类名类名boat和函数名和函数名message去标识符表中查找该成员函数的首地去标识符表中查找该成员函数的首地址,它查到的是址,它查到的是boat类的成员函数类的成员函数message的首地址,然后使用的首地址,然后使用call xxxxx语句替换该函数调用语句。语句替换该函数调用语句。*/虚函数虚函数l虚

18、函数的定义很简单,只要在成员函数原型前加一个关键字virtual即可。l如果一个基类的成员函数定义为虚函数,那么,它在它在它在它在所有派生类中也保持为虚函数所有派生类中也保持为虚函数所有派生类中也保持为虚函数所有派生类中也保持为虚函数,即使在派生类中省略了virtual关键字。lclass vehiclevirtualvirtual void message(void)cout Vehicle messagen;lclass car:public vehicleint passenger_load;public:(virtual)void message(void)cout message()

19、;unicycle-message();delete unicycle;lunicycle=new car;unicycle-message();unicycle-message();delete unicycle;lunicycle=new truck;unicycle-message();unicycle-message();delete unicycle;lunicycle=new boat;unicycle-message();unicycle-message();delete unicycle;l该程序的运行结果为:Vehicle messageCar messageVehicle

20、messageBoat message简单的分析简单的分析l基类vehicle的成员函数message被定义为虚函数,虽然其派生类中的message成员函数定义时,没有virtual关键字,但都是虚函数。都是虚函数。都是虚函数。都是虚函数。l从上面的结果我们确实看到:如果派生类中有与基类对应的方法,并且基类指针指向派生类的对象,那么基类指针调用的方法是派生类的方法。l我们应该注意到:main函数中有四个相同的语句“unicycle-message();”,但是它们的结果并不相同。l l哪一个类的哪一个类的哪一个类的哪一个类的messagemessage成员函数被调用,不是在编译时成员函数被调用

21、,不是在编译时成员函数被调用,不是在编译时成员函数被调用,不是在编译时确定的,而是根据运行时确定的,而是根据运行时确定的,而是根据运行时确定的,而是根据运行时unicycleunicycle指针指向的对象的指针指向的对象的指针指向的对象的指针指向的对象的类型确定的。类型确定的。类型确定的。类型确定的。l由于类truck没有覆盖基类的message成员函数,系统调用基类的message成员函数。l需要注意的是:要达到动态联编的效果,基类和派生类的对应函数不仅名字相同,而且返回类型、参数个不仅名字相同,而且返回类型、参数个不仅名字相同,而且返回类型、参数个不仅名字相同,而且返回类型、参数个数和类型

22、也必须相同。数和类型也必须相同。数和类型也必须相同。数和类型也必须相同。并不是所有覆盖的函数都变成虚并不是所有覆盖的函数都变成虚函数。函数。lclass car:public vehicleint passenger_load;public:(virtual)void message(void)cout Car messagen;void message(int n)/这个函数会覆盖基类的这个函数会覆盖基类的message函数,函数,但是它不是虚函数但是它不是虚函数;一个有意思的情况一个有意思的情况 lclass car:public vehicleint passenger_load;pri

23、vate:(virtual)void message(void)cout message()/rightl因为message()是car类的私有成员,所以在类外应该访问不到它,正象“mycar.message()”会出现编译错误一样。l但是,如果我们把它定义成虚函数,就可以通过基类指针访问到了。l对于v-message();,虽然v是vehicle类型指针,但是,它调用的是虚函数,所以实际上调用的是实际上调用的是实际上调用的是实际上调用的是carcar类的类的类的类的message()message()函数。函数。函数。函数。使用虚函数,我们可以获得良好使用虚函数,我们可以获得良好的可扩展性的

24、可扩展性l使用虚函数,我们可以获得良好的可扩展性,l在一个设计比较好的面向对象程序中,大多数大多数函数都是与基类的接口进行通信函数都是与基类的接口进行通信。l因为使用基类接口时,调用基类接口的程序不需改变就可以适应新类。l如果用户想添加新功能,他就可以从基类继承并添加相应的新功能。l例如,上面vehicle类的例子中已经实现了car、truck、boat三种交通工具,l然后我们定义一个“很复杂很复杂很复杂很复杂”的函数:,lvoid getmessage(vehicle*v).前面一堆代码v-message();.后面一堆代码 l若干天后,我们又想在这个程序中加入bus的功能,l可以从基类ve

25、hicle派生出bus类:class bus:public vehicleint passenger_load;public:void message(void)cout message()调用car的成员函数,这个函数的入口地址由函数的入口地址由unicycle指向的对指向的对象决定。象决定。一般实现方法如下:一般实现方法如下:l包含虚函数的对象,增加了一个隐含的数据成员,且是它的第一个数据成员,l该数据成员指向一个指针数组,而指针数组存储对象的虚函数地址,需要说明的是这个实现与具体的编译需要说明的是这个实现与具体的编译需要说明的是这个实现与具体的编译需要说明的是这个实现与具体的编译器有关。

26、器有关。器有关。器有关。l某一个类的虚函数地址表被该类的所有对象共享,甚至有可能两个类共享同一个虚函数地址表有可能两个类共享同一个虚函数地址表有可能两个类共享同一个虚函数地址表有可能两个类共享同一个虚函数地址表。内存开销包括:每一个对象增加了一个额外的数据成员。每一个类有一个指针表,用于存储该类各虚函数的地址。有虚函数的对象的内部组织有虚函数的对象的内部组织 unicycle-message()的调用过的调用过程是:程是:l首先检查unicycle指向的对象的隐含的数据成员,在我们前面所举的例子中,该数据成员指向的指针表只有一个元素,即message函数的入口地址,被调用的函数根据指针表确定。

27、l有虚函数的的对象的内部组织,我们可以用示意图来说明:正象我们在图看到的,有虚函数的所有对象均有一有虚函数的所有对象均有一有虚函数的所有对象均有一有虚函数的所有对象均有一个隐含的指针数据成员个隐含的指针数据成员个隐含的指针数据成员个隐含的指针数据成员,且指向存放虚函数入口地址的指针表。l类Vehicle对象与truck对象共用一个表,而car和boat有自己的message函数,所以,它们需要自己的虚函数指针表。lvirtual将一个成员函数说明为虚函数将一个成员函数说明为虚函数将一个成员函数说明为虚函数将一个成员函数说明为虚函数,对于编译器来讲,它的作用是告诉编译器,这个类含有虚函数,对于这

28、个函数不使用静态联编,而是使用动态联编机制。编译器就会按照动态联编的方案进行一系列的工作l对于每个包含虚函数的类,编译器都为其创建一个表创建一个表创建一个表创建一个表(称之为VTABLE表)。l在VTABLE表中放置的是每个类自己的虚函数地址放置的是每个类自己的虚函数地址放置的是每个类自己的虚函数地址放置的是每个类自己的虚函数地址,在每个包含虚函数的类中放置了一个指针(VPTRVPTR),),),),指向指向指向指向VTABLEVTABLE表表表表。l通过基类指针调用虚函数时,编译器会在函数调用的地方插入一段特定的代码。l这段代码的作用就是得到得到得到得到VPTRVPTR,找到,找到,找到,找

29、到VTABLEVTABLE,并,并,并,并在在在在VTABLEVTABLE表中找到相应的虚函数地址表中找到相应的虚函数地址表中找到相应的虚函数地址表中找到相应的虚函数地址,然后进行调用 l#include iostream.h/没有虚函数的类没有虚函数的类没有虚函数的类没有虚函数的类class A public:int a;A():a(0);l l/有一个虚函数的类有一个虚函数的类有一个虚函数的类有一个虚函数的类class Bpublic:int a;B():a(0)virtual void virtual void funcfunc()();l/有两个虚函数的类class Cpublic:i

30、nt a;C():a(0)virtual void func()virtual void func2();lvoid main()cout没有虚函数的类大小是sizeof(A)endl;cout有一个虚函数的类大小是sizeof(B)endl;cout有两个虚函数的类大小是sizeof(C)endl;A a;B b;C c;int*tmp;tmp=(int*)&b;/打印对象打印对象b中两个整型单元中的值中两个整型单元中的值cout(*tmp)endl;cout(*(tmp+1)endl;/改变改变b中成员中成员a的值的值b.a=1;/再次打印对象再次打印对象b两个整型单元中的值两个整型单元中

31、的值cout(*tmp)endl;cout(*(tmp+1)func3();lpb是B型指针,但指向的是C类对象,并调用虚函数func3()编译出的代码编译出的代码 l mov edx,DWORD PTR _pb$ebp;将;将this指针取出到指针取出到edx中,获得对象中,获得对象mov eax,DWORD PTR edx;将;将VPTR取出到取出到eax中,获得中,获得PTRmov esi,esp;与本文无关,不用管它;与本文无关,不用管它mov ecx,DWORD PTR _pb$ebp;将;将this指针作为参数传给函数指针作为参数传给函数call DWORD PTR eax+8;到

32、;到VTABLE中查找,并进行调用中查找,并进行调用 汇编过程汇编过程l寄存器edx存放的是对象的首地址,它对应该对象的this指针l第一句是将this指针取出。因为VPTR保存在对象的首部,正是this指针指向的地址,l第二句是将指向寄存器edx指向的位置的值,即this指针指向的双字,即VPTR取出,存在寄存器eax中。l第三句将堆栈指针保存起来,等该函数调用完返回后,进行检查,这是VC编译器的一种保护措施l第四句就是将this指针作为参数传给这个函数。l第五句是去VTABLE表中查找虚函数位置,因为一个一个一个一个指针是四个字节,指针是四个字节,指针是四个字节,指针是四个字节,func3func3在表中是第三个在表中是第三个在表中是第三个在表中是第三个,所以地址是eax8,然后进行调用。l这样,编译器就实现了动态联编。

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 生活休闲 > 生活常识

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com