C++ 函数重载(Overloading)与函数覆盖(Overriding)
继承:构建控件体系(QWidget → QPushButton → MyButton)多态:实现动态行为(paintEvent、data()、插件接口)最佳实践:虚函数 + override + 虚析构 + Q_OBJECT完整 Qt 项目 zip(含 .pro/CMake、.ui、资源、测试)QML 版本的多态示例(QAbstractListModel + ListView)插件系统完整实现(动
C++ 函数重载(Overloading)与函数覆盖(Overriding) 更系统、更深入、更实用,全部基于 现代 C++(C++17/20) 视角,并结合 Qt 项目中的真实实践。
目录
- 引言:重载与覆盖的核心区别与价值
- 函数重载(Overloading)详解
2.1 规则与限制
2.2 现代重载实践(C++11+)
2.3 Qt 中的重载典型场景 - 函数覆盖(Overriding)详解
3.1 规则与要求
3.2 现代覆盖实践(override / final / 协变返回)
3.3 Qt 中的覆盖典型场景 - 重载 vs 覆盖对比表(面试必备)
- 完整示例代码(VS 可运行 Demo)
- 自动化测试用例(Google Test)
- 常见陷阱与最佳实践
- 总结与进阶建议
1. 引言:重载与覆盖的核心区别与价值
| 项目 | 函数重载(Overloading) | 函数覆盖(Overriding) |
|---|---|---|
| 发生位置 | 同一个作用域(类内、全局、命名空间) | 继承关系中(基类 → 派生类) |
| 函数名 | 必须相同 | 必须相同 |
| 参数列表 | 必须不同(个数、类型、顺序) | 必须完全相同(或协变返回类型) |
| 返回类型 | 可以不同(但不能仅靠返回类型区分) | 必须相同或协变(C++11+ 支持协变返回) |
| 关键字 | 无需特殊关键字 | 推荐使用 override、final |
| 基类要求 | 无需 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 中最常见的覆盖场景:
- 自定义控件重写事件处理
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);
}
};
- 自定义模型重写数据提供
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,能写多简洁写多简洁,同时保留足够的约束和诊断能力。
更多推荐

所有评论(0)