虚函数必须在基类用virtual声明,子类重写可加override;私有虚函数不可重写,构造函数不能为虚,析构函数建议为虚;纯虚函数使类成抽象类;多态仅通过基类指针或引用调用时生效。

虚函数声明必须加 virtual 关键字
不加 virtual 就不是虚函数,哪怕子类写了同名函数也不会触发动态绑定。基类里声明一次就够了,子类重写时 virtual 可加可不加(C++11 起推荐加 override)。
常见错误:只在子类加 virtual,基类没加——这时调用仍是静态绑定,ptr->func() 永远执行基类版本。
- 基类函数必须是
public或protected,私有虚函数无法被子类访问和重写 - 构造函数不能是虚函数;析构函数建议声明为虚函数(尤其当基类指针可能 delete 派生类对象时)
- 纯虚函数写法是
virtual void func() = 0;,含纯虚函数的类变成抽象类,不能实例化
必须通过指针或引用调用才体现多态
直接用对象变量调用(如 Base b; b.func();)永远走静态绑定,跟虚函数无关。只有通过基类指针或引用指向派生类对象时,运行时才能决定调用哪个版本。
示例:
立即学习“C++免费学习笔记(深入)”;
class Base {
public:
virtual void say() { std::cout << "Base\n"; }
};
class Derived : public Base {
public:
void say() override { std::cout << "Derived\n"; }
};
<p>Base b;
Derived d;
b.say(); // 输出 Base —— 静态绑定
d.say(); // 输出 Derived —— 静态绑定(对象类型明确)
Base* ptr = &d;
ptr->say(); // 输出 Derived —— 动态绑定,关键在这里
override 是安全重写的必要保障
不写 override 也能编译通过,但一旦函数签名(参数、const、返回值)和基类不一致,就变成新函数而非重写,虚函数机制失效,且无警告。
- 加
override后,编译器会严格检查是否真正在重写某个虚函数,签名错、拼写错、const 不匹配都会报错 - 即使基类用的是
virtual,子类也应统一用virtual void func() override,更清晰、更安全 - 不要依赖 IDE 的“自动补全”来加
override,手动敲一遍能帮你确认签名是否真的一致
虚函数表(vtable)开销很小,但别滥用
每个含虚函数的类有一个隐式 vtable,每个对象带一个 vptr(通常 8 字节),调用虚函数比普通函数多一次间接寻址。对绝大多数业务逻辑毫无感知,但高频实时场景(如游戏内循环每帧调用上万次)需留意。
- 如果类确定不会被继承,就别加
virtual—— 不仅省空间,还能让编译器做更多优化(比如内联) - 虚函数不能是
inline(语法允许但实际无效),因为内联发生在编译期,而虚调用在运行期决定 - 模板函数不能是虚函数,二者机制冲突;需要类似能力时,考虑策略模式 + 类型擦除等替代方案
虚函数本身很简单,难的是判断「这里到底该不该用」——接口设计意图模糊、继承层次过深、或者把虚函数当成“方便扩展”的默认选项,反而会让后续维护更吃力。


















