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 ...
随机推荐
- python基础六之编码
python中编码的特点: 1,各个编码之间的二进制是不能互相识别的,会产生乱码 2,文件的储存和传输是不能用Unicode的 python3的编码 在python3中字符串在内存中是用Unicode ...
- $_GET $_POST $_REQUEST
<form action="__APP__/View/editArticle?id=5" method="GET"> <form>表单提 ...
- P1067 叠罗汉
题目描述 茜茜和聪聪是关系很好的同桌,有一天,茜茜觉得天空中的白云像棉花糖一样,应该很好吃吧,但是够不着,她很烦恼. 聪聪为了帮助茜茜解决烦恼,决定帮他把天空中的白云摘下来做成棉花糖给她吃. 但是聪聪 ...
- RocketMQ(消息重发、重复消费、事务、消息模式)
分布式开放消息系统(RocketMQ)的原理与实践 RocketMQ基础:https://github.com/apache/rocketmq/tree/rocketmq-all-4.5.1/docs ...
- H3C 端口接入控制方式
- 51nod 1282 时钟
1282 时钟 题目来源: Codility 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 有N个时钟,每个时钟有M个指针,P个刻度.时钟是圆形的,P个刻度均分 ...
- 如何在iOS手机上进行自动化测试
版权声明:允许转载,但转载必须保留原链接:请勿用作商业或者非法用途 Airtest支持iOS自动化测试,在Mac上为iOS手机部署iOS-Tagent之后,就可以使用AirtestIDE连接设备,像连 ...
- 2019-10-12-win10-uwp-非ui线程访问-ui-
title author date CreateTime categories win10 uwp 非ui线程访问 ui lindexi 2019-10-12 15:0:12 +0800 2018-2 ...
- dotnet 使用 lz4net 压缩 Stream 或文件
在 dotnet 可以使用 LZ4 这个无损的压缩算法,这个压缩算法的压缩率不高但是速度很快.这个库支持在 .NET Standard 1.6 .NET Core .NET Framework Mon ...
- P3803 FFT求多项式系数
P3803 FFT求多项式系数 传送门:https://www.luogu.org/problemnew/show/P3803 题意: 这是一道FFT模板题,求多项式系数 题解: 对a和b的系数求一个 ...