一、虚函数核心概念框架
1.1 虚函数定义
虚函数是通过virtual
关键字声明的成员函数,允许派生类重写基类的行为。其本质是为实现运行时多态服务,通过动态绑定机制,在程序运行时决定调用哪个类的函数实现。
类比理解:
想象一个图书馆管理系统,每个书架都有一个统一的借书接口。当借书时,系统根据实际书架类型( Hardcover/Book/Reference)选择对应的借书规则。
1.2 多态实现四要素
- 基类指针/引用
- 虚函数声明
- 派生类重写
- 动态绑定调用
关键概念:虚函数定义、多态特性、静态与动态绑定差异
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <iostream> using namespace std;
class Animal { public: virtual void speak() { cout << "Animal speak" << endl; } };
class Dog : public Animal { public: void speak() override { cout << "Dog barks" << endl; } };
class Cat : public Animal { public: void speak() override { cout << "Cat meows" << endl; } };
int main() { Animal* animals[2]; animals[0] = new Dog(); animals[1] = new Cat(); for (int i = 0; i < 2; ++i) { animals[i]->speak(); // 动态绑定实现多态 } delete animals[0]; delete animals[1]; return 0; }
|
二、虚函数表实现原理
每个包含虚函数的类都有一个隐藏的虚函数表,存储了该类所有虚函数的地址。
C++ 通过虚函数表(vtable)实现动态绑定:每个含虚函数的类有对应的 vtable 存储函数地址,对象含指向 vtable 的指针(vptr),派生类重写时更新 vtable 地址,调用虚函数时通过 vptr 找到 vtable 执行。
多继承下,派生类有多个 vtable,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <iostream> class Base1 { public: virtual void func1() { std::cout << "Base1::func1()" << std::endl; } virtual void func2() { std::cout << "Base1::func2()" << std::endl; } }; class Base2 { public: virtual void func1() { std::cout << "Base2::func1()" << std::endl; } virtual void func3() { std::cout << "Base2::func3()" << std::endl; } }; class Derived : public Base1, public Base2 { public: virtual void func1 () { std::cout << "Derived::func1 ()" << std::endl; } virtual void func4 () { std::cout << "Derived::func4 ()" << std::endl; } }; int main() { Derived d; Base1* b1 = &d; Base2* b2 = &d; b1->func1(); b2->func1(); b1->func2(); b2->func3(); Derived* dPtr = &d; dPtr->func4(); return 0; }
|
三、虚析构函数
基类指针删除派生类对象时,基类析构函数非虚会导致派生类析构函数不调用,造成资源泄漏。应将基类析构函数声明为虚函数:
1 2 3 4 5 6 7 8 9
| class Base { public: virtual ~Base() {} // 必须声明虚析构函数 };
class Derived : public Base { public: ~Derived() override { /* ... */ } };
|
若未声明虚析构函数,delete基类指针时只会调用基类析构函数,导致资源泄漏
四、纯虚函数与抽象类
纯虚函数声明时加= 0,无实现,含纯虚函数的类为抽象类,不能实例化,用于定义接口。
1 2 3 4 5 6 7 8 9 10
| class Shape { public: virtual double area () const = 0; virtual double perimeter () const = 0; }; class Circle : public Shape { public: double area () const override { return 3.14159 * radius * radius; } double perimeter() const override { return 2 * 3.14159 * radius; } };
|
五、虚函数使用注意事项
- 性能与内存开销:虚函数调用慢,存在 vtable 和 vptr 开销
- 继承规则:重写需保持签名一致,私有虚函数可重写但基类指针无法直接调用
- 特殊场景:模板成员函数非虚,构造函数中调用虚函数不动态绑定,析构函数应声明为虚函数
六、虚函数应用场景
用于框架设计、插件系统、回调机制、状态模式、策略模式等。如策略模式:
1 2 3 4 5 6 7 8 9 10
| class SortStrategy { public: virtual void sort (std::vector<int>& data) = 0; }; class BubbleSort : public SortStrategy { public: void sort (std::vector<int>& data) override { // 冒泡排序实现 } };
|
七、进阶实践指南
7.1 虚函数重载规则
1 2 3 4 5 6 7 8 9 10 11
| class Base { public: virtual void show(int) { } virtual void show(double) { } };
class Derived : public Base { public: void show(int) override { } void show(double) override { } };
|
- 虚函数的重载需要相同函数名但不同参数
- 多态调用需要完全匹配的参数类型
7.2 虚函数表的动态修改
1 2 3 4
| Animal* a = new Dog(); a->speak(); a = new Cat(); a->speak();
|
动态性说明:虚函数表是与对象绑定的,不同对象可能有不同的虚函数表地址