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;
  • &bB*
  • (void***)&b → 告诉编译器:把这个对象当作“指向 void** 的指针的指针”。
  • 解引用 pvptr*pvptrvoid**,就是 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,如果你不传它,内部访问成员时就会用到一个错误的地址。

posted @ 2025-08-01 20:07  xiaoluosibky  阅读(31)  评论(0)    收藏  举报