#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"); return 0; }
误区
你能估算出结果吗?如果你估算出的结果是A::fun和A::fun2,呵呵,恭喜恭喜,你中圈套了。其实真正的结果是B::fun和B::fun2,如果你想不通就接着往下看。给个提示,&A::fun和&A::fun2是真正获得了虚函数的地址吗?
首先我们回到第二部分,通过段实作代码,得到一个“通用”的获得虚函数地址的方法
#include<iostream> using namespace std; //将上面“虚函数示例代码2”添加在这里 void CallVirtualFun(void*pThis,intindex=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"); return 0; }
CallVirtualFun
现在我们拥有一个“通用”的CallVirtualFun方法。
这个通用方法和第三部分开始处的代码有何联系呢?联系很大。由于A::fun()和A::fun2()是虚函数,所以&A::fun和&A::fun2获得的不是函数的地址,而是一段间接获得虚函数地址的一段代码的地址,我们形象地把这段代码看作那段CallVirtualFun。
编译器在编译时,会提供类似于CallVirtualFun这样的代码,当你调用虚函数时,其实就是先调用的那段类似CallVirtualFun的代码,通过这段代码,获得虚函数地址后,最后调用虚函数,这样就真正保证了
多态性。同时大家都说虚函数的效率低,其原因就是,在调用虚函数之前,还调用了获得虚函数地址的代码。
其他信息
定义虚函数的限制:(1)非
类的成员函数不能定义为虚函数,类的成员函数中
静态成员函数和
构造函数也不能定义为虚函数,但可以将
析构函数定义为虚函数。实际上,优秀的程序员常常把
基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向
派生类定义的
对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。
(2)只需要在声明函数的类体中使用
关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。
(3)当将基类中的某一
成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。
(4)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为
基类的
派生类中,也不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。
虚函数联系到
多态,多态联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。