c++ 虚函数
虚函数原理
包含虚函数类的构成
1. 前提
- 类
B有虚函数 → 编译器会在对象中生成一个 虚表指针(vptr)。 - 这个
vptr指向一张 虚函数表(vtable)。 - vtable 里存的是 函数指针(每个虚函数对应一个地址)。
2. 内存结构示意
假设:
class B {
public:
virtual void f1() {}
virtual void f2() {}
};
B b;
大致内存布局:
+-------------------+
| vptr -> vtable ---+----> [ &B::f1, &B::f2, ... ]
+-------------------+
| 其他成员变量 |
+-------------------+
vptr:类型上是void**(指向函数指针数组的指针)。vtable:类型上是void*[](函数指针数组,每个元素是void*)。- 每个
void*存的是函数地址。
(1)把对象地址转成 void***
void*** pvptr = (void***)&b;
&b→B*(void***)&b→ 告诉编译器:把这个对象当作“指向void**的指针的指针”。- 解引用
pvptr:*pvptr→void**,就是vptr(虚表指针)。
b 是一个类对象 对b解引用得到b的地址
我们使用一个指针变量
pvptr接收这个地址这个指针变量所指向的地址的变量类型为
void**
(2)取出 vtable 地址
void** vtable = *pvptr;
vtable是一个指向函数指针数组的指针(void**)。vtable[0]→ 第一个虚函数地址(f1)。vtable[1]→ 第二个虚函数地址(f2)。
对得到的指针变量
pvptr进行解引用得到一个指向 函数指针数组的 指针(因为虚函数表是一个函数指针数组)
使用函数指针变量
vtable对得到的地址进行接收
vtable指针指向的地址存储的是一个函数指针数组
(3)调用虚函数
using Fn = void(*)(B*); // 函数类型
Fn f1 = (Fn)vtable[0]; // 取第一个虚函数
f1(&b); // 调用 f1
将得到的函数指针数组的元素取出,并将其强转为函数类型
这就相当于:
b.f1();
只是我们是直接从内存里读出来的函数地址来调用。
为什么f1(&b);要传入&b?
1. 虚函数其实就是普通成员函数
在底层,成员函数(包括虚函数)跟普通函数不同的一点是:
- 编译器会 隐式地 传入一个
this指针,指向当前对象。
例如:
class B {
public:
virtual void f1() {}
};
编译器内部会把它处理成类似:
void B_f1(B* this) { /* ... */ }
也就是说,它其实就是一个普通的全局函数,只不过第一个参数是 B* this。
2. vtable 里存的也是这种函数地址
所以:
vtable[0]存的函数地址就是类似void(B*)的函数。- 当我们手动取出来调用时,必须自己传入这个
this指针。 &b就是this。
3. 如果不传 &b 会怎样?
cpp复制编辑using Fn = void(*)(B*);
Fn f1 = (Fn)vtable[0];
f1(); // ❌ 缺少 this,调用会崩溃
因为函数体里会用到 this,如果你不传它,内部访问成员时就会用到一个错误的地址。

浙公网安备 33010602011771号