前言

(1)虚基表与虚函数表是两个完全不同的概念

  • 虚基表用来解决继承的二义性(虚基类可以解决)。
  • 虚函数用来实现泛型编程,运行时多态。

(2)虚函数是在基类普通函数前加virtual关键字,是实现多态的基础
(3)虚函数表其实不用我们管这个编译器会帮我们做好
注:无特别说明本文的虚表均指虚函数表

(一) 什么是虚函数表?

虚函数(Virtual Function)是通过一张虚函数表(VirtualTable)来实现的。简称为V-Table。虚表(virtual table),编译器为每个拥有虚函数的类都建有一张虚函数表,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)

(二)含虚函数的单继承

单继承时,派生类中仅有一个虚函数表。这个虚函数表和基类的虚函数表不是一个表(无论派生类有没有重写基类的虚函数),但是如果派生类没有重写基类的虚函数的话,基类和派生类的虚函数表指向的函数地址都是相同的。

#include <iostream>
using namespace std;class A
{
public :A(int a){this->a = a;}virtual void show(){cout << "a=" << a << endl;}
protected:int a;
};class B:public A
{
public:B(int a, int b) :A(a){this->b = b;}
/*  void show(){cout << "a= " << a << " b = " << b << endl;}*/
protected:int b;
};int main()
{A a(2);a.show();B b(1, 3);b.show();return 0;
}

此时类B,没有重写类A的show方法,仅仅是继承了父类

可以看出,两个类的__vfptr的值不同,但是每个槽内部的函数地址都是相同的。
下面在类B中重写类A的show方法:

#include <iostream>
using namespace std;class A
{
public :A(int a)  {this->a = a;}virtual void show()   {cout << "a=" << a << endl;}
protected:int a;
};class B:public A
{
public:B(int a, int b) :A(a)  {this->b = b;}void show()  {cout << "a= " << a << " b = " << b << endl;}
protected:int b;
};int main()
{A a(2);a.show();B b(1, 3);b.show();return 0;
}


通过上面可以总结:派生类内存布局,先是复制一份基类内存布局,然后是自己的布局(注意内存对齐)。虚表指针指向自己的虚表,派生类虚函数地址如果自己未覆盖,那么就是基类的,否则是自己的函数地址,并且可以看到派生类一旦重写父类的虚函数就会覆盖原来继承的,
关于这一点的讲解,我认为这个大佬讲的不错:虚函数表解析

(三)含虚函数的多继承

多继承情况下,派生类中有多个虚函数表,虚函数的排列方式和继承的顺序一致。派生类重写函数将会覆盖所有虚函数表的同名内容,派生类自定义新的虚函数将会在第一个类的虚函数表的后面进行扩充。

#include<iostream>
using namespace std;
class Base
{
public:Base(int base){  this->base = base;}virtual void show()  {cout << base << endl;}virtual void print() { cout << "test  Base " << endl; }
protected:int base;
};
class BaseA
{
public:BaseA(int basea){this->basea = basea;}virtual void show(){cout << basea << endl;}virtual void watch() { cout << "test  BaseA" << endl; }
protected:int basea;
};
class BaseB :public Base, public BaseA
{
public:BaseB(int base, int basea, int baseb) :Base(base), BaseA(basea){this->baseb = baseb;}void show(){cout << base << basea << baseb << endl;}virtual void print() { cout << "test  B " << endl; }//重写base的print方法,没有重写BASEA的watch方法
private:int baseb;
};
int main()
{Base base(1);BaseA baseA(2);BaseB baseB(3,3, 3);return 0;
}


这里通过编译器的部分可以看出来,未被重写的虚函数指针将和基类指向同一个位置,一旦被重写,函数指针就指向新的位置。先按照继承顺序,从左到右排布基类的布局包括虚标指针,然后排布自己的指针和数据;派生类虚表排布形式是按照继承顺序,是继承来的虚函数,如果有覆盖则换成自己的函数地址;然后是下一个基类,直至基类排布完毕。继承来的多张表是独立的(从内存布局中的多个虚表指针可以看出),且使用首地址+偏移量的形式来访问。
注意:如果派生类有自己的虚函数则会加在第一个基类的虚表末尾
以前关于虚函数表不太明白,今天网上搜集了些资料,在这里总结一下,如果有错误欢迎讨论啊~

附录

这里介绍一种查看内存布局的方法:
1.点击图中红圈打开“开发人员命令提示符

2.通过dos命令进入代码所在目录
3.使用cl命令的"/d1 reportAllClassLayout或reportSingleClassLayoutXXX"选项。这里的reportAllClassLayout选项会打印大量相关类的信息,一般用处不大。而reportSingleClassLayoutXXX选项的XXX代表要编译的代码中类的名字(这里XXX类),打印XXX类的内存布局和虚函数表(如果代码中没有对应的类,则选项无效)。
例如我的:

在vs中查看变量的另一种方法是:
在调试模式下,点击窗口==>自动窗口就可以了

注意一定是调试模式,否则没有此按钮,同时注意设置断点。

参考文章:
虚函数表解析
C++虚函数和虚函数表原理

c++虚函数和虚函数表相关推荐

  1. C++虚函数及虚函数表解析

    一.背景知识(一些基本概念) 虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数. 纯虚函数(Pure Virtual Functi ...

  2. 【虚函数指针 虚函数表】

    文章目录 虚函数指针和虚函数表 1.虚函数的含义 2.虚函数的作用 3.虚函数的实现原理 多态的实现原理 `普通类` `当类中存在虚函数` `子类继承父类不重写虚函数` 子类继承父类重写虚函数 1.虚 ...

  3. 【C++】虚函数与虚函数表

    1. 虚函数表的结构 #include <iostream> using namespace std;typedef void (*Fun)(void);class Base {publi ...

  4. 虚函数与纯虚函数以及虚函数表之间的关系

    1.虚函数 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.C++中虚函数的作用主要是实现多态机制.所谓多态就是用父类指针指向子类对象,然后通过父类指针调用实际子类的成员函数,这种技术 ...

  5. c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解

    文章目录 静态多态.动态多态 虚函数 哪些函数类型不可以被定义成虚函数? 虚函数的访问方式 析构函数中的虚函数 虚函数表指针 vptr 多继承下的虚函数表 虚基类表指针 bptr 纯虚函数 抽象类 虚 ...

  6. C++虚函数和虚函数表原理

    虚函数的地址存放于虚函数表之中.运行期多态就是通过虚函数和虚函数表实现的. 类的对象内部会有指向类内部的虚表地址的指针.通过这个指针调用虚函数. 虚函数的调用会被编译器转换为对虚函数表的访问: ptr ...

  7. C++虚函数,虚函数表,虚继承,虚继承表

    一.虚函数 类中用virtual关键字修饰的函数. 作用:主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的 ...

  8. C++中虚函数、虚指针和虚表详解

    关于虚函数的背景知识 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数. 存在虚函数的类都有一个一维的虚函数表叫做虚表.每一个类的对象都有一个指向虚表开始的虚指针.虚表是和类对应的 ...

  9. c++ map 析构函数_C++|类继承关系中的虚函数、虚析构函数、虚基类

    在继承关系中,虚函数.虚析构函数.虚基类中使用的关键字virtual都是在告诉编译器,此处要进行特殊处理: 虚函数:函数重写时的要求编译器动态绑定来实现多多态 : 虚析构函数:当基类指针指向在堆内实现 ...

最新文章

  1. Work with Alexa :Echo匹配连接到Alexa
  2. EXCEL-XML 代码相对行列转换绝对
  3. Django--网页管理实例解析
  4. 浅析AES和RSA加密算法的区别和适用场景
  5. UI设计配色专辑,设计师应用技巧
  6. SpringBoot(十四)_springboot使用内置定时任务Scheduled的使用(一)
  7. python 函数中参数的传递方式(三分钟读懂)
  8. python中定义类的关键字_在Python中,定义一个类使用什么关键字?
  9. uniapp小程序生成海报图
  10. 北京房价预测——线性回归
  11. 裁判文书网数据采集爬虫2021-08
  12. Ribbon常用配置
  13. 「雷军万字总结」小米十周年公开演讲全文
  14. python高德地图api调用实例_Python调用高德地图API实现经纬度换算、地图可视化
  15. LeetCode——字节跳动系列题目
  16. 录屏,webm格式转gif的小技巧
  17. eclipse启动失败,提示“发生了错误,请参阅日志文件.log
  18. 显示一张桌子的信息,包括桌子的形状(长方形、方形、圆形、椭圆形;使用Rect、Square、Circle、Ellipse)、腿数、高度、桌面面积。定义变量来保存桌子的信息,并显示各个信息的值。要点提示
  19. python按某列拆分excel表格_把一张Excel表按照固定列分成不同工作薄的小白方法...
  20. LFS系统安装镜像制作

热门文章

  1. 解决:Error response from daemon: manifest for xxx:latest not found: manifest unknown...
  2. Failed to bind properties under mybatis-plus.configuration.result-maps[0]
  3. xshell和Xftp连接Linux
  4. 支付宝和微信的JSSDK发起支付
  5. linux运行雷神之锤,Ubuntu18.04下可以完美运行Quake3..
  6. node js fork php,Node.js中execFile,spawn,exec和fork简介
  7. python模块里的函数及说明,Python模块 time与datetime模块的函数说明及使用实例
  8. qt样式表中背景图片的使用
  9. break详细讲解啊
  10. yum安装mysql5.7 简书_阿里云服务器(centos7.3)上安装jdk、tomcat、mysql、redis