【c++面向对象编程】第20篇:override与final关键字:现代C++对继承的控制

目录

一、一个古老的痛点

二、override:明确标记"我要重写"

语法

效果

[override 是"保险丝"](#override 是“保险丝”)

多个基类的重写

三、final:到此为止

[1. final 类:不能再有子类](#1. final 类:不能再有子类)

[2. final 虚函数:不能再被重写](#2. final 虚函数:不能再被重写)

示例:模板方法模式中用final固定骨架

[四、override + final 组合使用](#四、override + final 组合使用)

[五、与 virtual 的关系](#五、与 virtual 的关系)

六、完整例子:图形系统中的应用

七、三个常见错误

[1. 忘记写 override,签名错误悄悄隐藏](#1. 忘记写 override,签名错误悄悄隐藏)

[2. 把 final 写在类名后面而不是前面](#2. 把 final 写在类名后面而不是前面)

[3. 在不是虚函数的函数上使用 override/final](#3. 在不是虚函数的函数上使用 override/final)

八、现代C++最佳实践

九、这一篇的收获


一、一个古老的痛点

先看这段代码,找找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 组合使用

可以同时使用 overridefinal

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 的关系

virtualoverridefinal 的使用规则:

关键字 必须出现在 作用
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++最佳实践

  1. 任何重写虚函数的地方,都写上 override

    • 提高代码可读性(一眼看出是重写)

    • 编译器帮你检查签名

  2. 叶子类(没有子类的类)考虑标记 final

    • 表达设计意图

    • 可能让编译器优化(去虚拟化)

  3. 模板方法模式中的骨架函数可以标记 final

    • 防止子类破坏算法流程
  4. 不要在不需要的地方滥用 final

    • 过度使用 final 会限制扩展性

    • 除非确定不需要继承,否则保持开放


九、这一篇的收获

你现在应该理解:

  • override:标记重写函数,让编译器检查签名匹配,避免隐藏bug

  • final:可以放在类后(禁止继承)或虚函数后(禁止重写)

  • 组合使用override final 表示"重写并禁止进一步重写"

  • 最佳实践 :重写时永远写 override,叶子类考虑 final

💡 小作业:找一个你以前写的继承关系的代码,给所有重写的虚函数加上 override。故意改错一个签名(比如去掉 const、改参数类型),观察编译器的报错信息。尝试把某个叶子类标记为 final,然后试图继承它,看看报错。


下一篇预告 :第21篇《运算符重载基础:语法、规则与不可重载的运算符》------结束继承章节,进入运算符重载。C++允许你为自定义类型定义运算符的行为,比如让两个矩阵用 + 相加。哪些运算符可以重载?哪些不行?有什么规则?下篇开始。

相关推荐
AI科技星1 小时前
全域数学:从理论到现实的终极落地全记录 光速不变公理(v=c)+ 可见派维度常数公理(D_v=3)统一广义相对论与量子力学,解决物理学百年难题
c语言·开发语言
ch.ju1 小时前
Java程序设计(第3版)第三章——数组的定义方式
java·开发语言
郝学胜-神的一滴1 小时前
Qt 高级开发 004: 三大窗口类深度解析
开发语言·c++·qt·程序人生·系统架构
王老师青少年编程1 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串综合】:[NOIP 2004 普及组] FBI 树
c++·字符串·csp·高频考点·信奥赛·字符串综合·fbi树
楼田莉子1 小时前
Linux网络:多路转接IO
服务器·c++·后端·软件构建
无风听海1 小时前
OAuth 2.0 response_type完全指南
java·开发语言·oauth
Cyan_RA91 小时前
SpringMVC 数据格式化处理 详解
java·开发语言·spring·mvc·ssm·springmvc·数据格式化
测试员周周1 小时前
【Appium 系列】第08节-pytest 集成 — conftest.py 中的 fixture 与 hook
开发语言·人工智能·python·功能测试·appium·测试用例·pytest
Hui_AI7201 小时前
电商桌面自动化实战:用RPA实现抖店批量铺货
运维·开发语言·人工智能·自然语言处理·自动化·开源软件·rpa