目录

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; }
};

用表格的方法表示为:

!()(https://silenzio-markdown-image-hosting-service.oss-cn-beijing.aliyuncs.com/博客图床/C%2B%2B 链��%继承下的虚函数列表/1a885406e3318ccbbf5dce9961e1599.png)

结论

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

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++ 链式继承下的虚函数列表的更多相关文章

  1. C++链式继承

            继承,对于学习C++的每一个人来说,都不会陌生.在Qt的开发中,如果你需要对一个无边框的界面支持move操作,那么你就得通过继承重写虚函数来实现,这并不难,但如果我还需要对一个按钮支持 ...

  2. 虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

    #include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout < ...

  3. 谈谈c++中继承中的虚函数

      c++继承中的虚函数 c++是一种面向对象的编程语言的一个很明显的体现就是对继承机制的支持,c++中继承分很多种,按不同的分类有不同分类方法,比如可以按照基类的个数分为多继承和单继承,可以按照访问 ...

  4. ThinkPHP通过类的链式继承优化空操作的实现

    上篇<ThinkPHP空操作和空控制器的处理>中,在处理空操作时修改了父类Controller.class.php中代码,不到万不得已不能 修改基类控制器中的原码,此时可在子类与父类之间, ...

  5. C++学习 之 类的继承中的虚函数(笔记)

    1.多态行为 多态是面向对象语言的一种特征,让我们能够以类似的方式处理不同类型的对象.在C++中我们可以通过继承层次结构实现子类型多态. 我们可以通过下面的代码进一步了解多态: #include< ...

  6. C++ 子类继承父类纯虚函数、虚函数和普通函数的区别

    C++三大特性:封装.继承.多态,今天给大家好好说说继承的奥妙 1.虚函数: C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现.子类可以重写父类的虚函数实现子类 ...

  7. C++继承-重载-多态-虚函数

    C++ 继承 基类 & 派生类 一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数.定义一个派生类,我们使用一个类派生列表来指定基类.类派生列表以一个或多个基类命名,形式如下: ...

  8. DLL中类的显式链接(用虚函数进行显式链接)

    DLL的显式链接在某些时候比隐式链接具有更大的灵活性.比如,如果在运行时发现DLL无法找到,程序可以显示一个错误信息并能继续运行.当你想为你的程序提供插件服务时,显式链接也很有用处. 显式链接到全局C ...

  9. 【整理】C++虚函数及其继承、虚继承类大小

    参考文章: http://blog.chinaunix.net/uid-25132162-id-1564955.html http://blog.csdn.net/haoel/article/deta ...

随机推荐

  1. 【9101】求n!的值

    Time Limit: 10 second Memory Limit: 2 MB 问题描述 用高精度的方法,求n!的精确值(n的值以一般整数输入). Input 文件输入仅一行,输入n. Output ...

  2. 微信小程序之在线试题(1)

    最近在做一套公司的市场化培训项目,涉及到手机端在线答题的设计,首先摒弃app的模式,那就只剩下微信公众号和小程序,而公众号是可以关联小程序,所以我们只需要做好一套小程序. 因为篇幅问题,下面只讲解在线 ...

  3. 2019-5-21-NuGet-符号服务器

    title author date CreateTime categories NuGet 符号服务器 lindexi 2019-05-21 11:34:40 +0800 2019-05-08 21: ...

  4. HDU 6662 Acesrc and Travel (换根dp)

    Problem Description Acesrc is a famous tourist at Nanjing University second to none. During this sum ...

  5. RocketMQ各组件介绍

    Rocket 架构主要分为4部分: Producer 消息发布者,支持分布式集群部署.Produer 通过 MQ 负载均衡模块选择相应 Broker 中的 queue 进行消息投递,投递过程支持快速失 ...

  6. koa2+koa-art-template利用日期管道实现在jat模板中将时间戳转为日期时间

    var sp = require("silly-datetime"); var render = require("koa-art-template"); va ...

  7. 2019-5-31-SharpDx-进入全屏模式

    title author date CreateTime categories SharpDx 进入全屏模式 lindexi 2019-5-31 9:5:36 +0800 2019-5-30 20:1 ...

  8. 牛客练习赛11 假的字符串 (Trie树+拓扑找环)

    牛客练习赛11 假的字符串 (Trie树+拓扑找环) 链接:https://ac.nowcoder.com/acm/problem/15049 来源:牛客网 给定n个字符串,互不相等,你可以任意指定字 ...

  9. 牛客多校第一场 B Inergratiion

    牛客多校第一场 B Inergratiion 传送门:https://ac.nowcoder.com/acm/contest/881/B 题意: 给你一个 [求值为多少 题解: 根据线代的知识 我们可 ...

  10. Python1_Python的目录结构、执行顺序、__name__ == __main__

    Python执行顺序 python属于脚本语言,不像编译型的语言那样先将程序编译成二进制后再运行,而是动态地逐行解释运行: 也就是从脚本的第一行开始运行,没有统一的入口. python会从文件的第一行 ...