虚函数调用基于对象实际类型通过虚表动态绑定,与指针类型无关;子类未重写则沿用继承链最近实现;纯虚函数须由子类实现,否则无法实例化;强制调用基类版本需显式作用域限定;构造/析构中虚表未就绪,此时调用虚函数不触发多态。

虚函数调用走的是运行时动态绑定,不是看指针类型而是看对象实际类型
编译器在生成代码时,对 virtual 函数的调用不会直接跳转到某个固定地址,而是通过对象的虚表(vtable)查表找函数地址。这个虚表指针(vptr)在对象构造时就被初始化为对应类型的虚表地址——所以哪怕你用 Base* 指向一个 Derived 对象,调用 ptr->func() 仍会执行 Derived::func()。
关键点在于:虚表属于对象本身,不是指针;指针只负责提供访问入口。
子类没重写虚函数时,调用的是继承链上最近的那一个实现
如果 Derived 没有定义 func(),而 Base 有,那就调用 Base::func();如果 Base 是纯虚函数(virtual void func() = 0;),但 Derived 也没实现,那程序链接失败或运行时报纯虚函数调用错误(Pure virtual function call)。
-
Base声明virtual void f();→ 必须提供定义,否则子类不重写就调用它 -
Base声明virtual void f() = 0;→ 子类必须实现,否则无法实例化 - 多层继承(
Base → Mid → Derived)中,只要某一层实现了该虚函数,且更下层没覆盖,就沿用那一层的实现
强制调用基类实现要显式写作用域,不能靠指针类型“降级”
很多人误以为把 Derived* 转成 Base* 就能调用 Base::func(),其实不会——只要函数是 virtual,还是走虚表。真想调基类版,得手动指定:
立即学习“C++免费学习笔记(深入)”;
Derived d; Base* ptr = &d; ptr->func(); // 还是调 Derived::func() d.Base::func(); // ✅ 强制调 Base 版本 ptr->Base::func(); // ✅ 同样有效,作用域解析不依赖指针静态类型
注意:Base::func() 是编译期确定的,不查虚表,也不受对象实际类型影响。
虚函数表布局和析构函数的特殊性容易引发隐性问题
虚函数调用看似简单,但两个地方特别容易踩坑:
- 对象正在构造或析构时,虚表指针处于中间状态:构造
Derived时先调Base构造函数,此时vptr指向Base的虚表;等Base构造完才切到Derived的虚表。所以在Base构造函数里调虚函数,永远得不到Derived的重写版本 - 析构函数要是虚的(推荐总是声明为
virtual ~Base() = default;),否则通过Base*删除Derived对象会导致未定义行为——不是虚函数调用错,而是派生部分根本没被析构
虚函数机制本身很稳定,但它的生命周期依赖对象完整状态;一旦在构造/析构中途介入,行为就脱离预期。


















