蔚淦丞
发布于 2026-09-03 / 15 阅读
0
0

C++多态与虚函数

一、什么是多态

多态是 C++ 面向对象三大特性之一(封装、继承、多态)。

多态含义:父类指针 / 引用指向子类对象,调用函数时,执行的是子类的版本。

简单一句话: 同样的父类接口,不同的子类表现出不同行为。

多态分为两种:

  1. 静态多态(编译期):函数重载、模板

  2. 动态多态(运行期):虚函数(本文重点)

只有 虚函数 能实现 运行时多态


二、虚函数的作用

1. 普通成员函数的问题

如果父类、子类有同名函数: 用父类指针指向子类对象,永远调用父类函数,不会调用子类函数。

因为普通函数在编译期就确定调用地址(静态绑定)

2. 虚函数的作用

virtual 修饰的函数: 不在编译期绑定地址,而是在运行时根据真实对象类型,动态决定调用父类还是子类函数。

这就实现了:父类指针调用子类实现 —— 运行时多态。

3. 完整示例

class Base {
public:
    virtual void func() { }
};

class Son : public Base {
public:
    void func() override { }
};

int main() {
    Base* p = new Son;
    p->func(); // 执行 Son::func
    delete p;
}

三、重写(override)完整规则

子类重新实现父类的 虚函数,叫做「重写 / 覆盖」。

重写必须完全匹配

  1. 函数名相同

  2. 参数列表完全相同

  3. 返回值基本一致(允许协变返回)

  4. 必须是虚函数

override 关键字作用

  • 强制编译器检查是否合法重写

  • 防止手滑写错参数、函数名等导致重写未发生,让编译器来检查

  • 建议所有重写函数都加 override


四、重载、重写、隐藏(最容易混淆三兄弟)

1. 重载 Overload(静态多态)

同一作用域内,同名不同参

  • 参数个数不同

  • 参数类型不同

  • 和虚函数无关

2. 重写 Override(动态多态)

父子类,虚函数、完全同名同参。 实现运行多态。

3. 隐藏 Hide(最坑)

父子类 同名非虚函数,或者 参数不一致的虚函数

特点:

  1. 不触发多态

  2. 子类函数 隐藏 父类函数

  3. 父类指针调用永远走父类版本,不会走子类

三者一句话区分

  • 重载:同域同名不同参

  • 重写:父子同名同参 + virtual(多态)

  • 隐藏:父子同名但不满足重写条件(无多态)


五、虚函数底层机制(纯原理)

1. 虚函数表 vtable

每个拥有虚函数 / 继承虚函数的类,会生成一张虚函数表。 表中存放:虚函数的真实地址

2. 虚指针 vptr

每个对象内部会隐藏一个 vptr(虚指针) 指向当前类的虚函数表

3. 运行过程

  1. 对象创建时自动初始化 vptr

  2. 调用虚函数时,不看指针类型,看对象真实 vptr

  3. 通过 vtable 找到真实函数地址调用

所以:父类指针、子类对象 → 调用子类方法


六、虚析构函数(核心必懂)

1. 问题场景

当父类指针 delete 子类对象:

  • 如果析构不是虚函数,只会调用父类析构,不会调用子类析构

  • 导致子类内存泄漏、资源没释放

2. 虚析构作用

保证通过父类指针删除子类对象时,先调用子类析构,再调用父类析构。

3. 规则

  1. 只要类可能被继承,并且会用父类指针删子类,析构必须 virtual

  2. 虚析构不需要重写,自动链式调用

  3. 虚析构不影响普通虚函数

4. 正确写法

class Base {
public:
    virtual ~Base() = default;
};

七、重点:为什么没有「虚构造函数」?

结论:C++ 不存在虚构造函数

原因(透彻解释)

  1. 虚函数依赖 vptr,vptr 依赖对象

  2. 构造函数执行时,对象还没创建完成

  3. 对象没完成 → 没有 vptr → 无法动态绑定

多态的本质是:已有对象,再判断类型 构造函数是:正在创建对象,不可能多态

补充

  1. 构造函数执行期间,不存在多态

  2. 构造函数中调用虚函数,只会调用本类版本,不会触发子类重写


八、构造、析构中的虚函数行为(非常重要)

1. 构造函数里调用虚函数

不会多态,永远执行当前正在构造的类的函数

原因: 子类还没构造,vptr 此时指向父类 vtable

2. 析构函数里调用虚函数

不会多态 子类已经析构完毕,vptr 已经被切回父类 vtable

总结

构造、析构过程中,虚函数失效,无多态。


九、纯虚函数 & 抽象类

1. 纯虚函数写法

virtual void func() = 0;

2. 抽象类

包含至少一个纯虚函数的类,无法实例化。

3. 作用

  1. 定义统一接口

  2. 强制子类必须重写该函数

  3. 不需要父类实现

4. 继承规则

子类必须实现所有纯虚函数,否则子类也是抽象类,无法 new


十、多态完整终版总结(个人总结)

  1. 虚函数动态绑定(运行期),实现多态

  2. 父类指针指向子类对象,虚函数调用子类实现

  3. 重写必须同名同参 + virtual,否则变成隐藏

  4. 父类析构必须 virtual,防止父类指针删子类导致泄漏

  5. 没有虚构造函数,构造阶段对象不完整,无法多态

  6. 构造、析构内部调用虚函数不会触发多态

  7. 纯虚函数用于定义接口、强制子类实现

十一、隐藏、重写、重载 对照表

  1. 重载:同作用域、同名不同参、静态绑定、无 virtual

  2. 隐藏:父子同名、无virtual、无多态

  3. 重写:父子同名同参 virtual、动态绑定、多态生效


评论