静态绑定在编译期确定函数调用目标,依据声明类型、函数签名和作用域,适用于非虚函数、重载、模板、static成员、全局函数及构造函数等场景。
静态绑定(也叫早期绑定)指的是编译器在编译阶段就确定了函数调用的目标地址。它不依赖对象的实际类型,只看指针/引用的**声明类型**,以及重载解析、模板实例化、作用域查找等规则。
常见触发场景包括:普通非虚函数调用、函数重载、模板函数、static 成员函数、全局函数、构造函数(除虚基类初始化外)。
constexpr 函数必定静态绑定class Base {
public:
void func() { std::cout << "Base::func\n"; }
virtual void vfunc() { std::cout << "Base::vfunc\n"; }
};
class Derived : public Base {
public:
void func() { std::cout << "Derived::func\n"; } // 隐藏,非重写
void vfunc() override { std::cout << "Derived::vfunc\n"; }
};
Base* ptr = new Derived();
ptr->func(); // 静态绑定 → 调用 Base::func(不是 Derived::func)
ptr->vfunc(); // 动态绑定 → 调用 Derived::vfunc
动态绑定(晚期绑定)只对声明为 virtual 的成员函数生效,且必须通过指针或引用调用(不能是直接对象调用)。它在运行期通过对象的虚函数表(vtable)和虚表指针(vptr)完成函数地址解析。
关键前提有三个:virtual 关键字 + 指针/引用调用 + 对象内存中含有效 vptr(即完整对象已构造完毕)。
Base* ptr = new Derived(); ptr->vfunc(); // 运行时:读取 ptr 所指对象的 vptr → 查 vtable[1] → 跳转到 Derived::vfunc
动态绑定不是“慢”,而是比静态绑定多出两次内存访问:一次读 vptr,一次查 vtable。现代 CPU 的分支预测和缓存能缓解大部分影响,但仍有真实代价:
final 修饰虚函数或类,可让编译器重新启用静态绑定(GCC/Clang 支持)很多初学者以为只要用了指针就是动态绑定,其实不然。以下写法全部是静态绑定:
Base b; b.vfunc(); —— 直接对象调用,无 vptr 参与,哪怕函数是 virtual
Derived d; Base& ref = d; ref.func(); —— func() 非虚,绑定到 Base::func
ptr->Base::vfunc(); —— 显式限定作用域,强制静态绑定virtual 函数 —— 此时派生类部分尚未构造,vptr 指向当前类 vtable真正需要动态行为时,务必确认三点:函数带 virtual、调用表达式左侧是基类指针或引用、右侧对象是完整构造后的派生类实例。