不用纯虚函数数怎么实现同样的功能

定义/虚函数
简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。下面来看一段简单的代码:1234567891011121314151617181920212223242526272829#include&iostream&using&namespace&class&A{public:void&print(){cout&&"This&is&A"&&}};&class&B&:&public&A{public:void&print(){cout&&"This&is&B"&&}};&int&main(){A&a;B&b;a.print();b.print();return&0;}
输出结果/虚函数
分别是“ThisisA”、“ThisisB”。通过class A和class B的print()这个接口,可以看出这两个class因个体的差异而采用了不同的策略,但这是否真正做到了多态性呢?No,多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把main()处的代码改一改。1234567891011int&main(){A&a;B&b;A&*p1&=&&a;A&*p2&=&&b;p1-&print();p2-&print();return&0;}1234567891011class&A{public:virtual&void&print(){cout&&"This&is&A"&&}};&class&B&:&public&A{public:void&print(){cout&&"ThisisB"&&}};毫无疑问,class A的成员函数print()已经成了虚函数,那么class B的print()成了虚函数了吗?回答是Yes,只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以,class B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了(语法上可加可不加,不加的话编译器会自动加上,但为了阅读方便和规范性,建议加上)。现在重新运行main2的代码,这样输出的结果就是This is A和This is B了。作个简单的总结,指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。
实现/虚函数
虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在就来剖析虚函数。先定义两个类12345678910class&A{public:virtual&void&fun(){cout&&1&&}virtual&void&fun2(){cout&&2&&}};class&B&:&public&A{public:void&fun(){cout&&3&&}void&fun2(){cout&&4&&}};1234567891011121314#include&iostream&using&namespace&int&main(){void(*fun)(A*);A&*p=new&B;long&lVptrAmemcpy(&lVptrAddr,p,4);memcpy(&fun,reinterpret_cast&long*&(lVptrAddr),4);fun(p);delete&p;system("pause");return&0;}代码编写用VC或Dev-C++编译运行一下,看看结果是不是输出3,如果不是,那么太阳明天肯定是从西边出来。void (*fun)(A*); 这段定义了一个函数指针名字叫做fun,而且有一个A*类型的参数,这个函数指针待会儿用来保存从vtbl里取出的函数地址。A* p=new B; new B是向内存(内存分5个区:全局名字空间,自由存储区,寄存器,空间,栈)自由存储区申请一个内存的地址然后隐式地保存在一个指针中.然后把这个地址赋值给A类型的指针P。long lVptrA 这个long类型的变量待会儿用来保存vptr的值。memcpy(&lVptrAddr,p,4); 前面说了,他们的实例对象里只有vptr指针,所以就放心大胆地把p所指的4bytes内存里的东西复制到lVptrAddr中,所以复制出来的4bytes内容就是vptr的值,即vtbl的地址。现在有了vtbl的地址了,那么现在就取出vtbl第一个slot里的内容memcpy(&fun,reinterpret_cast&long*&(lVptrAddr),4); 取出vtbl第一个slot里的内容,并存放在函数指针fun里。需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指针,所以要把它先转变成指针类型fun(p); 这里就调用了刚才取出的函数地址里的函数,也就是调用了B::fun()这个函数,也许你发现了为什么会有参数p,其实类成员函数调用时,会有个this指针,这个p就是那个this指针,只是在一般的调用中自动帮你处理了而已,而在这里则需要自己处理。 释放由p指向的自由空间;system("pause"); 屏幕暂停;如果调用B::fun2()怎么办?那就取出vtbl的第二个slot里的值就行了memcpy(&fun,reinterpret_cast&long*&(lVptrAddr+4),4); 为什么是加4呢,因为一个指针的长度是4bytes,所以加4。或者memcpy(&fun,reinterpret_cast&long*&(lVptrAddr)+1,4); 这更符合数组的用法,因为lVptrAddr被转成了long*型别,所以+1就是往后移sizeof(long)的长度。
代码示例/虚函数
1234567891011121314151617181920212223#include&iostream&using&name&class&A{public:virtual&void&fun(){cout&&"A::fun"&&}virtual&void&fun2(){cout&&"A::fun2"&&}};class&B&:&public&A{public:void&fun(){cout&&"B::fun"&&}void&fun2(){cout&&"B::fun2"&&}};int&main(){void(A::*fun)();A&*p=new&B;fun=&A::(p-&*fun)();fun=&A::fun2;(p-&*fun)();delete&p;system("pause");return&0;}1234567891011121314151617#include&iostream&using&namespace&void&CallVirtualFun(void*pThis,intindex=0){void(*funptr)(void*);long&lVptrAmemcpy(&lVptrAddr,pThis,4);memcpy(&funptr,reinterpret_cast&long*&(lVptrAddr)+index,4);funptr(pThis);}int&main(){A&*p&=&new&B;CallVirtualFun(p);CallVirtualFun(p,1);system("pause");return&0;}拥有一个“通用”的CallVirtualFun方法。这个通用方法和第三部分开始处的代码联系很大。由于A::fun()和A::fun2()是虚函数,所以&A::fun和&A::fun2获得的不是函数的地址,而是一段间接获得虚函数地址的一段代码的地址,形象地把这段代码看作那段CallVirtualFun。编译器在编译时,会提供类似于CallVirtualFun这样的代码,当你调用虚函数时,其实就是先调用的那段类似CallVirtualFun的代码,通过这段代码,获得虚函数地址后,最后调用虚函数,这样就真正保证了多态性。同时大家都说虚函数的效率低,其原因就是,在调用虚函数之前,还调用了获得虚函数地址的代码。
限制/虚函数
(1)非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。
(2)只需要在声明函数的类体中使用“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。
(3)当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。
(4)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现这种同名函数。
虚函数联系到多态,多态联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。代码可以用VC6和Dev-C++4.9.8.0通过编译,且运行无问题。其中的类比方法只能看成模型,因为不同的编译器的底层实现是不同的。例如this指针,Dev-C++的gcc就是通过压栈,当作参数传递,而VC的编译器则通过取出地址保存在ecx中。所以这些类比方法不能当作具体实现。
作用/虚函数
虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。
当程序发现虚函数名前的关键字virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适的成员函数。虚函数是C++多态的一种表现。
例如:子类继承了父类的一个函数(方法),而把父类的指针指向子类,则必须把父类的该函数(方法)设为virtual(虚函数)。
([]&注:下行语义容易使人产生理解上的偏差,实际效果应为:
如存在:Base&-&&Derive1&-&&Derive2&及它们所拥有的虚函数func()
则在访问派生类Derive1的实例时,使用其基类Base及本身类型Derive1,或被静态转换的后续派生类Derive2的指针或引用,均可访问到Derive1所实现的func()。)
动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式:
1、指向基类的指针变量名-&虚函数名(实参表)
2、基类对象的引用名.&虚函数名(实参表)
使用虚函数,可以灵活的进行动态绑定,当然是以一定的开销为代价。如果父类的函数(方法)根本没有必要或者无法实现,完全要依赖子类去实现的话,可以把此函数(方法)设为virtual&函数名=0 把这样的函数(方法)称为纯虚函数。
如果一个类包含了纯虚函数,称此类为抽象类。
示例/虚函数
#include&iostream&
classCshape
voidSetColor(intcolor){m_nColor=}
virtualvoidDisplay(void){cout&&"Cshape"&&}
classCrectangle:publicCshape{
virtualvoidDisplay(void)
cout&&"Crectangle"&&
classCtriangle:publicCshape{
virtualvoidDisplay(void)
{cout&&"Ctriangle"&&}
classCellipse:publicCshape{
virtualvoidDisplay(void)
{cout&&"Cellipse"&&}
voidmain(){
CellipseobE
CtriangleobT
CrectangleobR
Cshape*pShape[4]={&obShape,&obEllipse,&obTriangle,&obRectangle};
for(inti=0;i&4;i++)
pShape[i]-&Display();
本程序运行结果:
Crectangle
如果把Cshape类里面virtual&void&Display(void)&中的virtual去掉的话
运行结果就不一样了:
Cshape条件
所以,从以上,实现需要三个条件:
1、&必须把动态联编的行为定义为类的虚函数。
2、&类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来。
3、&必须先使用基类指向子类型的对象,然后直接或者间接使用基类指针调用虚函数。
c++扩展/虚函数
下面是对C++的虚函数的理解。
简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。下面来看一段简单的代码。
voidprint(){cout&&"ThisisA"&&}
classB:publicA{
voidprint(){cout&&"ThisisB"&&}
//为了在以后便于区分,这段main()代码叫做main1
a.print();
b.print();
通过class&A和class&B的print()这个接口,可以看出这两个class因个体的差异而采用了不同的策略,但这是否真正做到了多态性呢?No,多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把main()处的代码改一改。
intmain(){//main2
p1-&print();
p2-&print();
运行一下看看结果,结果却是两个This&is&A。问题来了,p2明明指向的是class&B的对象但却是调用的class&A的print()函数,这不是所期望的结果,那么解决这个问题就需要用到虚函数
virtualvoidprint(){cout&&"ThisisA"&&}//现在成了虚函数了
classB:publicA{
voidprint(){cout&&"ThisisB"&&}//这里需要在前面加上关键字virtual吗?
毫无疑问,class&A的成员函数print()已经成了虚函数,那么class&B的print()成了虚函数了吗?回答是Yes,只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以,class&B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了。
现在重新运行main2的代码,这样输出的结果就是This&is&A和This&is&B了。指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。
&|&相关影像
互动百科的词条(含所附图片)系由网友上传,如果涉嫌侵权,请与客服联系,我们将按照法律之相关规定及时进行处理。未经许可,禁止商业网站等复制、抓取本站内容;合理使用者,请注明来源于。
登录后使用互动百科的服务,将会得到个性化的提示和帮助,还有机会和专业认证智愿者沟通。
此词条还可添加&
编辑次数:17次
参与编辑人数:8位
最近更新时间: 17:36:22
贡献光荣榜学习 C++ 的同志不知道有没有和我一样遇到过这样的困惑:C++中的虚函数到底怎么实现的?在各种继承关系中,虚函数表的结构到底是什么样的?曾经我是很想当然,可是后来在使用ATL的过程中,我发现并不是我想的那样。大家知道,利用C++语言本身的特性进行COM编程当然是很方便的事,但是你就得随时随地都知道那虚函数表里头到底是些什么东西。讲C++语法的书没有义务告诉你C++产生的虚函数表是什么样的,这就是头痛的所在。
自已做试验是件很快乐的事,我很愿意这么做。
首先写个函数,作为我们实验的基础。传入虚函数表指针,显示虚数表的内容。
void DispVFT(DWORD* pVFT)
printf("VFT Pointer:%p\n" , pVFT);
printf("Begin\n");
DWORD* p = (DWORD* )*pVFT;//得到VFT的首址
while(*p) file://这个地方我是看表项是不是为空来判断是否到了表尾,
file://大多数情况都是对的,不过不能为准
printf("VF:%p , %p\n", p , *p);
printf("End\n\n");
首先我们看单个类时的虚函数表的情况:
file://printf(%22in/ C1\n");
file://dispvft((dword*)this/);
virtual F1()
void main()
file://由于C1中没有成员数据,所有我们可以用这种方式判断
file://c1/中的虚函数表指针的个数
printf("vftptr count :%d\n" , sizeof(C1) / 4);
file://显示内存结构
DispVFT((DWORD* )&c1);
vftptr count :1
VFT Pointer:0012FF7C
VF: , ::F1)
很单纯,不用多讲,这是我们意料之中的结果。
下面我们进行简单继承的实验
class C2 : public C1
printf("In C1\n");
DispVFT((DWORD*)this);
virtual F2()
void main()
C1* pC1 = &c2;
printf("vftptr count :%d\n" , sizeof(C2) / 4);
printf("C1\n");
DispVFT((DWORD*)pC1);
printf("C2\n");
DispVFT((DWORD*)&c2);
VFT Pointer:0012FF7C
VF: , ::F1)
VFT Pointer:0012FF7C
VF: , ::F1) file://输出的第一项是表的首址与对应的表项内容,看看地址,与 In C1的不同,说明是不同的两个表
VF: , 0040108C(C2::F2)
vftptr count :1
VFT Pointer:0012FF7C
VF: , ::F1)
VF: , 0040108C(C2::F2)
VFT Pointer:0012FF7C
VF: , ::F1)
VF: , 0040108C(C2::F2)
大家可以看到最后虚函数表指针仍然是同一个,表中按顺序放入了C1(基类)与C2(派生类)的虚函数指针。
下面是多重继承
file://printf(%22in/ C1\n");
file://dispvft((dword*)this/);
virtual F1(){}
file://printf(%22in/ C1\n");
file://dispvft((dword*)this/);
virtual F2(){}
class C3 : public C1 , public C2
file://printf(%22in/ C1\n");
file://dispvft((dword*)this/);
virtual F3(){}
void main()
C2* pC2 = &c3;
C1* pC1 = &c3;
printf("vftptr count :%d\n" , sizeof(C3) / 4);
printf("C1\n");
DispVFT((DWORD*)pC1);
printf("C2\n");
DispVFT((DWORD*)pC2);
printf("C3\n");
DispVFT((DWORD*)&c3);
vftptr count :2
VFT Pointer:0012FF78
VF: , ::F1)
VF: , 0040101E(C3::F3)
VFT Pointer:0012FF7C
VF: , ::F2)
VF: , ::F1)
VF: , 0040101E(C3::F3)
VFT Pointer:0012FF78
VF: , ::F1)
VF: , 0040101E(C3::F3)
虚函数表指针变成两个了,也就是说现在是用两个虚函数表指针维护一个表,总结一下就是,虚函数表指针的个数等于基类的个数。至于虚函数表的个数,应该是三个。你可以把我在构造函数中加的代码去掉注释符,看看输出。你会发现每次输出的表的首地址都是不一样,那表当然也不是同一个表。
下面说说多层继承的情况:
printf("In C1\n");
DispVFT((DWORD*)this);
virtual F1(){}
class C2 : public C1
printf("In C2\n");
DispVFT((DWORD*)this);
virtual F2(){}
class C3 : public C2
printf("In C3\n");
DispVFT((DWORD*)this);
virtual F3(){}
void main()
C2* pC2 = &c3;
C1* pC1 = &c3;
printf("vftptr count :%d\n" , sizeof(C3) / 4);
printf("C1\n");
DispVFT((DWORD*)pC1);
printf("C2\n");
DispVFT((DWORD*)pC2);
printf("C3\n");
DispVFT((DWORD*)&c3);
VFT Pointer:0012FF7C
VF: , ::F1)
VFT Pointer:0012FF7C
VF:0042010C , ::F1) //这里是类C2的vftable,第一个输出是它首址与表项内容
VF: , ::F2)
VFT Pointer:0012FF7C
VF:0042109C , ::F1)
VF: , ::F2)
VF: , ::F3)
vftptr count :1
VFT Pointer:0012FF7C
VF:0042109C ,
VFT Pointer:0012FF7C
VF:0042109C ,
VFT Pointer:0012FF7C
VF:0042109C ,
得到的结果:我们看到了虚函数表指针是一个,可是你仔细看看每个构造函数的输出!输出的第一项是表的首址与对应的表项。大家可以看到,首址都是不一样的这说明是三个不同的表,那么这个类就有三个虚函数表。你可能会想,这三个表在什么时候用呢,事实上,在C3的实例被构造出来后,只有最后一个表,也就是C3的表在用,其它的表跟本就是没有用的,C++在没有通过你同意的情况下,在浪费你的空间(多重继承也存在同样的问题)。微软想了个办法把其它的不用的虚函数表去掉:__declspec(novtable)
class __declspec(novtable)C1
file://printf(%22in/ C1\n");
file://dispvft((dword*)this/); file://这里得去掉,既然没有那个表,怎么输出
virtual F1(){}
class __declspec(novtable)C2 : public C1
file://printf(%22in/ C2\n");
file://DispVFT((DWORD*)this);//这里得去掉,既然没有那个表,怎么输出
virtual F2(){}
class C3 : public C2
printf("In C3\n");
DispVFT((DWORD*)this);
virtual F3(){}
void main()
C2* pC2 = &c3;
C1* pC1 = &c3;
printf("vftptr count :%d\n" , sizeof(C3) / 4);
printf("C1\n");
DispVFT((DWORD*)pC1);
printf("C2\n");
DispVFT((DWORD*)pC2);
printf("C3\n");
DispVFT((DWORD*)&c3);
VFT Pointer:0012FF7C
VF:0042109C ,
vftptr count :1
VFT Pointer:0012FF7C
VF:0042109C ,
VFT Pointer:0012FF7C
VF:0042109C ,
VFT Pointer:0012FF7C
VF:0042109C ,
可以看到一切正常,只是在不知不觉中,你的程序瘦身成功,不过如果你决定要去掉类的虚函数表,你最好可以确定这个类应该是个被继承的基类,而不是最后派生使用的类。否则可能会出错,比如:
void func1(C1* p)
void main()
func1(&c2);
声明:该文章系网友上传分享,此内容仅代表网友个人经验或观点,不代表本网站立场和观点;若未进行原创声明,则表明该文章系转载自互联网;若该文章内容涉嫌侵权,请及时向
论文写作技巧
上一篇:下一篇:
相关经验教程5992人阅读
C++中虚函数功能的实现机制
要理解C++中虚函数是如何工作的,需要回答四个问题。
1、& 什么是虚函数。
虚函数由于必须是在类中声明的函数,因此又称为虚方法。所有以virtual修饰符开始的成员函数都成为虚方法。此时注意是virtual修饰的成员函数不是virtual修饰的成员函数名。
例如:基类中定义:
&&&&&&&& &&&&&&&&&&&&&&&&&& virtual void show();&&&&&&&&&& //由于有virtual修饰因此是虚函数
&&&&&&&&&&&&&&&&&&&&&&&&&&& voidshow(int);&&&&&&&&& //虽然和前面声明的show虚函数同名,但不是虚函数。
所有的虚函数地址都会放在所属类的虚函数表vtbl中。另外在基类中声明为虚函数的成员方法,到达子类时仍然是虚函数,即使子类中重新定义基类虚函数时未使用virtual修饰,该函数地址仍会放在子类的虚函数表vtbl中。
2、& 正确区分重载、重写和隐藏。
注意三个概念的适用范围:处在同一个类中的函数才会出现重载。处在父类和子类中的函数才会出现重写和隐藏。
重载:同一类中,函数名相同,但参数列表不同。
重写:父子类中,函数名相同,参数列表相同,且有virtual修饰。
隐藏:父子类中,函数名相同,参数列表相同,但没有virtual修饰;
&&&&&&&&&&&&&&&&&&& 或:函数名相同,参数列表不同,无论有无virtual修饰都是隐藏。
&&&&&&&&&&&&&&&&&& 基类中:(1)&&& virtual void show();&&&&&&&&&& //是虚函数
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& (2)&&& void show(int);&&&&&&&&& //不是虚函数
&&&&&&&&&&&&&&&&&& 子类中:(3)&&& void show();&&&&&&&&&&&&&&&&&&&&&&& //是虚函数
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& (4)&&& void show(int);&&&&&&&&& //不是虚函数
1,2构成重载,3,4构成重载,1,3构成重写,2,4构成隐藏。另外2,3也会构成隐藏,子类对象无法访问基类的void show(int)成员方法,但是由于子类中4的存在导致了子类对象也可以直接调用void show(int)函数,不过此时调用的函数不在是基类中定义的void show(int)函数2,而是子类中的与3重载的4号函数。
3、& 虚函数表是如何创建和继承的。
基类的虚函数表的创建:首先在基类声明中找到所有的虚函数,按照其声明顺序,编码0,1,2,3,4……,然后按照此声明顺序为基类创建一个虚函数表,其内容就是指向这些虚函数的函数指针,按照虚函数声明的顺序将这些虚函数的地址填入虚函数表中。例如若show放在虚函数声明的第二位,则在虚函数表中也放在第二位。
对于子类的虚函数表:首先将基类的虚函数表复制到该子类的虚函数表中。若子类重写了基类的虚函数show,则将子类的虚函数表中存放show的函数地址(未重写前存放的是子类的show虚函数的函数地址)更新为重写后函数的函数指针。若子类增加了一些虚函数的声明,则将这些虚函数的地址加到该类虚函数表的后面。
4、& 虚函数表是如何访问的。
当执行pBase-&show()时,要观察show在Base基类中声明的是虚函数还是非虚函数。若为虚函数将使用动态联编(使用虚函数表决定如何调用函数),若为非虚函数则使用静态联编(根据调用指针pBase的类型来确定调用哪个类的成员函数)。此处假设show为虚函数,首先:由于检查到pBase指针类型所指的类Base中show定义为虚函数,因此找到pBase所指的对象(有可能是Base类型也可能是Extend类型。),访问对象得到该对象所属类的虚函数表地址。其次:查找show在Base类中声明的位置在Base类中所有虚函数声明中的位序。然后到pBase所指对象的所属类(有可能是Extend哦,多态)的虚函数表中访问该位序的函数指针,从而得到要执行的函数。
&&&&&&&& 例如:
&&&&&&&&&&&&&&&&&& 基类Base::virtualvoid show();&&&&&&&&&&&&&&&& (1)
&&&&&&&&&&&&&&&&&& 子类Extend::virtualvoid show();&&&&&&&&&&&& (2)
&&&&&&&&&&&&&&&&&& E
&&&&&&&&&&&&&&&&&& Base*pBase=&
&&&&&&&&&&&&&&&&&& pBase-&show();
当执行pBase-&show();时首先到Base中查看show(),发现其为虚函数,然后访问pBase指向的ext对象,在对象中得到Extend类的虚函数表,在Base类声明中找到show()声明的位序0,访问Extend类的虚函数表的位置0,得到show的函数地址。注意若只有基类定义了virtual void show();而子类未重写virtual void show();即上面的函数(2),则Extend虚函数表中的位序0中存放的地址仍然是Base类中定义的virtual void show()函数,而若Extend类中重写了Base类中的virtual
void show()方法,则Extend的虚函数表中位序0的函数地址将被更新为Extend中新重写的函数地址。从而调用pBase-&show()时将产生多态的现象。
总结:当调用pBase-&show();时,执行的步骤:
1,& 判断Base类中show是否为虚函数。
2,& 若不是虚函数则找到pBase所指向的对象所属类Base。执行Base::show()。若是虚函数则执行步骤3.
3,& 访问pBase所指对象的虚函数表指针得到pBase所指对象所在类的虚函数表。
4,& 查找Base中show()在声明时的位序为x,到步骤3得到的虚函数表中找到位序x,从而得到要执行的show的函数地址。
5,& 根据函数地址和Base中声明的show的函数类型(形参和返回值)访问地址所指向的函数。
以上为虚函数的工作机制。
注意只有用virtual修饰的成员方法才会放到虚函数表中去。
子类对父类函数的隐藏将导致无法通过子类对象访问基类的成员方法。
因此给出以下建议:
1、& 若要在子类中重新定义父类的方法(有virtual为重写,无virtual为隐藏),则应确保子类中的函数声明和父类函数声明中的形参完全一样。但返回值类型是基类引用/指针的成员函数在重新定义时可以返回子类的引用/指针(返回值协变),这是由于子类的对象可以赋给基类引用/指针。
2、& 若基类中声明了函数的重载版本,则在派生类中重新定义时应该重新定义所有基类的重载版本。这是因为,重新定义一个函数,其他的基类重载版本将被隐藏,导致子类无法使用这些基类的成员方法。所以需要每个都重新定义。若一些父类的重载版本,子类确实不需要修改,则由于重新定义了一个重载版本,即使有些重载版本不需要修改也要重新定义,在定义体中直接调用基类的成员方法(使用作用于限定符访问)。
3、& 从虚函数的实现机制可以看到要想在子类中实现多态需要满足三个重要的条件。(1)在基类中函数声明为虚函数。(2)在子类中,对基类的虚函数进行了重写。(3)基类的指针指向了子类的对象。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:28719次
排名:千里之外
原创:17篇
(1)(2)(1)(8)(5)(1)(2)(1)

我要回帖

更多关于 虚函数的定义 的文章

 

随机推荐