C++系列之虚函数

    虚函数是C++一大核心技术,主要是为了实现多态而设计的。什么是多态,字面意思就是多种形态,是一种泛型,就是以不变的代码表示可变的行为,分为编译时多态和运行时多态。比如模板技术、函数重载可以认为是编译时多态,虚函数则是运行时多态。

    虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

/*******************************************************************************
  函 数 名		:  
  功能描述		:  C++系列之虚函数技术
  输入参数		:  None
  输出参数		:  None
  返 回 值		:  None
*******************************************************************************/
#include <iostream>
#include <stdlib.h>
#include <string>

using namespace std;

class CAnimal
{
public:
    virtual void Eat()
    {
        cout << "CAnimal类,虚函数Eat()" << endl;
    }

    virtual void Sleep()
    {
        cout << "CAnimal类,虚函数Sleep()" << endl;
    }
};

class CDog : public CAnimal
{
public:
    void Sleep()
    {
        cout << "CDog类继承CAnimal类,重载虚函数Sleep()" << endl;
    }

    virtual void Sound()
    {
        cout << "CDog类继承CAnimal类,增加虚函数Sound()" << endl;
    }
};

typedef void (*FUNC)(void);

int main(int argc, char *argv[])

{
    FUNC pFunc = NULL;

    cout << "=======CAnimal Begin=======" << endl;
    CAnimal aAnimal;
    //aAnimal.Eat();
    //aAnimal.Sleep();
    cout << "aAnimal地址 = " << &aAnimal << " , sizeof(CAnimal) = " << sizeof(CAnimal) << endl;

    cout << "aAnimal虚函数表地址 = " << (int*)&aAnimal << endl;
    cout << "aAnimal第一个虚函数 = " << (int*)*(int*)&aAnimal << " , 指向 = " << (int*)*((int*)*(int*)&aAnimal) << endl;
    cout << "aAnimal第二个虚函数 = " << (int*)*(int*)&aAnimal + 1 << " , 指向 = " << (int*)*((int*)*(int*)&aAnimal + 1) << endl;
    cout << "aAnimal虚函数表结尾 = " << (int*)*(int*)&aAnimal + 2 << " , 指向 = " << (int*)*((int*)*(int*)&aAnimal + 2) << endl;

    pFunc = (FUNC) * (int *)*(int *)&aAnimal;
    pFunc();

    pFunc = (FUNC) * ((int *)*(int *)&aAnimal + 1);
    pFunc();

    cout << "=======CAnimal End=======" << endl;
    cout << endl;
    cout << "=======CDog Begin=======" << endl;

    CDog aDog;
    //aDog.Eat();
    //aDog.Sleep();
    //aDog.Sound();
    cout << "aDog地址 = " << &aDog << " , sizeof(aDog) = " << sizeof(aDog) << endl;

    cout << "aDog虚函数表地址 = " << (int*)&aDog << endl;
    cout << "aDog第一个虚函数 = " << (int*)*(int*)&aDog << " , 指向 = " << (int*)*((int*)*(int*)&aDog) << endl;
    cout << "aDog第二个虚函数 = " << (int*)*(int*)&aDog + 1 << " , 指向 = " << (int*)*((int*)*(int*)&aDog + 1) << endl;
    cout << "aDog第三个虚函数 = " << (int*)*(int*)&aDog + 2 << " , 指向 = " << (int*)*((int*)*(int*)&aDog + 2) << endl;
    cout << "aDog虚函数表结尾 = " << (int*)*(int*)&aDog + 3 << " , 指向 = " << (int*)*((int*)*(int*)&aDog + 3) << endl;

    pFunc = (FUNC) * (int *)*(int *)&aDog;
    pFunc();

    pFunc = (FUNC) * ((int *)*(int *)&aDog + 1);
    pFunc();

    pFunc = (FUNC) * ((int *)*(int *)&aDog + 2);
    pFunc();

    cout << "=======CDog End=======" << endl;

    return 0;
}

执行结果:

此处 aAnimal虚函数表结尾 = 0x40b7d0 , 指向 = 0x3a434347 ,最后的指向为啥不是0 ???

对于main函数中的那些指针操作,我们以CAnimal的第二个虚函数Sleep为例解释一下:

(int *)*(int *)&aAnimal + 1

    先从aAnimal开始,&aAnimal获取aAnimal对象的地址,第一个int *表示强制转换为int型指针,再往外一个*表示从aAnimal对象首地址取4个字节存储值(32位机器,int占用4个字节),这里就是CAnimal类虚函数表的首地址了,再外层的int *表示将这4个字节解释为一个int型指针,然后后面的+1表示往后面移动一个int型指针长度,即虚函数表第二个表项也就是第二个虚函数的地址。

    CDog没有重载Eat虚函数,所以其虚函数和CAnimal指向同一个指令地址,CDog的Sleep虚函数和CAnimal的Sleep虚函数指向不同地址。

1. C++多态是通过是虚函数表实现的;

2. 每个类共享同一个虚函数表;

3. 虚函数表是按照类中声明虚函数顺序依次填充的,结尾为NULL;

4. 子类重载父类虚函数会覆盖父类虚函数在子类虚函数表中相应表项。

 

/*这里的一点争议的看法*/

    原文认为(int*)(&b)是虚表的地址,而很多网友都说,:(int *)*(int*)(&b)才是虚表地址.而(int*)*((int*)*(int*)(&b)); 才是虚表第一个虚函数的地址。其实看后面的调用pFun = (Fun)*((int*)*(int*)(&b)); 就可以看出,*((int*)*(int*)(&b));转成函数指针给pFun,然后正确的调用到了虚函数virtual void f()。

一些关于虚函数面试,以及论坛精彩彩蛋请看博主 https://blog.csdn.net/sunshinewave/article/details/51079204

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值