【C++】虚函数是什么?为什么需要它?

虚函数运行原理、“多态”如何实现(vtable 虚表机制),是 C++ 面向对象底层非常重要的知识点。


1. 虚函数是什么?为什么需要它?

  • 虚函数允许用父类指针/引用调用“子类自己的实现”。
  • 这就是多态(Polymorphism):同一个接口,不同的实现,运行时动态决定实际调用哪个函数。

2. 多态代码举例

class Base {
public:
    virtual void Print() { std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
    void Print() override { std::cout << "Derived" << std::endl; }
};

Base* p = new Derived();
p->Print(); // 输出 "Derived",而不是 "Base"

如果没有virtual,输出就是"Base"。有了virtual,输出"Derived"。


3. vtable(虚表)实现机制

什么是 vtable?

  • vtable(虚函数表)是编译器为每个有虚函数的类生成的一个“函数指针数组”
  • 每个对象实例里会有一个指向vtable的隐藏指针(通常叫vptr)。
  • vtable表里存放着该类所有虚函数的“实际实现函数地址”。

具体原理:

  • 当你通过父类指针/引用调用虚函数时,
  • 程序会通过对象里的vptr找到正确的vtable,然后调用vtable里实际存的子类实现地址
  • 所以最终无论用哪个指针,都会自动调到“真正的函数实现”。

4. 运行流程图解

  1. 声明类(有virtual函数)

  2. 编译时

    • 编译器为每个类生成vtable(如果有虚函数)。
    • 对象里自动加vptr,指向对应类的vtable。
  3. 运行时

    • 用基类指针/引用操作时,走vptr->vtable->实际函数地址,自动完成多态分发。

5. vtable/vptr的“模拟”代码

假设有:

class Base { virtual void f(); };
class Derived : public Base { void f() override; };

编译器背后大致做了这样的事:

typedef void(*FunPtr)();

struct vtable_Base {
    FunPtr f;
};

struct vtable_Derived {
    FunPtr f;
};

struct Base {
    vtable_Base* vptr;
};

struct Derived {
    vtable_Derived* vptr;
};

当你调用 p->f(); 时,其实是:

p->vptr->f(p);

(p作为this指针传进去)


6. 小结和常见面试点

  • 多态的底层原理: 靠虚函数表(vtable)+ 每个对象的vptr隐藏指针来实现。

  • 作用: 让“父类指针/引用指向子类对象”时,调用的是子类自己的实现。

  • 代价:

    • 每个对象多一个指针(vptr)。
    • 虚函数调用需要查表,比普通函数指针多一点点性能损耗,但几乎可以忽略。
  • 其它说明:

    • 不用virtual时,没有vtable/vptr,不支持多态。

7. 简单动图理解

Base* p = new Derived();
p->Print();

-----   内存结构大致如下 -----

p
│
└───> [vptr] ───> vtable (Derived类)
                        │
                        └──> Derived::Print
  • 编译时写成p->Print(),实际运行时会查vptr和vtable,执行Derived::Print()。

8. 调试技巧

  • sizeof 看类大小,含虚函数的类多一个指针(32位下4字节,64位下8字节)。
  • 可以用 GDB 查看对象的vptr和vtable。

9. 一张表对比

普通函数 虚函数
编译时绑定 运行时绑定
没有vtable/vptr 有vtable/vptr
没有多态 支持多态

结论

  • “虚函数 + vtable + vptr”= C++多态本质!
  • 让 父类指针/引用调用到正确的子类方法,灵活解耦、高级抽象

你可能感兴趣的:(C++,c++,java,开发语言)