一、什么是多态
多态是 C++ 面向对象三大特性之一(封装、继承、多态)。
多态含义:父类指针 / 引用指向子类对象,调用函数时,执行的是子类的版本。
简单一句话: 同样的父类接口,不同的子类表现出不同行为。
多态分为两种:
静态多态(编译期):函数重载、模板
动态多态(运行期):虚函数(本文重点)
只有 虚函数 能实现 运行时多态。
二、虚函数的作用
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)完整规则
子类重新实现父类的 虚函数,叫做「重写 / 覆盖」。
重写必须完全匹配
函数名相同
参数列表完全相同
返回值基本一致(允许协变返回)
必须是虚函数
override 关键字作用
强制编译器检查是否合法重写
防止手滑写错参数、函数名等导致重写未发生,让编译器来检查
建议所有重写函数都加
override
四、重载、重写、隐藏(最容易混淆三兄弟)
1. 重载 Overload(静态多态)
同一作用域内,同名不同参。
参数个数不同
参数类型不同
和虚函数无关
2. 重写 Override(动态多态)
父子类,虚函数、完全同名同参。 实现运行多态。
3. 隐藏 Hide(最坑)
父子类 同名非虚函数,或者 参数不一致的虚函数。
特点:
不触发多态
子类函数 隐藏 父类函数
父类指针调用永远走父类版本,不会走子类
三者一句话区分
重载:同域同名不同参
重写:父子同名同参 + virtual(多态)
隐藏:父子同名但不满足重写条件(无多态)
五、虚函数底层机制(纯原理)
1. 虚函数表 vtable
每个拥有虚函数 / 继承虚函数的类,会生成一张虚函数表。 表中存放:虚函数的真实地址
2. 虚指针 vptr
每个对象内部会隐藏一个 vptr(虚指针) 指向当前类的虚函数表
3. 运行过程
对象创建时自动初始化 vptr
调用虚函数时,不看指针类型,看对象真实 vptr
通过 vtable 找到真实函数地址调用
所以:父类指针、子类对象 → 调用子类方法
六、虚析构函数(核心必懂)
1. 问题场景
当父类指针 delete 子类对象:
如果析构不是虚函数,只会调用父类析构,不会调用子类析构
导致子类内存泄漏、资源没释放
2. 虚析构作用
保证通过父类指针删除子类对象时,先调用子类析构,再调用父类析构。
3. 规则
只要类可能被继承,并且会用父类指针删子类,析构必须 virtual
虚析构不需要重写,自动链式调用
虚析构不影响普通虚函数
4. 正确写法
class Base {
public:
virtual ~Base() = default;
};
七、重点:为什么没有「虚构造函数」?
结论:C++ 不存在虚构造函数
原因(透彻解释)
虚函数依赖 vptr,vptr 依赖对象
构造函数执行时,对象还没创建完成
对象没完成 → 没有 vptr → 无法动态绑定
多态的本质是:已有对象,再判断类型 构造函数是:正在创建对象,不可能多态
补充
构造函数执行期间,不存在多态
构造函数中调用虚函数,只会调用本类版本,不会触发子类重写
八、构造、析构中的虚函数行为(非常重要)
1. 构造函数里调用虚函数
不会多态,永远执行当前正在构造的类的函数
原因: 子类还没构造,vptr 此时指向父类 vtable
2. 析构函数里调用虚函数
不会多态 子类已经析构完毕,vptr 已经被切回父类 vtable
总结
构造、析构过程中,虚函数失效,无多态。
九、纯虚函数 & 抽象类
1. 纯虚函数写法
virtual void func() = 0;
2. 抽象类
包含至少一个纯虚函数的类,无法实例化。
3. 作用
定义统一接口
强制子类必须重写该函数
不需要父类实现
4. 继承规则
子类必须实现所有纯虚函数,否则子类也是抽象类,无法 new
十、多态完整终版总结(个人总结)
虚函数动态绑定(运行期),实现多态
父类指针指向子类对象,虚函数调用子类实现
重写必须同名同参 + virtual,否则变成隐藏
父类析构必须 virtual,防止父类指针删子类导致泄漏
没有虚构造函数,构造阶段对象不完整,无法多态
构造、析构内部调用虚函数不会触发多态
纯虚函数用于定义接口、强制子类实现
十一、隐藏、重写、重载 对照表
重载:同作用域、同名不同参、静态绑定、无 virtual
隐藏:父子同名、无virtual、无多态
重写:父子同名同参 virtual、动态绑定、多态生效