C++ 函数重载(Overloading)与函数覆盖(Overriding) 更系统、更深入、更实用,全部基于 现代 C++(C++17/20) 视角,并结合 Qt 项目中的真实实践。

目录

  1. 引言:重载与覆盖的核心区别与价值
  2. 函数重载(Overloading)详解
    2.1 规则与限制
    2.2 现代重载实践(C++11+)
    2.3 Qt 中的重载典型场景
  3. 函数覆盖(Overriding)详解
    3.1 规则与要求
    3.2 现代覆盖实践(override / final / 协变返回)
    3.3 Qt 中的覆盖典型场景
  4. 重载 vs 覆盖对比表(面试必备)
  5. 完整示例代码(VS 可运行 Demo)
  6. 自动化测试用例(Google Test)
  7. 常见陷阱与最佳实践
  8. 总结与进阶建议

1. 引言:重载与覆盖的核心区别与价值

项目 函数重载(Overloading) 函数覆盖(Overriding)
发生位置 同一个作用域(类内、全局、命名空间) 继承关系中(基类 → 派生类)
函数名 必须相同 必须相同
参数列表 必须不同(个数、类型、顺序) 必须完全相同(或协变返回类型)
返回类型 可以不同(但不能仅靠返回类型区分) 必须相同或协变(C++11+ 支持协变返回)
关键字 无需特殊关键字 推荐使用 overridefinal
基类要求 无需 virtual 基类函数必须是 virtual
绑定时机 编译时(静态绑定) 运行时(动态绑定,通过虚表)
目的 同一名字处理不同参数,提高接口复用性 派生类自定义基类行为,实现多态
典型场景 构造函数重载、运算符重载、工具函数 自定义控件绘制、事件处理、模型数据提供

一句话总结

  • 重载:让同一个名字“多才多艺”,编译期决定用哪个版本
  • 覆盖:让子类“改写”父类的行为,运行时决定调用哪个版本

2. 函数重载(Overloading)详解

2.1 规则与限制(现代 C++)
class Printer {
public:
    void print(int x)               { std::cout << "int: " << x << "\n"; }
    void print(double x)            { std::cout << "double: " << x << "\n"; }
    void print(const std::string& s){ std::cout << "string: " << s << "\n"; }

    // 重载解析失败示例(仅返回类型不同不行)
    // int  print(int x);   // 错误!不能仅靠返回类型区分
};

C++11+ 增强

  • 引用限定符重载(& / &&)
  • noexcept 作为重载依据(C++17+)
void foo(std::string& s) &  { std::cout << "左值\n"; }
void foo(std::string&& s) && { std::cout << "右值\n"; }
2.2 Qt 中的重载典型场景

Qt 大量使用函数重载,例如:

QWidget::setGeometry(int x, int y, int w, int h);      // 四个参数
QWidget::setGeometry(const QRect &rect);               // 矩形重载
QWidget::setGeometry(const QRectF &rect);              // 浮点矩形重载

3. 函数覆盖(Overriding)详解

3.1 现代覆盖写法(强烈推荐)
class Base {
public:
    virtual void process() = 0;
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    void process() override final {  // override 强制检查,final 禁止子类再重写
        std::cout << "Derived 处理逻辑\n";
    }
};
3.2 协变返回类型(Covariant Return Types)
class Base {
public:
    virtual Base* clone() const = 0;
};

class Derived : public Base {
public:
    Derived* clone() const override {  // 返回类型协变
        return new Derived(*this);
    }
};
3.3 Qt 中的覆盖典型场景

Qt 中最常见的覆盖场景:

  1. 自定义控件重写事件处理
class MyWidget : public QWidget {
protected:
    void paintEvent(QPaintEvent *event) override {
        QPainter painter(this);
        painter.fillRect(rect(), Qt::blue);  // 自定义绘制
        QWidget::paintEvent(event);          // 调用基类
    }

    void mousePressEvent(QMouseEvent *event) override {
        qDebug() << "鼠标按下";
        QWidget::mousePressEvent(event);
    }
};
  1. 自定义模型重写数据提供
class CustomModel : public QAbstractListModel {
public:
    QVariant data(const QModelIndex &index, int role) const override {
        if (role == Qt::DisplayRole) {
            return QString("Item %1").arg(index.row());
        }
        return QAbstractListModel::data(index, role);
    }
};

4. 完整示例项目:多态形状编辑器(VS/Qt Creator 可运行)

项目名称:ShapeEditor

shape.h(抽象基类)

#pragma once
#include <QObject>
#include <QPainter>

class Shape : public QObject {
    Q_OBJECT
public:
    explicit Shape(QObject *parent = nullptr) : QObject(parent) {}
    virtual ~Shape() = default;

    virtual void draw(QPainter *p) const = 0;
    virtual QString type() const = 0;
};

circle.h(覆盖示例)

#pragma once
#include "shape.h"

class Circle : public Shape {
    Q_OBJECT
public:
    Circle(qreal x, qreal y, qreal r, QObject *parent = nullptr);

    void draw(QPainter *p) const override;
    QString type() const override { return "圆形"; }

private:
    qreal m_x, m_y, m_r;
};

mainwindow.cpp(使用多态)

void MainWindow::on_addCircle_clicked() {
    auto circle = std::make_unique<Circle>(100, 100, 50);
    shapes.push_back(std::move(circle));
    ui->canvas->update();  // 触发 paintEvent
}

void CanvasWidget::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    for (const auto& shape : shapes) {
        shape->draw(&painter);  // 多态调用
    }
}

5. 常见陷阱与最佳实践

陷阱 后果 最佳实践
忘记写 virtual 无法实现多态 基类虚函数必须加 virtual
忘记写 override 误写成隐藏而非覆盖 C++11+ 强制写 override
基类无虚析构 delete 基类指针内存泄漏 所有接口类写 virtual ~Base() = default;
Qt 中信号槽函数未写 Q_OBJECT 信号槽失效 凡是有信号/槽的类必须加 Q_OBJECT
多重继承未处理菱形问题 数据重复、二义性 使用 virtual 继承

6. 总结

在 Qt 中:

  • 继承:构建控件体系(QWidget → QPushButton → MyButton)
  • 多态:实现动态行为(paintEvent、data()、插件接口)
  • 最佳实践:虚函数 + override + 虚析构 + Q_OBJECT

如果您需要:

  • 完整 Qt 项目 zip(含 .pro/CMake、.ui、资源、测试)
  • QML 版本的多态示例(QAbstractListModel + ListView)
  • 插件系统完整实现(动态加载 .dll)
  • 命令模式、装饰器模式、适配器模式的 Qt 实现

以下是对 C++ 模板(Template)与泛型编程(Generic Programming) 的系统、全面、现代(C++20/23 视角)的中文讲解,适合想真正理解并熟练运用模板的开发者。

内容从基础 → 中级 → 高级 → 实战 → 常见误区 → 现代趋势 完整展开。

一、什么是模板?什么是泛型编程?

概念 核心含义 典型代表 出现时间
模板 编译期代码生成机制,允许用“类型参数”编写一次代码,编译时为不同类型生成多份具体实现 template C++98
泛型编程 一种编程范式,强调“算法与数据结构分离”,代码对类型无关,只关心操作行为是否满足要求 STL(vector、algorithm 等) C++98 开始
概念(Concept) C++20 引入,对模板参数的约束(“这个类型必须支持 < 操作”) concept Integral = … C++20

一句话总结区别:

  • 模板 是语言特性(语法)
  • 泛型编程 是使用模板实现的编程风格/范式
  • 概念 是对泛型编程的约束与诊断工具(让错误更早、更清晰)

二、模板基础语法(必备)

1. 函数模板
template<typename T>          // T 是模板参数
T max(T a, T b) {
    return a > b ? a : b;
}

// 使用(编译器自动推导)
int    x = max(3, 7);         // T → int
double y = max(2.5, 1.8);     // T → double

// 显式指定类型(较少用)
float z = max<float>(1.2f, 3.4f);
2. 类模板
template<typename T, typename Alloc = std::allocator<T>>
class vector {
    // ...
};

vector<int> v1;               // T = int, Alloc = 默认
vector<double, MyAlloc> v2;   // 自定义分配器
3. 非类型模板参数(很常用)
template<int N>
struct Factorial {
    static constexpr int value = N * Factorial<N-1>::value;
};

template<>
struct Factorial<0> {
    static constexpr int value = 1;
};

static_assert(Factorial<10>::value == 3628800);
4. 模板模板参数(高级一点)
template<template<typename...> class Container, typename T>
void print_size(const Container<T>& c) {
    std::cout << c.size() << '\n';
}

std::vector<int> v;
print_size(v);   // Container → std::vector

三、现代 C++ 模板重要特性(C++11 → C++20)

C++版本 特性 典型写法示例 价值
C++11 变长模板参数(parameter pack) template<typename… Args> void print(Args…); 可变参数函数/容器最基础
C++11 constexpr 函数模板 template constexpr int fact() 编译期计算更强大
C++14 泛型 lambda auto cmp = [](auto a, auto b){ return a<b; } lambda 也能泛型
C++17 if constexpr if constexpr (std::is_integral_v) {} 模板内分支更清晰
C++17 类模板参数推导 std::pair p{1, 3.14}; 不必写 std::pair<int,double>
C++20 Concepts template<std::integral T> void f(T x); 约束 + 更好的错误信息
C++20 requires 表达式 requires std::is_same_v<T, int> 更细粒度的约束
C++20 abbreviated function templates void f(std::integral auto x); 极简写法

四、最经典的几个模板写法对比(面试/实战常考)

写法类型 代码示例 优点 缺点/限制 推荐场景
经典写法 template 最通用 错误信息极差 兼容老代码
C++20 concept template<std::integral T> 错误信息清晰、约束强 需要 C++20 新项目首选
requires 子句 template requires std::is_integral_v 约束写在后面,可读性好 稍微冗长 复杂约束时常用
abbreviated(缩写) void sort(std::ranges::random_access_range auto& r); 最简洁 可读性稍差、对初学者不友好 现代简洁代码

五、实战:一个综合示例(支持 C++17 & C++20)

#include <iostream>
#include <vector>
#include <concepts>  // C++20

// C++20 concept 版本
template<std::integral T>
T gcd(T a, T b) {
    while (b != 0) {
        T t = b;
        b = a % b;
        a = t;
    }
    return a;
}

// C++17 兼容写法(无 concept)
template<typename T>
requires std::is_integral_v<T>
T gcd_legacy(T a, T b) {
    while (b != 0) {
        T t = b;
        b = a % b;
        a = t;
    }
    return a;
}

// 变长模板 + fold expression(C++17)
template<typename... Args>
auto sum(Args... args) {
    return (args + ... + 0);  // C++17 fold expression
}

// 经典递归写法(C++11)
template<typename T>
T sum_recursive(T x) { return x; }

template<typename T, typename... Args>
T sum_recursive(T x, Args... args) {
    return x + sum_recursive(args...);
}

int main() {
    std::cout << gcd(48, 18) << '\n';           // 6
    std::cout << gcd_legacy(48LL, 18LL) << '\n'; // 6

    std::cout << sum(1, 2, 3, 4.5, 5.5) << '\n';           // 16
    std::cout << sum_recursive(1, 2, 3, 4.5, 5.5) << '\n'; // 16

    return 0;
}

六、模板常见误区与高阶技巧

误区 1:模板参数推导失败时错误信息极差
解决:C++20 concept + static_assert + requires

误区 2:滥用模板导致代码膨胀
解决:用概念约束 + 非模板接口 + type erasure(如 std::any / std::function)

误区 3:模板递归太深导致编译时间爆炸
解决:C++17 fold expression、C++20 consteval

高阶技巧

  • CRTP(Curiously Recurring Template Pattern)——静态多态
  • Tag Dispatching —— 根据类型分派不同实现
  • Type Erasure —— 运行时多态 + 模板(std::function、std::any 的底层原理)

七、总结:2025–2026 年模板使用推荐路线

场景 推荐写法 最低 C++ 版本
简单工具函数 template C++98
需要约束 template<std::integral T> C++20
可读性优先 template requires … C++20
最简写法 void f(std::integral auto x) C++20
兼容老编译器 template<typename T, typename = std::enable_if_t<…>> C++11/14
静态多态(性能敏感) CRTP C++98

一句话结论:
现代 C++ 项目中,优先使用 C++20 concept + abbreviated function templates,能写多简洁写多简洁,同时保留足够的约束和诊断能力。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐