虚函数表,以及虚函数指针是实现多态性(Polymorphism)的关键机制。多态性允许我们通过基类的指针或引用来调用派生类的函数

定义

虚函数(Virtual Function)

  • 定义:类中使用virtual 关键字修饰的函数 叫做虚函数

  • 语法

class Base {
public:
virtual void show() { cout << "Base show" << endl; }
};

虚函数表(Virtual Function Table)

  • 定义:当类含有至少一个虚函数时,编译器会为该类创建一个虚函数表。这个表是一个编译时构建的静态数组,存储了指向类中所有虚函数的指针。如果一个派生类重写了这些函数,那么在派生类的虚表中,相应函数的指针会被更新为指向派生类中的版本。
  • 作用v-table使得在运行时可以实现函数的动态绑定,允许通过基类的指针或引用调用正确的函数版本。

虚函数指针(Virtual Pointer)

  • 定义:每个含有虚函数的类的对象(实例化出的)会持有一个指向相应虚表的指针,这个指针通常被称为虚指针(vptr)。vptr是对象运行时的一部分,确保了当通过基类指针调用虚函数时,能够查找到正确的函数实现。
  • 作用:在对象的生命周期开始时,构造函数会设置vptr以指向相应的虚函数表。如果有派生类对象,它的构造函数会更新vptr,以指向派生类的虚函数表。这保证了通过基类的引用或指针调用虚函数也会执行最派生类的重写版本。

示例

#include <iostream>
using namespace std; class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
}; class Derived : public Base {
public:
void func1() override { cout << "Derived::func1" << endl; }
// func2() 继承自 Base
}; void printVTable(void* obj) {
cout << "vptr Address: " << obj << endl;
void** vTable = *(void***)obj;
cout << "VTable[0] (func1): " << vTable[0] << endl;
cout << "VTable[1] (func2): " << vTable[1] << endl;
} int main() {
Base* base = new Base();
Derived* derived = new Derived(); cout << "Base object:" << endl;
printVTable(base); cout << "\nDerived object:" << endl;
printVTable(derived); delete base;
delete derived; return 0;
}

程序输出如下,可以看到没用重写的func2函数地址是一样的。

Base object:
vptr Address: 0x8c1510
VTable[0] (func1): 0x422270
VTable[1] (func2): 0x4222b0 Derived object:
vptr Address: 0x8c1530
VTable[0] (func1): 0x422330
VTable[1] (func2): 0x4222b0

如下图所示:

面试题

(来自2025腾讯实习面试)场景题:一个类 A,里面有一个打印 helloworld 的虚函数,然后类 A 会在构造函数里调用这个虚函数,此时有个类 B,继承A,重写了这个 helloworld虚函数,问你在创建类 B 时,会打印 A 里的 helloworld 还是 B 里的。

代码如下:

class A {
public:
A() {
print();
}
virtual void print() {
cout << "A print" << endl;
}
}; class B : public A {
public:
void print() override {
cout << "B print" << endl;
}
};
int main() {
A* aTemp = new B();
delete aTemp;
}

解答:基类构造函数执行的时候,派生类的部分尚未初始化,因此调用的虚函数不会下发到派生类中。

最终会打印 A print,而不是类 B 里重写的版本

C++中虚表是什么的更多相关文章

  1. 揭开C++类中虚表的“神秘面纱”

    C++类中的虚表结构是C++对象模型中一个重要的知识点,这里咱们就来深入分析下虚表的在内存中的结构. C++一个类中有虚函数的话就会有一个虚表指针,其指向对应的虚表,一般一个类只会有一个虚表,每个虚表 ...

  2. C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址

    C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址 讲解之前,了解下什么是虚函数,什么是虚表指针,了解下语法,(也算复习了) 开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当 ...

  3. C++反汇编第二讲,反汇编中识别虚表指针,以及指向的虚函数地址

    讲解之前,了解下什么是虚函数,什么是虚表指针,了解下语法,(也算复习了) 开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当然也可以看原博客链接: http://blog.csdn.net ...

  4. C++中的初始化

    C++中的RAII机制指明”对象创建时通过构造函数进行初始化,析构时通过析构函数释放资源”,但实际中因类无构造函数时编译器将自动合成一个默认构造函数,该构造函数只服务于自身编译器构造需要而不负责对象成 ...

  5. 逆向实用干货分享,Hook技术第二讲,之虚表HOOK

    逆向实用干货分享,Hook技术第二讲,之虚表HOOK 正好昨天讲到认识C++中虚表指针,以及虚表位置在反汇编中的表达方式,这里就说一下我们的新技术,虚表HOOK 昨天的博客链接: http://www ...

  6. 初步学习C++中的继承关系

    继承机制是面向对象程序设计使代码能够复用的最重要的手段,它同意程序猿在保持原有类特性的基础上进行扩展,添加功能. 这样产生新的类,称派生类.继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂 ...

  7. C++继承、多态与虚表

    继承 继承的一般形式 子类继承父类,是全盘继承,将父类所有的东西都继承给子类,除了父类的生死,就是父类的构造和析构是不能继承的. 继承的访问权限从两方面看: 1.对象:对象只能直接访问类中公有方法和成 ...

  8. 图说C++对象模型:对象内存布局详解

    0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看. 本文的结论都在VS2013上得到验证.不同的编译器在内存布局的细节上可能有 ...

  9. C++STL - 模板的其他特性

    之前已经总结过函数模板和类模板了,对于模板还有一些其他的特性,这篇主要介绍这些特性.主要都是一些特殊情况. 模板的其他特性 1.缺省参数 (1)类模板的模板参数可以带有缺省值,实例化该模板时,如果提供 ...

  10. C/C++ 笔试题

    /////转自http://blog.csdn.net/suxinpingtao51/article/details/8015147#userconsent# 微软亚洲技术中心的面试题!!! 1.进程 ...

随机推荐

  1. NC201613 Jelly

    题目链接 题目 题目描述 Nancy喜欢吃果冻! Nancy钻进了一个 \(n \times n \times n\) 的果冻里,她想从(1,1,1)一路上.下.左.右.前.后六个方向吃到(n,n,n ...

  2. Java线程状态(生命周期)--一篇入魂

    1.线程状态(生命周期) 一个线程在给定的时间点只能处于一种状态. 线程可以有如下6 种状态: New (新创建):未启动的线程: Runnable (可运行):可运行的线程,需要等待操作系统资源: ...

  3. 【Android】使用Binder实现进程间传递对象案例

    1 前言 使用AIDL实现进程间通讯简单案例 和 使用AIDL实现进程间传递对象案例 中介绍了使用 AIDL 进行进程间通讯,其本质仍然是Binder,aidl 文件对应生成的接口中,将服务端调用的抽 ...

  4. Vue+SpringBoot+ElementUI实战学生管理系统-6.院系管理模块

    1.章节介绍 前一篇介绍了用户管理模块,这一篇编写院系管理模块,需要的朋友可以拿去自己定制.:) 2.获取源码 源码是捐赠方式获取,详细请QQ联系我 :)! 3.实现效果 院系列表 修改院系 4.模块 ...

  5. Java并发编程实例--10.使用线程组

    并发API提供的一个有趣功能是可以将多个线程组成一个组. 这样我们就能将这一组线程看做一个单元并且提供改组内线程对象的读取操作.例如 你有一些线程在执行同样的任务并且你想控制他们,不考虑有多少个线程仍 ...

  6. 《深入理解Java虚拟机》(五) JVM调优案例

    目录 问题 排查问题经过了如下的过程: 排除是否数据库卡顿造成 任务管理器 与客户沟通 至此开始通过JVM排查问题: JVM参数介绍 第一次Full GC 第二次Full GC截图 第三次Full G ...

  7. 我又踩坑了!如何为HttpClient请求设置Content-Type?

    1. 坑位 最近在重构认证代码,认证过程相当常规: POST /open-api/v1/user-info?client_id&timstamp&rd=12345&sign=* ...

  8. 关于 try... catch

    在逛论坛看见一个有意思的帖子,有点意思,记录下 关于"异常捕捉"(try catch)是否存在悖论? 一些我觉得有用的回复,放到下面了, 1. 当某些错误状况难以完全避免时,try ...

  9. virtualapp安装应用流程源码分析

    1. HomeActivity 为处理的入口 @Override protected void onActivityResult(int requestCode, int resultCode, In ...

  10. Qt+MySql开发笔记:Qt5.9.3的mingw32版本编译MySql8版本驱动并Demo连接数据库测试

    前言   之前特定的mysql版本msvc版本已经调通了,但是为了更好的跨平台,所以选择用mingw32版本,于是需要编译mysql驱动的mingw32版本的驱动库,以便提供给qt连接mysql使用. ...