override 与 final
更新时间:2026-08-27。本文是
languages/cpp/主题入门层第 29 篇。override是"我确实在重写"的声明,写错签名编译器当场报错;final是"到此为止"的封条,禁止继续重写。两个小关键字,把多态最常见的两类错误在编译期就拦下来。
本文要回答的问题
override解决了什么痛点?final用在哪些地方?- 为什么不写
override也没人拦你?
一、override:把"我以为在重写"变成"确实在重写"
上一篇文章说过,多态依赖"派生类重写基类虚函数"。但"重写"是靠签名一致匹配的——一旦签名写岔了,编译器不吭声,你的函数悄悄变成了一个新函数:
cpp
class Base {
public:
virtual void draw(int x) {}
};
class Derived : public Base {
public:
void draw(double x) { ... } // 本意是重写,但参数类型变了!
};draw(double) 和基类的 draw(int) 签名不同,不是重写——Derived 偷偷多了个新函数。运行时通过基类指针调 draw(int),走的还是基类版本,多态失效,且编译零报错。
override 解决的就是这个:
cpp
class Derived : public Base {
public:
void draw(double x) override { ... } // 编译错误:没有可重写的基类函数
};写上 override,编译器帮你核对:签名对不上基类任何虚函数 → 直接报错。override 是给编译器递的"检查单"。
二、怎么写是对的
cpp
class Base {
public:
virtual void f(int) {}
virtual void g() const {}
virtual ~Base() {}
};
class Derived : public Base {
public:
void f(int) override {} // 正确:重写 f
void g() const override {} // 正确:const 也算签名一部分
// void f(double) override {} // 错误:签名不匹配
};注意点:
- 重写时
virtual可以不写(基类已虚),但写override已成惯例; const是签名的一部分——基类g() const,派生类写g()(忘 const)就不是重写;- 基类析构函数是虚的,派生类析构自动重写它,不用也不能标
override。
三、final:到此为止
final 是"封条",两个用途:
cpp
class Base {
public:
virtual void f() {}
};
class Derived : public Base {
public:
void f() final {} // 1. 函数级:我重写了,但禁止再往下重写
};
// class Sub : public Derived { void f() override {} }; // 错误:f 已 finalcpp
class Sealed final { // 2. 类级:这个类不许被继承
};
// class X : public Sealed {}; // 错误:Sealed 是 final 的函数 final:派生类可以继承/重写到此为止,再往下的子类不能重写它;类 final:整类封死,谁也别想继承。后者常用于"设计上不该有子类"的类型(比如 std::string 的实现,虽然标准库未必这么标)。
四、为什么这两个关键字重要
| 场景 | 不写 override/final | 写了 |
|---|---|---|
| 重写签名写错 | 静默变成新函数,多态失效 | 编译报错 |
| 基类函数签名改动 | 子类悄悄失联 | 子类编译报错,提醒你去改 |
| 设计上禁止重写 | 依赖自觉 | 编译器强制 |
| 类不允许继承 | 依赖文档约定 | 编译器强制 |
本质是把"多态的契约"从运行期风险提前到编译期拦截。面向对象代码维护中,"基类改了,一堆子类悄悄失联"是最让人头疼的问题之一,override 就是这道防线。
五、与本站主线衔接
一句话总结
override 声明"我在重写基类虚函数",签名对不上编译器当场报错,把'写岔签名导致多态静默失效'的坑堵在编译期;final 是封条:函数级禁止再重写、类级禁止再继承;记住 const 属于签名的一部分,重写时别漏。