条件:
1、 必须把动态联编的行为定义为类的虚函数。
2、 类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来。
3、 必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数。
一,定义
简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。下面来看一段简单的代码
class A{ public: void print(){ cout<<"This is A"<<endl;} }; class B:public A{ public: void print(){ cout<<"This is B"<<endl;} }; int main(){ //为了在以后便于区分,我这段main()代码叫做main1 A a; B b; a.print(); b.print(); }通过class A和class B的print()这个接口,可以看出这两个class因个体的差异而采用了不同的策略,输出的结果也是我们预料中的,分别是This is A和This is B。但这是否真正做到了多态性呢?No,多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把main()处的代码改一改。
int main(){ //main2 A a; B b; A* p1=&a; A* p2=&b; p1->print(); p2->print(); }运行一下看看结果,哟呵,蓦然回首,结果却是两个This is A。问题来了,p2明明指向的是class B的对象但却是调用的class A的print()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数
class A{ public: virtual void print(){ cout<<"This is A"<<endl;} //现在成了虚函数了 }; class B:public A{ public: void print(){ cout<<"This is B"<<endl;} //这里需要在前面加上关键字virtual吗? };毫无疑问,class A的成员函数print()已经成了虚函数,那么class B的print()成了虚函数了吗?回答是Yes,我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以,class B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了。
二, 实现
(如果你没有看过《Inside The C++ Object Model》这本书,但又急切想知道,那你就应该从这里开始)
虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在我们就来剖析虚函数。我们先定义两个类
class A{ //虚函数示例代码 public: virtual void fun(){cout<<1<<endl;} virtual void fun2(){cout<<2<<endl;} }; class B:public A{ public: void fun(){cout<<3<<endl;} void fun2(){cout<<4<<endl;} };由于这两个类中有虚函数存在,所以编译器就会为他们两个分别插入一段你不知道的数据,并为他们分别创建一个表。那段数据叫做vptr指针,指向那个表。那个表叫做vtbl,每个类都有自己的vtbl,vtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址,请看图()()()()()()()()
#include<iostream> using namespace std; //将上面“虚函数示例代码”添加在这里 int main(){ void (*fun)(A*); A *p=new B; long lVptrAddr; memcpy(&lVptrAddr,p,4); memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4); fun(p); delete p; system("pause"); }用VC或Dev-C++编译运行一下,看看结果是不是输出3,如果不是,那么太阳明天肯定是从西边出来。现在一步一步开始分析
三, 代码示例
#include<iostream> using namespace std; class A{ //虚函数示例代码2 public: virtual void fun(){ cout<<"A::fun"<<endl;} virtual void fun2(){cout<<"A::fun2"<<endl;} }; class B:public A{ public: void fun(){ cout<<"B::fun"<<endl;} void fun2(){ cout<<"B::fun2"<<endl;} }; //end//虚函数示例代码2 int main(){ void (A::*fun)(); //定义一个函数指针 A *p=new B; fun=&A::fun; (p->*fun)(); fun = &A::fun2; (p->*fun)(); delete p; system("pause"); }你能估算出输出结果吗?如果你估算出的结果是A::fun和A::fun2,呵呵,恭喜恭喜,你中圈套了。其实真正的结果是B::fun和B::fun2,如果你想不通就接着往下看。给个提示,&A::fun和&A::fun2是真正获得了虚函数的地址吗?
#include<iostream> using namespace std; //将上面“虚函数示例代码2”添加在这里 void CallVirtualFun(void* pThis,int index=0){ void (*funptr)(void*); long lVptrAddr; memcpy(&lVptrAddr,pThis,4); memcpy(&funptr,reinterpret_cast<long*>(lVptrAddr)+index,4); funptr(pThis); //调用 } int main(){ A* p=new B; CallVirtualFun(p); //调用虚函数p->fun() CallVirtualFun(p,1);//调用虚函数p->fun2() system("pause"); }CallVirtualFun
from:http://baike.baidu.com/view/161302.htm