教案基准切换:新版 10 天安排 + 4 天版大纲(含中初级误区审计)
- docs/SCHEDULE_2026_10DAY.md:2026-07-05 新版 10 天安排正式文本, 取代 TRAINING_PLAN_2026.md 旧执行计划作为课堂设计基准 - docs/teaching/OUTLINE_4DAY.md:4 天讲授 + Day5 综合案例的正式教案大纲, 含逐天设计、旧主题去向决策(QSS→Day3、JSON→Day4、QThread→Day5 提升)、 中初级学员误区盲点审计(10 张新坑卡:所有权/编码路径/连接语义三主线)、 项目就绪度与高阶兴趣钩子评估 - OUTLINE.md 顶部加基准变更横幅,5 天版保留作历史参照与回退预案 - 顺带收编两篇此前未跟踪的自学文档 MODERN_CPP.md / MODERN_CPP_20PLUS.md (大纲与幻灯片多处链接它们)
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# 2026-07 新版 10 天实训安排(C++/Qt 方向)
|
||||
|
||||
> 2026-07-05 收到的新版执行安排,**取代** [`TRAINING_PLAN_2026.md`](TRAINING_PLAN_2026.md)
|
||||
> 「五、执行计划 (二) C++ 方向」作为课堂设计基准(方案书其余章节——质量保证、考核标准、
|
||||
> 备选项目等——继续有效)。配套教案:[`teaching/OUTLINE_4DAY.md`](teaching/OUTLINE_4DAY.md)。
|
||||
|
||||
## 每日固定时段结构
|
||||
|
||||
| 时段 | 内容 | 产出 |
|
||||
|---|---|---|
|
||||
| 08:30–11:00 | 讲授(含随堂练习) | 课堂代码 / 课堂笔记 / 课堂练习 |
|
||||
| 11:00–12:00 | 课堂练习 + 工程师指导、答疑 | 课堂练习 |
|
||||
| 13:30–16:00 | 讲授(含随堂练习) | 课堂代码 / 课堂笔记 / 课堂练习 |
|
||||
| 16:00–17:00 | 课堂作业 + 工程师答疑 | 课堂作业 |
|
||||
|
||||
## 十天安排
|
||||
|
||||
| 天 | 时段 | 授课内容 | 教学目标 |
|
||||
|---|---|---|---|
|
||||
| 第一天 | 上午 | 1) 开训典礼 2) 了解 C++ 的常用技术和框架(讲座) 3) C++ 开发环境搭建及使用 4) Makefile/CMake 项目构建工具 5) C++ 面向对象基础回顾(类、对象、封装) | 熟悉企业文化、实训安排与考评要求;了解 C++ 在各行各业中的应用;搭建开发环境;熟悉项目构建工具 |
|
||||
| 第一天 | 下午 | 1) C++ 核心特性(继承与多态、虚函数) 2) 内存管理(指针、引用与动态分配) 3) C++ 标准模板库(STL)常用容器简介 | 掌握/巩固 C++ 核心进阶特性 |
|
||||
| 第二天 | 上午 | 1) Qt 概述 2) 创建 Qt 项目 3) 第一个 Qt 小程序 | 熟悉 Qt 开发环境及工程结构;掌握 Qt 项目的创建与基本运行原理 |
|
||||
| 第二天 | 下午 | 1) 信号槽机制 2) 项目案例:简易计算器 | 深刻理解 Qt 的事件驱动核心;熟练实现组件间的通信与逻辑交互;通过案例熟悉 Qt 基本图形开发 |
|
||||
| 第三天 | 上午 | 1) QMainWindow 2) 对话框 QDialog | 能够搭建标准桌面软件的主窗口架构;掌握与用户进行弹窗交互的各种方式 |
|
||||
| 第三天 | 下午 | 1) 布局管理器 2) 常用控件 | 掌握界面控件的自适应排版及美化;熟练运用各类型控件进行数据展示和输入 |
|
||||
| 第四天 | 上午 | 1) Qt 消息机制和事件 2) 绘图事件及表格事件 | 深入理解 Qt 底层的事件循环与派发机制;能够拦截底层事件并实现自定义的 2D 图形绘制 |
|
||||
| 第四天 | 下午 | 1) 文件系统 | 掌握 Qt 环境下的本地文件管理操作;能够实现软件运行状态及用户数据的保存与读取 |
|
||||
| 第五天 | 全天 | 1) 综合项目案例讲解 | 通过综合案例讲解 Qt 开发流程、规范及方法 |
|
||||
| 第六天~第九天 | 全天 | 学员项目开发 | 掌握 C++ 项目的开发流程,并综合运用之前所学的技术知识完成项目开发 |
|
||||
| 第十天 | 上午 | 学员项目开发 | 在工程师指导下完成项目开发 |
|
||||
| 第十天 | 下午 | 项目答辩(13:30–答辩结束) | — |
|
||||
|
||||
> 注:每个半天的产出物均含课堂代码与课堂笔记;第五天产出为项目源码;
|
||||
> 第六天起产出为项目源码(第十天含答辩材料)。
|
||||
@@ -0,0 +1,647 @@
|
||||
# 现代 C++ 速览(C++11 / 14 / 17)
|
||||
|
||||
> 适用对象:已掌握 C++ 基础语法与面向对象概念、正在向「现代 C++」迁移的学习者。
|
||||
> 本仓库的编译基线是 **C++17**(见顶层 `CMakeLists.txt`),因此本文所有示例均可在
|
||||
> `-std=c++17` 下直接编译。每个特性标注引入版本:`C++11` `C++14` `C++17`。
|
||||
>
|
||||
> 配套材料:版本相关的「编译期」对照演示见
|
||||
> [`docs/VERSION_NOTES.md`](../VERSION_NOTES.md) 与 `tools/demo_versions/`;
|
||||
> 本文聚焦「现代 C++ 是什么、为什么、怎么用」。
|
||||
|
||||
---
|
||||
|
||||
## 0. 为什么把 C++11 叫做「分水岭」
|
||||
|
||||
在 2011 年之前,C++ 标准只有 C++98 和它的缺陷修复版 C++03。从 **C++11** 开始,标准
|
||||
委员会改为**三年一个发布周期**,此后 C++14、C++17、C++20、C++23… 节奏稳定。
|
||||
|
||||
| 版本 | 年份 | 定位 | 一句话概括 |
|
||||
|---|---|---|---|
|
||||
| C++98/03 | 1998/2003 | 初代 + 修勘 | 经典 C++:手动内存管理、裸指针、深拷贝 |
|
||||
| **C++11** | 2011 | **大革命** | 几乎像一门新语言:auto、移动、智能指针、lambda、并发、模板增强 |
|
||||
| **C++14** | 2014 | 小完善 | 填坑:`make_unique`、泛型 lambda、`decltype(auto)` |
|
||||
| **C++17** | 2017 | 中等增量 | 实用大件:结构化绑定、`string_view`/`optional`/`variant`、`filesystem`、`if constexpr` |
|
||||
| C++20 | 2020 | 又一次大改 | 概念、范围、协程、模块(本文不展开) |
|
||||
|
||||
**「现代 C++」的三个核心心智模型**(贯穿全文):
|
||||
|
||||
1. **RAII 优先**:资源 = 对象的生命周期,用析构函数自动释放,少写 `new`/`delete`。
|
||||
2. **值类别与移动**:值不只是「拷贝」,还有「移动」——别再无谓地拷贝临时对象。
|
||||
3. **编译期能做多少做多少**:`constexpr`、`static_assert`、类型推导,把工作前移。
|
||||
|
||||
---
|
||||
|
||||
## 1. 类型推导:让代码少写类型名
|
||||
|
||||
### 1.1 `auto` `C++11`
|
||||
|
||||
```cpp
|
||||
auto i = 42; // int
|
||||
auto& r = i; // int&(显式加 & 才保留引用)
|
||||
const auto& cr = i; // const int&
|
||||
auto p = std::make_pair(1, 2.0); // 不用写 std::pair<int,double>
|
||||
|
||||
for (auto& x : vec) { x *= 2; } // 范围 for(也是 C++11)
|
||||
```
|
||||
|
||||
**核心概念**:`auto` 的推导规则**与模板参数推导完全一致**——会**丢弃顶层 const 和引用**。
|
||||
所以要保留引用/const 必须显式写 `auto&` / `const auto&`。
|
||||
|
||||
**常见的坑**:
|
||||
- `auto x = expr;` 几乎总是「拷贝」。想要引用必须写 `&`,想要只读加 `const`。
|
||||
- `auto&&` 在推导语境是「转发引用」(见 §3),不是固定右值引用。
|
||||
- `auto` 不能用于数组、不能用于非静态成员的类型……推导不出就报错,通常很友好。
|
||||
|
||||
### 1.2 `decltype` `C++11`
|
||||
|
||||
```cpp
|
||||
int x = 1;
|
||||
decltype(x) a; // int —— 变量名 → 该变量类型
|
||||
decltype((x)) b; // int& —— 加括号 → 左值表达式 → 引用!
|
||||
```
|
||||
|
||||
**核心概念**:`decltype(变量名)` 给类型;`decltype((变量名))`(**带括号**)给引用。这一对括号
|
||||
的差异是最常见的考点。`decltype` 适合「我想要和某个表达式一模一样的类型,包括 const/引用」。
|
||||
|
||||
### 1.3 返回类型推导 `C++14`
|
||||
|
||||
```cpp
|
||||
auto square(int x) { return x * x; } // C++14:返回类型由 return 推导
|
||||
|
||||
template<typename T, typename U>
|
||||
auto add(T a, U b) { return a + b; } // 模板里尤其好用
|
||||
```
|
||||
|
||||
> C++11 只能写**尾随返回类型**:`auto f(int x) -> int { ... }`。C++14 起可直接 `auto`。
|
||||
|
||||
### 1.4 `decltype(auto)` `C++14`
|
||||
|
||||
```cpp
|
||||
decltype(auto) f() { return some_expr; } // 用 decltype 规则推导,保留引用/const
|
||||
```
|
||||
|
||||
解决「`auto` 会丢引用」的场景,常用于转发包装器、属性 getter。
|
||||
|
||||
### 1.5 结构化绑定 `C++17`
|
||||
|
||||
```cpp
|
||||
std::pair p = {1, std::string("hi")};
|
||||
auto [id, name] = p; // id=1, name="hi"
|
||||
|
||||
for (const auto& [key, val] : my_map) { // 遍历 map 终于不丑了
|
||||
std::cout << key << " => " << val << "\n";
|
||||
}
|
||||
|
||||
struct Point { int x, y; };
|
||||
auto [px, py] = Point{3, 4}; // 也能绑定公开成员
|
||||
```
|
||||
|
||||
**核心概念**:一次性把聚合/`pair`/`tuple`/数组的多个元素「拆」给若干变量,替代难看的
|
||||
`std::get<0>(...)` 或 `.first/.second`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 移动语义与完美转发:别再无谓拷贝
|
||||
|
||||
### 2.1 右值引用与移动构造 `C++11`
|
||||
|
||||
```cpp
|
||||
class Buffer {
|
||||
char* data_; size_t n_;
|
||||
public:
|
||||
Buffer(size_t n) : data_(new char[n]), n_(n) {}
|
||||
~Buffer() { delete[] data_; }
|
||||
|
||||
// 移动构造:窃取资源,把源对象置空
|
||||
Buffer(Buffer&& o) noexcept : data_(o.data_), n_(o.n_) {
|
||||
o.data_ = nullptr; o.n_ = 0;
|
||||
}
|
||||
Buffer& operator=(Buffer&& o) noexcept { /* 类似 */ return *this; }
|
||||
|
||||
// 拷贝(深拷贝)照常保留
|
||||
Buffer(const Buffer& o) : data_(new char[o.n_]), n_(o.n_) {
|
||||
std::memcpy(data_, o.data_, n_);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**核心概念**:
|
||||
- **左值 (lvalue)** 有名字、可取地址(如变量 `x`);**右值 (rvalue)** 是临时值(如 `Buffer(64)`、`std::move(x)`)。
|
||||
- 右值引用 `T&&` 只绑定右值,表示「这个值马上要销毁,资源可以偷走」。
|
||||
- 移动 = 「偷指针」而非「拷贝内容」,对 `vector`/`string`/`Buffer` 这类持堆资源的类型是巨大提速。
|
||||
|
||||
**`std::move` 的真相**:它**什么都不 move**,只是把左值**强制转换**成右值引用 (`static_cast<T&&>`),
|
||||
从而允许移动构造/赋值被选中。
|
||||
|
||||
```cpp
|
||||
Buffer a(64);
|
||||
Buffer b = std::move(a); // 调用移动构造,a 变成空壳(仍需析构,但便宜)
|
||||
```
|
||||
|
||||
**常见的坑**:
|
||||
- 移动后的对象处于「有效但未指定」状态,只能析构或重新赋值,不要继续读它。
|
||||
- 移动构造/赋值**强烈建议标 `noexcept`**:否则 `vector` 扩容时为安全起见会退回拷贝。
|
||||
- 不要对 `return x;` 里的局部变量写 `std::move(x)`——会**阻止**编译器的 RVO/隐式移动,反而更差。
|
||||
|
||||
### 2.2 完美转发 `C++11`
|
||||
|
||||
让包装函数把参数「原封不动」(保留 lvalue/rvalue、const)转交给目标函数。三块基石:
|
||||
|
||||
1. **转发引用** `T&&`(仅在模板推导语境,绑定 lvalue 与 rvalue)。
|
||||
2. **引用折叠**:`& + && = &`、`&& + && = &&` 等,让 `T` 编码了实参类别。
|
||||
3. **`std::forward<T>(x)`**:依据 `T` 把 `x` 还原成正确的值类别。
|
||||
|
||||
```cpp
|
||||
template<typename T, typename Arg>
|
||||
T my_make(Arg&& arg) {
|
||||
return T(std::forward<Arg>(arg)); // 左值→拷贝,右值→移动,自动选对
|
||||
}
|
||||
```
|
||||
|
||||
**典型应用**:`std::make_unique` / `make_shared`、容器的 `emplace_back`(原地构造,省一次拷贝/移动)。
|
||||
|
||||
> 关于 `T&&` 何时是「转发引用」、何时是「纯右值引用」,详见 §10.4 与配套课件。
|
||||
|
||||
---
|
||||
|
||||
## 3. 智能指针:RAII 的现代化 `C++11`(`make_unique` 为 `C++14`)
|
||||
|
||||
裸 `new`/`delete` 是现代 C++ 里**基本不该手写**的东西。三个智能指针各司其职:
|
||||
|
||||
| 类型 | 语义 | 开销 | 典型用途 |
|
||||
|---|---|---|---|
|
||||
| `std::unique_ptr<T>` | **独占**所有权,不可拷贝、可移动 | 几乎为零(默认等同裸指针) | **默认首选**;工厂返回、类成员持有资源 |
|
||||
| `std::shared_ptr<T>` | **共享**所有权,引用计数 | 控制块 + 原子计数 | 多处需要共同拥有一同一对象、所有权不唯一时 |
|
||||
| `std::weak_ptr<T>` | 观察 `shared_ptr`,不增加计数 | 一个指针 | 打破 `shared_ptr` 循环引用、缓存 |
|
||||
|
||||
```cpp
|
||||
// C++14:用 make_unique,异常安全、避免裸 new
|
||||
auto p = std::make_unique<Widget>(args...);
|
||||
|
||||
std::vector<std::unique_ptr<Shape>> shapes;
|
||||
shapes.push_back(std::make_unique<Circle>(1.0));
|
||||
shapes.push_back(std::make_unique<Square>(2.0));
|
||||
|
||||
auto shared = std::make_shared<Widget>(); // 一次堆分配(对象+控制块)
|
||||
std::weak_ptr<Widget> observer = shared; // 不延长生命周期
|
||||
if (auto locked = observer.lock()) { /* 仍然存活时使用 */ }
|
||||
```
|
||||
|
||||
**核心概念**:析构即释放——`unique_ptr` 出作用域自动 `delete`,`shared_ptr` 计数归零自动 `delete`。
|
||||
异常发生时栈展开也会调用析构,**资源不泄漏**。这就是 RAII(Resource Acquisition Is Initialization)。
|
||||
|
||||
**常见的坑**:
|
||||
- `make_unique` 是 **C++14** 才有的;C++11 只能 `std::unique_ptr<T>(new T(...))`。
|
||||
- `shared_ptr` 的控制块是**每个对象一份**——用同一个裸指针构造两个 `shared_ptr` 会产生两个控制块,双重释放:
|
||||
```cpp
|
||||
Widget* raw = new Widget;
|
||||
std::shared_ptr<Widget> a(raw); // ❌ 危险
|
||||
std::shared_ptr<Widget> b(raw); // ❌ 两个独立计数 → double free
|
||||
```
|
||||
- `shared_ptr` 循环引用(A 持有 B,B 持有 A)会泄漏,一端改用 `weak_ptr` 打环。
|
||||
- `shared_ptr` 线程安全**仅限引用计数操作**;指向的对象本身仍需另行同步。
|
||||
|
||||
> 历史:C++98 的 `std::auto_ptr` 因拷贝即「窃取」语义有坑,**C++17 已完全移除**。
|
||||
|
||||
---
|
||||
|
||||
## 4. Lambda 表达式:就地写函数对象
|
||||
|
||||
### 4.1 基本语法 `C++11`
|
||||
|
||||
```cpp
|
||||
[capture](params) mutable -> return_type { body }
|
||||
|
||||
auto add = [](int a, int b) { return a + b; }; // 就地定义一个函数对象
|
||||
add(1, 2); // 3
|
||||
|
||||
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // 降序
|
||||
|
||||
int factor = 3;
|
||||
std::transform(v.begin(), v.end(), v.begin(),
|
||||
[factor](int x) { return x * factor; }); // 捕获 factor(按值)
|
||||
```
|
||||
|
||||
**捕获方式**:
|
||||
- `[]` 不捕获;`[=]` 按值捕获所有用到的;`[&]` 按引用捕获所有用到的;
|
||||
- `[x]` 按值捕获 `x`;`[&x]` 按引用捕获 `x`;`[this]` 捕获当前对象指针(成员函数内常用);
|
||||
- `[=, &x]` 默认按值,但 `x` 按引用。
|
||||
|
||||
**核心概念**:lambda 本质是编译器生成的**匿名类(函数对象)**,捕获即成员变量。这让 STL
|
||||
算法从「写个函数对象/函数指针」解脱出来,可以**就地表达**比较、变换、过滤逻辑。
|
||||
|
||||
**常见的坑**:
|
||||
- `[&]` 捕获引用时,lambda 比 captured 变量活得久(如存进 `std::function` 后异步用)→ 悬垂引用。
|
||||
- 按值捕获默认使 lambda 为 `const`,要修改捕获的副本需加 `mutable`。
|
||||
- 捕获 `this` 后,对象被销毁再调用 lambda → 悬垂。
|
||||
|
||||
### 4.2 泛型 lambda `C++14`
|
||||
|
||||
```cpp
|
||||
auto plus = [](auto a, auto b) { return a + b; }; // 参数可用 auto
|
||||
plus(1, 2); // 3
|
||||
plus(std::string("a"), std::string("b")); // "ab"
|
||||
```
|
||||
|
||||
### 4.3 `constexpr` lambda `C++17`
|
||||
|
||||
```cpp
|
||||
constexpr auto sq = [](int x) { return x * x; };
|
||||
static_assert(sq(5) == 25); // 编译期可调用
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 强类型与统一初始化
|
||||
|
||||
### 5.1 `nullptr` 取代 `NULL` `C++11`
|
||||
|
||||
```cpp
|
||||
void f(int);
|
||||
void f(char*);
|
||||
f(0); // 调 f(int) —— 0 是 int!
|
||||
f(NULL); // 仍可能调 f(int)(NULL 的定义各不相同),坑
|
||||
f(nullptr); // 一定调 f(char*) —— nullptr 是独立的 std::nullptr_t 类型
|
||||
```
|
||||
|
||||
**核心概念**:`nullptr` 是**带类型**的空指针字面量,彻底消除「`NULL` 在重载时被当成整数」的经典歧义。
|
||||
|
||||
### 5.2 强类型枚举 `enum class` `C++11`
|
||||
|
||||
```cpp
|
||||
enum class Color { Red, Green, Blue }; // 不会污染外层作用域
|
||||
Color c = Color::Red;
|
||||
// int i = c; // ❌ 不能隐式转 int(这正是想要的安全性)
|
||||
int i = static_cast<int>(c); // ✅ 显式才行
|
||||
|
||||
enum OldColor { Red, Green }; // 旧式:Red/Green 泄漏到外层;可隐式转 int
|
||||
```
|
||||
|
||||
**核心概念**:`enum class`(scoped enum)的作用域封闭、不隐式转换,比 C 风格 enum 安全得多。
|
||||
还能指定底层类型:`enum class Color : uint8_t { ... };`。
|
||||
|
||||
### 5.3 统一初始化(花括号)`C++11`
|
||||
|
||||
```cpp
|
||||
int a[]{1, 2, 3};
|
||||
std::vector<int> v{1, 2, 3, 4};
|
||||
std::map<int, std::string> m{{1, "a"}, {2, "b"}};
|
||||
Point p{3, 4};
|
||||
Widget w{}; // 默认构造
|
||||
```
|
||||
|
||||
**核心概念**:`{}` 是统一的初始化语法,适用于容器、聚合、普通对象;还能**窄化检查**
|
||||
(`int{3.14}` 编译失败,而 `int(3.14)` 截断通过)。
|
||||
|
||||
**常见的坑(most vexing parse)**:
|
||||
```cpp
|
||||
Widget w(); // ❓ 被解析成「返回 Widget 的函数 w 的声明」!
|
||||
Widget w2{}; // ✅ 用花括号消除歧义,才是默认构造
|
||||
```
|
||||
花括号还能防窄化,但也引入新的歧义(与 `std::initializer_list` 构造函数的偏好),故现代风格里
|
||||
**简单值用 `=`、聚合/容器用 `{}`**,二者并存。
|
||||
|
||||
---
|
||||
|
||||
## 6. 类与面向对象的增强
|
||||
|
||||
| 特性 | 版本 | 作用 |
|
||||
|---|---|---|
|
||||
| `= default` / `= delete` | `C++11` | 显式要求/禁止某个特殊成员函数 |
|
||||
| `override` / `final` | `C++11` | 让编译器帮你检查虚函数重写、禁止进一步派生 |
|
||||
| 委托构造 | `C++11` | 一个构造函数调用同类的另一个构造函数 |
|
||||
| 继承构造 | `C++11`(C++17 调整语义) | `using Base::Base;` 复用基类构造函数 |
|
||||
| `inline` 变量 | `C++17` | 头文件里直接定义全局/静态变量,不再需要「声明+定义分离」 |
|
||||
|
||||
```cpp
|
||||
struct NonCopyable {
|
||||
NonCopyable() = default;
|
||||
NonCopyable(const NonCopyable&) = delete; // 禁止拷贝
|
||||
NonCopyable& operator=(const NonCopyable&) = delete;
|
||||
};
|
||||
|
||||
struct Base {
|
||||
virtual void f();
|
||||
virtual void g() final; // 子类不能再覆盖 g
|
||||
};
|
||||
struct Derived : Base {
|
||||
void f() override; // ✅ 签名必须匹配,拼错或签名不符会编译报错
|
||||
// void f() const override; // ❌ 与基类签名不符 → 编译错误(这正是 override 的价值)
|
||||
};
|
||||
|
||||
struct A {
|
||||
int x;
|
||||
A(int x) : x(x) {}
|
||||
A() : A(0) {} // 委托构造:调用 A(int)
|
||||
};
|
||||
|
||||
// C++17 inline 变量:头文件里安全地定义
|
||||
struct Counter {
|
||||
inline static int instances = 0; // 不再是「声明」,所有 TU 共享一份
|
||||
Counter() { ++instances; }
|
||||
};
|
||||
```
|
||||
|
||||
**核心概念**:
|
||||
- `override` 是**编译期契约**:你以为重写了,编译器没报错就一定是真重写了——杜绝「签名笔误导致隐藏而非重写」的隐蔽 bug。
|
||||
- `= delete` 用**意图清晰的方式**禁用函数,比「声明为 private 且不实现」更直接。
|
||||
- C++17 `inline` 变量终结了「头文件只能声明、定义放 cpp」的繁琐,对常量、单例尤其友好。
|
||||
|
||||
---
|
||||
|
||||
## 7. 模板的增强
|
||||
|
||||
### 7.1 可变参数模板 `C++11`
|
||||
|
||||
```cpp
|
||||
template<typename... Args>
|
||||
void print(Args... args) {
|
||||
(std::cout << ... << args) << "\n"; // ← 这是 C++17 折叠表达式,见 7.4
|
||||
}
|
||||
print(1, " + ", 2, " = ", 3);
|
||||
```
|
||||
|
||||
`typename... Args` 是「模板参数包」,`Args... args` 是「函数参数包」。C++11 里展开包要用
|
||||
递归模板,比较繁琐。
|
||||
|
||||
### 7.2 别名模板 `using` `C++11`
|
||||
|
||||
```cpp
|
||||
template<typename T> using Vec = std::vector<T>;
|
||||
Vec<int> v; // 等价 std::vector<int>
|
||||
|
||||
using StringList = std::vector<std::string>; // 也取代 typedef,更直观
|
||||
```
|
||||
|
||||
`using` 比 `typedef` 强在能模板化,且读起来「左到右」更自然。
|
||||
|
||||
### 7.3 `if constexpr` `C++17`
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
auto get_value(T t) {
|
||||
if constexpr (std::is_pointer_v<T>)
|
||||
return *t; // 仅当 T 是指针时才编译这一支
|
||||
else
|
||||
return t;
|
||||
}
|
||||
```
|
||||
|
||||
**核心概念**:`if constexpr` 在**编译期**按条件**丢弃**不成立的分支(不实例化)。
|
||||
替代了以前必须用模板特化/SFINAE 才能实现的「按类型分支」。
|
||||
|
||||
### 7.4 折叠表达式 `C++17`
|
||||
|
||||
```cpp
|
||||
template<typename... Args>
|
||||
auto sum(Args... args) { return (args + ...); } // 一元右折叠
|
||||
template<typename... Args>
|
||||
auto sum2(Args... args) { return (0 + ... + args); } // 二元,带初值
|
||||
|
||||
sum(1, 2, 3, 4); // 10
|
||||
```
|
||||
|
||||
让可变参数模板的「包展开」从递归套路变成一行,是 C++17 最受好评的小特性之一。
|
||||
|
||||
### 7.5 类模板参数推导 CTAD `C++17`
|
||||
|
||||
```cpp
|
||||
std::pair p(1, 2.0); // 推导出 std::pair<int, double>,无需写模板参数
|
||||
std::vector v = {1, 2, 3}; // std::vector<int>
|
||||
std::lock_guard lk(mtx); // 不用写 std::lock_guard<std::mutex>
|
||||
```
|
||||
|
||||
> 对 Qt 用户:`QSignalMapper` 之类的繁冗模板写法在 C++17 下可显著简化。
|
||||
|
||||
---
|
||||
|
||||
## 8. `constexpr`:编译期计算
|
||||
|
||||
`constexpr` 表示「**可能在编译期求值**」。从 C++11 到 C++17 限制逐步放开:
|
||||
|
||||
| 版本 | 能力 |
|
||||
|---|---|
|
||||
| `C++11` | 函数体**单条 return**;变量可用常量表达式初始化 |
|
||||
| `C++14` | 允许循环、局部变量、多语句、`if`/`switch` |
|
||||
| `C++17` | `if constexpr`;`constexpr` lambda;更多标准库函数(如 `std::array` 的部分操作)标记 constexpr |
|
||||
|
||||
```cpp
|
||||
constexpr int factorial(int n) { // C++14 起可写循环
|
||||
int r = 1;
|
||||
for (int i = 2; i <= n; ++i) r *= i;
|
||||
return r;
|
||||
}
|
||||
|
||||
int main() {
|
||||
constexpr int x = factorial(5); // 编译期求值
|
||||
int arr[factorial(4)]; // 数组大小,编译期确定
|
||||
static_assert(factorial(3) == 6);
|
||||
int n; std::cin >> n;
|
||||
int y = factorial(n); // 也可运行期调用(同一函数两用)
|
||||
}
|
||||
```
|
||||
|
||||
**核心概念**:`constexpr` 函数「能在编译期就算编译期,不能就退回运行期」——同一份代码两用,
|
||||
避免「一份宏/模板元编程 + 一份运行函数」的重复。
|
||||
|
||||
**`static_assert` `C++11`(无消息版本 `C++17`)**:编译期断言,失败即编译错误。
|
||||
```cpp
|
||||
static_assert(sizeof(int) >= 4, "需要 32 位 int");
|
||||
static_assert(true); // C++17:可省略消息
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 标准库的新组件
|
||||
|
||||
### 9.1 容器与算法增强
|
||||
|
||||
| 组件 | 版本 | 要点 |
|
||||
|---|---|---|
|
||||
| `std::array` | `C++11` | 固定大小、栈上、零开销数组(比裸数组更安全、有迭代器) |
|
||||
| `std::tuple` | `C++11` | 异构类型打包;C++17 配结构化绑定拆解 |
|
||||
| `std::unordered_map`/`unordered_set` | `C++11` | 哈希表,O(1) 平均查找 |
|
||||
| `emplace` / `emplace_back` | `C++11` | 原地构造,省一次拷贝/移动(完美转发的直接受益者) |
|
||||
| `std::initializer_list` | `C++11` | 让 `{1,2,3}` 能作为构造/赋值参数 |
|
||||
| `std::move`(算法版)/ `std::move_backward` | `C++11` | 用移动而非拷贝在区间间搬运 |
|
||||
| 透明比较器 `std::less<>` | `C++14` | 异构查找,`map<string,int>` 可用字符串字面量查而免构造 `string` |
|
||||
|
||||
```cpp
|
||||
std::map<std::string, int, std::less<>> m; // C++14 透明比较
|
||||
m.find("key"); // 直接用 const char* 查,不构造临时 string
|
||||
```
|
||||
|
||||
### 9.2 C++17 的「实用三件套」+ 文件系统
|
||||
|
||||
```cpp
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <string_view>
|
||||
#include <filesystem>
|
||||
|
||||
// optional:可能没有值
|
||||
std::optional<int> parse(std::string_view s) {
|
||||
int v{};
|
||||
/* ... */
|
||||
return v; // 有值
|
||||
return std::nullopt; // 无值
|
||||
}
|
||||
if (auto r = parse("42")) use(*r);
|
||||
|
||||
// variant:类型安全的联合,替代 union
|
||||
std::variant<int, double, std::string> v = "hello";
|
||||
v = 3.14;
|
||||
std::visit([](auto&& x) { std::cout << x; }, v); // 配 lambda 模式匹配
|
||||
|
||||
// string_view:不拷贝的字符串「视图」
|
||||
void log(std::string_view msg); // 接收 const char*、std::string、子串都不分配
|
||||
|
||||
// filesystem:跨平台文件操作(前身 boost::filesystem)
|
||||
namespace fs = std::filesystem;
|
||||
for (const auto& e : fs::directory_iterator("/tmp"))
|
||||
std::cout << e.path() << "\n";
|
||||
fs::create_directories("a/b/c");
|
||||
auto sz = fs::file_size("main.cpp");
|
||||
```
|
||||
|
||||
**核心概念**:
|
||||
- `optional`/`variant`/`any` 让「这个值可能不存在 / 可能是多种类型之一 / 任意类型」**进入类型系统**,
|
||||
编译器帮你保证访问安全,取代裸指针 + 约定、`union` + tag 的脆弱写法。
|
||||
- `string_view` 是**只读视图**,构造零开销——适合函数参数;但**不拥有**数据,调用方必须保证源串存活。
|
||||
- `filesystem` 让文件操作不再依赖 `<dirent.h>`/`<windows.h>`,一份代码跨平台。
|
||||
|
||||
**常见的坑**:
|
||||
- `string_view` 不保证以 `\0` 结尾,别直接传给需要 C 字符串的 API(要用 `.data()` 且确认有终止符)。
|
||||
- `variant` 默认构造为**第一个备选类型**的值,访问时类型不对会抛 `std::bad_variant_access`(或用 `std::get_if`)。
|
||||
|
||||
### 9.3 并发支持
|
||||
|
||||
| 组件 | 版本 | 作用 |
|
||||
|---|---|---|
|
||||
| `std::thread` | `C++11` | 跨平台线程 |
|
||||
| `std::mutex` / `std::lock_guard` / `std::unique_lock` | `C++11` | 互斥与 RAII 加锁 |
|
||||
| `std::async` / `std::future` / `std::promise` | `C++11` | 异步任务与结果传递 |
|
||||
| `std::atomic<T>` | `C++11` | 无锁原子操作 |
|
||||
| `std::condition_variable` | `C++11` | 线程间等待/通知 |
|
||||
| `std::shared_mutex`(读写锁) | `C++14` | 多读单写 |
|
||||
| `std::scoped_lock`(一次锁多个) | `C++17` | 彻底避免死锁的统一加锁 |
|
||||
|
||||
```cpp
|
||||
std::mutex m;
|
||||
int counter = 0;
|
||||
std::vector<std::thread> ts;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
ts.emplace_back([&]{
|
||||
std::lock_guard<std::mutex> lk(m); // RAII:构造加锁、析构解锁
|
||||
++counter;
|
||||
});
|
||||
for (auto& t : ts) t.join();
|
||||
```
|
||||
|
||||
> 注意:C++ 标准库的并发模型较底层。Qt 方向课程里更常用 `QThread` + 信号槽的 worker 模式,
|
||||
> 两者可对照学习——见 `docs/teaching/examples/qthread_worker/`。
|
||||
|
||||
---
|
||||
|
||||
## 10. 其它实用语言特性
|
||||
|
||||
### 10.1 属性 `[[...]]`
|
||||
|
||||
| 属性 | 版本 | 作用 |
|
||||
|---|---|---|
|
||||
| `[[noreturn]]` | `C++11` | 标注函数永不返回(如 `abort`、死循环) |
|
||||
| `[[deprecated]]` | `C++14` | 标记弃用,使用时编译警告 |
|
||||
| `[[deprecated("用 newFunc")]]` | `C++14` | 带提示信息 |
|
||||
| `[[fallthrough]]` | `C++17` | switch 显式贯穿(抑制警告) |
|
||||
| `[[nodiscard]]` | `C++17` | 返回值不可忽略(如 `malloc`、工厂函数) |
|
||||
| `[[maybe_unused]]` | `C++17` | 抑制「未使用」警告 |
|
||||
|
||||
```cpp
|
||||
[[nodiscard]] int* alloc();
|
||||
auto p = alloc(); // 不接收 → 警告,提醒你检查是否泄漏
|
||||
|
||||
switch (n) {
|
||||
case 1: prepare();
|
||||
[[fallthrough]]; // 我是故意贯穿的,别警告
|
||||
case 2: run();
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 `noexcept` `C++11`
|
||||
|
||||
```cpp
|
||||
void swap(T& a, T& b) noexcept; // 承诺不抛异常
|
||||
```
|
||||
|
||||
**核心概念**:`noexcept` 是函数**接口的一部分**——它让调用者(尤其是 STL 容器)敢于用移动
|
||||
而非拷贝。移动构造/赋值加 `noexcept` 对性能**至关重要**。`noexcept(expr)` 还能按条件判断:
|
||||
`void f() noexcept(noexcept(g()));`(f 是否不抛取决于 g)。
|
||||
|
||||
> 旧式动态异常规范 `throw(int, char)` 在 C++11 弃用、**C++17 移除**——见
|
||||
> `docs/VERSION_NOTES.md` 第 6 节与 `tools/demo_versions/dynamic_exception_spec.cpp`。
|
||||
|
||||
### 10.3 字符串与字面量 `C++11`
|
||||
|
||||
- **`std::to_string` / `std::stoi/stof/stod`**:数值 ↔ 字符串互转。
|
||||
- **原始字符串字面量** `R"(...)"`:反斜杠、引号都原样保留,写正则/路径/HTML 不再「转义地狱」。
|
||||
```cpp
|
||||
std::regex re(R"(\d+\.\d+)"); // 等价 "\\d+\\.\\d+"
|
||||
std::string path = R"(C:\Users\me)"; // 反斜杠原样
|
||||
```
|
||||
- **用户定义字面量 (UDL)**:`"hello"s` 是 `std::string`,`1h`/`1ms` 是 `std::chrono::duration`。
|
||||
```cpp
|
||||
using namespace std::string_literals;
|
||||
auto s = "hi"s + ", world"; // string + 拼接
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(200ms);
|
||||
```
|
||||
|
||||
### 10.4 杂项但常用
|
||||
|
||||
| 特性 | 版本 | 备注 |
|
||||
|---|---|---|
|
||||
| 范围 for | `C++11` | `for (auto& x : c)` |
|
||||
| `long long` / `char16_t` / `char32_t` | `C++11` | 标准化的 64 位整型、UTF-16/32 字符 |
|
||||
| `alignas` / `alignof` | `C++11` | 对齐控制与查询 |
|
||||
| `thread_local` | `C++11` | 线程局部存储 |
|
||||
| `using` 别名(取代 `typedef`) | `C++11` | 见 §7.2 |
|
||||
| 变量模板 | `C++14` | `template<typename T> constexpr T pi = T(3.14159265);` |
|
||||
|
||||
---
|
||||
|
||||
## 附 A:C++11/14/17 速查表
|
||||
|
||||
```
|
||||
类型推导 auto(11) decltype(11) 返回auto(14) decltype(auto)(14) 结构化绑定(17)
|
||||
移动语义 右值引用(11) std::move(11) std::forward(11) 完美转发(11)
|
||||
资源管理 unique_ptr/shared_ptr/weak_ptr(11) make_unique(14) RAII
|
||||
Lambda 基础(11) 泛型(14) constexpr(17)
|
||||
强类型 nullptr(11) enum class(11) 统一初始化(11)
|
||||
类增强 =default/=delete(11) override/final(11) 委托构造(11) inline变量(17)
|
||||
模板增强 可变参数(11) using别名(11) 变量模板(14) if constexpr(17) 折叠(17) CTAD(17)
|
||||
编译期 constexpr(11/14/17) static_assert(11, 无消息17)
|
||||
标准库 array/tuple/unordered(11) emplace(11) 透明比较(14)
|
||||
optional/variant/any/string_view/filesystem/并行算法(17)
|
||||
并发 thread/mutex/future/atomic(11) shared_mutex(14) scoped_lock(17)
|
||||
杂项 范围for/raw string/UDL/noexcept/属性[[...]](11)
|
||||
```
|
||||
|
||||
## 附 B:学习路径建议(给课堂学员)
|
||||
|
||||
1. **先建立心智模型**:RAII、移动语义、值类别——这三者理解了,剩下都是语法。
|
||||
2. **第一周可上手**:`auto`、范围 for、`nullptr`、`enum class`、智能指针、lambda——
|
||||
立刻让日常代码更短更安全。
|
||||
3. **进阶**:完美转发、`constexpr`、模板增强(`if constexpr`/折叠/CTAD)。
|
||||
4. **C++17 标配库**:`string_view`/`optional`/`variant`/`filesystem` 是日常工程刚需,
|
||||
写 Qt 程序时同样适用。
|
||||
5. **避坑读物**:Scott Meyers《Effective Modern C++》、cppreference.com(查权威细节)。
|
||||
|
||||
## 附 C:与本仓库的关联
|
||||
|
||||
- **编译基线**:所有示例在 `-std=c++17` 下编译(顶层 `CMakeLists.txt`)。
|
||||
- **跨版本验证**:想知道某特性在 C++11/14/17 下分别能否编译,用
|
||||
`tools/std_probe.py`(例:动态异常规范在 C++17 失败,见 `docs/VERSION_NOTES.md`)。
|
||||
- **Qt 侧**:本仓库 `p03/` 与 `docs/teaching/examples/` 大量使用了上述现代特性
|
||||
(智能指针管理 Qt 对象生命周期、lambda 连信号槽、`QString` 与 `string_view` 互转等)。
|
||||
- **下一步**:若希望把本文档的某个特性做成可编译运行的 `tools/demo_versions/` 最小示例,
|
||||
请明确提出,按项目规范(生成器 / errata 流程)补齐。
|
||||
@@ -0,0 +1,558 @@
|
||||
# C++20 与以后(C++20 / 23 / 26 概览)
|
||||
|
||||
> 本文是 [`MODERN_CPP.md`](MODERN_CPP.md)(C++11/14/17)的续篇。
|
||||
>
|
||||
> **范围与基线说明**:本仓库的编译基线仍是 **C++17**(见顶层 `CMakeLists.txt`),所以本文介绍的是 **C++20 起** 的「进阶/前瞻」特性——需要在 `-std=c++20` / `-std=c++23` 下编译,**不**是本仓库示例默认能用的版本。每节标注引入版本;附录给出本机 **GCC 13.3.0** 的实测可用性(不是猜的,每条都编过)。
|
||||
>
|
||||
> 为什么单列一份:C++20 是继 C++11 之后**最大**的一次语言更新,四大件(Concepts / Ranges / Coroutines / Modules)体量足够撑一份独立文档。
|
||||
|
||||
---
|
||||
|
||||
## 0. 版本脉络:C++20 是又一次「大爆炸」
|
||||
|
||||
| 版本 | 年份 | 定位 | 标志性内容 |
|
||||
|---|---|---|---|
|
||||
| C++17 | 2017 | 实用增量 | `optional`/`variant`/`string_view`/`filesystem`/结构化绑定 |
|
||||
| **C++20** | 2020 | **四大件** | Concepts · Ranges · Coroutines · Modules + `<=>` · `std::format` |
|
||||
| **C++23** | 2023 | 完善与补库 | `expected` · `std::print` · `mdspan` · `flat_map` · ranges 扩展 · 推导 `this` |
|
||||
| C++26 | 2026(开发中) | 暂定 | `std::execution`(Senders/Receivers) · `=delete("...")` · `inplace_vector` · 契约/反射(未定) |
|
||||
|
||||
Stroustrup 评价 C++20:「自 C++11 以来对 C++ 最大的冲击。」
|
||||
|
||||
**四大件一句话**:
|
||||
1. **Concepts(概念)**——给模板加约束,错误信息从「天书」变「人话」。
|
||||
2. **Ranges(范围)**——可组合、惰性的区间管道,STL 算法的现代化。
|
||||
3. **Coroutines(协程)**——语言级协程,生成器/异步的统一底座。
|
||||
4. **Modules(模块)**——替代 `#include`,更快编译 + 更干净的封装。
|
||||
|
||||
> **关于工具链**:本文附录 A 给出了**本机 GCC 13.3.0 的实测矩阵**。结论先行:
|
||||
> **C++20 几乎全可用**;**C++23 的语言特性多数可用,但不少库(`<print>`/`<mdspan>`/`<flat_map>` 等)要 GCC 14+**;**C++26 在 13.3 上基本用不了**(需 GCC 14+ 且开 `-std=c++2c`)。课堂演示时请按此选用。
|
||||
|
||||
---
|
||||
|
||||
## 1. Concepts:给模板加约束 `C++20`
|
||||
|
||||
### 1.1 问题:模板错误信息「天书」
|
||||
|
||||
一个没有约束的模板,对所有类型都「假装能用」,直到实例化时在**最深处**失败:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
void print(const T& x) { std::cout << x; } // 要求 x 可 <<
|
||||
|
||||
struct Foo {};
|
||||
print(Foo{}); // ❌ 报错:operator<< 找不到,外加一长串模板展开
|
||||
```
|
||||
|
||||
编译器把 `operator<<` 失败的整条实例化链路倒给你看,真正有用的「`Foo` 根本不可流式输出」
|
||||
被淹没在几十行噪声里。Concepts 的目标:**在实例化前就用一句人话告诉你类型不达标**。
|
||||
|
||||
### 1.2 三个能力合在一起:命名谓词 + `requires` 表达式 + 能力分派
|
||||
|
||||
下面是一个**能体现 Concepts 全部威力**的例子——一个「按类型能力分派」的通用 `print`:
|
||||
`int`/`std::string` 直接打印,容器(如 `vector`)递归逐元素打印,二者靠**类型能力**自动选对。
|
||||
|
||||
```cpp
|
||||
#include <concepts>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
// 概念 1:可流式输出——探查一个 operator<<,并要求返回类型是 ostream&
|
||||
template<typename T>
|
||||
concept Printable = requires(std::ostream& os, const T& t) {
|
||||
{ os << t } -> std::same_as<std::ostream&>;
|
||||
};
|
||||
|
||||
// 概念 2:可迭代——探查成员函数 begin()/end()
|
||||
template<typename T>
|
||||
concept Iterable = requires(const T& t) {
|
||||
t.begin();
|
||||
t.end();
|
||||
};
|
||||
|
||||
// 重载 A:可流式的直接打印(int / std::string / const char* ……)
|
||||
void print(const Printable auto& x) {
|
||||
std::cout << x;
|
||||
}
|
||||
|
||||
// 重载 B:可迭代、但本身不能直接 << 的容器 → 递归逐元素打印
|
||||
template<typename T>
|
||||
requires Iterable<T> && (!Printable<T>)
|
||||
void print(const T& c) {
|
||||
std::cout << "[ ";
|
||||
for (const auto& e : c) print(e); // 递归:容器套容器也能打
|
||||
std::cout << "] ";
|
||||
}
|
||||
|
||||
int main() {
|
||||
print(42); // → A
|
||||
print(std::string("s")); // → A(string 能 <<,不进 B)
|
||||
print(std::vector<int>{1, 2, 3}); // → B
|
||||
print(std::vector<std::vector<int>>{{1,2},{3,4}}); // → B 递归到 B 再到 A
|
||||
// print(Foo{}); // ❌ 编译错误:清晰,见下
|
||||
}
|
||||
```
|
||||
|
||||
运行输出:
|
||||
```
|
||||
42
|
||||
s
|
||||
[ 1 2 3 ]
|
||||
[ [ 1 2 ] [ 3 4 ] ]
|
||||
```
|
||||
|
||||
这个小例子同时展示了 Concepts 的**四个关键能力**(这正是 `add(Number)` 那种玩具例子看不到的):
|
||||
|
||||
| 能力 | 体现在 |
|
||||
|---|---|
|
||||
| **`requires` 表达式探查成员** | `Iterable` 用 `t.begin(); t.end();` 检查成员函数是否存在 |
|
||||
| **探查运算符 + 返回类型要求** | `Printable` 用 `{ os << t } -> std::same_as<std::ostream&>;` |
|
||||
| **能力分派(按类型能力重载)** | 两个 `print` 重载靠 `Printable` / `Iterable` 自动二选一 |
|
||||
| **概念可参与逻辑运算** | `requires Iterable<T> && (!Printable<T>)` 用 `!` 排除已处理情况 |
|
||||
|
||||
> **那个 `!Printable<T>` 是关键细节**:`std::string` 同时满足 `Printable` 和 `Iterable`
|
||||
> (它既能 `<<` 又有 `begin/end`)。若不加 `!Printable<T>`,两个重载都可行且互不约束
|
||||
> → 调用歧义报错。这里要求重载 B 只处理「可迭代但不可流式」的类型,让 `std::string`
|
||||
> 干净地落进重载 A。这是实际工程里反复用到的模式。
|
||||
|
||||
### 1.3 报错终于说人话了
|
||||
|
||||
把上面 `print(Foo{})` 的注释去掉,错误信息变成:
|
||||
|
||||
```
|
||||
required for the satisfaction of 'Printable<auto:16>' [with auto:16 = Foo]
|
||||
note: constraints not satisfied
|
||||
```
|
||||
|
||||
一句话点出「`Foo` 不满足 `Printable`」,不再有模板展开噪声。
|
||||
|
||||
### 1.4 挂载概念的三种写法(用最小的例子演示语法)
|
||||
|
||||
上例用了**简写模板语法** `void print(const Printable auto& x)`,最清爽。完整三种写法:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Number = std::integral<T> || std::floating_point<T>; // 组合标准库概念
|
||||
|
||||
// ① requires 子句(前置)
|
||||
template<typename T> requires Number<T>
|
||||
T add1(T a, T b) { return a + b; }
|
||||
|
||||
// ② requires 子句(尾随,适合约束长时)
|
||||
template<typename T>
|
||||
T add2(T a, T b) requires Number<T> { return a + b; }
|
||||
|
||||
// ③ 简写模板语法(推荐,省掉 typename)
|
||||
auto add3(Number auto a, Number auto b) { return a + b; }
|
||||
```
|
||||
|
||||
`requires(...) { 表达式序列 }` 检查这些表达式**是否合法**,还能用 `-> 概念` 附加返回类型要求,
|
||||
概念本身可由 `&&` `||` `!` 组合(如上例),也可由标准库概念(`<concepts>` 里的 `integral`、
|
||||
`floating_point`、`same_as`、`convertible_to`、`derived_from`…)拼装。
|
||||
|
||||
**常见的坑**:
|
||||
- **能力重叠 → 歧义**:一个类型同时满足多个概念、且无一个更「强」(subsumption)时,
|
||||
多个重载都可行 → 编译歧义。上面用 `!Printable<T>` 显式排除就是解法之一。
|
||||
- **约束太松仍会回到「天书」**:概念只写 `template<typename T>` 不加 `requires`,
|
||||
失败照样从深处爆出来——概念的价值在于**把约束前置、命名**。
|
||||
- **concept 是零开销**:纯编译期谓词,不参与运行期、不产生代码。
|
||||
|
||||
---
|
||||
|
||||
## 2. Ranges:可组合的区间管道 `C++20`
|
||||
|
||||
```cpp
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
namespace rv = std::views;
|
||||
|
||||
// 管道:惰性求值,不产生中间容器
|
||||
auto result = std::views::iota(1) // 1, 2, 3, ...
|
||||
| rv::filter([](int x){ return x % 2 == 0; }) // 偶数
|
||||
| rv::transform([](int x){ return x * x; }) // 平方
|
||||
| rv::take(5); // 取前 5 个 → 4,16,36,64,100
|
||||
|
||||
for (int x : result) std::cout << x << " ";
|
||||
}
|
||||
```
|
||||
|
||||
**核心概念**:
|
||||
- **视图 (view)**:轻量、**惰性**的区间适配器(如 `filter`/`transform`/`take`/`drop`)。不拷贝数据,遍历时才计算,可 O(1) 拷贝。
|
||||
- **管道 `|`**:像 Unix 管道一样把多个适配器串起来,`a | f | g`。
|
||||
- **ranges 算法**:`std::ranges::sort(v)` / `std::ranges::find(v, x)` 直接吃容器,不用 `v.begin()/v.end()`;还支持投影 `sort(people, {}, &Person::age)`。
|
||||
- **C++23 扩展**:`views::enumerate`(带下标遍历)、`views::zip`(并行拉链)、`views::slide`/`chunk`/`stride`/`adjacent` 等(见附录 A 可用性)。
|
||||
|
||||
**常见的坑**:
|
||||
- 视图是**惰性**的:`filter` 里的谓词在遍历时才执行,定义时不跑。
|
||||
- 视图**不拥有**底层容器:容器销毁后视图悬垂(类似 `string_view`)。
|
||||
- `iota(1)`(无上界)是无限区间,必须配合 `take` 之类才有穷,否则 `for` 死循环。
|
||||
|
||||
---
|
||||
|
||||
## 3. Coroutines:语言级协程 `C++20`
|
||||
|
||||
协程是能**暂停、再恢复**的函数,用三个关键字:`co_await`(等待异步操作)、`co_yield`(产出一个值)、`co_return`(结束)。
|
||||
|
||||
```cpp
|
||||
#include <coroutine>
|
||||
#include <iostream>
|
||||
|
||||
// 最小生成器(C++20 没有标准 std::generator,需自己写 promise_type;
|
||||
// C++23 才有 <generator> 直接可用,见附录 A)
|
||||
Generator<int> count_up(int from) {
|
||||
while (true)
|
||||
co_yield from++; // 每次调用 next() 唤醒,产出一个数
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto g = count_up(10);
|
||||
for (int i = 0; i < 3; ++i)
|
||||
std::cout << g() << " "; // 10 11 12
|
||||
}
|
||||
```
|
||||
|
||||
**核心概念**:
|
||||
- 函数体里出现 `co_await`/`co_yield`/`co_return` 之一,即为协程。
|
||||
- 协程**返回一个「协程句柄对象」**(其类型必须含 `promise_type`),调用者通过它控制暂停/恢复。
|
||||
- 框架只规定**机制**(如何挂起/恢复、如何把值传出去),**策略**(调度、I/O)由库或你自己的 `promise_type` 实现。所以**裸 C++20 协程偏底层**,实际多用库(CppCoro/ASIO/C++23 的 `std::generator`)。
|
||||
|
||||
**常见的坑**:
|
||||
- 协程帧在堆上分配,生命周期与返回的句柄绑定——句柄销毁前协程不能跑完 → 泄漏/悬垂。
|
||||
- 协程本身的「调度」要你自己接,标准库不提供线程池或事件循环(C++26 的 `std::execution` 在补这块,暂定)。
|
||||
|
||||
> Qt 侧:Qt 也有协程支持(Qt Coroutine / 协程化的信号槽),但课程内异步更常用 `QThread` + 信号槽,协程属进阶内容。
|
||||
|
||||
---
|
||||
|
||||
## 4. Modules:替代 `#include` `C++20`
|
||||
|
||||
```cpp
|
||||
// ---- greet.cppm(模块接口单元)----
|
||||
export module greet;
|
||||
export int hello() { return 42; }
|
||||
|
||||
// ---- main.cpp----
|
||||
import greet;
|
||||
int main() { return hello(); }
|
||||
```
|
||||
|
||||
**核心概念**:模块是**语义导入**——编译一次后缓存为二进制接口(`.gcm` 等),下游导入不重复解析,也不受宏污染。相比 `#include` 的「文本粘贴」,模块带来:
|
||||
- **更快编译**(无重复解析、无宏展开风暴、头文件顺序无关)。
|
||||
- **更好封装**(`export` 才对外可见,内部实现不泄漏符号/宏)。
|
||||
|
||||
**重要的现实情况**(务必在课堂上讲清):
|
||||
- 模块的**语言机制**已在 C++20 定稿,但**构建系统集成(CMake/Make/Ninja)与各编译器(GCC/Clang/MSVC)的支持在 C++20 周期是「能用但繁琐」**:GCC 要 `-fmodules-ts` 并安排模块的预编译顺序、生成 `.gcm` 缓存;CMake 到 3.28+ 才较好地支持。
|
||||
- `import std;`(直接导入整个标准库)要**更晚**的工具链(GCC 14+ / Clang 17+ / MSVC 19.36+)才好用。
|
||||
- 本机 GCC 13.3 可以编模块,但需要手动编排编译顺序(详见附录 A 的实测说明)。
|
||||
- **结论**:课堂以「讲机制 + 看语法」为主;要做成可运行示例,需投入构建脚本,超出本仓库「最小示例」范畴。
|
||||
|
||||
---
|
||||
|
||||
## 5. 三向比较 `<=>` `C++20`
|
||||
|
||||
```cpp
|
||||
#include <compare>
|
||||
struct Point {
|
||||
int x, y;
|
||||
auto operator<=>(const Point&) const = default; // 一行生成 <,<=,==,!=,>=,>
|
||||
};
|
||||
```
|
||||
|
||||
**核心概念**:`<=>`("宇宙飞船运算符")做**三向比较**,返回一个**比较类别**:
|
||||
- `std::strong_ordering`(`less`/`equal`/`greater`,全序,如 `int`)
|
||||
- `std::weak_ordering`(相等但不等价,如大小写不敏感的字符串)
|
||||
- `std::partial_ordering`(可能无序,如浮点 `NaN`)
|
||||
|
||||
`= default` 让编译器按成员逐一比较,自动派生全部 6 个关系运算符。也可以**只 `=default` `==`**,编译器会反过来补 `!=`。
|
||||
|
||||
```cpp
|
||||
struct V {
|
||||
int x;
|
||||
bool operator==(const V&) const = default; // 自动获得 !=
|
||||
};
|
||||
```
|
||||
|
||||
**常见的坑**:
|
||||
- `double` 之间是 `partial_ordering`(因为有 `NaN`),`a <=> b` 的结果可能是 `unordered`。
|
||||
- `operator<=>` 默认生成 `==`/`!=`;但若类含指针或不希望全序,需自己定义。
|
||||
|
||||
---
|
||||
|
||||
## 6. `consteval` 与 `constinit` `C++20`
|
||||
|
||||
```cpp
|
||||
consteval int sq(int x) { return x * x; } // 立即函数:必须在编译期求值
|
||||
constinit int g = sq(5); // 常量初始化(值可在运行期,但初始化在编译期)
|
||||
|
||||
int n;
|
||||
std::cin >> n;
|
||||
int a = sq(5); // OK,编译期
|
||||
// int b = sq(n); // ❌ n 不是常量,consteval 强制编译期 → 报错
|
||||
```
|
||||
|
||||
**区别**(三者别混):
|
||||
|
||||
| | `constexpr` | `consteval` | `constinit` |
|
||||
|---|---|---|---|
|
||||
| 修饰对象 | 变量 / 函数 | 仅函数 | 仅变量(静态/线程局部) |
|
||||
| 编译期求值 | **可能**(能在编译期就在,不能就运行期) | **必须**(一定是编译期,否则编译错误) | 初始化**必须**是常量表达式 |
|
||||
| 是否 `const` | 是 | (函数) | **不是**(值之后可改,只是初始化在编译期) |
|
||||
|
||||
**`constinit` 解决什么**:防止**静态初始化顺序灾难**(跨翻译单元的全局对象初始化顺序未定义)。要求初始化是常量表达式,就把对象放进「零开销」的常量初始化阶段,绕开顺序问题。
|
||||
|
||||
---
|
||||
|
||||
## 7. 更多 `constexpr`:编译期能做更多 `C++20`
|
||||
|
||||
C++20 把 `constexpr` 的边界大幅推开:
|
||||
|
||||
- **`constexpr` 动态内存分配**:`constexpr std::vector`、`constexpr std::string`(在常量表达式求值期间分配的内存需在求值结束前释放)。
|
||||
- **`constexpr` 函数里可用 `try/catch`、`std::is_constant_evaluated()`**(C++20,C++23 升级为 `if consteval`)。
|
||||
- 大量标准库函数(`<cmath>`、`<algorithm>` 的部分)标记为 `constexpr`。
|
||||
|
||||
```cpp
|
||||
constexpr int build_sum() {
|
||||
std::vector<int> v{1, 2, 3, 4}; // C++20:constexpr vector
|
||||
int s = 0;
|
||||
for (int x : v) s += x;
|
||||
return s; // 析构时释放,编译期合规
|
||||
}
|
||||
static_assert(build_sum() == 10);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Lambda 与函数对象增强
|
||||
|
||||
### 8.1 模板 lambda `C++20`
|
||||
|
||||
```cpp
|
||||
auto f = []<typename T>(std::vector<T> const& v) {
|
||||
return v.size();
|
||||
};
|
||||
```
|
||||
|
||||
让 lambda 像模板一样把类型当参数,**不需要再用 `auto` 模糊推导**——能在参数列表精确写出 `std::vector<T> const&`。
|
||||
|
||||
### 8.2 推导 `this`(显式对象参数)`C++23`
|
||||
|
||||
```cpp
|
||||
struct Builder {
|
||||
// self 是显式的「this 参数」,类型可推导 → lambda 能递归、能写 CRTP 式重载
|
||||
void log(this auto& self, std::string_view msg) { /* ... */ }
|
||||
};
|
||||
|
||||
// 递归 lambda(以前只能用 std::function 或 Y 组合子):
|
||||
auto fib = [](this auto& self, int n) -> int {
|
||||
return n < 2 ? n : self(n - 1) + self(n - 2);
|
||||
};
|
||||
```
|
||||
|
||||
**核心概念**:把 `this` 写成**显式参数**(`this auto& self`),类型可推导。一举多得:递归 lambda、const/非 const 重载统一、消除 CRTP 样板。
|
||||
|
||||
> 本机 GCC 13.3 **不支持**推导 `this`(附录 A),演示需 GCC 14+。
|
||||
|
||||
### 8.3 多维 `operator[]` 与静态 `operator()` `C++23`
|
||||
|
||||
```cpp
|
||||
struct Matrix {
|
||||
double operator[](int i, int j) { return data[i * cols + j]; } // 多维下标
|
||||
int data[100], cols;
|
||||
};
|
||||
struct Less { static bool operator()(int a, int b) { return a < b; } }; // 静态调用
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 杂项但常用的语言特性 `C++20/23`
|
||||
|
||||
| 特性 | 版本 | 示例 |
|
||||
|---|---|---|
|
||||
| 指定初始化 | `C++20` | `Point p{.x = 1, .y = 2};`(来自 C,受限形式:须按声明顺序、不嵌套) |
|
||||
| 范围 for 的初始化语句 | `C++20` | `for (auto& c = get(); auto& e : c) { ... }` |
|
||||
| `char8_t` | `C++20` | `u8""` 的类型从 `char` 变成独立的 `char8_t`(**ABI 破坏**,老代码要改) |
|
||||
| `using enum` | `C++20` | `using enum Color;` 把枚举值引入作用域 |
|
||||
| 聚合体 CTAD | `C++17/20` | `std::array a{1,2,3};` 推导 `array<int,3>` |
|
||||
| `if consteval` | `C++23` | 替代 `std::is_constant_evaluated()`,更清晰 |
|
||||
| `=delete("原因")` | `C++26` | 删函数时带诊断信息(本机 `-std=c++2b` 已可编译) |
|
||||
| 位域成员默认初始化 | `C++20` | `struct { unsigned a : 3 = 0; };` |
|
||||
|
||||
---
|
||||
|
||||
## 10. 标准库大件 `C++20`
|
||||
|
||||
### 10.1 `std::format`:Python 风格格式化 `C++20`
|
||||
|
||||
```cpp
|
||||
#include <format>
|
||||
std::string s = std::format("x = {}, pi ≈ {:.3f}", 42, 3.14159);
|
||||
```
|
||||
|
||||
替代 `printf`(不类型安全)和 `iostream`(冗长、慢)的中间路线:`{}` 占位 + 安全的类型检查。
|
||||
> **C++23** 的 `std::print`/`std::println` 让你直接 `std::println("x = {}", 42)`,连 `std::format`+`<<` 都省了(本机要 GCC 14+)。
|
||||
|
||||
### 10.2 `<chrono>` 日历与时区 `C++20`
|
||||
|
||||
```cpp
|
||||
using namespace std::chrono;
|
||||
auto year = 2025y;
|
||||
auto date = 2025y/January/1d; // 年/月/日:日历字段(字面量 y / Month / d)
|
||||
auto next_week = sys_days{date} + weeks{1}; // 日期 + 时长
|
||||
auto dur = days{7} + 12h + 30min; // 时长:180 小时(注意 days 是复数 = duration)
|
||||
```
|
||||
|
||||
把日历(年月日、星期)、时长、时区都纳入类型系统,编译器帮你抓「天数+小时数」这类单位错误。
|
||||
|
||||
**坑(极易踩)**:`7d`(单数字面量)是 `chrono::day` **日历字段**(一个月里的「几号」),
|
||||
**不是**「7 天的时长」。7 天的时长要写 `days{7}`(复数类型 = duration)或 `7*24h`。
|
||||
所以 `7d + 12h` **不能编译**——字段不能和时长相加;只有 `sys_days{date} + weeks{1}`
|
||||
这种「日期点 + 时长」才行。
|
||||
|
||||
### 10.3 `std::span`:连续内存的非拥有视图 `C++20`
|
||||
|
||||
```cpp
|
||||
void process(std::span<int> data); // 接收数组 / vector / 某段 —— 零拷贝、带长度
|
||||
int arr[4]; std::vector<int> v;
|
||||
process(arr); process(v); process(std::span{v}.subspan(2, 5));
|
||||
```
|
||||
|
||||
C++ 版的「指针+长度」:栈数组、`vector`、`array` 都能无开销绑定,函数签名终于不必在 `T*` 与 `vector<T>&` 之间二选一。
|
||||
|
||||
### 10.4 `std::jthread`:可停止的线程 `C++20`
|
||||
|
||||
```cpp
|
||||
#include <thread>
|
||||
std::jthread worker([](std::stop_token st){
|
||||
while (!st.stop_requested()) { /* ...干活... */ }
|
||||
});
|
||||
// worker 析构时自动 join,且可 worker.request_stop() 协作式取消
|
||||
```
|
||||
|
||||
比 `std::thread` 多两点:**析构自动 `join`**(不会 `std::terminate`)、内置**协作式取消**(`stop_token`)。
|
||||
|
||||
### 10.5 其它 `C++20` 库
|
||||
|
||||
| 头文件 | 用途 |
|
||||
|---|---|
|
||||
| `<bit>` | `std::bit_cast`(类型双关,替代 `memcpy` 把戏)、`popcount`、`countl_zero`、`rotr`、`bit_ceil` |
|
||||
| `<numbers>` | `std::numbers::pi`、`e`、`sqrt2`…(变量模板) |
|
||||
| `<source_location>` | `std::source_location::current()` 取当前文件/行/函数(替代 `__FILE__`/`__LINE__` 宏) |
|
||||
| `<version>` | 标准库特性检测宏 |
|
||||
| `<concepts>` | 标准概念库:`integral`/`floating_point`/`same_as`/`convertible_to`… |
|
||||
| `<ranges>` | 范围与视图 |
|
||||
| `<coroutine>` | 协程底层设施 |
|
||||
|
||||
---
|
||||
|
||||
## 11. C++23 速览
|
||||
|
||||
C++23 主要是**补 C++20 的坑 + 加库**:
|
||||
|
||||
| 类别 | 特性 | 一句话 |
|
||||
|---|---|---|
|
||||
| 错误处理 | `std::expected<T,E>` | 类型化的「成功/失败」,替代异常或返回码 |
|
||||
| 输出 | `std::print` / `std::println` | `std::format` 直接打印到流 |
|
||||
| 多维 | `std::mdspan` | 多维数组的非拥有视图(GPU/HPC 常用) |
|
||||
| 容器 | `std::flat_map` / `flat_set` | 有序但连续存储的「扁平」关联容器(缓存友好) |
|
||||
| 调试 | `std::stacktrace` | 运行期取调用栈 |
|
||||
| optional 单子 | `and_then` / `or_else` / `transform` | 链式处理「可能无值」 |
|
||||
| 工具 | `std::to_underlying` / `std::unreachable` / `std::byteswap` | 枚举转整数 / 标记不可达 / 字节反转 |
|
||||
| 范围 | `views::enumerate` / `zip` / `slide` / `chunk` / `adjacent` / `stride` | 区间管道再扩 |
|
||||
| 语言 | 推导 `this` / `if consteval` / 多维 `[]` / 静态 `operator()` | 见 §8 |
|
||||
|
||||
```cpp
|
||||
// expected:替代异常的「带类型的错误」
|
||||
std::expected<int, std::string> parse(std::string_view s) {
|
||||
if (s.empty()) return std::unexpected("empty");
|
||||
return 42;
|
||||
}
|
||||
if (auto r = parse("x")) use(*r); else handle(r.error());
|
||||
|
||||
// views::enumerate:带下标遍历,告别手动 i++
|
||||
for (auto [i, v] : std::views::enumerate(items)) { /* ... */ }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. C++26 展望(暂定,未完全定稿)
|
||||
|
||||
> **状态声明**:C++26 标准还在制定中,以下为「候选/已合并」特性,**本机 GCC 13.3 基本都还用不了**(需 GCC 14+,多数需 `-std=c++2c`,本机连该标志都不识别)。列在这里是为了让学员建立「C++ 还在持续演进」的认知。
|
||||
|
||||
- **`std::execution`(Senders/Receivers,P2300)**:标准化的异步/并行执行框架,C++ 终于有统一的「调度 + 组合异步操作」底座。
|
||||
- **`= delete("原因")`**:删函数带诊断信息(本机 `-std=c++2b` 已能编译,是少数现在能试的)。
|
||||
- **`std::inplace_vector`**:栈上、容量固定的 `vector`(不动态分配,适合嵌入式/实时)。
|
||||
- **`std::linalg`**:基于 `mdspan` 的线性代数(BLAS 风格)。
|
||||
- **`#embed`**:把二进制文件直接嵌入源码,替代 `xxd`/工具链把戏。
|
||||
- **打包下标 `a[1, 2, 3]`**:一次取多个元素。
|
||||
- **契约(Contracts)/ 静态反射(Reflection)**:呼声很高,但能否进 C++26 仍有变数(多次反复)。
|
||||
|
||||
---
|
||||
|
||||
## 附 A:本机 GCC 13.3.0 实测可用性矩阵
|
||||
|
||||
> 全部为 `-std=c++20` / `-std=c++23` / `-std=c++2b` 下的**实测**结果(`-Wall -pedantic-errors`)。✅ 可用 / ❌ 缺失或需更高版本。
|
||||
|
||||
### C++20(基本全可用)
|
||||
|
||||
| 特性 | 结果 | 备注 |
|
||||
|---|---|---|
|
||||
| Concepts(`requires` / 简写 / `<concepts>`) | ✅ | |
|
||||
| Ranges(`views::`、`ranges::sort`、管道) | ✅ | |
|
||||
| `<=>` / `<compare>` | ✅ | |
|
||||
| `consteval` / `constinit` | ✅ | |
|
||||
| `std::format` / `<format>` | ✅ | |
|
||||
| `std::span` / `<bit>` / `<numbers>` / `<source_location>` | ✅ | |
|
||||
| `std::jthread` + `stop_token` | ✅ | |
|
||||
| 指定初始化 / 范围 for 初始化语句 / `char8_t` / `using enum` | ✅ | |
|
||||
| 模板 lambda `[]<typename T>(...)` | ✅ | |
|
||||
| `<chrono>` 日历字面量 | ✅ | |
|
||||
| **Modules** | ⚠️ | 语言机制可用,但需手动编排编译顺序与 `.gcm` 缓存,构建系统集成在 CMake 3.28+ 才较好 |
|
||||
| Coroutines(`<coroutine>`) | ⚠️ | 机制可用,但 `std::generator` 要 C++23,C++20 需自写 `promise_type` |
|
||||
|
||||
### C++23(语言特性多数可用,库分两半)
|
||||
|
||||
| 特性 | 结果 | 备注 |
|
||||
|---|---|---|
|
||||
| `if consteval` | ✅ | |
|
||||
| 多维 `operator[]`(`a[i,j]`) | ✅ | |
|
||||
| 静态 `operator()` | ✅ | |
|
||||
| `std::to_underlying` / `std::unreachable` / `std::byteswap` | ✅ | |
|
||||
| `<expected>` | ✅ | |
|
||||
| `std::optional` 单子(`and_then`/`transform`/`or_else`) | ✅ | |
|
||||
| `views::enumerate` / `zip` / `slide` | ✅ | |
|
||||
| **推导 `this`**(`this auto& self`) | ❌ | 需 GCC 14+ |
|
||||
| **`<print>` / `std::println`** | ❌ | 需 GCC 14+ |
|
||||
| **容器 `std::format`**(`format("{}", vector)`) | ❌ | 需 GCC 14+ |
|
||||
| **`<mdspan>` / `<flat_map>` / `<inplace_vector>`** | ❌ | 需 GCC 14+ |
|
||||
| **`<stacktrace>`** | ⚠️ | 头文件存在,但需链接 `-lstdc++_exp`,本机**无该库** → 实际不可用 |
|
||||
| **`std::generator`** | ❌ | 需 GCC 14+ |
|
||||
|
||||
### C++26(本机基本不可用)
|
||||
|
||||
| 特性 | 结果 | 备注 |
|
||||
|---|---|---|
|
||||
| `-std=c++2c` / `-std=c++26` 标志 | ❌ | 本机 GCC 13.3 不识别该标志 |
|
||||
| `= delete("原因")` | ✅ | `-std=c++2b` 下可编译(少数能试的) |
|
||||
| `std::execution` / `inplace_vector` / `#embed` / 契约 / 反射 | ❌ | 全部需 GCC 14+,多数尚未实现 |
|
||||
|
||||
---
|
||||
|
||||
## 附 B:学习路径建议
|
||||
|
||||
1. **先吃透 C++17**(见 [`MODERN_CPP.md`](MODERN_CPP.md)):移动、智能指针、lambda、`optional`/`variant` 是 C++20 的前提。
|
||||
2. **C++20 入门顺序**:Concepts → Ranges → `<=>` → `std::format`/`span`/`jthread`;Modules 和 Coroutines 放最后(构建/调度复杂度高)。
|
||||
3. **C++23 实用件**(你的工具链够新时):`expected`、`std::print`、`views::enumerate`——日常工程立刻能受益。
|
||||
4. **C++26** 持续关注,不必急着用——标准与实现都还在收敛。
|
||||
5. **权威参考**:[cppreference.com](https://en.cppreference.com/w/)、[C++ 标准委员会邮件列表](https://www.open-std.org/jtc1/sc22/wg21/)、Sutter《Welcome to the Jungle / 历年 trip report》。
|
||||
|
||||
## 附 C:与本仓库的关联
|
||||
|
||||
- **本仓库基线 C++17**:本文件所有特性都**超出基线**,需显式开 `-std=c++20`+;仓库默认构建(`cmake -DCMAKE_BUILD_TYPE=Debug`)不会触及。
|
||||
- **如需在课堂上跑 C++20 演示**:可在 `tools/demo_versions/` 加一个 `-std=c++20` 的目标(需在生成器/CMake 里单独加 `COMPILE_OPTIONS`),与现有 `std_probe.py` 跨版本对比机制衔接。**这属于新增工程改动,需要你明确指令我才执行。**
|
||||
- **跨版本探针**:`tools/std_probe.py` 已支持把片段喂给任意 `-std=`,可用它复现本文附录 A 的矩阵。
|
||||
- **Qt 侧**:Qt 6 对 C++17 是基线、对 C++20 部分采用;本仓库用的隔离 Qt 5.14.2 以 C++17 为准,C++20 特性暂不混入 `p03/` 生成产物。
|
||||
@@ -1,4 +1,11 @@
|
||||
# 教案大纲 —「C++/Qt 方向」5 天实训(对齐官方方案书 Day1–5)
|
||||
# 教案大纲 —「C++/Qt 方向」5 天实训(对齐官方方案书 Day1–5)【已被 4 天版取代】
|
||||
|
||||
> **⚠️ 基准变更(2026-07-05)**:收到新版 10 天实训安排
|
||||
> ([`docs/SCHEDULE_2026_10DAY.md`](../SCHEDULE_2026_10DAY.md),Day1–4 讲授 + Day5
|
||||
> 综合案例 + Day6–10 项目开发),现行教案已改为
|
||||
> [`OUTLINE_4DAY.md`](OUTLINE_4DAY.md),幻灯片 `slides/day1–5.html` 也已按 4 天版重排。
|
||||
> 本文件对齐的是**旧方案书**的 Day1–5 结构,保留作历史参照与概念/坑卡素材库
|
||||
> (4 天版复用了本文全部 24 张卡片),以及 PRETEST 摸底大面积不达标时的回退预案。
|
||||
|
||||
> 配套材料:`docs/teaching/slides/day1.html` … `day5.html`(reveal.js 交互幻灯片,
|
||||
> 已 vendor 进仓库,离线可用,见文末「材料与播放方式」)。
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
# 4 天课堂设计(对齐 2026-07 新版 10 天安排)+ 中初级学员适配性评估
|
||||
|
||||
> **依据**:2026-07-05 收到的新版 10 天安排(正式全文见
|
||||
> [`docs/SCHEDULE_2026_10DAY.md`](../SCHEDULE_2026_10DAY.md),简表见 §0.1),取代
|
||||
> `docs/TRAINING_PLAN_2026.md` 的旧执行计划作为课堂设计基准。新安排本身就是
|
||||
> 「**4 天讲授(Day1–4)+ Day5 综合案例讲解 + Day6–10 项目开发/答辩**」结构,
|
||||
> 因此本文即新基准下的正式教案大纲;此前对齐旧方案书的 5 天版
|
||||
> [`OUTLINE.md`](OUTLINE.md) 保留作历史参照与素材库(其 24 张概念/坑卡在本文
|
||||
> 全部复用)。幻灯片 `day1.html`…`day5.html` **已按本文重排**(§9 落地状态)。
|
||||
|
||||
## 0. 基准与显式假设(供随时纠偏)
|
||||
|
||||
### 0.1 新版 10 天安排(简表,讲授部分逐条对应)
|
||||
|
||||
| 天 | 时段 | 内容 |
|
||||
|---|---|---|
|
||||
| 1 | 上午讲 | 开训典礼;C++ 常用技术和框架(讲座);开发环境搭建;Makefile/CMake;面向对象基础回顾(类/对象/封装) |
|
||||
| 1 | 下午讲 | C++ 核心特性(继承与多态、虚函数);内存管理(指针、引用与动态分配);STL 常用容器简介 |
|
||||
| 2 | 上午讲 | Qt 概述;创建 Qt 项目;第一个 Qt 小程序 |
|
||||
| 2 | 下午讲 | 信号槽机制;项目案例:简易计算器 |
|
||||
| 3 | 上午讲 | QMainWindow;对话框 QDialog |
|
||||
| 3 | 下午讲 | 布局管理器;常用控件 |
|
||||
| 4 | 上午讲 | Qt 消息机制和事件;绘图事件及表格事件 |
|
||||
| 4 | 下午讲 | 文件系统 |
|
||||
| 5 | 全天 | 综合项目案例讲解(本文 §8 给出案例设计) |
|
||||
| 6–10 | — | 学员项目开发;Day10 下午项目答辩 |
|
||||
|
||||
每个半天的固定时段结构:**08:30–11:00 讲授**(2.5h,产出=课堂代码+笔记+随堂练习)、
|
||||
**11:00–12:00 课堂练习+工程师答疑**(1h);下午 **13:30–16:00 讲授**、
|
||||
**16:00–17:00 课堂作业+答疑**(1h)。
|
||||
|
||||
### 0.2 显式假设
|
||||
|
||||
1. **受众**:学员已掌握 C++ 基础语法与 OOP 概念,Day1 定位是「回顾和提升」。
|
||||
仍用 [`PRETEST.md`](PRETEST.md)(10 题/10 分钟,双平行卷)实测:**Day1 上午
|
||||
「OOP 基础回顾」开讲前**发卷,按结果决定回顾环节的推进速度(分层预案见 PRETEST)。
|
||||
2. **讲授块内部节奏**:2.5h 纯讲对中初级学员超出注意力极限,本设计把每个讲授块
|
||||
切成 **3–4 个「讲 25–30min + 随堂微练 10–15min」循环**(新安排的产出列本就
|
||||
含「课堂练习」);11:00/16:00 后的整段练习时间做当天的任务卡。
|
||||
3. **新安排未点名的旧主题的处置**(关键决策,§4 有完整对照):QSS 并入 Day3 下午
|
||||
(新安排该时段目标明含「美化」);JSON 并入 Day4 下午(「用户数据的保存与读取」
|
||||
的自然载体);表格用 QTableWidget 讲、Model/View 降为延伸钩子;**QThread 移入
|
||||
Day5 综合案例的「提升」环节**(理由与风险见 §6);动画/状态机降为自学指针;
|
||||
Vcpkg 降为 CMake 环节的 1 分钟概念;OpenCV 移除(旧方案书本就标注可选)。
|
||||
4. 工具链沿用仓库现状:host gcc/g++ + CMake/Ninja(Part1)、隔离 Qt 5.14.2(Part3),
|
||||
构建命令见根 [`README.md`](../../README.md);Makefile 只在 Day1 讲「CMake 生成
|
||||
构建系统」时作对照概念,不单独实操。
|
||||
|
||||
---
|
||||
|
||||
## 1. 四天总览(主题 → 仓库资产映射)
|
||||
|
||||
| Day | 上午 | 下午 | 当日作业产出 |
|
||||
|---|---|---|---|
|
||||
| 1 | 开训 + C++ 技术版图 + 环境/CMake `CMakeLists.txt` + OOP 回顾 `p01/ch04` | 继承/多态/虚析构 `p01/ch04/s08` + 内存管理 `p01/ch03` + STL `p01/ch09` | Part1 全绿构建 + 2 道 C++ 改错题 |
|
||||
| 2 | Qt 概述 + 项目创建 + 第一个程序 `p03/ch02` `ch03` | 信号槽 `p03/ch04` → 计算器案例 | 可运算的简易计算器 |
|
||||
| 3 | QMainWindow `p03/ch05` + QDialog `p03/ch06` | 布局/控件 `p03/ch08` + QSS `examples/qss_styling` | 计算器升级:主窗口化 + 美化 |
|
||||
| 4 | 事件机制 `p03/ch09` + 绘图 `p03/ch10` + 表格 | 文件 IO `p03/ch11` + JSON `examples/json_io` | 本地数据存储小工具(写入→重启→读回) |
|
||||
|
||||
四天作业刻意串成一条线:Day2 计算器 → Day3 给它套 QMainWindow+QSS → Day4 数据
|
||||
落盘,Day5 综合案例是同一能力栈的完整版,学员进入 Day6 项目开发前已完整走过
|
||||
3 轮「编码→构建→运行→演示」闭环。
|
||||
|
||||
---
|
||||
|
||||
## 2. 逐天设计
|
||||
|
||||
概念/坑卡标注:**[沿用]**= 取自 5 天版 `OUTLINE.md` 的同名卡片(内容不重抄,
|
||||
以该文件为准);**★[新增]**= 本轮中初级误区审计(§5)补入的卡片,正文写全。
|
||||
|
||||
### Day 1 上午:开训 / C++ 技术版图 / 环境与 CMake / OOP 回顾
|
||||
|
||||
讲授块切分(2.5h):
|
||||
|
||||
| 环节 | 内容 | 素材 |
|
||||
|---|---|---|
|
||||
| 开训典礼(≈40min,非教学) | 团队/实训安排/考评要求 | 组织方主持 |
|
||||
| C++ 常用技术和框架(讲座 25min) | 桌面(Qt)/后端/嵌入式/游戏四大版图;C++11→20 标准演进一页图(兴趣钩子→ [`MODERN_CPP.md`](MODERN_CPP.md)) | 无仓库代码,讲座式 |
|
||||
| 环境搭建 + CMake(30min 讲 + 15min 随堂练) | 用本仓库当真教材:`-S -B` 生成 / `--build` 编译两阶段;`option()`、`add_executable`;Makefile 是 CMake 的生成目标之一(对照概念);Vcpkg 一分钟概念 | 根 `CMakeLists.txt`、`p01/CMakeLists.txt` |
|
||||
| **PRETEST 摸底(10min)** | 发 A/B 卷,收卷即批 L1 题 | [`PRETEST.md`](PRETEST.md) |
|
||||
| OOP 基础回顾(30min,按摸底结果调速) | 类/对象/封装;namespace 作用域 | `p01/ch04`、`p01/ch03/namespace_syntax.cpp` |
|
||||
|
||||
概念/坑卡:**CMake 项目构建[沿用]**、**namespace 作用域[沿用]**、外加——
|
||||
|
||||
- ★**编译错误 vs 链接错误的排查分界**:概念——`error:` 带文件行号是编译期(语法/
|
||||
类型),`undefined reference` 无行号是链接期(缺定义/缺库/缺目标文件),排查方向
|
||||
完全不同;坑——中初级学员见到 `undefined reference` 习惯回头查语法,方向就错了。
|
||||
此卡是 Day2「忘写 `Q_OBJECT` 报链接错」的前置铺垫。
|
||||
|
||||
11:00–12:00 课堂练习:**任务卡 1**——独立完成 Part1 配置+构建
|
||||
(`cmake -S . -B build -G Ninja -DBUILD_QT_PART=OFF && cmake --build build`,
|
||||
144 目标全绿,能说出 `option(BUILD_QT_PART)` 的作用)。
|
||||
|
||||
### Day 1 下午:继承与多态 / 内存管理 / STL 简介
|
||||
|
||||
| 环节 | 内容 | 素材 |
|
||||
|---|---|---|
|
||||
| 继承/多态/虚函数(40min 讲 + 15min 练) | 虚函数表机制、重写 vs 重载;虚析构与内存泄漏 | `p01/ch04/s08/virtual_dtor_purpose.cpp`;函数重载 `p01/ch03/function_overload_mechanism.cpp` |
|
||||
| 内存管理(40min 讲 + 15min 练) | 指针/引用/动态分配;引用的本质;**所有权**——谁负责 delete | `p01/ch03/reference_essence.cpp` |
|
||||
| STL 常用容器(25min 讲 + 15min 练) | vector/map/string 三件套;迭代器统一接口 | `p01/ch05/class_template_basics.cpp`、`p01/ch09/s03/vector_iterator.cpp` |
|
||||
|
||||
概念/坑卡:**函数重载决议[沿用]**、**引用的本质[沿用]**、**虚析构与内存
|
||||
泄漏[沿用]**、**模板与 STL 速览[沿用]**(含迭代器失效坑)、外加——
|
||||
|
||||
- ★**所有权心智模型:谁负责 delete**:概念——内存管理的核心问题不是「会不会 new」
|
||||
而是「这块资源归谁管、由谁释放」,现代答案是 RAII/智能指针(`unique_ptr` 独占、
|
||||
`shared_ptr` 共享,详见 [`MODERN_CPP.md`](MODERN_CPP.md) §2);坑——双重释放、
|
||||
忘记释放、释放后使用三兄弟全部源于所有权不明。**本卡是 Day2 Qt 对象树的前置
|
||||
铺垫**:Qt 用父子所有权回答同一个问题,两套体系混用有专门的坑(Day2)。
|
||||
|
||||
16:00–17:00 作业:从 `docs/ERRATA.md` 挑 2 条与今天知识点相关的记录(虚析构/引用
|
||||
/重载类),在源文件里理解「wiki 原文错在哪、为什么」,各写一句话解释;选做——用
|
||||
`tools/std_probe.py` 验证一个版本差异点(对照 `docs/VERSION_NOTES.md`)。
|
||||
|
||||
### Day 2 上午:Qt 概述 / 创建 Qt 项目 / 第一个 Qt 小程序
|
||||
|
||||
| 环节 | 内容 | 素材 |
|
||||
|---|---|---|
|
||||
| Qt 概述(25min) | Qt 是什么/能做什么;Qt 5.14 vs 系统 Qt 隔离(本仓库 `.qt514/` 的做法);模块结构 Core/Gui/Widgets | 根 `CMakeLists.txt` 的 Qt 版本断言 |
|
||||
| 创建 Qt 项目(30min 讲 + 15min 练) | CMake 方式创建(`find_package(Qt5)`、AUTOMOC);构建开关 `-DBUILD_QT_PART=ON` | `p03/CMakeLists.txt`、`p03/ch02/CMakeLists.txt` |
|
||||
| 第一个 Qt 小程序(40min 讲 + 20min 练) | `QApplication` + `exec()` 事件循环;控件创建与显示;QObject 对象树 | `p03/ch02/minimal_qt_app.cpp`、`p03/ch03/button_creation.cpp` |
|
||||
|
||||
概念/坑卡:**最小 Qt 程序结构[沿用]**(含忘 `Q_OBJECT` 链接错——正好接 Day1
|
||||
链接错误卡)、**控件创建与显示[沿用]**(对象树/parent)、外加——
|
||||
|
||||
- ★**栈对象与父子所有权打架**:概念——Qt 父子树在 parent 析构时 `delete` 所有子
|
||||
对象,这要求子对象必须建在堆上;坑——把带 parent 的控件建在栈上
|
||||
(`QPushButton btn(&window);`),作用域结束栈析构一次、parent 再 delete 一次 →
|
||||
双重释放崩溃。与 Day1 所有权卡呼应:**Qt 树管的对象不要再用智能指针/栈/手动
|
||||
delete 去管**,一个对象只能有一个所有者。
|
||||
|
||||
11:00–12:00 课堂练习:跑通 `minimal_qt_app` 与 `button_creation`
|
||||
(`QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>` 退出码 0);改造:窗口
|
||||
标题、按钮文字换成自己的学号姓名(第一次触碰 QString 中文——为下午的编码卡埋点)。
|
||||
|
||||
### Day 2 下午:信号槽机制 → 案例:简易计算器
|
||||
|
||||
| 环节 | 内容 | 素材 |
|
||||
|---|---|---|
|
||||
| 信号槽(50min 讲 + 20min 练) | 观察者模式;内置/自定义/lambda 三种写法;新旧语法对比;连接方式 Direct/Queued/Auto | `p03/ch04/builtin_signal_slot.cpp`、`custom_signal_slot.cpp`、`lambda_signal_slot.cpp` |
|
||||
| 计算器案例(40min 带练) | 数字/运算键盘 + 点击信号槽 + 结果显示;**布局用教师提供的 `QGridLayout` 脚手架「先用后学」**(布局原理 Day3 下午讲透,今天只求能排出键盘) | `p03/ch03` + `p03/ch04` + 脚手架片段(授课时现场给出) |
|
||||
|
||||
概念/坑卡:**信号槽三种写法[沿用]**(含旧语法静默失败、`QOverload` 消歧义)、外加——
|
||||
|
||||
- ★**lambda 槽的生命周期与第五参数**:概念——`connect` 第五参数(context object)
|
||||
决定连接的生存期,lambda 槽应传 context,对象销毁连接自动断开;坑——lambda 按引用
|
||||
捕获局部变量、或捕获 `this` 而不传 context,对象销毁后信号再来就是悬空访问——这是
|
||||
从「能用 lambda」到「敢在项目里用 lambda」之间最大的坑。
|
||||
- ★**重复 connect 导致槽触发多次**:概念——`connect` 每调一次新增一条连接,同一对
|
||||
信号槽连两次槽就被调两次(`Qt::UniqueConnection` 可防重);坑——把 `connect` 写在
|
||||
会被多次执行的函数里,现象是「点一次按钮弹两次框」,学员往往去查按钮而不是查连接。
|
||||
- ★**QString 与中文编码**:概念——`QString` 内部是 UTF-16,与 `std::string`(字节串)
|
||||
互转要经 `toUtf8()`/`fromUtf8()`;坑——源码不存成 UTF-8、或用 `QString(char*)`
|
||||
隐式转换中文字面量,界面就是乱码。**中文教学环境第一天写界面必踩**,上午练习的
|
||||
学号姓名改造就是现场复现点。
|
||||
|
||||
16:00–17:00 作业:完成计算器——至少支持两个数 + 加减乘除,结果正确显示在
|
||||
`QLabel`/`QLineEdit`;界面不重叠、按钮可点击(验收同 5 天版计算器任务卡 1)。
|
||||
|
||||
### Day 3 上午:QMainWindow / 对话框 QDialog
|
||||
|
||||
| 环节 | 内容 | 素材 |
|
||||
|---|---|---|
|
||||
| QMainWindow(45min 讲 + 20min 练) | 主窗口架构:菜单栏/工具栏/状态栏/中心部件/停靠窗 | `p03/ch05/central_widget.cpp`、`dock_widget.cpp` |
|
||||
| QDialog(45min 讲 + 20min 练) | 模态 vs 非模态(三种非模态写法对比:栈/堆/`WA_DeleteOnClose`);标准对话框 `QMessageBox`/`QFileDialog` | `p03/ch06/modal_dialog.cpp`、`modeless_dialog_stack.cpp`、`modeless_dialog_heap.cpp`、`modeless_dialog_deleteonclose.cpp`、`message_box_question.cpp`、`file_dialog.cpp` |
|
||||
|
||||
概念/坑卡(本节两张为本文新写,5 天版未覆盖 ch05/ch06):
|
||||
|
||||
- **QMainWindow 结构**:概念——QMainWindow 不是「大号 QWidget」,它有固定的区域
|
||||
布局协议(菜单/工具/状态/中心/停靠各就其位),中心部件必须 `setCentralWidget`
|
||||
显式安放;坑——直接往 QMainWindow 上 `addWidget`/摆控件而不设中心部件,布局行为
|
||||
不可预期;菜单动作 `QAction` 忘了 `connect` 到槽,菜单显示正常点了没反应。
|
||||
- **模态与非模态的生命周期**:概念——模态 `exec()` 阻塞等结果、返回值即用户选择;
|
||||
非模态 `show()` 立即返回,对话框对象必须活过函数作用域(堆 + parent,或
|
||||
`WA_DeleteOnClose` 自清理);坑——**栈上建非模态对话框 `show()` 完函数一返回就
|
||||
析构,窗口一闪而过**——`p03/ch06` 三个 modeless 版本正是这个坑的对照教材,也是
|
||||
Day2 栈/堆所有权卡的第三次复现。
|
||||
|
||||
11:00–12:00 课堂练习:给昨天的计算器套上 QMainWindow——加菜单「帮助→关于」弹
|
||||
`QMessageBox`;加一个「历史」停靠窗(可先放空 QLabel)。
|
||||
|
||||
### Day 3 下午:布局管理器 / 常用控件(+QSS 美化)
|
||||
|
||||
| 环节 | 内容 | 素材 |
|
||||
|---|---|---|
|
||||
| 布局管理器(40min 讲 + 15min 练) | QHBox/QVBox/QGrid;嵌套布局;伸缩因子——回收 Day2 计算器脚手架,讲透当时「先用后学」的部分 | `p03/ch08/custom_widget.cpp` |
|
||||
| 常用控件(35min 讲 + 15min 练) | QLabel 文本/HTML/图片、QLineEdit、按钮族;资源系统 `.qrc` | `p03/ch08/label_text_html.cpp`、`label_pixmap.cpp`、`label_movie.cpp` |
|
||||
| QSS 界面美化(25min 讲 + 20min 练) | 选择器/伪状态/层叠——对应新安排「自适应排版及**美化**」目标 | `docs/teaching/examples/qss_styling/qt_qss_styling.cpp` + [README](examples/qss_styling/README.md) |
|
||||
|
||||
概念/坑卡:**布局管理[沿用]**(同控件不能属两个布局;布局后 `setGeometry` 被
|
||||
覆盖)、**资源系统 .qrc[沿用]**(路径静默失败/AUTORCC)、**QSS 美化[沿用]**
|
||||
(ID 选择器要 `setObjectName`;`setStyleSheet` 级联失控)。
|
||||
|
||||
16:00–17:00 作业:计算器升级——数字键/运算键/等号键用 QSS 区分颜色,至少 1 个按钮
|
||||
有 `:hover` 或 `:pressed` 效果;整体布局改为可随窗口缩放(验收同 5 天版 QSS 任务卡)。
|
||||
|
||||
### Day 4 上午:Qt 消息机制和事件 / 绘图事件及表格事件
|
||||
|
||||
| 环节 | 内容 | 素材 |
|
||||
|---|---|---|
|
||||
| 事件循环与派发(35min 讲 + 15min 练) | `exec()` 事件循环回收 Day2 概念;事件 vs 信号槽两套机制;**主线程阻塞=界面冻结**(为 Day5 QThread 提升埋点) | `p03/ch02/minimal_qt_app.cpp` |
|
||||
| 事件拦截(30min 讲 + 15min 练) | 重写 `xxxEvent()` vs `installEventFilter()`;`accept()`/`ignore()` 传播 | `p03/ch09/event_override_customwidget.cpp`、`event_filter_mainwindow.cpp`、`mouse_event.cpp` |
|
||||
| 绘图事件(30min 讲 + 15min 练) | `paintEvent` + `QPainter`;`update()` 异步重绘;绘图设备一瞥 | `p03/ch10/qpainter_basic.cpp`(设备类 `paint_device_*.cpp` 作参考) |
|
||||
| 表格(15min 演示) | `QTableWidget` 单元格填充与 `cellClicked`/`itemChanged` 事件(item 类控件够项目用);Model/View 架构一页概念 + 延伸钩子 | `docs/teaching/examples/table_widget/qt_table_widget_basics.cpp`(M/V 钩子:`examples/model_view/`) |
|
||||
|
||||
概念/坑卡:**事件循环:事件 vs 信号[沿用]**、**自定义事件处理/事件过滤器
|
||||
[沿用]**(重写忘调基类丢默认行为)、**QPainter 绘图[沿用]**(`paintEvent`
|
||||
之外绘制 UB;忘 `update()` 不重画)、**Model/View 架构[沿用,降为概念页]**。
|
||||
|
||||
11:00–12:00 课堂练习:给控件装事件过滤器拦截鼠标按下并打印坐标;在 `paintEvent`
|
||||
里画一个组合图形(棋盘格或仪表盘刻度)。
|
||||
|
||||
### Day 4 下午:文件系统(+JSON)
|
||||
|
||||
| 环节 | 内容 | 素材 |
|
||||
|---|---|---|
|
||||
| 文件读写(40min 讲 + 20min 练) | `QFile`/`QTextStream`/`QDataStream`;`QIODevice` 统一抽象 | `p03/ch11/qfile_basic.cpp`、`qtextstream_io.cpp`、`qdatastream_io.cpp` |
|
||||
| JSON 解析与写入(35min 讲 + 20min 练) | round-trip 正确性标准;`QJsonDocument/Object/Array/Value` | `docs/teaching/examples/json_io/qt_json_parse_write.cpp` + [README](examples/json_io/README.md) |
|
||||
|
||||
概念/坑卡:**QIODevice 统一读写抽象[沿用]**(`open()` 不检查静默失败)、
|
||||
**JSON 序列化/反序列化[沿用]**(`QJsonParseError` 不检查静默空文档;类型不匹配
|
||||
返回默认值不报错)、外加——
|
||||
|
||||
- ★**相对路径与工作目录**:概念——`QFile("data.json")` 的相对路径基于**进程当前
|
||||
工作目录**,不是源码目录也不一定是可执行文件目录;IDE、终端、`tools/run_qt.sh`
|
||||
三种跑法工作目录可能各不相同;坑——「IDE 里能读到文件,命令行跑就说不存在」,
|
||||
中初级学员几乎必问。正解:`QCoreApplication::applicationDirPath()` 拼路径,或
|
||||
只读资源走 `.qrc`(呼应 Day3 资源卡)。
|
||||
- ★**文本编码再遇(文件版)**:概念——`QTextStream` 默认编码随平台/区域设置而变
|
||||
(Qt 5 默认本地编码),跨平台交换文本必须显式 `setCodec("UTF-8")`;坑——Windows
|
||||
写的文件 Linux 读出乱码(或反过来)。与 Day2 的 QString 编码卡同根源、隔两天
|
||||
第二次出现,刻意间隔重复形成「编码要显式指定」的条件反射。
|
||||
|
||||
16:00–17:00 作业:本地数据存储小工具——「学员信息」结构(姓名/成绩/标签)增加记录
|
||||
并保存到 JSON 文件,重启程序读回全部记录(至少 2 条、字段不丢,验收同 5 天版
|
||||
存储任务卡 2)。**这是 Day5 综合案例的直接前身。**
|
||||
|
||||
---
|
||||
|
||||
## 3. Day5 综合项目案例讲解(全天,衔接设计)
|
||||
|
||||
新安排 Day5 的目标是「通过综合案例讲解 Qt 开发流程、规范及方法」。建议案例:
|
||||
**「学员信息管理器」**——把四天作业线收束成完整小软件,正好覆盖全部已学内容并
|
||||
补上项目开发前的最后三块拼图:
|
||||
|
||||
| 时段 | 内容 | 覆盖 |
|
||||
|---|---|---|
|
||||
| 上午 1 | 需求拆解 → 界面骨架:QMainWindow + 菜单/工具栏 + QTableWidget 列表 + 增改对话框 | Day3 全部 + Day4 表格 |
|
||||
| 上午 2 | 数据层:记录结构 ↔ QJson 序列化 ↔ 落盘读回;信号槽把界面和数据接起来 | Day4 下午 + Day2 |
|
||||
| 下午 1 | **提升:QThread 防卡界面**——导入大文件时界面不冻结(worker 对象模式、跨线程不碰 GUI) | `docs/teaching/examples/qthread_worker/` + [README](examples/qthread_worker/README.md)(旧 5 天版 D4A 的概念/坑卡照用) |
|
||||
| 下午 2 | 开发流程与规范:Git 提交粒度(对应任务粒度、信息可追溯,忌"update");代码走查怎么做;考评三维度(管理/技术/素养);分组定项目方向 | `docs/TRAINING_PLAN_2026.md` 质量保证节 + `docs/images/` 三图 |
|
||||
| 兴趣钩子(穿插) | 动画/状态机各 5min 演示(给界面加转场动效的方向) | `examples/property_animation/`、`state_machine/` |
|
||||
|
||||
QThread 放 Day5 而非砍掉的理由:**「主线程阻塞=界面冻结」是学员项目里最高频的
|
||||
崩溃/卡死来源**,新安排 Day1–4 没有它的位置,但综合案例的「导入数据」情节是它
|
||||
最自然的出场时机;且 Day4 上午事件循环卡已埋点,Day5 只需 60–70 分钟就能讲透
|
||||
worker 模式三件套(亲和性/moveToThread 时序/跨线程信号槽自动 Queued)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 评估 A:新安排条目覆盖核对 + 旧主题去向
|
||||
|
||||
**新安排讲授条目 → 本设计位置**:逐条对齐(§2 各表第一列即新安排原文条目),
|
||||
无遗漏;「Makefile/CMake」按 §0.2 假设 4 以 CMake 实操为主、Makefile 为对照概念。
|
||||
|
||||
**旧 5 天版有、新安排未点名的主题的去向**:
|
||||
|
||||
| 旧主题 | 去向 | 理由 |
|
||||
|---|---|---|
|
||||
| QSS 美化 | Day3 下午(正式讲授) | 新安排 Day3 下午目标原文含「美化」;且计算器案例线需要它 |
|
||||
| JSON | Day4 下午(正式讲授) | 「用户数据的保存与读取」的事实标准载体;项目实作必用 |
|
||||
| Model/View | Day4 上午概念页 + 延伸钩子 | 项目层面 QTableWidget 够用;完整 M/V 对中初级学员超纲 |
|
||||
| QThread 多线程 | Day5 提升环节(保留讲授) | 见 §3 论证;完全砍掉是项目期最大风险点 |
|
||||
| 动画/状态机 | Day5 钩子 + 自学指针 | 项目使用频率低;示例与 README 完整保留 |
|
||||
| Scrum/团队协同/考核标准 | 开训典礼(组织方)+ Day5 下午 2 | 讲授权重下调,但 Git 习惯与考评三维度保留在进项目前最后一课 |
|
||||
| Vcpkg | Day1 CMake 环节 1 分钟概念 | 新安排未点名;保留「包管理器是什么」的认知即可 |
|
||||
| OpenCV | 移除(不再作自学指针占课时) | 旧方案书即标注可选;新安排无此条 |
|
||||
|
||||
## 5. 评估 B:中初级学员常见误区/盲点覆盖审计(核心交付)
|
||||
|
||||
审计画像:「有一定 C++ 基础」= PRETEST L1 全对、L2 部分对的学员。逐条核对三个
|
||||
层面的高频误区是否有对应「概念/坑」卡。**✔=沿用 5 天版已覆盖卡;★=本轮新增;
|
||||
☆=本文为 ch05/ch06 新写;—=有意不覆盖(附理由)**。
|
||||
|
||||
**C++ 语言层(Day1 集中处理)**:
|
||||
|
||||
| 误区/盲点 | 覆盖 | 位置 |
|
||||
|---|---|---|
|
||||
| 引用可空/可重绑、值传递 vs 引用传递、悬空引用 | ✔ | Day1 下午 + PRETEST L1 |
|
||||
| 虚析构缺失泄漏、多态机制、重写 vs 重载 | ✔ | Day1 下午 + PRETEST L2/L3 |
|
||||
| 重载决议二义性、隐式转换扩大候选集 | ✔ | Day1 下午 + PRETEST L2 |
|
||||
| 迭代器失效 | ✔ | Day1 下午 STL 卡 |
|
||||
| 头文件 `using namespace` 污染 | ✔ | Day1 上午 namespace 卡 |
|
||||
| **「谁负责 delete」所有权模型缺失** | ★ | Day1 下午新卡(内存管理环节,位置比旧版更贴切) |
|
||||
| **编译错误 vs 链接错误分不清** | ★ | Day1 上午新卡 |
|
||||
| static 初始化顺序、const 细则、移动语义细节 | — | 项目实作用不到临界深度;[`MODERN_CPP.md`](MODERN_CPP.md) 作自学 |
|
||||
|
||||
**Qt 框架层(Day2–4 + Day5)**:
|
||||
|
||||
| 误区/盲点 | 覆盖 | 位置 |
|
||||
|---|---|---|
|
||||
| 忘 `Q_OBJECT` → 链接错且不提宏 | ✔ | Day2 上午(有 Day1 链接卡打底) |
|
||||
| 忘 `exec()` 窗口一闪而过、多个 QApplication | ✔ | Day2 上午 |
|
||||
| 忘传 parent 泄漏 | ✔ | Day2 上午 |
|
||||
| **栈上建带 parent 的控件 → 双重释放** | ★ | Day2 上午新卡 |
|
||||
| **非模态对话框建在栈上一闪而过** | ☆ | Day3 上午新卡(同一所有权误区第三次复现,ch06 三个 modeless 对照文件是现成教材) |
|
||||
| **lambda 槽捕获悬空 / 缺 context 参数** | ★ | Day2 下午新卡 |
|
||||
| **重复 connect → 槽触发多次** | ★ | Day2 下午新卡 |
|
||||
| 旧字符串语法静默失败、重载信号消歧义 | ✔ | Day2 下午 |
|
||||
| **QString/std::string 混淆、中文乱码** | ★ | Day2 下午新卡(上午练习已现场埋复现点) |
|
||||
| QMainWindow 不设中心部件、QAction 忘连接 | ☆ | Day3 上午新卡 |
|
||||
| 同控件属两个布局、布局后手动 `setGeometry` 被覆盖 | ✔ | Day3 下午 |
|
||||
| `.qrc` 路径错静默失败 | ✔ | Day3 下午 |
|
||||
| QSS 级联失控、ID 选择器要 objectName | ✔ | Day3 下午 |
|
||||
| 重写事件函数忘调基类 | ✔ | Day4 上午 |
|
||||
| `paintEvent` 之外绘制 UB、忘 `update()` | ✔ | Day4 上午 |
|
||||
| 改模型不发信号视图不刷新 | ✔(概念页) | Day4 上午 |
|
||||
| `open()` 不检查、JSON 解析错误静默 | ✔ | Day4 下午 |
|
||||
| **相对路径随工作目录漂移** | ★ | Day4 下午新卡 |
|
||||
| **QTextStream 默认编码平台相关 → 跨机乱码** | ★ | Day4 下午新卡 |
|
||||
| 工作线程碰 GUI 崩溃、moveToThread 时序 | ✔ | Day5 提升环节(Day4 上午已埋「阻塞=冻结」认知) |
|
||||
| Qt Designer/.ui 工作流 | — | 本课程纯代码路线(仓库示例均无 .ui);项目期按需补 |
|
||||
|
||||
**工程/协作层**:
|
||||
|
||||
| 误区/盲点 | 覆盖 | 位置 |
|
||||
|---|---|---|
|
||||
| CMake「生成 vs 构建」两阶段混淆 | ✔ | Day1 上午 |
|
||||
| 新标准特性 ≠ 工具链可用 | ✔ | Day1 讲座钩子 + `std_probe.py` 作业选做 |
|
||||
| commit 粒度失控、信息写"update" | ✔ | Day5 下午 2(进项目前最后一课,即时生效) |
|
||||
| 只重技术忽略考评其余维度 | ✔ | Day5 下午 2 |
|
||||
|
||||
**审计结论**:共 **10 张新卡**(★8 + ☆2),集中三条主线:①**所有权与生命周期**
|
||||
(谁 delete → 栈控件双重释放 → 非模态对话框一闪而过 → lambda 捕获悬空,四天内
|
||||
四次复现同一心智模型,这是「学过 C++ 语法」与「写得出不崩的 Qt 程序」之间最大的
|
||||
鸿沟);②**编码与路径**(中文乱码 Day2 → 文件乱码 Day4 → 工作目录 Day4,中文教学
|
||||
环境必踩且旧版完全空白);③**连接语义**(重复 connect、context 参数,现象诡异、
|
||||
自查困难,教了能省项目期大量答疑)。新安排把 C++ 扩到整天,反而让 ①在 Day1 就能
|
||||
立住地基——比旧 5 天版(C++ 只有半天)对中初级学员更友好。
|
||||
|
||||
## 6. 评估 C:Day6–10 项目开发就绪度
|
||||
|
||||
| 项目必备能力 | 训练强度 | 位置 |
|
||||
|---|---|---|
|
||||
| 主窗口+菜单+对话框的标准桌面骨架 | 讲+练+案例 | Day3 + Day5 |
|
||||
| 控件/布局/美化 | 讲+练+作业(计算器线) | Day2–3 |
|
||||
| 对象间通信(信号槽三种写法) | 讲+练 | Day2 |
|
||||
| 数据持久化(JSON round-trip) | 讲+练+作业+案例 | Day4–5 |
|
||||
| 表格类数据展示 | 演示+案例 | Day4–5 |
|
||||
| 事件拦截/自绘 | 讲+练 | Day4 |
|
||||
| 耗时操作不卡 UI | 讲+案例(Day5 提升) | Day4 埋点 + Day5 |
|
||||
| 崩溃自查(所有权/编码/连接三主线) | 10 张坑卡 + 四次间隔复现 | Day1–4 贯穿 |
|
||||
| Git/流程/考评认知 | 讲 | Day5 |
|
||||
| 综合组装 | 全程案例走查 | Day5 |
|
||||
|
||||
**结论:就绪度充分**,且作业线(计算器→主窗口化→数据落盘→综合案例)保证每个学员
|
||||
进项目前至少完成 3 个递进的可运行程序。两个前提条件:① PRETEST 摸底通过(不通过
|
||||
则触发 PRETEST 分层预案,Day1 下午减少 STL 时长、加固 OOP);② **Day5 必须保住
|
||||
QThread 提升环节**——若 Day5 案例讲解超时挤掉它,项目期「界面冻结/跨线程崩溃」
|
||||
答疑成本会数倍返还,这是本设计标记的最高优先级保底项。
|
||||
|
||||
## 7. 评估 D:高阶兴趣钩子(每处 1–2 分钟点到 + 自学入口)
|
||||
|
||||
| Day | 钩子(挂在当天刚学的内容上) | 自学入口 |
|
||||
|---|---|---|
|
||||
| 1 | C++ 不是停滞的语言:11「几乎新语言」→ 20 四大件 → 26 反射;现场用 `std_probe.py` 编译验证一个新特性 | [`MODERN_CPP.md`](MODERN_CPP.md)、[`MODERN_CPP_20PLUS.md`](MODERN_CPP_20PLUS.md)(含本机 GCC 实测矩阵) |
|
||||
| 2 | 信号槽的幕后:打开 `build/` 里真实的 `moc_*.cpp` 看 moc 生成了什么 | 本仓库构建产物 + Qt 元对象系统文档 |
|
||||
| 3 | QSS 与前端 CSS 的同源关系;Qt Designer/QML 是另一条 UI 路线 | Qt 官方 Style Sheets 文档 |
|
||||
| 4 | 今天的 QTableWidget 背后是完整的 Model/View 架构(数据/展示彻底解耦、一模型多视图) | `examples/model_view/` + Qt Model/View 长文 |
|
||||
| 5 | QtConcurrent/线程池是 QThread 的更高抽象;动画/状态机给界面加转场 | `examples/property_animation/`、`state_machine/`、[`MODERN_CPP_20PLUS.md`](MODERN_CPP_20PLUS.md) §协程 |
|
||||
|
||||
设计原则:钩子全部是**当天内容的自然延伸**(学了信号槽→看 moc 产物;学了表格→
|
||||
看 Model/View),不做空降的「高级话题清单」——好奇心要挂在已有的钩子上。
|
||||
|
||||
## 8. 风险与缓解
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|---|---|
|
||||
| 2.5h 讲授块对中初级学员过长 | §0.2 假设 2 的「讲 25–30 + 练 10–15」循环切分;随堂微练全部有明确产出(跑通/改造/复现坑) |
|
||||
| Day2 下午计算器早于布局课(新安排固有顺序) | 教师提供 QGridLayout 脚手架「先用后学」,Day3 下午回收讲透;脚手架片段随幻灯片提供 |
|
||||
| Day5 案例讲解超时挤掉 QThread 提升 | 上午严格按 §3 时段推进;万一超时,砍动画/状态机钩子而非 QThread(§6 保底项) |
|
||||
| PRETEST 摸底不达标 | Day1 下午 STL 环节让位 OOP 加固(PRETEST 分层预案);新安排 C++ 占整天,回旋余地本就比旧 5 天版大 |
|
||||
| ~~表格环节无现成 QTableWidget 示例~~(已解决) | 已补 `examples/table_widget/qt_table_widget_basics.cpp`,离屏验证通过 |
|
||||
|
||||
## 9. 落地清单(2026-07-05 已全部执行)
|
||||
|
||||
1. ✅ **幻灯片重排**:`day1.html`…`day5.html` 已按本文 §2/§3 重组(Day1 全 C++、Day3
|
||||
新增 ch05/ch06、Day5 改综合案例结构);10 张新卡已按 `custom.css` 现有
|
||||
`.concept-box`/`.pitfall-box` 样式补入(幻灯片内标注 ★/☆),「大纲↔幻灯片
|
||||
一一对应」约定保持。
|
||||
2. ✅ **坑点最小复现示例**已补入 `docs/teaching/examples/`(手工维护区,不影响生成器):
|
||||
`pitfall_stack_widget`(`--crash` 触发真实双重释放崩溃)、`pitfall_lambda_capture`
|
||||
(`--dangle` 触发真实悬空访问)、`pitfall_double_connect`(确定性 2 次→1 次对比)、
|
||||
`table_widget`(QTableWidget 最小示例)。默认运行均为正确写法、离屏验证退出码 0。
|
||||
3. ✅ **Day5 综合案例参考实现**:`docs/teaching/examples/student_manager/`
|
||||
(QMainWindow + QTableWidget + 对话框 + JSON round-trip + QThread 后台导入;
|
||||
离屏自测模式自动校验「写入→读回」与导入闭环)。
|
||||
4. ✅ 新 10 天安排正式文本已落入 [`docs/SCHEDULE_2026_10DAY.md`](../SCHEDULE_2026_10DAY.md);
|
||||
`OUTLINE.md` §0 与幻灯片 `index.html` 已补两版关系说明。
|
||||
Reference in New Issue
Block a user