C++ 链式继承下的虚函数列表
目录
1.虚函数列表的位置
2.虚函数列表的内容
3.链式继承中虚函数列表的内容
注:
虚函数列表 又称为虚表, vtbl , 指向它的指针称为vptr, vs2019中称为__vfptr
操作系统: windows 10 专业版 64位
编译器: Visual Studio 2019 Community
1.虚函数列表的位置
结论
编译器一般会保证指向虚函数列表的指针存在于对象实例中最前面的位置
而虚函数列表中的内容, 就是多个函数指针
代码验证:
首先声明一个基类Base和一个派生类Derived
class Base
{
public:
virtual void f() { std::cout << "Base1::f" << std::endl; }
virtual void g() { std::cout << "Base1::g" << std::endl; }
virtual void h() { std::cout << "Base1::h" << std::endl; }
virtual void i() { std::cout << "Base1::i" << std::endl; }
};
class Derived : public Base
{
virtual void g() override { std::cout << "Derived::g" << std::endl; }
virtual void h1() { std::cout << "Derived::i1" << std::endl; }
};
然后实例化一个派生类的对象
Derived derived;
现在我们打印出该对象的地址
std::cout << "derived对象的地址: " << (&derived) << std::endl;
由于我们假定指向虚函数列表的<指针>存在于对象实例中最前面的位置
那么我们可以认定, derived对象的地址中的开头是一个指针的地址(称之为指针pA)
而这个指针(pA)指向虚函数列表中的开头, 也就是一个函数指针(称之为指针pF)
所以这个指针(pA), 是一个指向指针的指针, 即指向指针(pF)的指针(pA)
基于这个推测, 我们将derived对象的地址即指针pA的地址进行一个类型转换
使用reinterpret_cast<int**>关键字, 将其转换为一个指向指针的指针
reinterpret_cast<int**>(&derived)
现在我们对这个指针(pA)的地址, 取其内容, 就会得到pA的内容
std::cout << "derived对象中第一个指针的内容: " << *reinterpret_cast<int**>(&derived) << std::endl;
根据上面的推测, 这个内容, 就是虚函数列表的地址
控制台输出如下:

通过vs2019中, 可以直接查看到derived的__vfptr对象的地址, 和控制台打印的内容是相同的

2.单继承中虚函数列表的内容
基类中有4个函数, 分别为
f();
g();
h();
i();
派生类中有2个函数,分别为
g();
i1();
现在使用表格的方式表示出来, 方便查看, 进行了override的函数, 会放在同一行

结论 在虚函数列表中, 函数的布局如下图所示:

代码验证请看链式继承中虚函数列表的内容
3.链式继承中虚函数列表的内容
声明3个类, 其继承关系为Derived继承Base2, Base2继承Base1
class Base1 {
public:
virtual void f() { std::cout << "Base1::f" << std::endl; }
virtual void g() { std::cout << "Base1::g" << std::endl; }
virtual void h() { std::cout << "Base1::h" << std::endl; }
virtual void i() { std::cout << "Base1::i" << std::endl; }
};
class Base2 : public Base1{
public:
virtual void f()override { std::cout << "Base2::f" << std::endl; }
virtual void h1() { std::cout << "Base2::h1" << std::endl; }
};
class Derived : public Base2 {
public:
virtual void g()override { std::cout << "Derived::g" << std::endl; }
virtual void i1() { std::cout << "Derived::i1" << std::endl; }
};
用表格的方法表示为:
结论
在虚函数列表中, 函数的布局如下图所示:

Derive只有一个虚函数表, 是在Base2的虚函数表上, 进行类似于单继承的覆盖
同理, Base2也有一张虚函数表, 是在Base1的虚函数表上, 进行单继承的覆盖
代码验证

////////////////////////////////////////////////////////////////////////////////
// 链式继承
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
class Base1 {
public:
virtual void f() { std::cout << "Base1::f" << std::endl; }
virtual void g() { std::cout << "Base1::g" << std::endl; }
virtual void h() { std::cout << "Base1::h" << std::endl; }
virtual void i() { std::cout << "Base1::i" << std::endl; }
};
class Base2 : public Base1{
public:
virtual void f()override { std::cout << "Base2::f" << std::endl; }
virtual void h1() { std::cout << "Base2::h1" << std::endl; }
};
class Derived : public Base2 {
public:
virtual void g()override { std::cout << "Derived::g" << std::endl; }
virtual void i1() { std::cout << "Derived::i1" << std::endl; }
};
using Fun = void(*)(void);
int main()
{
Fun pFun = nullptr;
// 操作系统: windows 10 专业版 32/64位都可以
// 编译器 : Visual Studio 2019 Community
std::cout << sizeof(int) << std::endl; // 32位:4 64位:4
std::cout << sizeof(long) << std::endl; // 32位:4 64位:4
std::cout << sizeof(int*) << std::endl; // 32位:4 64位:8
std::cout << "-------------------------------------------------------------------------------------- " << std::endl;
std::cout << "Base1的虚表如下 " << std::endl;
Base1 base1;
std::cout << "base1对象的地址: " << (&base1) << std::endl;
std::cout << "base1对象中第一个指针的地址(不是内容): " << (&base1) << std::endl;
// &base1 是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个虚表(虚表的内容也是一堆指针)
std::cout << "base1对象中第一个指针的内容: " << *reinterpret_cast<int**>(&base1) << std::endl;
std::cout << "base1虚函数表地址: " << *reinterpret_cast<int**>(&base1) << std::endl;
// 虚函数表地址, 也是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个函数指针
std::cout << "base1虚函数表 — 第一个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1))))) << std::endl;
pFun = reinterpret_cast<Fun>(* (reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)))));
std::cout << "base1虚函数表 — 第一个函数内容:";
pFun(); // base1::f
std::cout << std::endl;
// 注意次数的偏移量, 32位偏移量是+1, 64位偏移量是+2
std::cout << "base1虚函数表 — 第二个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1))+1*(sizeof(int*)/sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 1 * (sizeof(int*) / sizeof(int)))));
std::cout << "base1虚函数表 — 第二个函数内容:";
pFun(); // base1::g
std::cout << std::endl;
std::cout << "base1虚函数表 — 第三个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 2 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 2 * (sizeof(int*) / sizeof(int)))));
std::cout << "base1虚函数表 — 第三个函数内容:";
pFun(); // base1::h
std::cout << std::endl;
std::cout << "base1虚函数表 — 第四个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 3 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 3 * (sizeof(int*) / sizeof(int)))));
std::cout << "base1虚函数表 — 第四个函数内容:";
pFun(); // base1::i
std::cout << std::endl;
std::cout << "-------------------------------------------------------------------------------------- " << std::endl;
std::cout << "Base2的虚表如下 " << std::endl;
Base2 base2;
std::cout << "base2对象的地址: " << (&base2) << std::endl;
std::cout << "base2对象中第一个指针的地址(不是内容): " << (&base2) << std::endl;
// &base1 是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个虚表(虚表的内容也是一堆指针)
std::cout << "base2对象中第一个指针的内容: " << *reinterpret_cast<int**>(&base2) << std::endl;
std::cout << "base2虚函数表地址: " << *reinterpret_cast<int**>(&base2) << std::endl;
// 虚函数表地址, 也是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个函数指针
std::cout << "base2虚函数表 — 第一个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)))));
std::cout << "base2虚函数表 — 第一个函数内容:";
pFun(); // base2::f
std::cout << std::endl;
std::cout << "base2虚函数表 — 第二个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 1 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 1 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虚函数表 — 第二个函数内容:";
pFun(); // base1::g
std::cout << std::endl;
std::cout << "base2虚函数表 — 第三个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 2 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 2 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虚函数表 — 第三个函数内容:";
pFun(); // base1::h
std::cout << std::endl;
std::cout << "base2虚函数表 — 第四个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 3 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 3 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虚函数表 — 第四个函数内容:";
pFun(); // base1::i
std::cout << std::endl;
std::cout << "base2虚函数表 — 第五个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 4 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 4 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虚函数表 — 第五个函数内容:";
pFun(); // base2::h1
std::cout << std::endl;
std::cout << "-------------------------------------------------------------------------------------- " << std::endl;
std::cout << "Derived的虚表如下 " << std::endl;
Derived Derived;
std::cout << "Derived对象的地址: " << (&Derived) << std::endl;
std::cout << "Derived对象中第一个指针的地址(不是内容): " << (&Derived) << std::endl;
// &base1 是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个虚表(虚表的内容也是一堆指针)
std::cout << "Derived对象中第一个指针的内容: " << *reinterpret_cast<int**>(&Derived) << std::endl;
std::cout << "Derived虚函数表地址: " << *reinterpret_cast<int**>(&Derived) << std::endl;
// 虚函数表地址, 也是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个函数指针
std::cout << "Derived虚函数表 — 第一个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)))));
std::cout << "Derived虚函数表 — 第一个函数内容:";
pFun(); // base2::f
std::cout << std::endl;
std::cout << "Derived虚函数表 — 第二个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 1 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 1 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第二个函数内容:";
pFun(); // Derived::g
std::cout << std::endl;
std::cout << "Derived虚函数表 — 第三个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 2 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 2 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第三个函数内容:";
pFun(); // base1::h
std::cout << std::endl;
std::cout << "Derived虚函数表 — 第四个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 3 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 3 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第四个函数内容:";
pFun(); // base1::i
std::cout << std::endl;
std::cout << "Derived虚函数表 — 第五个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 4 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 4 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第五个函数内容:";
pFun(); // base2::h1
std::cout << std::endl;
std::cout << "Derived虚函数表 — 第六个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 5 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 5 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第六个函数内容:";
pFun(); // Derived::i1
std::cout << std::endl;
return 0;
}
C++ 链式继承下的虚函数列表的更多相关文章
- C++链式继承
继承,对于学习C++的每一个人来说,都不会陌生.在Qt的开发中,如果你需要对一个无边框的界面支持move操作,那么你就得通过继承重写虚函数来实现,这并不难,但如果我还需要对一个按钮支持 ...
- 虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte
#include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout < ...
- 谈谈c++中继承中的虚函数
c++继承中的虚函数 c++是一种面向对象的编程语言的一个很明显的体现就是对继承机制的支持,c++中继承分很多种,按不同的分类有不同分类方法,比如可以按照基类的个数分为多继承和单继承,可以按照访问 ...
- ThinkPHP通过类的链式继承优化空操作的实现
上篇<ThinkPHP空操作和空控制器的处理>中,在处理空操作时修改了父类Controller.class.php中代码,不到万不得已不能 修改基类控制器中的原码,此时可在子类与父类之间, ...
- C++学习 之 类的继承中的虚函数(笔记)
1.多态行为 多态是面向对象语言的一种特征,让我们能够以类似的方式处理不同类型的对象.在C++中我们可以通过继承层次结构实现子类型多态. 我们可以通过下面的代码进一步了解多态: #include< ...
- C++ 子类继承父类纯虚函数、虚函数和普通函数的区别
C++三大特性:封装.继承.多态,今天给大家好好说说继承的奥妙 1.虚函数: C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现.子类可以重写父类的虚函数实现子类 ...
- C++继承-重载-多态-虚函数
C++ 继承 基类 & 派生类 一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数.定义一个派生类,我们使用一个类派生列表来指定基类.类派生列表以一个或多个基类命名,形式如下: ...
- DLL中类的显式链接(用虚函数进行显式链接)
DLL的显式链接在某些时候比隐式链接具有更大的灵活性.比如,如果在运行时发现DLL无法找到,程序可以显示一个错误信息并能继续运行.当你想为你的程序提供插件服务时,显式链接也很有用处. 显式链接到全局C ...
- 【整理】C++虚函数及其继承、虚继承类大小
参考文章: http://blog.chinaunix.net/uid-25132162-id-1564955.html http://blog.csdn.net/haoel/article/deta ...
随机推荐
- java 事件监听机制组成
事件源(组件) 事件(Event) 监听器(Listener) 事件处理(引发事件后处理方式) 事件监听机制流程图 务必记牢: 确定事件源(容器或组件) 通过事件源对象的addXXXListener( ...
- Vue.js 学习笔记 第7章 组件详解
本篇目录: 7.1 组件与复用 7.2 使用props传递数据 7.3 组件通讯 7.4 使用slot分发内容 7.5 组件高级用法 7.6 其他 7.7 实战:两个常用组件的开发 组件(Compon ...
- idea运行项目时报错:Error:java无效的源发行版:1.8
解决办法:project structure中设置 JDK 和language 匹配即可.如图: 另外如果有maven,需要把maven中JDK版本设置成一样的.
- WPF 使用 Composition API 做高性能渲染
在 WPF 中很多小伙伴都会遇到渲染性能的问题,虽然 WPF 的渲染可以甩浏览器渲染几条街,但是还是支持不了游戏级的渲染.在 WPF 使用的 DX 只是优化等级为 9 和 DX 9 差不多的性能,微软 ...
- 51nod 天气晴朗的魔法 - (Kruskall最小生成树)
题目: 基准时间限制:1 秒 空间限制:131072 KB 51nod魔法学校近日开展了主题为“天气晴朗”的魔法交流活动. N名魔法师按阵法站好,之后选取N - 1条魔法链将所有魔法师的魔力连接 ...
- JS(JavaScript)的进一步了解8(更新中···)
元素节点的树状图 document>documentElement>body>tagName offsetLeft/offsetTop 结合运动 滚动轮播 1.DOM 全称:docu ...
- 如何用python“优雅的”调用有道翻译?
前言 其实在以前就盯上有道翻译了的,但是由于时间问题一直没有研究(我的骚操作还在后面,记得关注),本文主要讲解如何用python调用有道翻译,讲解这个爬虫与有道翻译的js“斗争”的过程! 当然,本文仅 ...
- 在windows+nginx的curl操作请求超时的问题
之前的项目在apache下进行本地curl操作的时候请求不会超时 后来要在nginx下开发的时候,我在项目中写一个curl操作的test.php文件,请求相同项目下的一个index.php文件,然后c ...
- Vijos1917 艾酱最喜欢的数字 [求绝对众数]
1.题意:第一行一个数字N,表示一共有多少个数字,第二行N个数字,保证其中至少有一个数字出现次数超过一半,任务是求出这个出现最多的数. 2.分析:本题是明显的求众数的问题,常规思路为开一个大数组,在读 ...
- Python3 安装pylint 及与PyCharm关联
使用如下命令: pip3 install pylint 安装完后可以看到在你的python3的目录底下的Scripts目录下有pylint.exe了 然后就可以使用pylint 评估你的代码了,如: ...