一、虚函数核心概念框架

1.1 虚函数定义

虚函数是通过virtual关键字声明的成员函数,允许派生类重写基类的行为。其本质是为实现运行时多态服务,通过动态绑定机制,在程序运行时决定调用哪个类的函数实现。

类比理解:

想象一个图书馆管理系统,每个书架都有一个统一的借书接口。当借书时,系统根据实际书架类型( Hardcover/Book/Reference)选择对应的借书规则。

1.2 多态实现四要素

  1. 基类指针/引用
  2. 虚函数声明
  3. 派生类重写
  4. 动态绑定调用

关键概念:虚函数定义、多态特性、静态与动态绑定差异

示例

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; }
};

五、虚函数使用注意事项

  1. 性能与内存开销:虚函数调用慢,存在 vtable 和 vptr 开销
  2. 继承规则:重写需保持签名一致,私有虚函数可重写但基类指针无法直接调用
  3. 特殊场景:模板成员函数非虚,构造函数中调用虚函数不动态绑定,析构函数应声明为虚函数

六、虚函数应用场景

用于框架设计、插件系统、回调机制、状态模式、策略模式等。如策略模式:

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(); // 调用Dog的实现
a = new Cat();
a->speak(); // 调用Cat的实现

动态性说明:虚函数表是与对象绑定的,不同对象可能有不同的虚函数表地址