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

发布于:2025-06-27 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

一、虚函数(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::func
    virtual void func() { cout << "Base::func" << endl; }
};

class Derived : public Base {
public:
    Derived() { func(); } // 调用 Derived::func
    void 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::func1
    b->func2(); // 输出 Base::func2
    return 0;
}

调试输出

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

八、总结

8.1 核心要点

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

8.2 设计建议

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

网站公告

今日签到

点亮在社区的每一天
去签到