一、虚函数(Virtual Function)
1. 基本概念
- 虚函数是 C++ 中实现 动态多态(运行时多态)的核心机制。
- 通过
virtual
关键字声明,允许派生类 重写(override) 基类的函数实现。 - 虚函数的存在使得基类指针或引用可以调用 实际对象类型 的函数。
2. 语法与使用
class Base {
public:
virtual void func() {
cout << "Base::func()" << endl;
}
};
class Derived : public Base {
public:
void func() override { // 使用 override 明确表示重写(C++11 起支持)
cout << "Derived::func()" << endl;
}
};
关键点:
- 基类中声明虚函数时使用
virtual
。 - 派生类重写虚函数时,建议使用
override
关键字(避免拼写错误或签名不匹配)。 - 虚函数可以是成员函数,但不能是静态成员函数或构造函数(析构函数可以是虚函数)。
二、多态(Polymorphism)
1. 多态的类型
-
静态多态(编译时多态):
- 通过 函数重载、模板 或 运算符重载 实现。
- 函数调用在编译时确定。
int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; }
-
动态多态(运行时多态):
- 通过 虚函数 和 继承 实现。
- 函数调用在运行时根据对象的实际类型决定。
Base* ptr = new Derived(); ptr->func(); // 调用 Derived::func()
2. 动态多态的实现原理
- 虚函数表(vtable):
- 每个包含虚函数的类都有一个虚函数表,表中存储该类所有虚函数的地址。
- 例如,
Base
类有虚函数func()
,则其虚函数表包含Base::func
的地址。
- 虚函数指针(vptr):
- 每个对象内部包含一个指向其所属类的虚函数表的指针(vptr)。
- 当派生类重写虚函数时,其虚函数表中对应的函数地址会被替换为派生类的实现。
内存模型示例:
Derived 对象内存布局:
+-------------------+
| vptr (指向 Derived 的 vtable) |
| 其他成员变量... |
+-------------------+
Derived 的 vtable:
+-------------------+
| &Derived::func |
+-------------------+
3. 动态绑定的过程
Base* ptr = new Derived();
ptr->func(); // 运行时根据 ptr 指向的对象的 vptr 找到 vtable,再调用 func()
三、虚函数与多态的核心规则
1. 虚析构函数
-
若类可能被继承,且需要通过基类指针释放派生类对象,基类析构函数必须为虚函数。
否则会导致派生类析构函数未被调用,引发内存泄漏。
示例:class Base { public: virtual ~Base() { cout << "Base destroyed"; } }; class Derived : public Base { public: ~Derived() { cout << "Derived destroyed"; } }; Base* ptr = new Derived(); delete ptr; // 输出:Derived destroyed → Base destroyed
2. 纯虚函数与抽象类
-
纯虚函数:没有默认实现的虚函数,语法为
virtual void func() = 0;
。 -
抽象类:包含纯虚函数的类,不能实例化,仅作为接口定义。
示例:class Shape { public: virtual double area() const = 0; // 纯虚函数 }; class Circle : public Shape { public: double area() const override { return 3.14 * r * r; } };
3. override 与 final 关键字(C++11)
-
override
:明确表示派生类函数重写基类虚函数,编译器会检查签名是否匹配。 -
final
:禁止派生类重写某个虚函数,或禁止某个类被继承。
示例:class Base { public: virtual void func() {} }; class Derived : public Base { public: void func() override final {} // Derived::func 不可被进一步重写 };
四、多态的应用场景
1. 统一接口,多样实现
-
定义基类接口,不同派生类实现具体逻辑(如不同图形计算面积)。
示例:Shape* shapes[] = { new Circle(), new Square() }; for (auto shape : shapes) { cout << shape->area() << endl; // 调用不同子类的 area() }
2. 回调机制
- 通过基类指针传递回调函数,实现事件处理、插件系统等。
3. 工厂模式
- 基类定义创建接口,派生类实现具体对象的创建。
五、注意事项与性能
1. 虚函数开销
- 时间开销:虚函数调用需要间接寻址(通过 vptr 和 vtable),比普通函数调用稍慢。
- 空间开销:每个对象需要存储 vptr,每个类需要存储 vtable。
- 优化建议:避免在性能关键路径中过度使用虚函数。
2. 设计原则
- 优先使用组合而非继承:避免复杂的继承层次。
- 接口隔离:抽象类应定义最小必要接口(参考接口隔离原则)。
六、总结
特性 | 虚函数 | 多态 |
---|---|---|
目的 | 实现运行时动态绑定 | 提供统一的接口,支持多种具体实现 |
核心机制 | 虚函数表(vtable)、虚函数指针(vptr) | 继承 + 虚函数 |
使用场景 | 需要根据对象类型调用不同函数时 | 接口抽象、回调、工厂模式等 |
性能影响 | 有间接调用开销 | 灵活性优先,牺牲少量性能 |
关键点:
- 虚函数是实现动态多态的基础,依赖虚函数表和虚函数指针。
- 多态的核心是 “一个接口,多种实现”,通过基类指针或引用操作派生类对象。
- 合理使用
override
、final
和虚析构函数,是写出健壮代码的关键。