目录
[override 是"保险丝"](#override 是“保险丝”)
[1. final 类:不能再有子类](#1. final 类:不能再有子类)
[2. final 虚函数:不能再被重写](#2. final 虚函数:不能再被重写)
[四、override + final 组合使用](#四、override + final 组合使用)
[五、与 virtual 的关系](#五、与 virtual 的关系)
[1. 忘记写 override,签名错误悄悄隐藏](#1. 忘记写 override,签名错误悄悄隐藏)
[2. 把 final 写在类名后面而不是前面](#2. 把 final 写在类名后面而不是前面)
[3. 在不是虚函数的函数上使用 override/final](#3. 在不是虚函数的函数上使用 override/final)
一、一个古老的痛点
先看这段代码,找找bug:
cpp
class Base {
public:
virtual void draw(int x, int y) const;
};
class Circle : public Base {
public:
void draw(int x, int y) { // 想重写,但忘了const
// 画圆的代码
}
};
问题:Circle::draw 没有 const,参数虽然都是 (int, int),但签名不同------因为成员函数的 const 也是签名的一部分。
结果:这不是重写,而是隐藏 。Base::draw 被藏起来了,多态失效。编译器完全不会警告,bug悄悄溜进代码。
C++11的 override 解决了这个问题。
二、override:明确标记"我要重写"
语法
cpp
class Circle : public Base {
public:
void draw(int x, int y) const override { // 加override
// 画圆的代码
}
};
效果
-
签名正确:正常编译,表明这是个重写
-
签名错误:编译错误,告诉你哪里不匹配
cpp
class Base {
public:
virtual void draw(int x, int y) const;
virtual void resize(double factor);
};
class Circle : public Base {
public:
void draw(int x, int y) override; // ❌ 错误:缺少const
void draw(int x, int y) const override; // ✅ 正确
void resize(int factor) override; // ❌ 错误:参数类型应该是double
};
override 是"保险丝"
override 不改变函数的行为------它只是让编译器帮你验证 。如果去掉 override,代码可能仍然能编译(只是变成了隐藏),加上 override 就把隐患暴露了。
多个基类的重写
在多继承中,override 同样有效:
cpp
class Drawable {
public:
virtual void draw() = 0;
};
class Scalable {
public:
virtual void scale(double factor) = 0;
};
class Shape : public Drawable, public Scalable {
public:
void draw() override; // 重写 Drawable::draw
void scale(double f) override; // 重写 Scalable::scale
};
三、final:到此为止
final 有两个用途:禁止类被继承 、禁止虚函数被重写。
1. final 类:不能再有子类
cpp
class Base final { // 这个类不能被继承
// ...
};
class Derived : public Base { // ❌ 编译错误!Base是final的
};
使用场景:
-
设计意图就是叶子类,不希望有人扩展
-
安全原因:防止意外继承破坏不变量
-
性能优化:编译器可以更激进地去虚拟化
2. final 虚函数:不能再被重写
cpp
class Base {
public:
virtual void func() final; // 派生类不能再重写func
};
class Derived : public Base {
public:
void func() override; // ❌ 编译错误!func是final的
};
使用场景:
-
基类提供了"终极"实现,不允许派生类改变
-
框架设计中的模板方法模式:某些步骤固定,不允许子类干预
示例:模板方法模式中用final固定骨架
cpp
class GameAI {
public:
// 模板方法:骨架是final的,不让子类改变流程
virtual void turn() final {
collectResources();
buildUnits();
attack();
}
protected:
virtual void collectResources() = 0;
virtual void buildUnits() = 0;
virtual void attack() = 0;
};
class OrcAI : public GameAI {
protected:
void collectResources() override { /* 兽人的采集 */ }
void buildUnits() override { /* 兽人的建造 */ }
void attack() override { /* 兽人的攻击 */ }
// turn() 不能被重写,流程固定
};
四、override + final 组合使用
可以同时使用 override 和 final:
cpp
class Base {
public:
virtual void func();
};
class Derived : public Base {
public:
void func() override final; // 重写Base的func,并且禁止进一步重写
};
class GrandChild : public Derived {
public:
void func() override; // ❌ 错误!Derived::func是final的
};
含义:这个函数是对基类的重写,同时告诉后代"不要再重写我了"。
五、与 virtual 的关系
virtual、override、final 的使用规则:
| 关键字 | 必须出现在 | 作用 |
|---|---|---|
virtual |
基类中首次声明 | 声明函数为虚函数 |
override |
派生类中重写函数 | 检查是否正确重写 |
final |
派生类中(类或函数) | 禁止进一步重写或继承 |
常见组合:
cpp
// 基类:只用virtual
class Base {
virtual void f1();
virtual void f2();
virtual void f3();
};
// 派生类:override + 可选的final
class Derived : public Base {
void f1() override; // 普通重写
void f2() override final; // 重写且禁止再重写
void f3() final; // 可以省略override(但不推荐)
};
最佳实践 :派生类中重写虚函数时,永远写上 override。它不改变行为,但能捕获大多数签名错误。
六、完整例子:图形系统中的应用
cpp
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
// 基类:形状接口
class Shape {
public:
virtual void draw() const = 0;
virtual void resize(double factor) = 0;
virtual ~Shape() = default;
};
// 圆形:可以进一步被继承
class Circle : public Shape {
protected:
double radius;
public:
Circle(double r) : radius(r) {}
void draw() const override {
cout << "画一个半径为 " << radius << " 的圆" << endl;
}
void resize(double factor) override {
radius *= factor;
}
};
// 带边界的圆形:重写draw,但不能再被继承
class BorderedCircle final : public Circle {
private:
double borderWidth;
public:
BorderedCircle(double r, double bw) : Circle(r), borderWidth(bw) {}
void draw() const override final { // final阻止进一步重写
cout << "画一个半径为 " << radius
<< "、边框宽 " << borderWidth << " 的圆" << endl;
}
// resize 继承了Circle的版本,没有被重写
};
// 错误的示范:试图继承final类
// class BadCircle : public BorderedCircle {}; // ❌ 编译错误
// 普通矩形:可以被继承
class Rectangle : public Shape {
protected:
double w, h;
public:
Rectangle(double width, double height) : w(width), h(height) {}
void draw() const override {
cout << "画一个 " << w << "x" << h << " 的矩形" << endl;
}
void resize(double factor) override {
w *= factor;
h *= factor;
}
};
// 正方形:重写resize,但resize是final
class Square final : public Rectangle {
public:
Square(double side) : Rectangle(side, side) {}
void resize(double factor) override final {
w *= factor;
h = w; // 保持正方形
cout << "正方形缩放,新边长: " << w << endl;
}
// 不能进一步重写resize
};
int main() {
vector<unique_ptr<Shape>> shapes;
shapes.push_back(make_unique<Circle>(5.0));
shapes.push_back(make_unique<BorderedCircle>(3.0, 0.5));
shapes.push_back(make_unique<Rectangle>(4.0, 6.0));
shapes.push_back(make_unique<Square>(4.0));
cout << "=== 初始绘制 ===" << endl;
for (const auto& s : shapes) {
s->draw();
}
cout << "\n=== 缩放后 ===" << endl;
for (auto& s : shapes) {
s->resize(1.5);
s->draw();
}
return 0;
}
输出:
text
=== 初始绘制 ===
画一个半径为 5 的圆
画一个半径为 3、边框宽 0.5 的圆
画一个 4x6 的矩形
画一个 4x4 的矩形
=== 缩放后 ===
画一个半径为 7.5 的圆
画一个半径为 4.5、边框宽 0.5 的圆
画一个 6x9 的矩形
正方形缩放,新边长: 6
画一个 6x6 的矩形
七、三个常见错误
1. 忘记写 override,签名错误悄悄隐藏
cpp
class Base {
public:
virtual void process(int x) const;
};
class Derived : public Base {
public:
void process(int x) { // 忘了const,没有override
// 这是隐藏,不是重写!
}
};
加上 override 就能发现错误。
2. 把 final 写在类名后面而不是前面
cpp
final class MyClass {}; // ❌ 错误
class MyClass final {}; // ✅ 正确
3. 在不是虚函数的函数上使用 override/final
cpp
class MyClass {
public:
void func() override; // ❌ 错误:基类没有对应的虚函数
void foo() final; // ❌ 错误:不是虚函数不能用final
};
八、现代C++最佳实践
-
任何重写虚函数的地方,都写上
override-
提高代码可读性(一眼看出是重写)
-
编译器帮你检查签名
-
-
叶子类(没有子类的类)考虑标记
final-
表达设计意图
-
可能让编译器优化(去虚拟化)
-
-
模板方法模式中的骨架函数可以标记
final- 防止子类破坏算法流程
-
不要在不需要的地方滥用
final-
过度使用
final会限制扩展性 -
除非确定不需要继承,否则保持开放
-
九、这一篇的收获
你现在应该理解:
-
override:标记重写函数,让编译器检查签名匹配,避免隐藏bug -
final:可以放在类后(禁止继承)或虚函数后(禁止重写) -
组合使用 :
override final表示"重写并禁止进一步重写" -
最佳实践 :重写时永远写
override,叶子类考虑final
💡 小作业:找一个你以前写的继承关系的代码,给所有重写的虚函数加上
override。故意改错一个签名(比如去掉const、改参数类型),观察编译器的报错信息。尝试把某个叶子类标记为final,然后试图继承它,看看报错。
下一篇预告 :第21篇《运算符重载基础:语法、规则与不可重载的运算符》------结束继承章节,进入运算符重载。C++允许你为自定义类型定义运算符的行为,比如让两个矩阵用 + 相加。哪些运算符可以重载?哪些不行?有什么规则?下篇开始。