当前位置: 首页 > news >正文

C++ 第二阶段:继承与多态 - 第二节:虚函数与虚函数表

目录

一、虚函数(Virtual Function)的概念

1.1 虚函数的定义

二、虚函数表(Virtual Table)的工作原理

2.1 虚函数表的基本结构

2.2 虚函数指针(vptr)

三、虚函数的调用流程

3.1 调用步骤

四、虚函数与多态的关系

4.1 多态的实现条件

4.2 静态绑定 vs 动态绑定

五、虚函数的注意事项

5.1 性能开销

5.2 纯虚函数与抽象类

5.3 构造函数与虚函数

六、多继承与虚函数表

6.1 多继承下的虚函数表

6.2 菱形继承问题与虚继承

七、虚函数表的调试与查看

7.1 使用调试工具查看虚函数表

7.2 代码示例

八、总结

8.1 核心要点

8.2 设计建议

C++从入门到入土学习导航_c++学习进程-CSDN博客


一、虚函数(Virtual Function)的概念

1.1 虚函数的定义

  • 虚函数是通过 virtual 关键字声明的成员函数,允许在派生类中重写(覆盖)基类的实现。
  • 核心作用:实现运行时多态(动态绑定),即通过基类指针或引用调用派生类的重写函数。
class Base {
public:virtual void func() { cout << "Base::func" << endl; } // 虚函数
};class Derived : public Base {
public:void func() override { cout << "Derived::func" << endl; } // 重写虚函数
};

二、虚函数表(Virtual Table)的工作原理

2.1 虚函数表的基本结构

  • **虚函数表(vtable)**是编译器为每个包含虚函数的类生成的一个静态数组。
  • 虚函数表项:每个虚函数表项存储一个指向虚函数的函数指针。
  • 虚函数表的生成规则
    • 每个类(包括派生类)都有自己的虚函数表。
    • 如果派生类重写了基类的虚函数,则虚函数表中对应的函数指针会被更新为派生类的实现。
    • 如果派生类没有重写某个虚函数,则虚函数表中保留基类的函数指针。
class Base {
public:virtual void func1() {}virtual void func2() {}
};class Derived : public Base {
public:void func1() override {} // 重写 func1// func2 未重写
};

虚函数表示例

  • Base 的虚函数表:[Base::func1, Base::func2]
  • Derived 的虚函数表:[Derived::func1, Base::func2]

2.2 虚函数指针(vptr)

  • **虚函数指针(vptr)**是编译器自动添加到每个对象的隐藏指针。
  • 作用:指向对象所属类的虚函数表。
  • 特点
    • 在对象构造时初始化。
    • 在继承链中被继承,指向当前类的虚函数表。
Base* b = new Derived(); // b->vptr 指向 Derived 的虚函数表
b->func1(); // 动态绑定到 Derived::func1

三、虚函数的调用流程

3.1 调用步骤

  1. 获取虚函数指针:通过对象的 vptr 找到虚函数表。
  2. 查找虚函数表项:根据函数在虚函数表中的偏移量(索引)找到对应的函数指针。
  3. 调用函数:通过函数指针跳转到实际的函数实现。

示意图

对象内存布局:
+----------------+
|    vptr        |  --> 虚函数表地址
+----------------+
| 其他成员变量   |
+----------------+虚函数表:
+----------------+
| &Derived::func1|  --> 函数指针
+----------------+
| &Base::func2   |  --> 函数指针
+----------------+

四、虚函数与多态的关系

4.1 多态的实现条件

  • 前提条件
    1. 基类中声明虚函数。
    2. 派生类重写虚函数。
    3. 通过基类指针或引用调用虚函数。
Base* ptr = new Derived();
ptr->func(); // 动态绑定到 Derived::func

4.2 静态绑定 vs 动态绑定

  • 静态绑定(早期绑定):在编译时确定函数调用(普通函数)。
  • 动态绑定(晚期绑定):在运行时通过虚函数表确定函数调用(虚函数)。

五、虚函数的注意事项

5.1 性能开销

  • 调用成本:虚函数调用需要两次间接寻址(vptr → 虚函数表 → 函数地址),比普通函数稍慢。
  • 内存占用:每个对象需要额外存储一个 vptr(通常为 4 或 8 字节)。

5.2 纯虚函数与抽象类

  • 纯虚函数:声明为 virtual void func() = 0; 的虚函数。
  • 抽象类:包含至少一个纯虚函数的类,不能实例化对象。
  • 作用:强制派生类实现接口。
class AbstractBase {
public:virtual void pureFunc() = 0; // 纯虚函数
};class Concrete : public AbstractBase {
public:void pureFunc() override { /* 实现 */ }
};

5.3 构造函数与虚函数

  • 构造函数中的虚函数调用:在构造函数中调用虚函数时,调用的是当前正在构造的类的版本(而非派生类的版本)。
  • 原因:构造函数执行时,对象尚未完全构造完成,vptr 指向当前类的虚函数表。
class Base {
public:Base() { func(); } // 调用 Base::funcvirtual void func() { cout << "Base::func" << endl; }
};class Derived : public Base {
public:Derived() { func(); } // 调用 Derived::funcvoid func() override { cout << "Derived::func" << endl; }
};// 输出:
// Base::func (在 Base 构造函数中)
// Derived::func (在 Derived 构造函数中)

六、多继承与虚函数表

6.1 多继承下的虚函数表

  • 每个基类的虚函数表独立存在:派生类会继承多个基类的虚函数表。
  • 虚函数表的数量:取决于继承的基类数量(每个基类生成一个虚函数表)。
class Base1 {
public:virtual void func1() {}
};class Base2 {
public:virtual void func2() {}
};class Derived : public Base1, public Base2 {
public:void func1() override {}void func2() override {}
};

虚函数表结构

  • Base1 的虚函数表:[Base1::func1]
  • Base2 的虚函数表:[Base2::func2]
  • Derived 的虚函数表:
    • Base1 部分:[Derived::func1]
    • Base2 部分:[Derived::func2]

6.2 菱形继承问题与虚继承

  • 菱形继承问题:两个基类继承自同一个祖先类,派生类中会有多个祖先类的副本。
  • 解决方案:使用 虚继承virtual 关键字)共享祖先类。
class A {};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};

虚继承的虚函数表

  • 共享的祖先类 A 只生成一份虚函数表,避免冗余。

七、虚函数表的调试与查看

7.1 使用调试工具查看虚函数表

  • GDB 命令info vtbl <对象> 查看虚函数表。
  • LLDB 命令x/4wx <对象地址> 查看内存中的虚函数表指针。

7.2 代码示例

#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; }
};int main() {Base* b = new Derived();b->func1(); // 输出 Derived::func1b->func2(); // 输出 Base::func2return 0;
}

调试输出

Base::func1 (Derived::func1)
Base::func2 (Base::func2)

八、总结

8.1 核心要点

  • 虚函数是实现运行时多态的核心机制。
  • 虚函数表是编译器为每个类生成的静态数组,存储虚函数的地址。
  • 虚函数指针vptr)是对象的隐藏指针,指向虚函数表。
  • 多继承下,对象可能包含多个虚函数表,虚继承可解决菱形继承问题。

8.2 设计建议

  • 优先使用虚函数:当需要动态绑定时,使用虚函数。
  • 避免过度使用虚函数:普通函数的调用效率更高。
  • 合理设计抽象类:使用纯虚函数定义接口,强制派生类实现。
http://www.lqws.cn/news/497143.html

相关文章:

  • DataWhale-零基础络网爬虫技术(三、爬虫进阶技术)
  • 短期项目与长期目标如何同时兼顾
  • SpringCloud系列(32)--使用Hystrix进行全局服务降级
  • 基于分布式部分可观测马尔可夫决策过程与联邦强化学习的低空经济智能协同决策框架
  • 基于协议转换的 PROFIBUS DP 与 ETHERNET/IP 在石化生产中的协同运行实践
  • SwiftUI学习笔记day4: Lecture 4 | Stanford CS193p 2023
  • Lnmp和XunRuiCMS一键部署(Rocky linux)
  • Spring Boot 集成 Apache Kafka 实战指南
  • WebRTC(八):SDP
  • Origin绘制三Y轴柱状图、点线图、柱状点线图
  • pyhton基础【15】函数进阶一
  • C++11 static_assert(基于Boost库)从入门到精通
  • MOS管损坏原因,封装失效、栅极失效、雪崩失效、过流和过压损坏
  • Spring Bean生命周期(基于spring-beans-6.2.6分析)
  • 酒店住宿自助入住系统——店铺自动运营—仙盟创梦IDE
  • 零基础学习RabbitMQ(1)--概述
  • iperf3使用方法
  • 无人驾驶汽车运动控制分为纵向控制和横向控制
  • DMDRS部署实施手册(ORACLE=》DM)
  • Spring Boot多数据源配置实战指南
  • Reactor模式与Proactor模式理解
  • 【c++八股文】Day3:inline与define与constexpr
  • Android手机屏幕分为几种
  • Docker高级管理--Dockerfile镜像制作
  • 【科技公司的管理】
  • 【目标检测】图像处理基础:像素、分辨率与图像格式解析
  • Spring AI框架的实战应用指南
  • vscode 插件
  • 以玄幻小说方式打开深度学习词嵌入算法!! 使用Skip-gram来完成 Word2Vec 词嵌入(Embedding)
  • 显示任何结构的数组对象数据【向上自动滚动】