虚函数、虚表与多继承
qihemm 2025-06-10 16:24 18 浏览 0 评论
经常在编译错误中看到的vTable究竟是什么?
为什么要有虚函数
C++的设计理念是:用不到的功能就不要在运行时花费时间。正因如此,C++中会有静态绑定、动态绑定、虚函数这些概念。
对比其他一些面向对象的语言,可以认为它们所有成员函数都是虚函数,都是动态绑定,而C++则为了性能考虑,只有实际用到时,即成员函数有virtual修饰,才开启动态绑定。
静态绑定与动态绑定
所谓静态绑定,是指成员函数的地址在编译期就可以确定,运行时则直接跳转到对应地址执行;而动态绑定则是指编译期不确定,只有到运行时才能找到函数地址,这需要两次额外的寻址指令:第1次找到虚表,第2次从虚表中找到函数地址。
哪些情况会出现动态绑定?答案是只有使用指针或引用调用虚成员函数时才会出现。
例如:
class VirtualClass {
public:
virtual void f() {}
};
class SubClass : public VirtualClass {
virtual void f() override {}
};
int main() {
auto* p = new SubClass();
p->f(); // 动态绑定
}
之所以不能用静态绑定,是因为p不仅可以指向VirtualClass对象,也可以指向它的子类SubClass ,而我们在编译期并不能确定它具体指向哪个类。
内存布局与虚表指针
在分析虚表之前,先讨论一下类的内存布局。
一个类实例需要占据一定的内存空间,而空间的大小以及其中内容的排布,对同一个类的所有实例都是相同的。例如下面这个类:
class Layout {
public:
short s;
int i;
long long int l;
void f();
};
在Visual Studio的工具中可以看到它的内存排布:
而当我们把成员函数f改成虚函数时:
class Layout {
public:
short s;
int i;
long long int l;
virtual void f();
};
我们发现,前8个字节增加了一个指针 vfptr 即虚表指针,它指向一个虚表。从另一个角度,我们也可以理解为,Layout类的前8个字节用来标识它的实际类型(Layout或其某个子类)。
虚表
前面说到了虚表,那么虚表到底是什么呢?
虚表就是一个数组,它存储了一系列函数指针。只有包含虚函数的类才会有虚表,一个类的所有实例公用一个虚表。虚表中的每个指针则是指向这个类的所有虚函数。
下面的代码和对应的示意图可以看得很清楚:
class Instrument {
public:
virtual void play() {};
virtual void adjust() {};
};
class Wind : public Instrument {
public:
virtual void play() override { printf("Wind play"); }
virtual void adjust() override { printf("Wind adjust"); }
int score = 1;
};
class Brass : public Wind {
public:
virtual void play() override { printf("Brass play"); }
virtual void what() { printf("Brass what"); }
int score = 2;
};
int main() {
Instrument* list[4];
list[0] = new Instrument();
list[1] = new Wind();
list[2] = new Brass();
list[3] = new Brass();
return 0;
}
从这个例子中可以看到,当子类重写(override)父类的虚函数时,虚表中的对应指针也会修改,但顺序不变。当子类新增虚函数时,则会在虚表末尾新增。
按照这样的规则,当我们把子类的指针或引用向上类型转换时,它的虚表完全可以当做时父类的虚表来使用,无需关心实际类型。
多继承
上面说到,子类的内存布局和虚表都兼容父类,但这时又出现一个新的问题,如果有多继承怎么办?如何同时兼容两个父类呢?
其实,多继承的情况,子类会的内存布局会将两个父类依次排布,也就是会有两个虚表指针。
例如:
class Flyable {
public:
virtual void fly() {}
int hight = 0;
};
class Runnable {
public:
virtual void run() {}
int speed = 0;
};
class Bird : public Flyable, public Runnable {
public:
virtual void fly() override {}
virtual void run() override {}
virtual void eat() {}
int weight = 0;
};
Bird的内存布局如下:
从图上可以清楚看到,0x00 ~ 0x0f 这部分内存布局兼容Flyable类,0x10 ~ 0x1f 兼容,0x20之后的地址是Bird类自己的成员变量。
而Bird类自己的虚成员函数 eat() 会加在哪个虚表里呢?答案是加到第一个虚表中,和单继承的情况类似。
指针偏移
这时你可能又要问了,当Bird*类型向上转换成Runnable*之后,再调用虚函数时,又怎么知道此时应该去0x10的位置,而非0x00找虚表指针呢?
答案是,不需要。因为在做类型转换的时候,会直接将指针偏移到0x10的位置,我们来验证一下:
int main() {
Bird* b = new Bird();
Runnable* r = b;
printf("b: %x\nr: %x\nb == r: %d", b, r, b == r);
}
output:
b: ee617fb0
r: ee617fc0
b == r: 1
可以看到,b和r的地址确实不同,但当我们做比较运算时,结果却是相等。所以大多数时候,我们不需要关注这里的指针偏移。
但这样一来,也存在一个坑,就是我们不能将Bird*类型先转成void*之后,再强转成Runnable*类型,因为这样的转换不会做指针偏移。
对于包含虚表的类,做类型转换时一般用dynamic_cast,但不支持void*。
还是以上面的继承关系为例:
int main() {
Bird* b = new Bird();
Flyable* f = b;
void* v = b;
printf("sc: %x\ndc: %x", static_cast<Runnable*>(v), dynamic_cast<Runnable*>(f));
}
output:
sc: 96079740
dc: 96079750
可以看到, v和 f实际指向的时同一个 Bird对象,但两种类型转换后指针却不同,就是因为 static_cast将 void*转换到 Runnable 时没有做指针偏移。而 dynamic_cast会动态检查对象的实际类型,所以总能做出正确的指针偏移。
更多思考
就到此为止了吗?其实还有其他更复杂的情况,例如多继承时,两个父类包含相同签名的虚函数;例如有菱形继承、虚继承的情况。这些复杂情况在实际应用中较少碰到,就不做详细讨论了。
另外再提一下,C++中没有“虚成员变量”,当我们做向上类型转换后,就无法直接获取到子类的成员变量了,只能通过虚函数来获取。
- 上一篇:C++虚函数
- 下一篇:C++常用知识点汇总(基础)
相关推荐
- VLOOKUP的18种高阶用法大公开!99%的人都不知道的神操作!
-
作为被头条用户催更的Excel课代表,今天带来让HR追着要模板、让老板主动加薪的VLOOKUP终极指南!从基础到高阶一网打尽,文末送36个行业专用模板!一、为什么你的VLOOKUP总报错?血泪大数据...
- Vlooup公式,2种模糊查找匹配,1分钟学会
-
工作中,VLOOKUP公式使用频率是很高的,用来各种查找匹配问题今天我们分享两种模糊查找匹配问题,一种是文本的模糊查找匹配,一种是数字的模糊查找匹配问题1、文本模糊查找匹配使用模拟数据举个例子,原始数...
- 与vlookup功能相似的函数,照样搞定表格数据查询,简单还实用
-
在日常表格数据处理工作,说到数据查询,很多小伙伴首先想到的是Vlookup函数,老师的教程中也多次讲到Vlookup函数的用法和实例。其实在Excel中还有其他的数据查询函数公式或技巧,今天我们先来学...
- 别再折腾VLOOKUP了!DGET逆向查找10秒通关,小白必看
-
今天要掀翻一个“过气网红”——VLOOKUP!你是不是也经历过这些崩溃瞬间:逆向查找要交换列顺序,复制粘贴到手软!多条件查找要嵌套MATCH,公式长到怀疑人生!别忍了!今天教你用DGET函数一键封...
- 职场新人必学!VLOOKUP函数10分钟速成指南
-
正文:"今天来讲解办公人入职期初函数VLOOKUP,这是所有职场人最重要也是最基础的技能。掌握它,90%的数据查找再不用求人!特别献给刚入职场的你——别让Excel成为加班理由。"——...
- 巧用Vlookup函数揪出“第三者”(vlookup第三个参数是什么)
-
在一张Excel表格的重复记录中,让你快速列出每种不同物品第2次或第n次出现的记录,你会怎么做?Vlookup函数就有这个本事。举例来说,产品或者物流表格中往往会记录有同一货物的多笔数据(如下图的今日...
- 分享12个VLOOKUP超经典用法(vlookup通俗易懂)
-
刚毕业那会,面试的时候经常会被问到会不会用Excel?我就理直气壮地回答:“会啊。”毕竟,简历上可是写着熟练。接着面试官扔出一句“那你会VLOOKUP吗?”我还是会一口咬定:“我会。“其实,我都没用过...
- 查找匹配别只知道Vlookup,Sumifs也可以!
-
工作中遇到查找匹配问题的时候,大家第一反应是不是都想到的Vlookup公式呢,有没有小伙伴们给Sumifs一点点机会的呢,有时候Sumifs比Vlookup更好用1、Vlookup公式举个例子,左边是...
- Excel函数讲解:VLOOKUP函数,轻松玩转数据查找
-
常用函数系列教学:VLOOKUP函数讲解(46)。不懂VLOOKUP函数怎么高效查找数据?闲话少叙直接开讲。基本含义:VLOOKUP函数用于在表格按垂直方向(到)上查找返回行数据。如何使用及注意事项?...
- CHOOSEROWS+CHOOSECOLS原来是一个超级查找函数组合!
-
场景一:要在学生名册中,抽查一名学生成绩。公式:=CHOOSEROWS(A1:D5,2)解析:第一参数A1:D5为数据区域,第二参数2表示提取第2行数据。把数据区域改为A2:D5,结合RANDBETW...
- 数据查询不止有vlookup函数,自定义zlookup函数查询操作更高效
-
Excel数据查询,相信大家首先会想到vlookup函数。毋庸置疑vlookup函数在Excel数据查询中作用是非常的强大。但是它也有一些不能实现的数据查询。如上图所示,我们需要根据人员的出现次数,提...
- 「EXCEL进阶」VLOOKUP函数怎么查询一个值返回多个结果
-
前言:VLOOKUP函数一般一次只能返回一个结果,本例介绍通过辅助列的方法使VLOOKUP函数查询一个值,返回这个值对应的多个结果。使用场景举例:根据表格中同一数值,返回对应值的多个结果。比如这张数据...
- WPS查找能手VLOOKUP函数使用方法讲解
-
各位同学好!今天我们来深度剖析WPS最实用的查找工具——VLOOKUP函数。这个函数能帮你在表格中快速定位并提取所需数据,可以帮你快速核对两批数据差异,还可以合并多个表格的关联信息,甚至可以帮你制作动...
- Excel常用10个函数:跨表查找Vlookup,适用于大数据中查找精确值
-
Hello大家好,我是Office米,今天,我们将和大家一起分享交流,常用的10个函数之一:查找引用函数VLOOKUP。在说VLOOKUP函数之前,我们要先了解,平时Excel日常工作中会遇到哪些问题...
- 掌握了这个套路,无论用 Excel vlookup 函数查找第几次结果都很轻松
-
用vlookup查找默认情况下是一对一出结果,如果要一对多查找,就需要用到各种技巧,具体方法我写过非常多了,可以搜索一下历史记录。只要掌握了今天这个套路,无论你想查找第几次重复值,都易如反掌。案例...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- VLOOKUP的18种高阶用法大公开!99%的人都不知道的神操作!
- Vlooup公式,2种模糊查找匹配,1分钟学会
- 与vlookup功能相似的函数,照样搞定表格数据查询,简单还实用
- 别再折腾VLOOKUP了!DGET逆向查找10秒通关,小白必看
- 职场新人必学!VLOOKUP函数10分钟速成指南
- 巧用Vlookup函数揪出“第三者”(vlookup第三个参数是什么)
- 分享12个VLOOKUP超经典用法(vlookup通俗易懂)
- 查找匹配别只知道Vlookup,Sumifs也可以!
- Excel函数讲解:VLOOKUP函数,轻松玩转数据查找
- CHOOSEROWS+CHOOSECOLS原来是一个超级查找函数组合!
- 标签列表
-
- 正版织梦模板 (30)
- 单片机c语言入门基础知识 (32)
- 手机编程游戏 (29)
- 优秀企业网站模板 (34)
- python编程入门自学书籍 (34)
- phpcms安装 (30)
- 自学excel免费视频教程全集 (36)
- php加密系统源码 (29)
- vlookup函数查找 (30)
- 电脑怎么下载java (32)
- vba编程实例速成150例 (30)
- 函数subtotal的用法 (31)
- java教程txt (32)
- java软件开发面试题 (30)
- sql数据库备份与还原方法 (33)
- 后台管理系统网站模板 (30)
- html表单属性有哪些 (31)
- 初中数学三角函数公式 (32)
- python爬虫教程 (30)
- 三角函数值对照表0到360度 (33)
- oracle数据库下载教程 (31)
- index函数什么意思 (34)
- indirect函数的详细用法 (31)
- excel函数round用法 (32)
- vlookup一对多查询并提取 (35)