封装,继承和多态,是C++的三大特性。提到多态,就会提到虚函数virtual;提到虚函数,不得不说虚函数表。我们知道,在一个类Class中,如果有定义虚函数,那么这个类对象所占用的存储空间中,会保存一个指向虚函数表的指针,结果是这个类的大小会增加4,即一个指针的大小。

那么这个指针存储在类的什么地方?虚函数表里是如何存放各个虚函数的?在具有继承关系的不同类中,虚函数表中的存储有什么变化?本文Jungle将对此做个测试。

1.有无虚函数,对类大小的影响

前文已经说到,如果一个类定义了虚函数,sizeof的结果会增加4,增加的是一个指向虚函数表的指针。这里简单做个测试验证一下。

定义一个类如下,我们用sizeof看下其大小是多少?

class Base {
public:Base() {cout << "Base::Base()" << endl;a = 1;b = 2;}void func_1() {cout << "Base::func_1()" << endl;}~Base() {cout << "Base::~Base()" << endl;}private:int a;int b;
};

控制台打印的结果如下:

占8个字节(2个int类型,4*2 = 8),内存中存储情况如下:

现在为类增加一些成员:

class Base {
public:Base() {cout << "Base::Base()" << endl;a = 1;b = 2;}void func_1() {cout << "Base::func_1()" << endl;}virtual void func_2() {cout << "Base::func_2()" << endl;}virtual void func_3() {cout << "Base::func_3()" << endl;}~Base() {cout << "Base::~Base()" << endl;}private:int a;int b;
};

如上代码,增加了两个虚函数func_2和func_3,再看下sizeof的结果:

比之前增加了4个字节。其实不论增加多少个虚函数,都只增加4个字节

2.指向虚函数表的指针存储在哪里?

我们知道,增加的4个字节是类中存放了指向虚函数表的指针。那么这个指针存放在类的什么位置呢?——类的起始地址处。我们可以通过如下代码来测试下:

/*
*  Function name: Test1
*  Description  : 测试类的虚函数表,类中包括两个虚函数
*                 1. Print size of object base;
*                 2. Print member of object base;
*                 3. Get the address of virtual function and call it, which proves the existence of virtual function table.
*  Return       : None
*/
void Test1()
{Base base;Base* pBase = &base;cout << "base Size: " << sizeof(base) << endl;// 虚函数表地址放在对象开始处printf("虚函数表地址: 0x%x\n", *(int*)pBase);// 然后才存放其他成员变量printf("%d\n", *(int*)((int*)pBase + 1));printf("%d\n", *(int*)((int*)pBase + 2));typedef void(*pFunc)();pFunc fun;for (int i = 0; i < 2; i++) {fun = (pFunc)*((int*)(*(int*)pBase) + i);fun();}
}

代码运行结果如下:

3.虚函数表里存放的什么?

从上面的代码和运行结果可以得出结论,虚函数表里存放的是各个虚函数的地址,如下图:

4.继承关系下的虚函数表

4.1. 继承但不覆写

如果基类Base定义了虚函数,其子类继承基类的虚函数,并且子类也定义有自己的虚函数,这时候虚函数表会是什么情况呢?

代码如下所示,基类Base有普通成员函数func_1,两个虚函数func_2和func_3。派生类Base2除了继承Base的方法外,自己另外定义了两个成员变量c和d,以及两个虚函数func_4和func_5.

class Base {
public:Base() {cout << "Base::Base()" << endl;a = 1;b = 2;}void func_1() {cout << "Base::func_1()" << endl;}virtual void func_2() {cout << "Base::func_2()" << endl;}virtual void func_3() {cout << "Base::func_3()" << endl;}~Base() {cout << "Base::~Base()" << endl;}private:int a;int b;
};class Base2 : public Base {
public:Base2() {c = 3;d = 4;}virtual void func_4() {cout << "Base2::func_4()" << endl;};virtual void func_5() {cout << "Base2::func_5()" << endl;};
private:int c;int d;
};

我们用如下的代码来测试下这种场景下虚函数表是如何安排的:

/*
*  Function name: Test2
*  Description  : 测试继承类的虚函数表,基类中包括两个虚函数,派生类中包含两个虚函数,且不覆写
*                 1. Print size of object base2;
*                 2. Print member of object base2;
*                 3. Get the address of virtual function and call it, which proves the existence of virtual function table.
*  Return       : None
*/
void Test2()
{Base2 base2;Base2* pBase2 = &base2;cout << "base2 Size : " << sizeof(base2) << endl;// 虚函数表地址放在对象开始处printf("虚函数表地址:0x%x\n", *(int*)(pBase2));// 然后存放其他成员变量printf("%d\n", *(int*)((int*)pBase2 + 1));printf("%d\n", *(int*)((int*)pBase2 + 2));printf("%d\n", *(int*)((int*)pBase2 + 3));printf("%d\n", *(int*)((int*)pBase2 + 4));// 取出虚函数typedef void(*pFunc)();pFunc fun;/**     Base :: virtual func_2*     Base :: virtual func_3*     Base2:: virtual func_4*     Base2:: virtual func_5*/for (int i = 0; i < 4; i++) {fun = (pFunc)*(((int*)*(int*)pBase2) + i);fun();}
}

代码运行结果如下图:

根据测试代码以及运行结果,我们可以得出结论,具有单继承关系的子类,其类对象中保存了一个指向虚函数表的指针,虚函数表依次存放基类的虚函数和自己定义的虚函数,接着保存基类的成员和自己定义的成员。如下图:

4.2. 继承,子类覆写父类虚函数

如果子类覆写了父类的虚函数呢?如下代码,子类Base3继承自Base,但Base3除了定义了自己的虚函数func_6外,重新实现了父类Base的虚函数func_3,这时候虚函数表情况是怎样的呢?

class Base3 :public Base {
public: Base3() {e = 5;f = 6;}// 覆写func_3virtual void func_3() {cout << "Base3::func_3()" << endl;};virtual void func_6() {cout << "Base3::func_6()" << endl;};
private:int e;int f;
};

我们用如下代码打印一下:

/*
*  Function name: Test3
*  Description  : 测试继承类的虚函数表,基类中包括两个虚函数,派生类中包含两个虚函数,覆写基类的其中一个虚函数
*                 1. Print size of object base3;
*                 2. Print member of object base3;
*                 3. Get the address of virtual function and call it, which proves the existence of virtual function table.
*  Return       : None
*/
void Test3()
{Base3 base3;Base3* pBase3 = &base3;cout << "base3 Size : " << sizeof(base3) << endl;// 虚函数表地址放在对象开始处printf("虚函数表地址:0x%x\n", *(int*)pBase3);// 然后存放其他成员变量printf("%d\n", *((int*)pBase3 + 1));printf("%d\n", *((int*)pBase3 + 2));printf("%d\n", *((int*)pBase3 + 3));printf("%d\n", *((int*)pBase3 + 4));typedef void(*pFunc)();pFunc fun;/**     Base :: virtual func_2*     Base3:: virtual func_3*     Base3:: virtual func_6*/for (int i = 0; i < 3; i++) {fun = (pFunc)*(((int*)*(int*)pBase3) + i);fun();}
}

代码运行结果如图:

可以看到,Base3的虚函数表中,依次存放的是Base的虚函数func_2,由于Base中的func_3被自己重新实现了,所以虚函数表中存放的是Base3定义的虚函数func_3,接下来是Base3的虚函数func_6.

4.3. 多重继承下的虚函数表

有两个父类Father1和Father2, Father1有成员int x, 虚函数func_1;Father2有成员int y,虚函数func_2。子类Child同时继承自Father1和Father2,此外还有成员int z和虚函数func_3。各个类的定义代码如下:

class Father1 {
public:Father1() {x = 111;}virtual void func_1() {cout << "Father1::func_1()" << endl;}
private:int x;
};class Father2 {
public:Father2() {y = 222;}virtual void func_2() {cout << "Father2::func_2()" << endl;}
private:int y;
};class Child :public Father1, public Father2 {
public:Child() {z = 333;}virtual void func_3() {cout << "Child::func_3()" << endl;}
private:int z;
};

我们用如下代码进行打印:

/*
*  Function name: Test4
*  Description  : 测试继承类的虚函数表,类Child有两个父类Father1和Father2
*                 1. Print size of object Child;
*                 2. Print member of object Child;
*                 3. Get the address of virtual function and call it, which proves the existence of virtual function table.
*  Return       : None
*/
void Test4()
{Child child;Child* pChild = &child;cout << "child Size : " << sizeof(child) << endl;// 虚函数表地址放在对象开始处printf("虚函数表 1 地址:0x%x\n", *(int*)pChild);printf("虚函数表 2 地址:0x%x\n", *((int*)pChild + 2));// 然后存放其他成员变量printf("%d\n", *((int*)pChild + 1));printf("%d\n", *((int*)pChild + 3));printf("%d\n", *((int*)pChild + 4));typedef void(*pFunc)();pFunc fun;/**     Father1 :: virtual func_1*     Child:: virtual func_3*/for (int i = 0; i < 2; i++) {fun = (pFunc)*(((int*)*(int*)pChild) + i);fun();}/**     Father2 :: virtual func_2*/for (int i = 0; i < 1; i++) {fun = (pFunc)*(((int*)*((int*)pChild + 2)) + i);fun();}
}

运行结果如下:

我们可以看到,Child的size为20,说明除了3个整型占据12个字节外,还多出了8个字节,即Child有两张虚表! 而且在内存中存储顺序如下图:

需要注意的是,上述所有类的析构函数也应该定义为虚析构函数,本文只是为了说明虚函数表,所以并未展示出虚析构函数的代码。

探究C++:虚函数表究竟怎么回事?相关推荐

  1. 【深入探究C++虚函数表——从内存的角度】

    在正式讨论虚函数前,我们需要明确c++的设计思想--零成本抽象 对于下面的这个类 class A {public:int x; }; 这个类的大小为4,也就是一个int的大小. 我们在跑这个类,等同于 ...

  2. 虚函数继承与虚函数表-汇编码分析

    (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) 参考:https://www.equestionanswers.com/cpp/vptr-and-vta ...

  3. 深入剖析C++多态、VPTR指针、虚函数表

    在讲多态之前,我们先来说说关于多态的一个基石------类型兼容性原则. 一.背景知识 1.类型兼容性原则 类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代.通过公有继承,派 ...

  4. C++多态的原理(虚函数指针和虚函数表)

    C++多态的原理 (虚函数指针和虚函数表) 1.虚函数指针和虚函数表 2.继承中的虚函数表 2.1单继承中的虚函数表 2.2多继承中的虚函数表 3.多态的原理 4.总结 1.虚函数指针和虚函数表 以下 ...

  5. C++对象的内存布局1---基础篇----C++ 虚函数表解析

    [-] 前言 虚函数表 一般继承(无虚函数覆盖) 一般继承(有虚函数覆盖) 多重继承(无虚函数覆盖) 多重继承(有虚函数覆盖) 安全性 结束语 附录一:VC中查看虚函数表 附录 二:例程 前言 C++ ...

  6. C++中的虚函数表介绍

            在C++语言中,当我们使用基类的引用或指针调用一个虚成员函数时会执行动态绑定.因为我们直到运行时才能知道到底调用了哪个版本的虚函数,所以所有虚函数都必须有定义.通常情况下,如果我们不使 ...

  7. 虚函数表剖析,网上转的,呵呵

    http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html C++虚函数表解析(转) C++中的虚函数的作用主要是实现了多态的机制.关于多 ...

  8. C++ 虚函数表解析

    转载自 https://blog.csdn.net/zhou191954/article/details/44919479 C++ 虚函数表解析 前言 C++中的虚函数的作用主要是实现了多态的机制.关 ...

  9. C++对象内存布局--①测试虚函数表属于类

    C++对象内存布局--①测试虚函数表属于类 测试1:同一个类的多个对象共享同一张虚函数表.   //虚函数表.cpp //2010年8月18日 //测试虚函数表,说明虚函数表属于类所有.同一个类的多个 ...

最新文章

  1. 反距离加权法高程_干货:企业估值的收益法、成本法和市场法
  2. MAC下PHP7.1.23安装intl3.0.0
  3. [转]项目经理面试指南
  4. jquery 获取Input 值
  5. 不要再new一个对象了!程序员脱离单身秘籍
  6. python语言程序设计慕课_中国大学MOOC(慕课)_Python语言程序设计基础_试题及答案...
  7. Martix工作室考核题 —— 打印一个菱形
  8. 什么是 SAP C/4HANA Foundation
  9. rxjs里b = a.pipe(map(mapFn))的执行示意图
  10. LeetCode 655. Print Binary Tree (C++)
  11. Shiro 权限验证原理
  12. 魔鬼训练Day2作业
  13. android 镜像投屏开发,Android 投屏实现纪要
  14. 台式计算机拆卸步骤,拆卸和组装台式计算机主机的说明步骤
  15. vue + echarts 以山西地图为例
  16. 中国电子学会-全国青少年机器人技术等级考试标准 (1-6级)
  17. 小麦苗博客用到的图片
  18. 如何将 elasticsearch 版本从 openshift-logging 4.2.36 降级到 v4.2.29
  19. 通过MIME标准实现无插件极速生成多Sheet Excel文件
  20. 经济金融经典书籍推荐(中文版)——转自豆瓣

热门文章

  1. 目标检测-Oriented RepPoints for Aerial Object Detection(CVPR 2022)
  2. 实现JPanel切换
  3. 将word文档中的图片批量导出到文件夹中的办法
  4. 从零开始的数模学习(4):熵权法(评价类模型)
  5. python毕业论文开题报告_本科毕业论文开题报告怎么写-如何写毕业设计的开题报告?...
  6. 怎么将微博图片中的水印去掉
  7. 洲际酒店集团加速布局粤港澳大湾区,与华侨城酒店集团达成合作
  8. Python实现AI变脸
  9. folly库安装(4)folly依赖的重要组件安装:double-conversion, google-gflags, glog, fmt, googletest, boost等
  10. 学Java需要数学好吗?数学基础差就不能学Java吗?