一、多态核心概念
1. 什么是多态?
同一个行为,不同对象有不同实现 。父类引用 / 指针 指向 子类对象,调用函数时,执行子类重写的版本。
2. 多态价值
- 降低耦合,代码高扩展
- 父类统一接口,子类自由实现
- 新增子类无需修改原有业务代码,符合开闭原则
3. 实现多态的三大必要条件
- 必须存在继承关系
- 父类函数必须加 virtual 虚函数
- 父类指针 / 引用 指向 子类对象(向上转型)
二、静态绑定 & 动态绑定
- **静态绑定(早绑定)**编译阶段确定函数地址,普通成员函数默认都是静态绑定。
- 动态绑定(晚绑定) 运行阶段根据真实对象类型 匹配函数,虚函数触发动态绑定 → 产生多态。
三、虚函数 virtual 基础语法
父类声明虚函数,子类重写该函数,即可构成多态。
virtual 返回值 函数名(参数);
完整多态演示
#include <iostream>
using namespace std;
// 父类
class Animal
{
public:
// 虚函数
virtual void speak()
{
cout << "动物发出声音" << endl;
}
};
// 子类Dog
class Dog : public Animal
{
public:
// 重写虚函数
void speak()
{
cout << "小狗汪汪叫" << endl;
}
};
// 子类Cat
class Cat : public Animal
{
public:
void speak()
{
cout << "小猫喵喵叫" << endl;
}
};
向上转型 + 多态调用
// 父类指针指向子类对象
void doSpeak(Animal *a)
{
a->speak();
}
int main()
{
Dog d;
Cat c;
doSpeak(&d);
doSpeak(&c);
return 0;
}
输出:
小狗汪汪叫
小猫喵喵叫
同一接口 speak(),不同子类不同表现,多态生效。
四、函数重写(override)规则
-
子类函数与父类函数名、参数、返回值完全一致
-
父类必须是 virtual 虚函数
-
子类可加
override关键字显式标记,编译器校验void speak() override;
重写 / 重载 / 隐藏 三者区分
- 重载:同类中,同名不同参,编译绑定
- 隐藏:继承中,子类同名函数屏蔽父类,无 virtual
- 重写:继承 + 虚函数,运行绑定,实现多态
五、向上转型 & 向下转型
1. 向上转型(多态核心,常用)
子类对象 赋值给 父类指针 / 引用语法安全、自动转换,多态全部依赖它。
Animal* a = new Dog;
2. 向下转型(不安全,少用)
父类指针强制转回子类,需要手动强转,容易越界。
Dog* d = (Dog*)a;
六、虚析构函数(工程必踩坑)
当父类指针指向子类堆对象,delete 释放时:
- 若析构非虚:只调用父类析构,子类资源不释放 → 内存泄漏
- 父类加
virtual虚析构:先子类析构,再父类析构
标准写法
class Animal
{
public:
virtual ~Animal(){}
};
只要类中有虚函数,必须把析构写成虚析构。
七、纯虚函数 & 抽象类
1. 纯虚函数语法
virtual void func() = 0;
2. 抽象类
包含至少一个纯虚函数的类,特点:
- 无法实例化对象
- 只做父类,定义统一接口
- 子类必须重写所有纯虚函数,否则子类也是抽象类
示例
class Shape
{
public:
// 纯虚函数
virtual void getArea() = 0;
};
// 圆形子类必须实现纯虚函数
class Circle : public Shape
{
public:
void getArea() override
{
cout << "计算圆形面积" << endl;
}
};
八、多态底层简单理解
- 含有虚函数的类,内部会生成虚函数表 (vtable)
- 对象自带虚表指针 (vptr)
- 运行时通过虚表指针,找到真实子类的函数地址
- 实现运行时动态绑定
九、高频易错点
- 忘了写
virtual,只会触发隐藏,没有多态 - 重写时参数 / 返回值不一致,不构成重写
- 父类指针 delete 子类对象,未写虚析构 → 内存泄漏
- 抽象类直接实例化对象,编译报错
- 混淆重载、重写、隐藏三种概念
十、今日核心总结
- 多态三要素:继承 + 虚函数 + 向上转型
- virtual 开启动态绑定,运行时决定调用哪个函数
- 子类重写虚函数,实现个性化逻辑
- 有虚函数必须搭配虚析构
- 纯虚函数 → 抽象类,用于定义标准接口