news 2026/8/9 21:17:40

虚函数虚表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
虚函数虚表

一、基本原理

1.虚函数:用virtual关键字生声明的函数,允许派生类重写

2.虚表:每个包含虚函数的类(或其派生类)会有一个全局唯一的虚表,存储该类所有虚函数的地址

3.虚指针(vptr):每个对象会包含一个指向其类虚表的指针(通常在对象内存布局的开头)

当通过基类指针或引用调用虚函数时,程序会通过对象的虚指针找到对应类的虚表,在虚表中查找要调用的虚函数地址,调用该地址对应的函数(实现动态绑定)。

二、同名覆盖

同名覆盖:派生类中定义的与基类同名的函数,覆盖了基类的同名函数,导致基类的该函数在派生类作用域内不可直接访问的现象。

其本质可分为两种场景,需结合基类函数是否为虚函数区分:

场景1:虚函数的同名覆盖(即“重写”,Override)

当基类函数是虚函数时,派生类定义与基类“函数名、参数列表、返回值完全一致”(协变除外)的同名函数,会覆盖基类虚函数的实现 —— 这是动态多态的核心,覆盖效果在运行时生效(根据对象实际类型确定调用哪个版本)。

场景 2:非虚函数的同名覆盖(即“隐藏”,Hide)

当基类函数非虚函数时,派生类定义与基类同名的函数(无论参数列表是否相同),会遮蔽基类的同名函数 —— 这是编译期的作用域遮蔽,覆盖效果在编译时生效(根据指针/引用的声明类型确定调用哪个版本)。

三、代码示例

class Object { int value; public: Object(int x = 0) :value(x) { cout << "Object" << endl; } virtual void add(int a) { cout << "Object::add:" << a << endl; } virtual void func()const { cout << "Object::func:" << endl; } virtual void print()const { cout << "Object::print:" << endl; } }; class Base :public Object { int num; public: Base(int x = 0) :Object(x + 10), num(x) { cout << "Base" << endl; } virtual void add(int a) { cout << "Base::add:" << a << endl; } virtual void func()const { cout << "Base::func:" << endl; } virtual void show() { cout << "Base::show:" << endl; } }; class Test :public Base { int sum; public: Test(int x = 0) :Base(x + 10), sum(x) { cout << "Test" << endl; } virtual void func()const { cout << "Test::func:" << endl; } virtual void print() const { cout << "Test::print" << endl; } virtual void show() { cout << "Test::show:" << endl; } }; int main() { Test test; return 0; }

执行Test test;时,完整流程(含 vptr 设置 + 成员初始化):

1.分配 Test 对象内存:内存中包含vptr + Object::value + Base::num + Test::sum,vptr 初始为野指针。

2.执行 Object 构造函数:

第一步:设置 vptr 指向Object的虚表;

第二步:执行 Object 的初始化列表value(x)(x=20,由 Base 传递而来);

第三步:执行 Object 构造函数体(你的代码中无额外逻辑,仅输出 "Object");

Object 构造完成。

3.执行 Base 构造函数:

第一步:设置 vptr 指向Base的虚表;

第二步:执行 Base 的初始化列表Object(x+10)(已执行完毕)、num(x)(x=10,由 Test 传递而来);

第三步:执行 Base 构造函数体(输出 "Base");

Base 构造完成。

4.执行 Test 构造函数:

第一步:设置 vptr 指向Test的虚表;

第二步:执行 Test 的初始化列表Base(x+10)(已执行完毕)、sum(x)(x=0,默认参数);

第三步:执行 Test 构造函数体(输出 "Test");

Test 构造完成。

派生类向基类传参

1.传参入口:派生类构造函数通过 初始化列表 给直接基类传参(不能在构造函数体内传);

2.传递规则:多层继承时,参数按 “最派生类→中间基类→顶层基类” 逐层传递,每层都可对参数加工(如x+10);

3.默认值优先级:若派生类给基类传了参数,基类构造函数就不用自己的默认值;没传参时,基类才用自身默认值。

例:Test test;(无显式传参)

Test(x=0 默认值)→ 给 Base 传0+10=10;

Base(收 10)→ 给Object传10+10=20;

最终顶层基类Object用20初始化自身成员。

注意:

1.无论继承多少层,一个对象只有一个vptr,多层继承的vptr是 “覆盖更新”,而非“多个 vptr 并存”;

2.vptr的设置是“构造函数内的第一步”,早于成员初始化,这是 C++ 的规定,目的是避免构造期间调用虚函数时,意外访问到子类中未初始化的成员,导致内存错误或逻辑异常;

3.构造期间 vptr 指向“当前正在构造的类的虚表”,因此构造函数内调用虚函数不会触发多态(比如 Object 构造时调用add,只会调用Object::add,不会调用 Base 或 Test 的版本)。

四、纯虚函数和抽象类

纯虚函数是C++中一种特殊的虚函数,它在基类中声明但不提供实现,强制要求派生类必须重写该函数。纯虚函数的声明格式为:virtual 返回类型 函数名(参数列表) = 0;

含有纯虚函数的类称为抽象类(只要含有纯虚函数这个类就是抽象类),类中只有接口,没有具体的实现方法,所以抽象类不能实例化对象;

继承纯虚函数的派生类,如果没有完全实现基类纯虚函数,依然是抽象类,不能实例化对象。

抽象类的使用规则:

1.抽象类只能作为其他类的基类,不能创建抽象类的对象;

2.抽象类不能用作参数类型、函数返回值类型或显式类型转换;

3.可以定义抽象类的指针和引用,此指针可以指向(引用可以引用)它的派生类,从而实现运行时多态。

五、面试常见问题

为什么构造函数不能定义成虚函数?

虚函数能实现动态多态,本质是依赖虚指针(vptr)虚函数表(vtable)

每个含虚函数的对象,都有一个vptr,指向所属类的 vtable;

而vptr的初始化,是构造函数执行的第一步—— 进入构造函数后,先把 vptr 指向当前类的 vtable,再初始化成员变量。

若构造函数是虚函数:

调用虚函数需要通过 vptr 查找 vtable;

但 vptr 要等构造函数执行才会初始化,相当于“要调用虚构造函数,必须先有 vptr;但 vptr 要等构造函数执行才存在”,形成逻辑死循环

为什么基类的析构函数要声明为虚函数?
如果基类析构函数不是虚函数,通过基类指针删除派生类对象时,只会调用基类的析构函数,导致派生类的资源泄漏。

基类的析构函数不是虚函数时,通过基类指针删除派生类对象时,会发生以下过程:

  1. 编译阶段编译器根据指针 / 引用的声明类型(Object),将析构函数调用绑定到基类的析构函数(~Object())。
  2. 运行阶段:程序执行时,只会调用编译阶段绑定好的基类析构函数,而派生类的析构函数完全不会被触发,因此派生类析构函数不会被调用。

基类析构函数是虚函数时,通过基类指针删除派生类对象时,过程如下:​​​​​​

  1. 运行阶段:程序根据指针实际指向的派生类对象,找到派生类的析构函数并调用;
  2. 派生类析构函数执行完毕后,会自动调用基类的析构函数(这是 C++ 的默认规则:派生类析构函数执行时,会隐式调用基类的析构函数,保证基类部分的资源被释放);
  3. 最终,派生类对象的派生部分基类部分都会被完整销毁。

虚函数和纯虚函数的区别

虚函数和纯虚函数可以出现在同一个类中,该类称为抽象基类。(含有纯虚函数的类称为抽象基类)

使用方式不同:虚函数可以直接使用,纯虚函数必须在派生类中实现后才能使用;

定义形式不同:虚函数在定义时在普通函数的基础上加上 virtual 关键字,纯虚函数定义时除了加上virtual 关键字还需要加上 =0;

虚函数必须实现,否则编译器会报错;

对于实现纯虚函数的派生类,该纯虚函数在派生类中被称为虚函数,虚函数和纯虚函数都可以在派生类中重写;

析构函数最好定义为虚函数,特别是对于含有继承关系的类;析构函数可以定义为纯虚函数,此时,其所在的类为抽象基类,不能创建实例化对象。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/9 6:19:04

电动汽车永磁同步电机的电磁设计与最优控制探索

永磁同步电机具有效率高、功率密度大、鲁棒性强以及调速范围广等优点&#xff0c;被广泛应用于家用电器、航空航天、轨道交通与电动汽车等领域&#xff0c;是当前电机领域研究和应用热点。 本文以电动汽车驱动用永磁同步电机电磁设计和最优控制为研究内容&#xff0c;对永磁同步…

作者头像 李华
网站建设 2026/8/9 13:28:39

如何快速配置glibc-all-in-one:完整安装与使用指南

如何快速配置glibc-all-in-one&#xff1a;完整安装与使用指南 【免费下载链接】glibc-all-in-one &#x1f381;A convenient glibc binary and debug file downloader and source code auto builder 项目地址: https://gitcode.com/gh_mirrors/gl/glibc-all-in-one gl…

作者头像 李华
网站建设 2026/8/8 7:46:42

可以把 Windows 从 C盘迁移到 SSD 吗?

可以把 Windows 从 C盘迁移到 SSD 吗&#xff1f;yes, you can move windows from the c: drive to an ssd, and doing so can make your computer faster. the process usually means copying the operating system, programs, and settings from an old hard drive to a new …

作者头像 李华
网站建设 2026/8/9 22:00:39

突破浏览器壁垒:CSS框架跨平台兼容性完全解决方案

突破浏览器壁垒&#xff1a;CSS框架跨平台兼容性完全解决方案 【免费下载链接】OSWorld [NeurIPS 2024] OSWorld: Benchmarking Multimodal Agents for Open-Ended Tasks in Real Computer Environments 项目地址: https://gitcode.com/GitHub_Trending/os/OSWorld 在现…

作者头像 李华