初步认识虚函数(三)

#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)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类派生类中,也不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。
虚函数联系到多态,多态联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。
原文地址:https://www.cnblogs.com/ye-ming/p/9294259.html