# 虚函数怎么实现的?
# 简要回答
虚函数通过“虚函数表”(vtable)实现,编译器为含有虚函数的类生成一个虚表(vtable),类对象中包含一个虚指针(vptr)指向该表,即虚表。
通过虚表实现运行时的动态绑定,从而支持多态。
# 详细回答
虚函数的实现机制如下: 第一,虚函数表(vtable):每个类有一个虚表,存储该类的虚函数地址列表。
如果某个类有虚函数,编译器就会自动生成这张表。
第二,虚指针(vptr): 每个含虚函数的类对象都有一个隐藏的成员,叫虚指针,指向当前类的虚表,这个指针用于运行时确定函数的真实调用目标。
第三,调用过程: 当通过基类指针或引用调用一个虚函数时,程序会:
先找到对象里的 vptr;
然后根据 vptr 找到对应类的 vtable;
再从 vtable 中取出正确的函数地址并调用它。
第四,动态绑定(多态): 这种通过虚表间接调用函数的方式,就是运行时多态。它让程序在运行时决定调用哪个类的函数,而不是编译时决定。
# 代码示例
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() {
cout << "Base::show()" << endl;
}
};
class Derived : public Base {
public:
void show() override {
cout << "Derived::show()" << endl;
}
};
int main() {
Base* b = new Derived();
b->show(); // 输出:Derived::show()
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
创建 Derived 对象时,它的 vptr 指向 Derived 类的虚表。
当执行 b->show(),实际运行的是 Derived::show(),这是通过 vptr → vtable 间接跳转实现的。
# 知识拓展
- 试试图解

非虚函数调用过程:编译器在编译时就决定调用哪个函数(静态绑定),效率高,但无法实现多态。
虚析构函数:当通过基类指针删除派生类对象时,基类析构函数必须是虚函数,否则不会调用派生类析构函数,可能导致资源泄露。
- 面试官可能追问
vtable 和 vptr 的内存布局是怎样的?
答:vptr 通常是放在对象内存布局的 第一个位置,这样通过对象首地址就能快速访问虚函数表。
多继承时,一个对象可能有 多个 vptr(每个继承链一个),对应多个 vtable。
虚函数和普通函数在调用开销上的不同?
答:虚函数和普通函数在调用机制上的最大区别在于绑定时机和调用方式;
普通函数是在编译时就完成绑定,调用时直接跳转到函数地址,效率较高;
而虚函数是在运行时通过对象中的虚指针(vptr)查找对应的虚函数表(vtable),再从表中找到目标函数地址进行调用,这种多了一次间接寻址的过程导致其调用开销相对较大。
不过,正因为虚函数采用动态绑定,才支持运行时多态,而普通函数不具备这种能力。
简单来说,虚函数的灵活性是以一点性能损耗为代价换来的。
评论
验证登录状态...