教案对齐官方方案书:重写为 5 天 C++/Qt 教学设计,补齐 6 个 Qt 技术缺口示例
- docs/teaching/OUTLINE.md、slides/day1-5.html、index.html:替换此前自定义的 4 天方案,严格对齐 docs/TRAINING_PLAN_2026.md「C++方向」Day1-5 课堂内容 (Day6-10 为纯项目实作,不在教案范围) - docs/teaching/examples/:官方方案书要求但仓库 p03/(wiki 抓取内容) 未覆盖的 QSS/Model-View/JSON/QThread/QPropertyAnimation/QStateMachine 六个技术点, 各补一个最小可运行示例 + README 教学文档,均已离屏验证跑通;手工维护, 不属于 gen_part3.py 生成产物 - 根 CMakeLists.txt:接入 docs/teaching/examples 构建(BUILD_QT_PART 分支内), 不改动生成器管理的 p01/p03 CMakeLists - 任务卡改为目标/验收标准/基础知识参考三段式模板,custom.css 配套新增样式 - docs/teaching/PRETEST.md:10 题 10 分钟 C++ 摸底测验,验证教案假定的受众 基础是否成立,并与 OUTLINE.md §0 的分层预案挂钩
This commit is contained in:
@@ -51,4 +51,9 @@ if(BUILD_QT_PART)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
add_subdirectory(p03)
|
||||
|
||||
# 教学补充示例(手工维护,非 tools/gen_part3.py 生成产物):方案书课程要求
|
||||
# 但仓库 p03/ 的 wiki 抓取内容未覆盖的技术点(QSS/Model-View/JSON/QThread/
|
||||
# 动画/状态机),见 docs/teaching/examples/。
|
||||
add_subdirectory(docs/teaching/examples)
|
||||
endif()
|
||||
|
||||
+284
-202
@@ -1,211 +1,145 @@
|
||||
# 教案大纲 —「Qt 方向」4 天实训
|
||||
# 教案大纲 —「C++/Qt 方向」5 天实训(对齐官方方案书 Day1–5)
|
||||
|
||||
> 配套材料:`docs/teaching/slides/day1.html` … `day4.html`(reveal.js 交互幻灯片,已
|
||||
> vendor 进仓库,离线可用,见文末「材料与播放方式」)。
|
||||
> 配套材料:`docs/teaching/slides/day1.html` … `day5.html`(reveal.js 交互幻灯片,
|
||||
> 已 vendor 进仓库,离线可用,见文末「材料与播放方式」)。
|
||||
>
|
||||
> 依据:`docs/TRAINING_PLAN_2026.md`《四川大学软件学院 2026 年夏 C/C++ 项目实习
|
||||
> 实训方案书 V1.2》「五、执行计划 (二) C++方向」。方案书 C++方向共 10 天,
|
||||
> **Day6–10 是纯项目实作(学员编码+教员指导+成果展示),没有课堂讲授内容**,
|
||||
> 不在本教案范围内;本教案覆盖 Day1–5 的全部课堂教学。
|
||||
|
||||
## 0. 受众假设(显式声明,供随时纠偏)
|
||||
## 0. 受众假设与本轮调整说明(显式声明,供随时纠偏)
|
||||
|
||||
**本教案假定学员已掌握 C++ 基础语法与面向对象概念(类、继承、多态等术语不陌生),
|
||||
但未接触过 Qt。** 这是本次问答中用户明确选择的组合:
|
||||
**本教案假定学员已掌握 C++ 基础语法与面向对象概念**,方案书对 Day1 下午的官方
|
||||
定位就是"C++ 高级程序开发**回顾和提升**"——复习强化,不是零基础教学。这条假设
|
||||
不是拍脑袋定的,Day1 模块 B 开始前建议先用
|
||||
[`docs/teaching/PRETEST.md`](PRETEST.md)(10 题、10 分钟摸底测验)实测一次,
|
||||
按测验结果决定是照原节奏推进还是回退到语法讲解(该文件里有对应的分层预案)。
|
||||
|
||||
- 先修水平:「已熟悉 C++/OOP,只是不了解 Qt」
|
||||
- 4 天主线:「3 天 C++ 强化(含 OOP)+ 1 天 Qt 桌面」
|
||||
**与仓库里此前一版教案的关系**:`docs/teaching/` 下此前有一版自定义 4 天教案
|
||||
(3 天 C++ 强化 + 1 天 Qt,讲:练 ≈ 1:3),是当时按用户选定的另一种日程假设做的。
|
||||
**本版替换那一版**,严格对齐官方 10 天方案书 C++ 方向的 Day1–5 结构——差异很
|
||||
大:官方结构里 C++ 只占 Day1 下午半天(其余 4.5 天都是 Qt),所以 C++ 内容从
|
||||
原来的 3 天深度压缩为半天的**精选亮点 + 自学指针**,不再逐章深入;Qt 内容则
|
||||
从原来压缩的 1 天摊开到 Day2–5 四天,覆盖面明显更广。
|
||||
|
||||
两者合起来的落地方式是:**Part1(C++/OOP)3 天不讲入门语法,改为「强化 / 纠错 /
|
||||
深挖」定位**——重点讲仓库里已经踩过并记录在 `docs/ERRATA.md` 的真实坑、
|
||||
`docs/VERSION_NOTES.md` 里的跨版本反直觉结论,以及对象模型、虚函数表这类「学过但
|
||||
未必讲透」的机制;**Part3(Qt)压缩进 1 天**,定位是「快速建立骨架认知 + 挑一个
|
||||
方向动手做出一个可跑的小工具」,不追求覆盖全部 29 个目标。
|
||||
**内容缺口的显式处理**(这是本轮设计的关键决策,写清楚以便后续纠偏):
|
||||
|
||||
> 如果实际学员水平达不到「已熟悉 C++/OOP」,Day1–3 的讲授部分需要退回到语法讲解,
|
||||
> 练习任务保持不变但预期完成度应下调——教师可据此现场调整,无需改讲义结构。
|
||||
- 官方方案书 Day3–4 提到的 **Model/View、JSON 解析、QThread 多线程、
|
||||
QPropertyAnimation 动画、QStateMachine 状态机**,仓库 `p03/`(wiki 抓取内容)
|
||||
原本完全没有对应代码。本轮已在 `docs/teaching/examples/` 补齐 **6 个最小可
|
||||
运行示例**(含配套 README 教学文档),构建已接入根 `CMakeLists.txt`(
|
||||
`add_teaching_example` 分组,见该文件注释),**均已 `QT_QPA_PLATFORM=offscreen`
|
||||
离屏验证跑通**。这些示例是手工维护的教学补充,**不属于** `tools/gen_part3.py`
|
||||
的生成产物,重新生成 `p01`/`p03` 不会影响它们。
|
||||
- 以下两项**仍按"纯概念 / 自学"处理,不写可运行代码**(原因见括号):
|
||||
- **Vcpkg 包管理器**(Day1)——需要安装真实 vcpkg 工具链,超出"最小示例"
|
||||
范畴,且与仓库当前依赖面(host gcc/g++ + 隔离 Qt 5.14.2)无关;
|
||||
- **OpenCV 人脸/目标检测**(Day5)——方案书原文本身就标注"可选择",属于
|
||||
非必修拓展,引入 OpenCV 会新增一条完整的三方库工具链。
|
||||
- 如果后续需要这两项也做成可运行示例,请明确提出——这会涉及新增外部依赖,
|
||||
需要单独确认。
|
||||
|
||||
## 1. 总体目标
|
||||
|
||||
1. 学员能解释 C++ 相对 C 的关键扩展机制的**原理**(不是背语法),并能指出这些语法在
|
||||
哪个标准版本才成立/失效。
|
||||
2. 学员能独立分析并修正一段有缺陷的 C++/Qt 代码(本仓库的 `ERRATA*.md` 就是活教材)。
|
||||
3. 学员能读懂并扩展一个 Qt 桌面小程序(信号槽、控件、事件、绘图、文件 IO 至少覆盖一类)。
|
||||
4. 每天结束都有一个**可运行、可验证**的产出(不是抄书面练习)。
|
||||
1. 学员能说出 C++ 相对 C 的关键扩展机制在**哪个标准版本**成立/失效(复习向,
|
||||
不重新从语法教起)。
|
||||
2. 学员能读懂并扩展一个 Qt 桌面小程序,覆盖信号槽、控件、布局/QSS、事件、
|
||||
绘图、文件 IO/JSON、Model/View、多线程、动画/状态机**中的大多数**(覆盖面
|
||||
是 Qt 教学的重点,深度服从进度)。
|
||||
3. 每天结束都有一个**可运行、可验证**的产出。
|
||||
4. 学员能看懂本次实训的项目管理流程(JIRA/Git/OASIS),为 Day6–10 的项目实作
|
||||
打基础。
|
||||
|
||||
## 2. 课时结构(讲:练 ≈ 1:3 的落地方式)
|
||||
## 2. 课时结构
|
||||
|
||||
每天 6 课时(45 分钟/课时 ×6 = 270 分钟),分两个模块,模块内部再拆讲/练/答疑:
|
||||
每天 6 课时(45 分钟/课时 ×6 = 270 分钟),分两个模块。**Day1 上午是开训/职业
|
||||
素养内容,没有编码练习**,用讨论型任务卡代替;其余全部保持"讲:练"结构:
|
||||
|
||||
| 模块 | 时长 | 讲授 | 练习 | 小结/答疑 | 讲:练 |
|
||||
|---|---|---|---|---|---|
|
||||
| 模块 A(上午,3 课时) | 135min | 35min | 95min | 5min | ≈1:2.7 |
|
||||
| 模块 B(下午,3 课时) | 135min | 35min | 95min | 5min | ≈1:2.7 |
|
||||
| 模块 | 时长 | 讲授 | 练习/讨论 | 小结/答疑 |
|
||||
|---|---|---|---|---|
|
||||
| 模块 A(上午,3 课时) | 135min | 35min | 95min | 5min |
|
||||
| 模块 B(下午,3 课时) | 135min | 35min | 95min | 5min |
|
||||
|
||||
全天讲授 70min / 练习 190min,比例 ≈1:2.7,接近题目要求的 1:3;`docs/teaching/slides/`
|
||||
每天的 HTML 幻灯片按「模块 A 讲 → 模块 A 练习任务卡 → 模块 B 讲 → 模块 B 练习任务卡」
|
||||
组织,练习任务卡本身就是幻灯片的一部分(放投影上,学员对着做),教师在 95min 内巡场答疑。
|
||||
Day2–5 讲:练 ≈ 1:2.7;Day1 模块 A 是讨论型任务(不追求"练"的编码产出),
|
||||
模块 B(C++/CMake/Vcpkg)内容压缩,35min 讲授只做"巡礼式"精选亮点。
|
||||
|
||||
## 3. 四天总览
|
||||
## 3. 五天总览
|
||||
|
||||
| Day | 主题 | 模块 A | 模块 B |
|
||||
| Day | 官方主题 | 模块 A | 模块 B |
|
||||
|---|---|---|---|
|
||||
| 1 | C++ 对 C 的扩展 + 类和对象基础 | `p01/ch02` `p01/ch03` 强化精讲 | `p01/ch04/s01–s03` 封装/构造析构 |
|
||||
| 2 | OOP 进阶 | `p01/ch04/s04–s06` 对象模型/友元/运算符重载 | `p01/ch04/s07–s08` 继承/多态 |
|
||||
| 3 | 泛型与标准库 | `p01/ch05` 模板 + `p01/ch06` 类型转换 | `p01/ch07` 异常 + `p01/ch08` IO流 + `p01/ch09` STL |
|
||||
| 4 | Qt 桌面全景 | `p03/ch02–ch05` 项目/信号槽/主窗口 | `p03/ch06` `p03/ch08–ch11` 对话框/控件/事件/绘图/文件(速览+分组选做) |
|
||||
| 1 | 开训 + C++回顾/工具链 | 团队建设/Scrum/C++企业应用(讨论,无编码) | C++精选回顾 + CMake实操 + Vcpkg概念 |
|
||||
| 2 | Qt开发环境/信号槽/基础控件 | 项目结构/信号槽 `p03/ch02–04` | 控件/布局/QSS美化 `p03/ch08` + 新示例 `qt_qss_styling` → 计算器案例 |
|
||||
| 3 | 高级控件/事件/文件IO/JSON | Model/View 新示例 + 事件拦截 `p03/ch09` | 文件IO `p03/ch11` + JSON 新示例 → 本地数据存储案例 |
|
||||
| 4 | 2D绘图/多线程/动画/状态机 | 2D绘图 `p03/ch10` + QThread 新示例 | 动画+状态机新示例 → 交互式小游戏案例 |
|
||||
| 5 | 团队协同/综合案例/OpenCV可选 | 团队协同开发(概念,用方案书图) | 综合案例复盘 + OpenCV/AI 自学指针 |
|
||||
|
||||
标注约定:下文表格中 **讲** = 课堂演示讲解,**练** = 学员必须动手完成,**参考** = 不
|
||||
在课堂展开、留作课后自学(文件仍在仓库里,链接照给)。24 课时不可能覆盖全部 173 个
|
||||
目标,所有清单都是**精选**而非全集,未点名文件默认按「参考」处理。
|
||||
标注约定:**讲** = 课堂演示讲解,**练** = 学员必须动手完成,**参考** = 不在
|
||||
课堂展开、留作课后自学。所有清单都是**精选**而非全集。
|
||||
|
||||
---
|
||||
|
||||
## Day 1 — C++ 对 C 的扩展 + 类和对象基础
|
||||
## Day 1 — 开训 + C++ 回顾/工具链
|
||||
|
||||
### 模块 A:C++ 对 C 的扩展(强化,非入门)
|
||||
|
||||
**讲(35min)**——只挑「学过但容易讲不透」的点,每点配 ERRATA/VERSION_NOTES 出处:
|
||||
|
||||
| 知识点 | 文件 | 强化角度 |
|
||||
|---|---|---|
|
||||
| namespace 在函数体内定义为什么非法 | `p01/ch03/namespace_syntax.cpp`(对照 `docs/ERRATA.md` `[58954439:3/6/7]`) | wiki 原文的错误写法 + 编译器报错原文,讲“作用域”不是“文件顺序” |
|
||||
| 函数重载的决议机制 | `p01/ch03/function_overload_mechanism.cpp`、`function_overload_basics_2.cpp`(`ERRATA` `[58954439:42] ambiguous-overload`) | 名字改写(name mangling)与重载集合,为什么会出现二义性 |
|
||||
| const 的本质 vs `#define` | `p01/ch03/const_diff_summary.cpp`、`const_vs_define.cpp` | 类型检查、作用域、编译期替换的本质差异 |
|
||||
| 引用的本质 | `p01/ch03/reference_essence.cpp` | “引用是自动解引用的 const 指针”这一实现视角 |
|
||||
| 三目运算符可否作左值——纯版本差异 | `p01/ch03/ternary_operator.c` vs `ternary_operator.cpp` | 现场用 `tools/std_probe.py` 演示,对照 `docs/VERSION_NOTES.md` §2 |
|
||||
|
||||
**练(95min)**:
|
||||
|
||||
- 任务卡 1:在 `p01/ch03` 挑 8–10 个文件(namespace / reference / overload / const 系列),
|
||||
逐个 `tools/run.sh <target>` 跑通,对照 `docs/ERRATA.md` 里的对应条目,写一句话解释
|
||||
“wiki 原文错在哪、为什么现在的写法是对的”。
|
||||
- 任务卡 2(版本探针实操):
|
||||
```bash
|
||||
tools/std_probe.py --lang c --file p01/ch03/ternary_operator.c \
|
||||
--standards c89,c99,c11,c17 --pedantic-errors
|
||||
```
|
||||
记录哪个标准报错、哪个不报错,对照 `docs/VERSION_NOTES.md` §2 的结论。
|
||||
- 任务卡 3(拔高,选做):阅读 `ERRATA.md` `[58954439:42]`,解释 dedupe 脚本为什么会把
|
||||
重载函数改名导致二义性——这是一个「工具误伤正确代码」的真实案例,训练读 diff 的能力。
|
||||
|
||||
### 模块 B:类和对象基础
|
||||
### 模块 A:团队建设 / 敏捷 Scrum / C++ 在企业中的应用(讨论型,无编码)
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
| 知识点 | 文件 | 强化角度 |
|
||||
|---|---|---|
|
||||
| 封装:C 结构体+函数 vs C++ 类 | `p01/ch04/s01/class_encapsulation.c` 对比 `class_encapsulation.cpp` | 同一问题两种语言写法并排看 |
|
||||
| 构造/析构时机 | `p01/ch04/s03/ctor_and_dtor.cpp` | 强调 `ERRATA` 里一串 `missing-include`(`[58954451:0..13]`)——wiki 原文常见疏漏,编译器不报错但运行期用到 `strcpy`/`strlen` 会炸 |
|
||||
| 深拷贝 vs 浅拷贝 | `p01/ch04/s03/deep_vs_shallow_copy.cpp` | 用析构函数里的打印现场演示悬垂指针/double free |
|
||||
| 静态成员与单例 | `p01/ch04/s03/static_singleton.cpp` | 静态成员的生命周期 |
|
||||
|
||||
**练(95min)**:
|
||||
|
||||
- 任务卡 1:以 `p01/ch04/s02/point_and_circle.cpp` 或 `cube_class_design.cpp` 为起点,
|
||||
新增一个成员函数(如计算面积/体积),编译运行验证。
|
||||
- 任务卡 2:修改 `deep_vs_shallow_copy.cpp`,故意触发浅拷贝析构两次释放同一块内存,
|
||||
用 `run.sh` 观察崩溃或输出异常,再改回深拷贝对比。
|
||||
- 任务卡 3:参照 `static_singleton.cpp` 独立写一个单例类(禁止拷贝构造)。
|
||||
|
||||
**当日产出验证**:`cmake --build build --target all_p01_ch03` 与 `all_p01_ch04_s03`
|
||||
全绿;至少完成 1 个浅拷贝崩溃复现 + 修复对比。
|
||||
|
||||
---
|
||||
|
||||
## Day 2 — OOP 进阶
|
||||
|
||||
### 模块 A:对象模型 / 友元 / 运算符重载
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
| 知识点 | 文件 |
|
||||
| 内容 | 素材 |
|
||||
|---|---|
|
||||
| this 指针与常量成员函数 | `p01/ch04/s04/this_pointer.cpp`、`const_member_function.cpp` |
|
||||
| 友元破坏封装的取舍 | `p01/ch04/s05/friend_syntax.cpp` |
|
||||
| 运算符重载全景(=, ==, [], (), ++/--) | `p01/ch04/s06/overload_assignment.cpp`、`overload_equality.cpp`、`overload_function_call.cpp`、`overload_increment_decrement.cpp` |
|
||||
| 团队建设与文化、实训安排与考评要求 | 口头介绍,无固定素材 |
|
||||
| 项目开发过程管理(JIRA/Git/OASIS/Wiki) | `docs/TRAINING_PLAN_2026.md` 「七、质量保证」+ 图 `docs/images/training_service_flow.png`、`quality_four_platforms.png` |
|
||||
| 敏捷 Scrum 项目开发管理 | 同上「服务流程图」里的迭代环节(需求分析→需求评审→项目研发→阶段评审→项目测试) |
|
||||
| C++ 在企业中的应用 | 口头介绍,结合往届案例(如有) |
|
||||
|
||||
**练(95min)**:
|
||||
**讨论(95min,分组,非编码)**:
|
||||
|
||||
- 任务卡 1:给一个自定义类实现 `operator=`(配合深拷贝)+ `operator==`,参照
|
||||
`overload_assignment.cpp` / `overload_equality.cpp`。
|
||||
- 任务卡 2:实现一个重载了 `operator[]` 的简单容器类(参照
|
||||
`overload_pointer_operator.cpp` 的思路),做边界检查。
|
||||
- 任务卡 3(选做):`overload_with_friend.cpp`——用友元函数实现 `operator<<`,
|
||||
讨论“为什么流输出运算符通常不能是成员函数”。
|
||||
- **任务卡 1**
|
||||
- 目标:结合服务流程图,说清楚本组打算怎么分工使用 Jira(任务跟踪)+ Git(代码版本管理)
|
||||
- 验收标准:每组提交一句话分工说明(谁负责建 Jira 看板/谁负责 Git 仓库初始化),能在图上指出对应环节
|
||||
- 基础知识 · 参考:`docs/images/training_service_flow.png`;`docs/TRAINING_PLAN_2026.md` 「七、质量保证 (一)(二)」
|
||||
- **任务卡 2**
|
||||
- 目标:选定实训期间打算做的项目方向(Linux C 方向 或 C++/Qt 方向的备选商业项目之一)
|
||||
- 验收标准:每组报出项目名称 + 一句话说明选择理由
|
||||
- 基础知识 · 参考:`docs/TRAINING_PLAN_2026.md` 「八、备选商业项目介绍」
|
||||
|
||||
### 模块 B:继承与多态
|
||||
### 模块 B:C++ 环境搭建 / 高级程序回顾 / CMake / Vcpkg(压缩讲解)
|
||||
|
||||
**讲(35min)**:
|
||||
**讲(35min,巡礼式,只挑「学过但容易讲不透」的点,不逐章展开)**:
|
||||
|
||||
| 知识点 | 文件 | 强化角度 |
|
||||
| 知识点 | 文件 | 一句话说明 |
|
||||
|---|---|---|
|
||||
| 继承中构造/析构的顺序 | `p01/ch04/s07/inheritance_ctor_dtor_order.cpp` | 先基类后派生类构造,析构相反 |
|
||||
| 同名成员遮蔽 | `p01/ch04/s07/same_name_members.cpp` | 作用域遮蔽 vs 重写 |
|
||||
| 虚继承解决菱形问题 | `p01/ch04/s07/diamond_virtual_inheritance.cpp` | 内存模型图示(vbptr) |
|
||||
| 纯虚函数/抽象类 | `p01/ch04/s08/pure_virtual_dtor.cpp` | `ERRATA` `[58954469:5] abstract-instantiation`——wiki 原文误实例化了抽象类 |
|
||||
| 虚析构的意义 + 裁切问题 | `p01/ch04/s08/virtual_dtor_purpose.cpp`、`upcast_problem.cpp` | 用基类指针 delete 派生类对象不加 virtual 析构的内存泄漏演示 |
|
||||
| namespace 作用域 | `p01/ch03/namespace_syntax.cpp` | 对照 `docs/ERRATA.md`,"作用域"不是"文件顺序" |
|
||||
| 函数重载决议 | `p01/ch03/function_overload_mechanism.cpp` | 名字改写与重载集合,二义性从哪来 |
|
||||
| 引用的本质 | `p01/ch03/reference_essence.cpp` | "引用是自动解引用的 const 指针" |
|
||||
| 虚析构与内存泄漏 | `p01/ch04/s08/virtual_dtor_purpose.cpp` | 基类指针 delete 派生类对象的经典坑 |
|
||||
| 模板与 STL 速览 | `p01/ch05/class_template_basics.cpp`、`p01/ch09/s03/vector_iterator.cpp` | 泛型编程 + 容器/迭代器,只点名不展开 |
|
||||
| CMake 项目构建 | 本仓库 `CMakeLists.txt`、`p03/CMakeLists.txt` | 用真实项目当教材:`option(BUILD_QT_PART)`、`add_subdirectory`、每个示例一个 `add_executable` 目标 |
|
||||
| Vcpkg 包管理器(概念,无演示) | 无仓库代码 | C++ 生态的包管理器,解决"第三方库怎么装、怎么被 CMake 找到"的问题;本仓库用的是"隔离 Qt 手动装"而非 vcpkg,两种思路都要认识 |
|
||||
|
||||
**练(95min)**:
|
||||
|
||||
- 任务卡 1:跑 `upcast_problem.cpp`,观察未加 `virtual` 时的行为,然后手动加上
|
||||
`virtual` 关键字重新编译对比输出差异。
|
||||
- 任务卡 2:设计一个含虚析构的基类 + 2 个派生类,用基类指针数组管理对象,验证
|
||||
析构顺序正确、无内存泄漏(可选 `valgrind` 或简单打印计数)。
|
||||
- 任务卡 3(选做):`diamond_virtual_inheritance.cpp` 与 `diamond_virtual_inheritance_2.cpp`
|
||||
对比普通继承 vs 虚继承的成员地址/构造次数。
|
||||
- **任务卡 1 · 走一遍真实构建流程**
|
||||
- 目标:独立完成本仓库 Part1 的配置+构建,不看着教师操作
|
||||
- 验收标准:`cmake -S . -B build -G Ninja -DBUILD_QT_PART=OFF` 配置成功;`cmake --build build` 144 个目标全绿;能说出 `option(BUILD_QT_PART)` 的作用
|
||||
- 基础知识 · 参考:仓库根 `CMakeLists.txt`;<span class="ext-ref">CMake 官方文档 Tutorial(选读·需联网)</span>
|
||||
- **任务卡 2 · 修一个真实的坑**
|
||||
- 目标:从 `docs/ERRATA.md` 挑 2 条记录,在对应源文件里理解"wiki 原文错在哪、现在的写法为什么对"
|
||||
- 验收标准:能用一句话向同组同学解释清楚,不能只念代码
|
||||
- 基础知识 · 参考:`docs/ERRATA.md`
|
||||
- **任务卡 3(选做)· 版本探针**
|
||||
- 目标:用 `tools/std_probe.py` 验证一个标准版本差异点(如三目运算符可否作左值)
|
||||
- 验收标准:跑出至少一个"该标准报错、另一个标准不报错"的对比结果,对照 `docs/VERSION_NOTES.md` §2
|
||||
- 基础知识 · 参考:`tools/std_probe.py`;`docs/VERSION_NOTES.md`
|
||||
|
||||
**当日产出验证**:一个「基类指针管理派生类对象、正确释放」的可运行小程序。
|
||||
**当日产出验证**:`cmake --build build --target all` (Part1) 全绿;每组提交一句话
|
||||
分工说明 + 至少 1 条 ERRATA 坑点讲解。
|
||||
|
||||
---
|
||||
|
||||
## Day 3 — 泛型与标准库
|
||||
## Day 2 — Qt 开发环境 / 信号槽 / 基础控件 / 布局与 QSS
|
||||
|
||||
### 模块 A:模板 + 类型转换
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
| 知识点 | 文件 |
|
||||
|---|---|
|
||||
| 函数模板 vs 类模板 | `p01/ch05/function_template.cpp`、`class_template_basics.cpp` |
|
||||
| 模板的局限性 | `p01/ch05/template_limitations.cpp`、`template_vs_function.cpp` |
|
||||
| 四种显式类型转换 | `p01/ch06/static_cast_demo.cpp`、`dynamic_cast_demo.cpp`、`const_cast_demo.cpp` |
|
||||
|
||||
**练(95min)**:
|
||||
|
||||
- 任务卡 1:仿照 `class_template_basics.cpp` 写一个通用 `Pair<T1, T2>` 或简易 `Stack<T>` 类模板。
|
||||
- 任务卡 2:用 `dynamic_cast` 对一个多态类层次做安全向下转型判断(失败返回 `nullptr` 的分支要写出来),
|
||||
参照 `dynamic_cast_demo.cpp`。
|
||||
- 任务卡 3(选做):`class_template_derives_class.cpp` / `class_template_derives_template.cpp`——
|
||||
类模板的派生关系。
|
||||
|
||||
### 模块 B:异常 + IO 流 + STL
|
||||
|
||||
**讲(35min,三块内容合并速览,各分配约 12min)**:
|
||||
|
||||
| 知识点 | 文件 |
|
||||
|---|---|
|
||||
| 异常语法与栈展开 | `p01/ch07/exception_syntax.cpp`、`stack_unwinding.cpp` |
|
||||
| 自定义异常类 / `std::exception` 体系 | `p01/ch07/custom_exception_class.cpp`、`std_exception_library.cpp` |
|
||||
| 文件 IO(文本/二进制) | `p01/ch08/ascii_file_io.cpp`、`binary_file_io.cpp` |
|
||||
| STL 三大组件速览 | `p01/ch09/s02/stl_components_case.cpp` |
|
||||
| 常用容器 + 迭代器 | `p01/ch09/s03/vector_iterator.cpp`、`list_data_structure.cpp` |
|
||||
| 算法 + 函数对象 | `p01/ch09/s04/predicate.cpp`、`function_object.cpp`、`traversal_algorithms.cpp` |
|
||||
|
||||
**练(95min,任选其二完成,第三个留参考)**:
|
||||
|
||||
- 任务卡 1:给 Day2 写的类加异常处理——非法参数抛自定义异常,`catch` 后打印信息不崩溃。
|
||||
- 任务卡 2:用 `vector` + `<algorithm>`(`sort` / `find_if`)完成一个小任务(如“找出成绩表中不及格的学生”)。
|
||||
- 任务卡 3:参照 `binary_file_io.cpp` 把一个结构体数组写入二进制文件再读出校验。
|
||||
|
||||
**当日产出验证**:至少 1 个模板类 + 1 个使用 STL 容器/算法解决实际小问题的程序。
|
||||
|
||||
---
|
||||
|
||||
## Day 4 — Qt 桌面全景(1 天,快速建立骨架认知 + 动手做一个可跑小工具)
|
||||
|
||||
> 定位声明:Day1–3 是「强化已知」,Day4 是「Qt 零基础入门」——**讲授压缩为骨架级
|
||||
> 讲解,练习是「跟着例子改」而不是从零写**。9 个章节、29 个目标不追求全覆盖,
|
||||
> 模块 B 采用分组选做。所有 Qt 目标运行前需先执行
|
||||
> `tools/install_qt_host.sh`(一次性)与
|
||||
> `cmake -S . -B build -G Ninja -DBUILD_QT_PART=ON -DCMAKE_PREFIX_PATH=$PWD/.qt514/5.14.2/gcc_64`。
|
||||
|
||||
### 模块 A:项目结构 / 第一个程序 / 信号槽 / 主窗口
|
||||
### 模块 A:项目结构 / 第一个程序 / 信号槽
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
@@ -213,59 +147,207 @@
|
||||
|---|---|
|
||||
| 最小 Qt 程序结构(`QApplication` + `exec()`) | `p03/ch02/minimal_qt_app.cpp` |
|
||||
| 控件创建与显示 | `p03/ch03/button_creation.cpp` |
|
||||
| 内置信号槽 / 自定义信号槽 / lambda 槽 | `p03/ch04/builtin_signal_slot.cpp`、`custom_signal_slot.cpp`、`lambda_signal_slot.cpp` |
|
||||
| `QMainWindow` 中心部件与停靠窗口 | `p03/ch05/central_widget.cpp`、`dock_widget.cpp` |
|
||||
| 内置 / 自定义 / lambda 信号槽 | `p03/ch04/builtin_signal_slot.cpp`、`custom_signal_slot.cpp`、`lambda_signal_slot.cpp` |
|
||||
|
||||
**练(95min)**:
|
||||
|
||||
- 任务卡 1:编译运行 `minimal_qt_app` 与 `button_creation`(先用离屏模式验证再选做真实窗口):
|
||||
```bash
|
||||
QT_QPA_PLATFORM=offscreen tools/run_qt.sh minimal_qt_app
|
||||
```
|
||||
- 任务卡 2:仿 `custom_signal_slot.cpp` 给按钮连接一个自定义信号槽(点击后改变一个 `QLabel` 文本)。
|
||||
- 任务卡 3:用 `QMainWindow` + `dock_widget.cpp` 搭一个「中心区+侧边栏」的窗口骨架。
|
||||
- **任务卡 1 · 跑通最小程序**
|
||||
- 目标:编译运行 `minimal_qt_app` 与 `button_creation`
|
||||
- 验收标准:`QT_QPA_PLATFORM=offscreen tools/run_qt.sh minimal_qt_app` 与 `button_creation` 均退出码为 0,无崩溃
|
||||
- 基础知识 · 参考:`p03/ch02/minimal_qt_app.cpp`;`p03/ch03/button_creation.cpp`
|
||||
- **任务卡 2 · 自定义信号槽改文本**
|
||||
- 目标:给一个按钮连接自定义信号槽,点击后改变一个 `QLabel` 的文本内容
|
||||
- 验收标准:编译通过;离屏或有 X 环境下点击/模拟点击后 `QLabel` 文本确实改变;用 `connect` 语法而非直接函数调用
|
||||
- 基础知识 · 参考:`p03/ch04/custom_signal_slot.cpp`(主参考)、`lambda_signal_slot.cpp`(lambda 槽写法对比)
|
||||
- **任务卡 3(选做)· 重载信号消歧义**
|
||||
- 目标:仿 `builtin_signal_slot.cpp`,给一个类定义两个同名不同参的信号,用 `QOverload` 消歧义连接
|
||||
- 验收标准:两个重载信号都能各自触发对应的槽
|
||||
- 基础知识 · 参考:`p03/ch04/builtin_signal_slot.cpp`
|
||||
|
||||
### 模块 B:对话框 / 控件 / 事件 / 绘图 / 文件(速览 + 分组选做)
|
||||
### 模块 B:常用控件 / 布局管理 / QSS 界面美化 → 计算器案例
|
||||
|
||||
**讲(35min,五个方向各约 7min 速览,只给「这个方向能做什么」的骨架认知)**:
|
||||
**讲(35min)**:
|
||||
|
||||
| 方向 | 文件 |
|
||||
| 知识点 | 文件 |
|
||||
|---|---|
|
||||
| 对话框(模态/非模态/文件/消息框) | `p03/ch06/modal_dialog.cpp`、`file_dialog.cpp`、`message_box_question.cpp` |
|
||||
| 常用控件(图片/文本标签、自定义控件) | `p03/ch08/label_pixmap.cpp`、`label_text_html.cpp`、`custom_widget.cpp` |
|
||||
| 事件机制(鼠标事件、事件过滤器) | `p03/ch09/mouse_event.cpp`、`event_filter_mainwindow.cpp` |
|
||||
| 绘图(`QPainter`、绘图设备) | `p03/ch10/qpainter_basic.cpp`、`paint_device_qimage.cpp` |
|
||||
| 文件系统(`QFile`/`QTextStream`/`QDataStream`) | `p03/ch11/qfile_basic.cpp`、`qtextstream_io.cpp`、`qdatastream_io.cpp` |
|
||||
| 图片/文本标签控件 | `p03/ch08/label_pixmap.cpp`、`label_text_html.cpp` |
|
||||
| 自定义控件与布局(`QHBoxLayout`) | `p03/ch08/custom_widget.cpp` |
|
||||
| **QSS 界面美化(教学补充)** | `docs/teaching/examples/qss_styling/qt_qss_styling.cpp` |
|
||||
|
||||
**练(95min,三选一,分组完成,组内可互相帮忙)**:
|
||||
**练(95min,计算器案例——练习任务,非预建方案)**:
|
||||
|
||||
- **方案 A:图片查看器雏形** —— `label_pixmap.cpp` + `file_dialog.cpp`:用文件对话框选图片,
|
||||
加载到 `QLabel` 显示。
|
||||
- **方案 B:简易绘图板** —— `qpainter_basic.cpp` + `mouse_event.cpp`:监听鼠标拖动事件,
|
||||
用 `QPainter` 画出轨迹。
|
||||
- **方案 C:文本文件小工具** —— `qfile_basic.cpp` + `qtextstream_io.cpp`:读一个文本文件、
|
||||
统计行数/字符数,写回一个新文件。
|
||||
- **任务卡 1 · 计算器雏形**
|
||||
- 目标:组合 `button_creation.cpp`(多个数字/运算符按钮)+ `custom_signal_slot.cpp`(点击信号槽)+ `custom_widget.cpp`(`QGridLayout` 或 `QVBoxLayout` 排布按钮),做出一个能加减乘除的最小计算器
|
||||
- 验收标准:至少支持两个数 + 一种运算符的计算并在 `QLabel`/`QLineEdit` 上正确显示结果;界面不重叠、按钮可点击
|
||||
- 基础知识 · 参考:`p03/ch03/button_creation.cpp`、`p03/ch04/custom_signal_slot.cpp`、`p03/ch08/custom_widget.cpp`
|
||||
- **任务卡 2 · 用 QSS 美化计算器**
|
||||
- 目标:给任务卡 1 的计算器套一份 QSS 样式(数字键/运算键/等号键区分颜色)
|
||||
- 验收标准:至少 2 类按钮有不同的 `background-color`;至少 1 个按钮有 `:hover` 或 `:pressed` 效果
|
||||
- 基础知识 · 参考:`docs/teaching/examples/qss_styling/qt_qss_styling.cpp`、[README](examples/qss_styling/README.md)
|
||||
|
||||
**当日产出验证**:每组现场用 `QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>`(或有
|
||||
X 环境时直接运行)演示自己选做的方案,教师验收「编译通过 + 交互符合预期」。
|
||||
**当日产出验证**:现场演示计算器(`QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>`
|
||||
或有 X 环境直接运行),至少完成一次正确运算 + QSS 美化。
|
||||
|
||||
---
|
||||
|
||||
## Day 3 — 高级控件与 Model/View / 事件机制 / 文件 IO 与 JSON
|
||||
|
||||
### 模块 A:Model/View 架构 / 事件机制
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
| 知识点 | 文件 |
|
||||
|---|---|
|
||||
| **Model/View 模型视图架构(教学补充)** | `docs/teaching/examples/model_view/qt_model_view_basics.cpp` |
|
||||
| 事件循环机制(复用 Day2 概念) | `p03/ch02/minimal_qt_app.cpp` 的 `app.exec()` |
|
||||
| 自定义事件处理 / 事件过滤器 | `p03/ch09/event_override_customwidget.cpp`、`event_filter_mainwindow.cpp` |
|
||||
| 鼠标事件 | `p03/ch09/mouse_event.cpp` |
|
||||
|
||||
**练(95min)**:
|
||||
|
||||
- **任务卡 1 · Model/View 换视图**
|
||||
- 目标:把 `qt_model_view_basics.cpp` 里的 `QListView` 换成 `QTableView`,模型代码不改
|
||||
- 验收标准:编译通过,界面正常展示同一份数据,证明"同一个模型可以喂给不同视图"
|
||||
- 基础知识 · 参考:`docs/teaching/examples/model_view/qt_model_view_basics.cpp`、[README](examples/model_view/README.md)
|
||||
- **任务卡 2 · 事件过滤器实操**
|
||||
- 目标:仿 `event_filter_mainwindow.cpp`,给一个控件装事件过滤器,拦截鼠标按下事件并打印坐标
|
||||
- 验收标准:点击控件后终端/调试输出能看到坐标信息
|
||||
- 基础知识 · 参考:`p03/ch09/event_filter_mainwindow.cpp`、`mouse_event.cpp`
|
||||
|
||||
### 模块 B:文件 IO / JSON 解析 → 本地数据存储案例
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
| 知识点 | 文件 |
|
||||
|---|---|
|
||||
| 文本文件读写 | `p03/ch11/qfile_basic.cpp`、`qtextstream_io.cpp` |
|
||||
| 结构化二进制读写 | `p03/ch11/qdatastream_io.cpp` |
|
||||
| **JSON 解析与写入(教学补充)** | `docs/teaching/examples/json_io/qt_json_parse_write.cpp` |
|
||||
|
||||
**练(95min,本地数据存储案例——练习任务,非预建方案)**:
|
||||
|
||||
- **任务卡 1 · JSON 落盘**
|
||||
- 目标:把 `qt_json_parse_write.cpp` 的 JSON 数据实际写入一个文件(结合 `qfile_basic.cpp` 的 `QFile` 写法),再读回来解析校验
|
||||
- 验收标准:程序运行后磁盘上生成一个 `.json` 文件,内容可读;重新运行程序能正确解析出之前写入的数据
|
||||
- 基础知识 · 参考:`docs/teaching/examples/json_io/qt_json_parse_write.cpp`、[README](examples/json_io/README.md)、`p03/ch11/qfile_basic.cpp`
|
||||
- **任务卡 2 · 本地数据存储小工具**
|
||||
- 目标:设计一个"学员信息"结构(姓名/成绩/标签),支持增加一条记录并追加保存到 JSON 文件,重启程序后能读出之前保存的全部记录
|
||||
- 验收标准:至少能存 2 条以上记录并在重启后完整读回;数据字段不丢失
|
||||
- 基础知识 · 参考:`docs/teaching/examples/json_io/`、`p03/ch11/qtextstream_io.cpp`
|
||||
|
||||
**当日产出验证**:现场演示本地数据存储小工具,验证"写入 → 重启 → 读回"完整闭环。
|
||||
|
||||
---
|
||||
|
||||
## Day 4 — 2D 图形绘制 / 多线程 / 动画框架与状态机
|
||||
|
||||
### 模块 A:2D 绘图 / 多线程
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
| 知识点 | 文件 |
|
||||
|---|---|
|
||||
| `QPainter` 基础绘图 | `p03/ch10/qpainter_basic.cpp` |
|
||||
| 绘图设备(`QImage`/`QPixmap`/`QPicture`) | `p03/ch10/paint_device_qimage.cpp`、`paint_device_pixmaps.cpp`、`qpicture_io.cpp` |
|
||||
| **QThread 多线程(教学补充)** | `docs/teaching/examples/qthread_worker/qt_thread_worker.cpp` |
|
||||
|
||||
**练(95min)**:
|
||||
|
||||
- **任务卡 1 · 画一个简单图形界面**
|
||||
- 目标:仿 `qpainter_basic.cpp`,在 `paintEvent` 里画一个组合图形(如棋盘格/仪表盘刻度)
|
||||
- 验收标准:编译通过,离屏或 X 环境下能看到图形正确绘制(可截图验证)
|
||||
- 基础知识 · 参考:`p03/ch10/qpainter_basic.cpp`
|
||||
- **任务卡 2 · 感受多线程不卡界面**
|
||||
- 目标:把 `qt_thread_worker.cpp` 里的耗时计算换成一个更长的循环或 `QThread::msleep`,观察主线程/事件循环没有被卡住
|
||||
- 验收标准:能说清楚"如果把这段计算直接放主线程会发生什么"(界面无响应),并解释 `moveToThread` 起的作用
|
||||
- 基础知识 · 参考:`docs/teaching/examples/qthread_worker/qt_thread_worker.cpp`、[README](examples/qthread_worker/README.md)
|
||||
|
||||
### 模块 B:动画框架 / 状态机 → 交互式小游戏案例
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
| 知识点 | 文件 |
|
||||
|---|---|
|
||||
| **QPropertyAnimation 动画(教学补充)** | `docs/teaching/examples/property_animation/qt_property_animation.cpp` |
|
||||
| **QStateMachine 状态机(教学补充)** | `docs/teaching/examples/state_machine/qt_state_machine.cpp` |
|
||||
|
||||
**练(95min,交互式小游戏案例——练习任务,选做,非预建方案)**:
|
||||
|
||||
- **任务卡 1 · 动画 + 状态机组合**
|
||||
- 目标:把 `qt_property_animation.cpp` 的动画和 `qt_state_machine.cpp` 的状态切换组合到同一个窗口——例如按钮在"红灯"状态时禁止移动,"绿灯"状态时点击才触发动画
|
||||
- 验收标准:状态切换正确(红灯不触发动画,绿灯触发动画);动画播放完整不报错
|
||||
- 基础知识 · 参考:`docs/teaching/examples/property_animation/`、`docs/teaching/examples/state_machine/`
|
||||
- **任务卡 2(选做)· 简易反应力小游戏**
|
||||
- 目标:结合任务卡 1 + 鼠标事件(Day3 `mouse_event.cpp`)+ 多线程计时(Day4 `qt_thread_worker.cpp`),做一个"状态变化后限时点击"的最小交互游戏
|
||||
- 验收标准:游戏至少有"开始→进行中→结束"三个可辨识状态,计时或计分逻辑不崩溃
|
||||
- 基础知识 · 参考:上述三个教学补充示例 + `p03/ch09/mouse_event.cpp`
|
||||
|
||||
**当日产出验证**:分组演示动画+状态机组合小工具(任务卡 2 选做,量力而行)。
|
||||
|
||||
---
|
||||
|
||||
## Day 5 — 团队协同开发 / 综合案例复盘 / OpenCV(可选自学)
|
||||
|
||||
### 模块 A:团队协同开发(概念讲解)
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
| 内容 | 素材 |
|
||||
|---|---|
|
||||
| 项目开发过程管理全景(JIRA + Git + WIKI + 钉钉直播) | `docs/images/training_service_flow.png` |
|
||||
| 质量保证体系(四平台一标准) | `docs/images/quality_four_platforms.png` |
|
||||
| 标准化考核标准 | `docs/images/assessment_standard.png` |
|
||||
| 代码团队协同开发实操建议 | `docs/TRAINING_PLAN_2026.md` 「七、质量保证 (二) Git」 |
|
||||
|
||||
**讨论(95min)**:
|
||||
|
||||
- **任务卡 1 · 分工复盘**
|
||||
- 目标:结合考核标准图(项目管理评估/项目技术评估/职业素养评估三大块),对照 Day1 任务卡 1 定下的分工,检查执行情况
|
||||
- 验收标准:每组能说出至少 1 项做得好的、1 项需要改进的
|
||||
- 基础知识 · 参考:`docs/images/assessment_standard.png`
|
||||
|
||||
### 模块 B:综合案例复盘 + OpenCV/AI(可选,自学指针)
|
||||
|
||||
**讲(35min)**:
|
||||
|
||||
| 内容 | 说明 |
|
||||
|---|---|
|
||||
| Day2–4 案例回顾 | 请 1–2 组现场展示计算器/本地存储/动画状态机小游戏 |
|
||||
| OpenCV 与 AI 机器学习(可选,自学指针,无配套代码) | 方案书原文标注"可选择";如学员感兴趣,自学方向为 `cv::CascadeClassifier` 人脸检测入门,<span class="ext-ref">OpenCV 官方教程(需联网,选读)</span> |
|
||||
|
||||
**练(95min,综合演练)**:
|
||||
|
||||
- **任务卡 1 · 三选一深化**
|
||||
- 目标:从计算器 / 本地数据存储 / 动画状态机小游戏中选一个继续深化(加一个新功能)
|
||||
- 验收标准:现场演示新增功能正常工作,编译无报错
|
||||
- 基础知识 · 参考:回顾 Day2–4 对应的教学补充示例与 README
|
||||
|
||||
**当日产出验证**:每组现场演示深化后的案例,教师按考核标准图三大块给出反馈,
|
||||
为 Day6–10 的正式项目实作分组定方向。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证与考核方式
|
||||
|
||||
- 每天结束前 10 分钟(占用小结时间的延伸,或作为课后作业):学员提交当天练习任务卡的
|
||||
产出文件,用 `cmake --build build` 验证全绿。
|
||||
- Day4 结束:三选一小工具的现场演示,作为本次实训的最终考核点。
|
||||
- 教师侧参考答案:所有练习任务的「正确写法」在仓库对应文件里已经存在(生成器产出即
|
||||
正确版本),可直接用作评分参照;`docs/ERRATA*.md` 记录了每一处修正,可用于讲解
|
||||
“错在哪、对在哪”。
|
||||
- 每天结束前 10 分钟:学员提交当天任务卡的产出文件,用 `cmake --build build` 验证
|
||||
(Qt 相关目标需先 `-DBUILD_QT_PART=ON -DCMAKE_PREFIX_PATH=$PWD/.qt514/5.14.2/gcc_64`)。
|
||||
- Day5 结束:三选一深化案例的现场演示,作为 Day1–5 阶段的考核点,同时对齐
|
||||
方案书「标准化的考核标准」三大评估维度(项目管理/项目技术/职业素养)。
|
||||
- 教师侧参考答案:仓库已有的 `p03/` 文件和 `docs/teaching/examples/` 里的教学
|
||||
补充示例都是可运行的正确版本,可直接用作评分参照;`docs/ERRATA*.md` 记录了
|
||||
每一处 wiki 原文修正,可用于讲解"错在哪、对在哪"。
|
||||
|
||||
## 5. 材料与播放方式
|
||||
|
||||
- 大纲:本文件 `docs/teaching/OUTLINE.md`。
|
||||
- 交互幻灯片:`docs/teaching/slides/day1.html` … `day4.html`,`index.html` 为入口页。
|
||||
- 交互幻灯片:`docs/teaching/slides/day1.html` … `day5.html`,`index.html` 为入口页。
|
||||
- 教学补充示例:`docs/teaching/examples/`,6 个最小可运行 Qt 程序 + README,
|
||||
构建方式见根 `CMakeLists.txt` 里 `docs/teaching/examples` 一节;运行方式与
|
||||
`p03/` 里的示例一致:`QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>`。
|
||||
- 技术方案:reveal.js 5.2.1(仅 `dist/` 核心 + `plugin/highlight`)已 vendor 进
|
||||
`docs/teaching/slides/reveal/`,**零 CDN 依赖**,断网教室也能播放。
|
||||
`docs/teaching/slides/reveal/`,**零 CDN 依赖**,断网教室也能播放。任务卡里的
|
||||
`<span class="ext-ref">` 标注的官方文档/外部教程是**选读项**,需要联网才能
|
||||
打开,不影响幻灯片本身离线播放。
|
||||
- 播放方式(代码链接用相对路径指向仓库源文件,需要 HTTP 而非 `file://` 才能正常跳转):
|
||||
```bash
|
||||
cd /home/charles/workspaces/qt-course
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
# C++ 基础摸底小测验(10 题,预计 10 分钟)
|
||||
|
||||
> 用途:验证 `docs/teaching/OUTLINE.md` §0 里"学员已掌握 C++ 基础语法与 OOP
|
||||
> 概念"这条假设是否成立。建议在 **Day1 模块 B 开始前**(C++ 回顾环节之前)
|
||||
> 发给学员,闭卷、不查资料,10 分钟内独立完成。
|
||||
>
|
||||
> 题型:选择题 + 代码阅读(判断输出/找 bug)+ 1 道简答,覆盖 Day1 回顾要点
|
||||
> 涉及的引用、重载、构造析构顺序、虚函数多态、运算符重载、模板、STL、
|
||||
> const、深浅拷贝九个知识点。
|
||||
|
||||
---
|
||||
|
||||
## 第 1 题(选择,30 秒)
|
||||
|
||||
下列关于"引用"和"指针"的说法,**错误**的是?
|
||||
|
||||
A. 引用必须在定义时初始化
|
||||
B. 引用可以为 `nullptr`
|
||||
C. 引用一旦绑定就不能再指向其他对象
|
||||
D. 函数参数用引用可以避免拷贝开销
|
||||
|
||||
## 第 2 题(代码阅读,1 分钟)
|
||||
|
||||
```cpp
|
||||
void f(int x) { std::cout << "int " << x; }
|
||||
void f(double x) { std::cout << "double " << x; }
|
||||
|
||||
int main() { f(3.0f); }
|
||||
```
|
||||
|
||||
这段代码输出什么?
|
||||
|
||||
## 第 3 题(选择,30 秒)
|
||||
|
||||
关于虚函数,下列说法**正确**的是?
|
||||
|
||||
A. 基类析构函数不加 `virtual`,通过基类指针 `delete` 派生类对象一定不会有问题
|
||||
B. 构造函数可以是虚函数
|
||||
C. 纯虚函数所在的类不能被直接实例化
|
||||
D. 派生类必须重写基类的每一个虚函数
|
||||
|
||||
## 第 4 题(代码阅读,1 分钟)
|
||||
|
||||
```cpp
|
||||
class A { public: A(){ std::cout<<"A"; } ~A(){ std::cout<<"~A"; } };
|
||||
class B : public A { public: B(){ std::cout<<"B"; } ~B(){ std::cout<<"~B"; } };
|
||||
|
||||
int main(){ B b; }
|
||||
```
|
||||
|
||||
请写出输出的完整顺序。
|
||||
|
||||
## 第 5 题(找 bug,1.5 分钟)
|
||||
|
||||
```cpp
|
||||
class Buffer {
|
||||
char* data;
|
||||
public:
|
||||
Buffer(int n) { data = new char[n]; }
|
||||
~Buffer() { delete[] data; }
|
||||
};
|
||||
|
||||
void use() {
|
||||
Buffer a(10);
|
||||
Buffer b = a; // 用 a 拷贝构造 b
|
||||
}
|
||||
```
|
||||
|
||||
`use()` 函数运行到结束时会发生什么问题?请说明原因。
|
||||
|
||||
## 第 6 题(选择,30 秒)
|
||||
|
||||
关于函数模板,下列说法**正确**的是?
|
||||
|
||||
A. 模板函数一旦定义,编译器就会为所有可能的类型生成代码
|
||||
B. 编译器会根据调用时的实参类型自动推导模板参数,并生成对应的实例
|
||||
C. 模板函数不能被重载
|
||||
D. 调用模板函数时必须显式指定所有模板参数
|
||||
|
||||
## 第 7 题(代码阅读,1 分钟)
|
||||
|
||||
```cpp
|
||||
std::vector<int> v = {3, 1, 4, 1, 5};
|
||||
for (int x : v) {
|
||||
if (x % 2 == 0) std::cout << x;
|
||||
}
|
||||
```
|
||||
|
||||
这段代码输出什么?
|
||||
|
||||
## 第 8 题(选择,30 秒)
|
||||
|
||||
下列哪个函数声明表示"这个成员函数不会修改对象的成员变量"?
|
||||
|
||||
A. `void print()`
|
||||
B. `void print() const`
|
||||
C. `const void print()`
|
||||
D. `void const print()`
|
||||
|
||||
## 第 9 题(代码阅读,1 分钟)
|
||||
|
||||
```cpp
|
||||
class Point {
|
||||
public:
|
||||
int x, y;
|
||||
Point operator+(const Point& other) const {
|
||||
return Point{x + other.x, y + other.y};
|
||||
}
|
||||
};
|
||||
|
||||
Point p1{1, 2}, p2{3, 4};
|
||||
Point p3 = p1 + p2;
|
||||
std::cout << p3.x << "," << p3.y;
|
||||
```
|
||||
|
||||
这段代码输出什么?
|
||||
|
||||
## 第 10 题(简答,1.5 分钟)
|
||||
|
||||
用一两句话说明"深拷贝"和"浅拷贝"的区别,以及为什么一个类里如果有指针成员、
|
||||
又没有自己实现拷贝构造函数,可能会引发问题。
|
||||
|
||||
---
|
||||
|
||||
## 答案与评分(教师用,建议测验时不发给学员)
|
||||
|
||||
| 题号 | 答案 | 对应知识点 · 出处 |
|
||||
|---|---|---|
|
||||
| 1 | B(引用不能为空,这是它和指针的关键区别之一) | 引用的本质 · `p01/ch03/reference_essence.cpp` |
|
||||
| 2 | `double 3`(`float` 到 `double` 是浮点提升,比 `float` 到 `int` 的转换更优先匹配 `f(double)`) | 函数重载决议 · `p01/ch03/function_overload_mechanism.cpp` |
|
||||
| 3 | C(A 错——没有 virtual 析构会导致派生类部分不被析构;B 错——构造函数不能是虚函数;D 错——不重写就继承基类实现,纯虚函数才必须重写) | 虚函数与抽象类 · `p01/ch04/s08` |
|
||||
| 4 | `A B ~B ~A`(先构造基类再构造派生类;析构顺序相反) | 继承中构造/析构顺序 · `p01/ch04/s07/inheritance_ctor_dtor_order.cpp` |
|
||||
| 5 | 浅拷贝:`b` 的 `data` 和 `a` 的 `data` 指向同一块内存;函数结束时 `~Buffer()` 被调用两次,对同一块内存 `delete[]` 两次(double free),行为未定义/可能崩溃 | 深拷贝 vs 浅拷贝 · `p01/ch04/s03/deep_vs_shallow_copy.cpp` |
|
||||
| 6 | B(模板是"按需实例化",不是提前生成所有版本) | 函数模板 · `p01/ch05/function_template.cpp` |
|
||||
| 7 | `4`(3,1,4,1,5 里只有 4 是偶数) | STL 容器与范围 for · `p01/ch09/s03/vector_iterator.cpp` |
|
||||
| 8 | B(`const` 放在参数列表后面,修饰的是"this 指向的对象不可修改") | const 成员函数 · `p01/ch04/s04/const_member_function.cpp` |
|
||||
| 9 | `4,6`(`1+3=4`,`2+4=6`) | 运算符重载 · `p01/ch04/s06/overload_assignment.cpp` |
|
||||
| 10 | 浅拷贝只复制指针本身(地址),深拷贝复制指针指向的内容;没有自定义拷贝构造函数时,编译器生成的默认拷贝构造函数是浅拷贝,两个对象的指针成员会指向同一块内存,析构时重复释放(double free)或修改一个对象的数据影响另一个对象 | 深拷贝 vs 浅拷贝 · `p01/ch04/s03/deep_vs_shallow_copy.cpp` |
|
||||
|
||||
### 评分建议
|
||||
|
||||
- **8–10 题正确**:具备 `OUTLINE.md` §0 假设的基础,按 Day1 模块 B 原定的
|
||||
"巡礼式回顾"节奏推进即可。
|
||||
- **5–7 题正确**:部分薄弱点——对照上表找出学员集中出错的知识点(比如普遍
|
||||
在第 4/5 题失分,说明构造析构顺序和深浅拷贝没吃透),讲课时在对应知识点
|
||||
多花 5–10 分钟,其余部分维持原节奏。
|
||||
- **0–4 题正确**:明显达不到"已掌握 C++ 基础语法与 OOP 概念"的假设——按
|
||||
`OUTLINE.md` §0 里的预案,Day1 模块 B 的讲授部分退回到语法讲解,练习任务
|
||||
保持不变但预期完成度应下调,不需要重排整个教案结构。
|
||||
|
||||
## 使用方式
|
||||
|
||||
- 建议纸质或线上表单均可,闭卷、不查资料、不讨论,10 分钟收卷;
|
||||
- 只用于摸底分层,不计入 `docs/TRAINING_PLAN_2026.md` 的正式考核成绩;
|
||||
- 大规模学员时可以只统计"每题正确率"而不用逐份细看,正确率低于 50% 的题
|
||||
对应知识点在 Day1 模块 B 里适当加时。
|
||||
@@ -0,0 +1,28 @@
|
||||
# 手工维护 —— 不是 tools/gen_part3.py 的生成产物,重新生成 p01/p03 不会影响这里。
|
||||
#
|
||||
# 教学补充示例:方案书「C++方向」课程要求、但仓库 p03/(wiki 抓取内容)未覆盖
|
||||
# 的技术点,每个子目录一个最小可运行 Qt 程序 + 配套 README.md 教学文档。
|
||||
# 不计入 README.md/CLAUDE.md 里「144+29=173 个目标」的生成器目标计数。
|
||||
|
||||
function(add_teaching_example name dir)
|
||||
add_executable(${name} ${dir}/${name}.cpp)
|
||||
set_target_properties(${name} PROPERTIES
|
||||
CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF AUTOMOC ON)
|
||||
target_link_libraries(${name} PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets)
|
||||
target_compile_options(${name} PRIVATE -Wall -Wextra)
|
||||
endfunction()
|
||||
|
||||
add_teaching_example(qt_qss_styling qss_styling)
|
||||
add_teaching_example(qt_model_view_basics model_view)
|
||||
add_teaching_example(qt_json_parse_write json_io)
|
||||
add_teaching_example(qt_thread_worker qthread_worker)
|
||||
add_teaching_example(qt_property_animation property_animation)
|
||||
add_teaching_example(qt_state_machine state_machine)
|
||||
|
||||
add_custom_target(all_teaching_examples DEPENDS
|
||||
qt_qss_styling
|
||||
qt_model_view_basics
|
||||
qt_json_parse_write
|
||||
qt_thread_worker
|
||||
qt_property_animation
|
||||
qt_state_machine)
|
||||
@@ -0,0 +1,56 @@
|
||||
# JSON 解析与写入 —— 教学补充示例
|
||||
|
||||
> 对应方案书「C++方向 Day3」:"Qt 文件 IO 操作与 JSON 等主流数据格式解析"。
|
||||
> 仓库 `p03/ch11` 的 wiki 抓取内容覆盖了 `QFile`/`QTextStream`/`QDataStream`,
|
||||
> 唯独没有 JSON,本示例是手工补写的教学补充,**不属于**
|
||||
> `tools/gen_part3.py` 的生成产物。
|
||||
|
||||
## 概念
|
||||
|
||||
Qt 从 5.0 起自带 JSON 支持(`QtCore` 模块,不需要额外依赖第三方 JSON 库),
|
||||
核心是四个类:
|
||||
|
||||
- `QJsonValue` —— 一个 JSON 值(字符串/数字/布尔/数组/对象/null);
|
||||
- `QJsonObject` —— 键值对集合,对应 JSON 里的 `{ ... }`;
|
||||
- `QJsonArray` —— 值的列表,对应 JSON 里的 `[ ... ]`;
|
||||
- `QJsonDocument` —— 顶层容器,负责整个文档和字节流(`QByteArray`)之间的
|
||||
相互转换(序列化/反序列化)。
|
||||
|
||||
与 `p03/ch11` 已有的 `QTextStream`/`QDataStream` 对比:那两个解决的是"文本行"
|
||||
和"二进制结构体"的读写;JSON 解决的是"带层级结构的数据"(对象嵌数组嵌对象)的
|
||||
读写,是目前最常见的配置文件/网络接口数据格式。
|
||||
|
||||
## 代码走读
|
||||
|
||||
见 [`qt_json_parse_write.cpp`](qt_json_parse_write.cpp):
|
||||
|
||||
1. 用 `QJsonObject`/`QJsonArray` 构造一份"学员信息"数据;
|
||||
2. `QJsonDocument(obj).toJson(QJsonDocument::Indented)` 序列化成带缩进的可读
|
||||
字节流(`QJsonDocument::Compact` 则是不带空白的紧凑格式,写文件/网络传输
|
||||
常用这个);
|
||||
3. `QJsonDocument::fromJson(bytes)` 反序列化回来,`.isObject()` 判断顶层类型后
|
||||
逐个字段取值。
|
||||
|
||||
这个示例不涉及 GUI,纯粹是数据结构的构造/序列化/反序列化,所以用
|
||||
`QCoreApplication` 而不是 `QApplication`,不需要 `QT_QPA_PLATFORM=offscreen`。
|
||||
|
||||
## 验证方式
|
||||
|
||||
```bash
|
||||
tools/run_qt.sh qt_json_parse_write
|
||||
```
|
||||
|
||||
预期终端输出:先打印一段带缩进的 JSON 文本,再打印从中解析出的姓名/分数/标签。
|
||||
|
||||
## 练习方向(供任务卡引用)
|
||||
|
||||
- 把结果写入一个真实文件(结合 `p03/ch11/qfile_basic.cpp` 的 `QFile` 写法),
|
||||
再从文件读回来解析,构成一个完整的"落盘 JSON 配置"流程;
|
||||
- 故意构造一段格式错误的 JSON 字符串喂给 `fromJson`,观察 `parsed.isNull()`
|
||||
为 `true` 时如何做错误提示(而不是让程序崩溃或读到脏数据)。
|
||||
|
||||
## 参考
|
||||
|
||||
- 本仓库对照:[`p03/ch11/qfile_basic.cpp`](../../../../p03/ch11/qfile_basic.cpp)、
|
||||
[`qtextstream_io.cpp`](../../../../p03/ch11/qtextstream_io.cpp)
|
||||
- Qt 官方文档:*JSON Support in Qt*(doc.qt.io,选读·需联网)
|
||||
@@ -0,0 +1,49 @@
|
||||
// ============================================================================
|
||||
// qt_json_parse_write —— 教学补充示例(非 wiki 生成,手工维护)
|
||||
// 用途:方案书「C++方向 Day3」要求的 JSON 等主流数据格式解析,仓库 p03/ch11
|
||||
// 原有 wiki 抓取内容只覆盖 QFile/QTextStream/QDataStream,没有 JSON,
|
||||
// 故补一个最小示例。
|
||||
// 说明:JSON 读写不需要 GUI,用 QCoreApplication 即可,不需要
|
||||
// QT_QPA_PLATFORM=offscreen,直接 tools/run_qt.sh qt_json_parse_write 即可。
|
||||
// 配套文档:docs/teaching/examples/json_io/README.md
|
||||
// ============================================================================
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QDebug>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
// 1) 构造一个 JSON 对象(对应课程里“学员信息”这类结构)
|
||||
QJsonObject student;
|
||||
student["name"] = QStringLiteral("张三");
|
||||
student["score"] = 92;
|
||||
QJsonArray tags;
|
||||
tags.append(QStringLiteral("Qt"));
|
||||
tags.append(QStringLiteral("C++"));
|
||||
student["tags"] = tags;
|
||||
|
||||
// 2) 序列化:QJsonObject -> QJsonDocument -> 字节数组(可直接写文件)
|
||||
QJsonDocument doc(student);
|
||||
QByteArray bytes = doc.toJson(QJsonDocument::Indented);
|
||||
qDebug().noquote() << "序列化结果:\n" << bytes;
|
||||
|
||||
// 3) 反序列化:字节数组 -> QJsonDocument -> 取值
|
||||
QJsonDocument parsed = QJsonDocument::fromJson(bytes);
|
||||
if (!parsed.isNull() && parsed.isObject()) {
|
||||
QJsonObject obj = parsed.object();
|
||||
qDebug() << "姓名:" << obj["name"].toString();
|
||||
qDebug() << "分数:" << obj["score"].toInt();
|
||||
const QJsonArray parsedTags = obj["tags"].toArray();
|
||||
for (const QJsonValue& v : parsedTags)
|
||||
qDebug() << "标签:" << v.toString();
|
||||
} else {
|
||||
qDebug() << "JSON 解析失败";
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Model/View 模型视图架构 —— 教学补充示例
|
||||
|
||||
> 对应方案书「C++方向 Day3」:"高级控件应用与 Model/View 模型视图架构"。
|
||||
> 仓库 `p03/` 的 wiki 抓取内容里没有 Model/View 相关代码,本示例是手工补写的
|
||||
> 教学补充,**不属于** `tools/gen_part3.py` 的生成产物。
|
||||
|
||||
## 概念
|
||||
|
||||
Qt 的 Model/View 架构把"数据"和"展示"彻底解耦成三层:
|
||||
|
||||
- **Model**(模型):只管数据,不知道自己会被怎么展示。本示例用现成的
|
||||
`QStandardItemModel`(也可以继承 `QAbstractListModel`/`QAbstractTableModel`
|
||||
自己实现,工作量更大,教学上先用现成模型建立概念);
|
||||
- **View**(视图):只管展示和交互,不持有数据副本,展示什么完全来自
|
||||
`setModel()` 绑的那个模型。本示例用 `QListView`;
|
||||
- **Selection Model**(选中状态):视图的选中状态也被抽成独立对象
|
||||
`QItemSelectionModel`,可以多个视图共享同一个选中状态。
|
||||
|
||||
对比"没有 Model/View"的写法(比如直接往 `QListWidget` 里塞 `addItem()`):数据
|
||||
和展示是绑死的;Model/View 的好处在于同一份数据可以同时喂给
|
||||
`QListView`/`QTableView`/`QComboBox` 等多个视图,改数据所有视图自动同步。
|
||||
|
||||
## 代码走读
|
||||
|
||||
见 [`qt_model_view_basics.cpp`](qt_model_view_basics.cpp):
|
||||
|
||||
1. 建一个 `QStandardItemModel`,塞 4 行字符串数据;
|
||||
2. 建一个 `QListView`,`setModel()` 绑定上面的模型——视图本身不存数据;
|
||||
3. 监听 `view->selectionModel()->currentChanged` 信号,选中项变化时更新一个
|
||||
`QLabel`,体现"视图状态变化 → 通知外部"的解耦方式;
|
||||
4. 代码末尾手动 `setCurrentIndex()` 选中第一行,是为了离屏自动化验证时也能
|
||||
触发一次 `currentChanged`(真实交互中这一步由用户点击触发)。
|
||||
|
||||
## 验证方式
|
||||
|
||||
```bash
|
||||
QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_model_view_basics
|
||||
```
|
||||
|
||||
## 练习方向(供任务卡引用)
|
||||
|
||||
- 把 `QListView` 换成 `QTableView`,模型不变,观察"同一份模型换个视图"是怎么
|
||||
做到的;
|
||||
- 增加一个"删除选中行"按钮,调用 `model->removeRow(view->currentIndex().row())`,
|
||||
体会数据变化后视图自动刷新(不需要手动重画)。
|
||||
|
||||
## 参考
|
||||
|
||||
- Qt 官方文档:*Model/View Programming*(doc.qt.io,选读·需联网)
|
||||
@@ -0,0 +1,61 @@
|
||||
// ============================================================================
|
||||
// qt_model_view_basics —— 教学补充示例(非 wiki 生成,手工维护)
|
||||
// 用途:方案书「C++方向 Day3」要求的 Model/View 模型视图架构,仓库 p03/ 原有
|
||||
// wiki 抓取内容未覆盖这一点,故补一个最小示例。
|
||||
// 配套文档:docs/teaching/examples/model_view/README.md
|
||||
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_model_view_basics
|
||||
// ============================================================================
|
||||
|
||||
#include <QApplication>
|
||||
#include <QWidget>
|
||||
#include <QListView>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QStandardItemModel>
|
||||
#include <QTimer>
|
||||
#include <QItemSelectionModel>
|
||||
|
||||
class ModelViewDemo : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ModelViewDemo(QWidget* parent = nullptr) : QWidget(parent) {
|
||||
// 模型:数据本身,与展示方式无关
|
||||
auto* model = new QStandardItemModel(this);
|
||||
model->appendRow(new QStandardItem(QStringLiteral("需求分析")));
|
||||
model->appendRow(new QStandardItem(QStringLiteral("项目设计")));
|
||||
model->appendRow(new QStandardItem(QStringLiteral("编码实现")));
|
||||
model->appendRow(new QStandardItem(QStringLiteral("测试及回归")));
|
||||
|
||||
// 视图:只负责展示,不持有数据
|
||||
auto* view = new QListView(this);
|
||||
view->setModel(model);
|
||||
|
||||
auto* selectedLabel = new QLabel(QStringLiteral("当前选中:(无)"), this);
|
||||
|
||||
// 选中项变化 → 视图层通知 → 界面同步更新,模型和视图之间没有直接耦合
|
||||
connect(view->selectionModel(), &QItemSelectionModel::currentChanged,
|
||||
this, [selectedLabel](const QModelIndex& current, const QModelIndex&) {
|
||||
if (current.isValid())
|
||||
selectedLabel->setText(QStringLiteral("当前选中:%1").arg(current.data().toString()));
|
||||
});
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
layout->addWidget(view);
|
||||
layout->addWidget(selectedLabel);
|
||||
|
||||
// 离屏自动化验证:选中第一行,触发一次 currentChanged
|
||||
view->setCurrentIndex(model->index(0, 0));
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
ModelViewDemo w;
|
||||
w.resize(280, 220);
|
||||
w.show();
|
||||
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
|
||||
QTimer::singleShot(200, &app, &QApplication::quit);
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "qt_model_view_basics.moc"
|
||||
@@ -0,0 +1,51 @@
|
||||
# QPropertyAnimation 动画框架 —— 教学补充示例
|
||||
|
||||
> 对应方案书「C++方向 Day4」:"QT 动画框架(QPropertyAnimation)与状态机实战"
|
||||
> 里的动画部分(状态机部分见 [`../state_machine/`](../state_machine/README.md))。
|
||||
> 仓库 `p03/` 的 wiki 抓取内容里没有动画相关代码,本示例是手工补写的教学补充,
|
||||
> **不属于** `tools/gen_part3.py` 的生成产物。
|
||||
|
||||
## 概念
|
||||
|
||||
`QPropertyAnimation` 能直接对 Qt 的**属性系统**(`Q_PROPERTY`,比如
|
||||
`geometry`、`windowOpacity`、`pos`)做插值动画,不需要手写定时器 + 每帧计算
|
||||
坐标/透明度再手动调用 `update()`。三个关键参数:
|
||||
|
||||
- `setStartValue()` / `setEndValue()` —— 起止值;
|
||||
- `setDuration()` —— 动画时长(毫秒);
|
||||
- `setEasingCurve()` —— 缓动曲线,控制"匀速/加速/回弹"等运动节奏
|
||||
(`QEasingCurve` 提供几十种预设曲线)。
|
||||
|
||||
动画对象默认不会自动播放,需要显式 `start()`;`start(QAbstractAnimation::DeleteWhenStopped)`
|
||||
可以让动画播放完自动 `delete` 自己,不用手动管理生命周期。
|
||||
|
||||
## 代码走读
|
||||
|
||||
见 [`qt_property_animation.cpp`](qt_property_animation.cpp):
|
||||
|
||||
1. 一个按钮初始位置在左上角 `QRect(10, 10, 100, 30)`;
|
||||
2. `QPropertyAnimation` 绑定按钮的 `"geometry"` 属性,800ms 内从起始矩形动画到
|
||||
右下角的 `QRect(180, 140, 120, 40)`(位置和大小同时变化);
|
||||
3. `QEasingCurve::OutBounce` 让动画结束时有一个"回弹"的视觉效果,
|
||||
直观体现"缓动曲线改变的是运动节奏,不是起止值";
|
||||
4. 离屏验证等待 1100ms(比动画时长 800ms 多留 300ms 余量)再退出,确保动画
|
||||
真的播放完整。
|
||||
|
||||
## 验证方式
|
||||
|
||||
```bash
|
||||
QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_property_animation
|
||||
```
|
||||
|
||||
有 X 环境直接运行可以看到按钮从左上角带回弹效果移动到右下角。
|
||||
|
||||
## 练习方向(供任务卡引用)
|
||||
|
||||
- 把 `QEasingCurve::OutBounce` 换成 `QEasingCurve::Linear`/`InOutQuad`,
|
||||
对比运动节奏的差异;
|
||||
- 再加一个 `QPropertyAnimation` 动画 `windowOpacity`(从 1.0 到 0.3),用
|
||||
`QParallelAnimationGroup` 让位置动画和透明度动画同时播放。
|
||||
|
||||
## 参考
|
||||
|
||||
- Qt 官方文档:*Animation Framework*、*QPropertyAnimation*(doc.qt.io,选读·需联网)
|
||||
@@ -0,0 +1,38 @@
|
||||
// ============================================================================
|
||||
// qt_property_animation —— 教学补充示例(非 wiki 生成,手工维护)
|
||||
// 用途:方案书「C++方向 Day4」要求的 QPropertyAnimation 动画框架,仓库 p03/
|
||||
// 原有 wiki 抓取内容未覆盖这一点,故补一个最小示例。
|
||||
// 配套文档:docs/teaching/examples/property_animation/README.md
|
||||
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_property_animation
|
||||
// ============================================================================
|
||||
|
||||
#include <QApplication>
|
||||
#include <QWidget>
|
||||
#include <QPushButton>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QTimer>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QWidget window;
|
||||
window.resize(320, 200);
|
||||
|
||||
auto* button = new QPushButton(QStringLiteral("看我移动"), &window);
|
||||
button->setGeometry(10, 10, 100, 30);
|
||||
|
||||
// QPropertyAnimation 直接驱动 Qt 属性系统里的 geometry 属性,
|
||||
// 不需要手写定时器 + 逐帧计算坐标。
|
||||
auto* anim = new QPropertyAnimation(button, "geometry", &window);
|
||||
anim->setDuration(800);
|
||||
anim->setStartValue(QRect(10, 10, 100, 30));
|
||||
anim->setEndValue(QRect(180, 140, 120, 40));
|
||||
anim->setEasingCurve(QEasingCurve::OutBounce);
|
||||
|
||||
anim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
|
||||
window.show();
|
||||
// 离屏/自动化验证:动画时长 800ms,额外等 300ms 确保播放完再退出
|
||||
QTimer::singleShot(1100, &app, &QApplication::quit);
|
||||
return app.exec();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# QSS 界面美化 —— 教学补充示例
|
||||
|
||||
> 对应方案书「C++方向 Day2」:"基础 UI 控件、布局管理与 QSS 界面美化"。
|
||||
> 仓库 `p03/` 的 wiki 抓取内容里没有 QSS 相关代码,本示例是手工补写的教学补充,
|
||||
> **不属于** `tools/gen_part3.py` 的生成产物,重新生成 `p01`/`p03` 不会影响它。
|
||||
|
||||
## 概念
|
||||
|
||||
QSS(Qt Style Sheets)是 Qt 提供的类 CSS 样式语言,用来把"控件长什么样"和"控件
|
||||
做什么"分离开:
|
||||
|
||||
- 类型选择器:`QPushButton { ... }` —— 命中所有该类型控件;
|
||||
- ID 选择器:`QPushButton#okButton { ... }` —— 通过 `setObjectName()` 精确命中
|
||||
某一个控件;
|
||||
- 伪状态:`:hover`、`:pressed`、`:disabled` 等 —— 不用手写事件处理就能做交互反馈;
|
||||
- 既可以调用 `qApp->setStyleSheet(...)` 全局生效,也可以 `widget->setStyleSheet(...)`
|
||||
只作用于这个控件和它的子控件。
|
||||
|
||||
## 代码走读
|
||||
|
||||
见 [`qt_qss_styling.cpp`](qt_qss_styling.cpp):
|
||||
|
||||
1. 三个控件(标题 `QLabel`、确定/取消两个 `QPushButton`)分别设置 `objectName`;
|
||||
2. 整个窗口调一次 `setStyleSheet()`,用 ID 选择器分别美化每个控件,
|
||||
`okButton`/`cancelButton` 还各自定义了 `:hover`/`:pressed` 效果;
|
||||
3. 不改变任何业务逻辑(没有新增信号槽),只改变外观——这正是 QSS 的定位:
|
||||
与逻辑正交。
|
||||
|
||||
## 验证方式
|
||||
|
||||
```bash
|
||||
QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_qss_styling # 离屏跑通,200ms 自动退出
|
||||
tools/run_qt.sh qt_qss_styling # 有 X 环境可直接看窗口
|
||||
```
|
||||
|
||||
## 练习方向(供任务卡引用)
|
||||
|
||||
- 把 `okButton`/`cancelButton` 换成类型选择器 `QPushButton { ... }` 一次性美化,
|
||||
对比 ID 选择器和类型选择器的适用场景差异;
|
||||
- 给 `title` 加一个 `:hover` 伪状态(`QLabel` 默认不接收鼠标事件,需要先
|
||||
`setAttribute(Qt::WA_Hover)` 之类的前置条件——留给学员自己踩一次坑)。
|
||||
|
||||
## 参考
|
||||
|
||||
- 本仓库对照:[`p03/ch08/custom_widget.cpp`](../../../../p03/ch08/custom_widget.cpp)
|
||||
(展示了 `QHBoxLayout` 布局管理,可与本示例的样式部分组合练习)
|
||||
- Qt 官方文档:*Qt Style Sheets Reference*(doc.qt.io,选读·需联网)
|
||||
@@ -0,0 +1,58 @@
|
||||
// ============================================================================
|
||||
// qt_qss_styling —— 教学补充示例(非 wiki 生成,手工维护)
|
||||
// 用途:方案书「C++方向 Day2」要求的 QSS 界面美化,仓库 p03/ 原有 wiki 抓取内容
|
||||
// 未覆盖这一点,故补一个最小示例。
|
||||
// 配套文档:docs/teaching/examples/qss_styling/README.md
|
||||
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_qss_styling
|
||||
// ============================================================================
|
||||
|
||||
#include <QApplication>
|
||||
#include <QWidget>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QTimer>
|
||||
|
||||
class StyledPanel : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit StyledPanel(QWidget* parent = nullptr) : QWidget(parent) {
|
||||
auto* title = new QLabel(QStringLiteral("QSS 样式演示"), this);
|
||||
title->setObjectName("title");
|
||||
|
||||
auto* okBtn = new QPushButton(QStringLiteral("确定"), this);
|
||||
okBtn->setObjectName("okButton");
|
||||
|
||||
auto* cancelBtn = new QPushButton(QStringLiteral("取消"), this);
|
||||
cancelBtn->setObjectName("cancelButton");
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
layout->addWidget(title);
|
||||
layout->addWidget(okBtn);
|
||||
layout->addWidget(cancelBtn);
|
||||
|
||||
// 整个窗口一份样式表:按 objectName 选择器精确命中控件,
|
||||
// 也可以只用类型选择器(QPushButton { ... })一次性套所有按钮。
|
||||
setStyleSheet(
|
||||
"QWidget { background-color: #f4f6f8; }"
|
||||
"QLabel#title { font-size: 18px; font-weight: bold; color: #2b6cb0; padding: 6px; }"
|
||||
"QPushButton { border-radius: 6px; padding: 6px 14px; color: white; }"
|
||||
"QPushButton#okButton { background-color: #2f855a; }"
|
||||
"QPushButton#okButton:hover { background-color: #276749; }"
|
||||
"QPushButton#cancelButton { background-color: #a0522d; }"
|
||||
"QPushButton#cancelButton:pressed { background-color: #7b3f22; }"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
StyledPanel panel;
|
||||
panel.resize(240, 160);
|
||||
panel.show();
|
||||
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
|
||||
QTimer::singleShot(200, &app, &QApplication::quit);
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "qt_qss_styling.moc"
|
||||
@@ -0,0 +1,56 @@
|
||||
# QThread 多线程 —— 教学补充示例
|
||||
|
||||
> 对应方案书「C++方向 Day4」:"QT 多线程编程(QThread、互斥锁与防止 UI 阻塞机制)"。
|
||||
> 仓库 `p03/` 的 wiki 抓取内容里没有多线程相关代码,本示例是手工补写的教学
|
||||
> 补充,**不属于** `tools/gen_part3.py` 的生成产物。
|
||||
|
||||
## 概念
|
||||
|
||||
Qt 官方推荐的多线程模式是 **worker-object + moveToThread**,而不是继承
|
||||
`QThread` 重写 `run()`(后者是早期教程常见但容易踩坑的写法:很容易在错误的
|
||||
线程里调用槽函数)。核心步骤:
|
||||
|
||||
1. 把耗时逻辑写成一个普通 `QObject` 子类(`Worker`),逻辑放在一个槽函数里;
|
||||
2. 创建一个 `QThread` 对象,调用 `worker.moveToThread(&thread)`——这一步之后,
|
||||
`worker` 的槽函数会在 `thread` 里执行,而不是在创建它的线程里;
|
||||
3. 用信号槽驱动流程:`thread.started` 触发 `worker.process()`,
|
||||
`worker.resultReady` 触发主线程里的回调;
|
||||
4. 关键点:**跨线程的信号槽连接默认是"排队连接"(queued connection)**——
|
||||
Qt 会自动把回调调度回接收者所在的线程的事件循环里执行,不需要手动加锁
|
||||
就能安全地把子线程算出来的结果"带回"主线程。
|
||||
|
||||
方案书提到的"互斥锁"用于多个线程**同时读写同一块内存**的场景;本示例里子
|
||||
线程和主线程之间只通过信号槽传递数据(没有共享变量),所以不需要
|
||||
`QMutex`——这本身也是一个教学点:**优先用信号槽传递数据,退而求其次才用锁**。
|
||||
|
||||
## 代码走读
|
||||
|
||||
见 [`qt_thread_worker.cpp`](qt_thread_worker.cpp):
|
||||
|
||||
1. `Worker::process()` 模拟一次耗时计算(循环求和),完成后 `emit resultReady(sum)`;
|
||||
2. `worker.moveToThread(&workerThread)` 把 worker 挪到子线程;
|
||||
3. `workerThread.started` → `worker.process`:线程一启动就开始干活;
|
||||
4. `worker.resultReady` → 主线程 lambda:打印结果,并判断当前
|
||||
`QThread::currentThread()` 确实是主线程(证明排队连接真的把回调带回来了);
|
||||
5. 结果打印后调用 `workerThread.quit()` 结束子线程事件循环,
|
||||
`workerThread.finished` 再触发 `QCoreApplication::quit()` 结束整个程序。
|
||||
|
||||
## 验证方式
|
||||
|
||||
```bash
|
||||
tools/run_qt.sh qt_thread_worker
|
||||
```
|
||||
|
||||
预期终端输出一行"子线程计算结果:...(当前在主线程打印)"。
|
||||
|
||||
## 练习方向(供任务卡引用)
|
||||
|
||||
- 把 `Worker::process()` 里的耗时计算换成读一个大文件/睡眠几秒,感受主线程
|
||||
没有被卡住(如果这是一个真实 GUI 程序,界面依然能响应鼠标点击);
|
||||
- 故意去掉 `moveToThread` 这一行,观察 `resultReady` 的回调是在哪个线程执行的
|
||||
(`QThread::currentThread()` 判断结果会反过来),理解 `moveToThread` 到底做了
|
||||
什么。
|
||||
|
||||
## 参考
|
||||
|
||||
- Qt 官方文档:*QThread*、*Threads and QObjects*(doc.qt.io,选读·需联网)
|
||||
@@ -0,0 +1,53 @@
|
||||
// ============================================================================
|
||||
// qt_thread_worker —— 教学补充示例(非 wiki 生成,手工维护)
|
||||
// 用途:方案书「C++方向 Day4」要求的 QThread 多线程编程(避免耗时任务阻塞主
|
||||
// 线程/界面),仓库 p03/ 原有 wiki 抓取内容未覆盖这一点,故补一个最小示例。
|
||||
// 说明:用 QCoreApplication 演示跨线程信号槽通信的核心模式(moveToThread +
|
||||
// 排队连接),不需要 GUI;真实项目里把“耗时计算”换成真实业务逻辑、
|
||||
// 把“打印结果”换成“更新界面控件”即可,模式不变。
|
||||
// 配套文档:docs/teaching/examples/qthread_worker/README.md
|
||||
// ============================================================================
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QThread>
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
|
||||
// Worker 不继承 QThread,而是被 moveToThread 挪到子线程执行——这是 Qt 官方推荐
|
||||
// 的写法(继承 QThread 重写 run() 是早期常见但容易踩坑的旧写法)。
|
||||
class Worker : public QObject {
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
void process() {
|
||||
// 模拟耗时计算(真实场景:大文件解析、网络请求、复杂运算……)
|
||||
// 注意用 qint64:1 加到 1000000 的和是 500000500000,超过 int 上限会溢出。
|
||||
qint64 sum = 0;
|
||||
for (int i = 1; i <= 1000000; ++i) sum += i;
|
||||
emit resultReady(sum);
|
||||
}
|
||||
signals:
|
||||
void resultReady(qint64 result);
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
QThread workerThread;
|
||||
Worker worker;
|
||||
worker.moveToThread(&workerThread);
|
||||
|
||||
// 子线程启动后立即开始工作
|
||||
QObject::connect(&workerThread, &QThread::started, &worker, &Worker::process);
|
||||
// 工作完成 → 排队连接自动把回调调度回主线程 → 打印结果 → 结束线程与事件循环
|
||||
QObject::connect(&worker, &Worker::resultReady, &app, [&](qint64 result) {
|
||||
qDebug() << "子线程计算结果:" << result
|
||||
<< "(当前在" << (QThread::currentThread() == &workerThread ? "子线程" : "主线程") << "打印)";
|
||||
workerThread.quit();
|
||||
});
|
||||
QObject::connect(&workerThread, &QThread::finished, &app, &QCoreApplication::quit);
|
||||
|
||||
workerThread.start();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "qt_thread_worker.moc"
|
||||
@@ -0,0 +1,58 @@
|
||||
# QStateMachine 状态机 —— 教学补充示例
|
||||
|
||||
> 对应方案书「C++方向 Day4」:"QT 动画框架(QPropertyAnimation)与状态机实战"
|
||||
> 里的状态机部分(动画部分见 [`../property_animation/`](../property_animation/README.md))。
|
||||
> 仓库 `p03/` 的 wiki 抓取内容里没有状态机相关代码,本示例是手工补写的教学
|
||||
> 补充,**不属于** `tools/gen_part3.py` 的生成产物。
|
||||
>
|
||||
> 小提示:`QStateMachine`/`QState` 属于 **QtCore** 模块,不需要额外
|
||||
> `find_package`,这也是本示例能直接放进现有 Qt5::Core 依赖里的原因。
|
||||
|
||||
## 概念
|
||||
|
||||
状态机框架用"状态 + 转换条件"描述一个对象的行为,替代手写一堆 `if/else` 或
|
||||
`switch` 去判断"现在是什么状态、该往哪个状态跳":
|
||||
|
||||
- `QState` —— 一个状态,可以用 `assignProperty(obj, "属性名", 值)` 声明
|
||||
"进入这个状态时,某个对象的某个属性应该变成什么值"(不用手写槽函数);
|
||||
- `addTransition(信号源, 信号, 目标状态)` —— 声明"某个信号触发时,从当前状态
|
||||
转换到目标状态";
|
||||
- `QStateMachine` —— 持有所有状态,`setInitialState()` 指定初始状态,
|
||||
`start()` 启动。
|
||||
|
||||
对比方案书里"棋牌类游戏""售货机"这类案例常见的手写状态机(一个枚举 + 一堆
|
||||
`switch(state)`):`QStateMachine` 把"状态该长什么样"(`assignProperty`)和
|
||||
"什么时候切换状态"(`addTransition`)分开声明,状态一多,可读性明显好于手写
|
||||
`switch`。
|
||||
|
||||
## 代码走读
|
||||
|
||||
见 [`qt_state_machine.cpp`](qt_state_machine.cpp):
|
||||
|
||||
1. 定义 `redState`/`greenState` 两个 `QState`,各自用 `assignProperty` 声明
|
||||
进入该状态时按钮的文本和样式;
|
||||
2. `redState->addTransition(&button, &QPushButton::clicked, greenState)`,
|
||||
反向同理——点击按钮在两态间来回切换(红灯 → 绿灯 → 红灯 → ……);
|
||||
3. `machine.setInitialState(redState)` + `machine.start()` 启动状态机;
|
||||
4. 离屏验证用 `QTimer::singleShot` 模拟一次 `button.click()`,观察状态确实从
|
||||
红灯切换到绿灯。
|
||||
|
||||
## 验证方式
|
||||
|
||||
```bash
|
||||
QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_state_machine
|
||||
```
|
||||
|
||||
## 练习方向(供任务卡引用)
|
||||
|
||||
- 加一个 `yellowState`(黄灯),改成红→绿→黄→红的三态循环,体会状态数量增加
|
||||
时 `QStateMachine` 写法的可读性优势;
|
||||
- 给某个状态加一个定时器转换:`redState->addTransition(timer, &QTimer::timeout, greenState)`,
|
||||
实现"红灯持续 3 秒后自动变绿灯"(不需要用户点击)。
|
||||
|
||||
## 参考
|
||||
|
||||
- 本仓库对照:[`p03/ch06/modal_dialog.cpp`](../../../../p03/ch06/modal_dialog.cpp)
|
||||
等对话框示例里也有"根据用户操作决定下一步界面"的朴素状态切换,可以对比
|
||||
"手写 if/else" 和 "QStateMachine 声明式写法" 的差异;
|
||||
- Qt 官方文档:*The State Machine Framework*(doc.qt.io,选读·需联网)
|
||||
@@ -0,0 +1,47 @@
|
||||
// ============================================================================
|
||||
// qt_state_machine —— 教学补充示例(非 wiki 生成,手工维护)
|
||||
// 用途:方案书「C++方向 Day4」要求的状态机实战(QStateMachine 属于 QtCore,
|
||||
// 不需要额外模块),仓库 p03/ 原有 wiki 抓取内容未覆盖这一点,故补一个
|
||||
// 最小示例:一个“红灯/绿灯”两态按钮,点击在两态间切换。
|
||||
// 配套文档:docs/teaching/examples/state_machine/README.md
|
||||
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_state_machine
|
||||
// ============================================================================
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPushButton>
|
||||
#include <QStateMachine>
|
||||
#include <QState>
|
||||
#include <QTimer>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QPushButton button;
|
||||
button.resize(140, 40);
|
||||
|
||||
QStateMachine machine;
|
||||
auto* redState = new QState(&machine);
|
||||
auto* greenState = new QState(&machine);
|
||||
|
||||
// 进入某个状态时执行的动作:改文本、改样式——状态机负责“流转”,
|
||||
// 具体每个状态“长什么样”交给 assignProperty/连接槽函数。
|
||||
redState->assignProperty(&button, "text", QStringLiteral("红灯:禁止通行"));
|
||||
redState->assignProperty(&button, "styleSheet", QStringLiteral("background-color:#c0392b;color:white;"));
|
||||
greenState->assignProperty(&button, "text", QStringLiteral("绿灯:允许通行"));
|
||||
greenState->assignProperty(&button, "styleSheet", QStringLiteral("background-color:#27ae60;color:white;"));
|
||||
|
||||
// 点击按钮触发状态切换:red --clicked--> green --clicked--> red ...
|
||||
redState->addTransition(&button, &QPushButton::clicked, greenState);
|
||||
greenState->addTransition(&button, &QPushButton::clicked, redState);
|
||||
|
||||
machine.setInitialState(redState);
|
||||
machine.start();
|
||||
|
||||
button.show();
|
||||
|
||||
// 离屏/自动化验证:100ms 后模拟一次点击(red -> green),
|
||||
// 再等 200ms 确认状态机正常处理完过渡后自动退出
|
||||
QTimer::singleShot(100, &button, [&button]() { button.click(); });
|
||||
QTimer::singleShot(300, &app, &QApplication::quit);
|
||||
return app.exec();
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
.tag-lecture { background: #2b6cb0; color: #fff; } /* 讲 */
|
||||
.tag-practice { background: #2f855a; color: #fff; } /* 练 */
|
||||
.tag-discuss { background: #b7791f; color: #fff; } /* 议:讨论型任务,非编码 */
|
||||
|
||||
.task-card {
|
||||
border-left: 6px solid #2f855a;
|
||||
@@ -31,6 +32,25 @@
|
||||
}
|
||||
.task-card h4 { margin: 0 0 6px 0; color: #2f855a; text-transform: none; font-size: 1.05em; }
|
||||
|
||||
.task-card dl.task-spec { margin: 8px 0 0 0; }
|
||||
.task-card dl.task-spec dt {
|
||||
font-weight: 700;
|
||||
color: #2f855a;
|
||||
font-size: 0.85em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.task-card dl.task-spec dt:first-child { margin-top: 0; }
|
||||
.task-card dl.task-spec dd { margin: 3px 0 0 0; }
|
||||
|
||||
.ext-ref { color: #a0522d; font-style: italic; }
|
||||
|
||||
/* 讨论型任务卡(Day1 模块A:无编码产出,用琥珀色区分“练”的绿色) */
|
||||
.task-card.discuss-card { border-left-color: #b7791f; background: #fffaf0; }
|
||||
.task-card.discuss-card h4 { color: #b7791f; }
|
||||
.task-card.discuss-card dl.task-spec dt { color: #b7791f; }
|
||||
|
||||
.filelink, code.filelink {
|
||||
font-family: 'Courier New', monospace;
|
||||
background: #eef2f7;
|
||||
|
||||
+152
-250
@@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Day 1 · C++ 对 C 的扩展 + 类和对象基础</title>
|
||||
<title>Day 1 · 开训 + C++ 回顾/工具链</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="reveal/reveal.css">
|
||||
<link rel="stylesheet" href="reveal/theme.css">
|
||||
@@ -15,161 +15,88 @@
|
||||
|
||||
<section>
|
||||
<h1>Day 1</h1>
|
||||
<h3>C++ 对 C 的扩展 + 类和对象基础</h3>
|
||||
<p class="module-badge">Qt 方向实训 · 第 1 / 4 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<h3>开训 + C++ 回顾/工具链</h3>
|
||||
<p class="module-badge">C++/Qt 方向实训 · 第 1 / 5 天 · 对齐官方方案书 Day1</p>
|
||||
<ul>
|
||||
<li>模块 A(3 课时):C++ 对 C 的扩展 —— 强化,非入门</li>
|
||||
<li>模块 B(3 课时):类和对象基础</li>
|
||||
<li>模块 A(3 课时):团队建设 / 敏捷 Scrum / C++ 在企业中的应用(讨论型,无编码)</li>
|
||||
<li>模块 B(3 课时):C++ 环境搭建 / 高级程序回顾 / CMake / Vcpkg(压缩讲解)</li>
|
||||
</ul>
|
||||
<p><small>大纲:<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
|
||||
<p><small>本教案假定学员已掌握 C++ 基础语法与 OOP 概念——方案书对今天下午的
|
||||
官方定位是"回顾和提升",不是零基础教学。今天开始的 5 天覆盖方案书 C++方向
|
||||
Day1–5 全部课堂内容,Day6–10 是纯项目实作。</small></p>
|
||||
<div class="verify-box" style="font-size:0.5em;">
|
||||
<h4>模块 B 开始前:10 分钟摸底测验</h4>
|
||||
<p>这条假设不是拍脑袋定的——建议模块 B 讲授前先用
|
||||
<a class="filelink" target="_blank" href="../PRETEST.md">docs/teaching/PRETEST.md</a>
|
||||
(10 题、10 分钟)实测一次,按结果决定原节奏推进还是回退到语法讲解。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 A:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 讲授 35min</p>
|
||||
<h2>C++ 对 C 的扩展 <span class="tag tag-lecture">讲</span></h2>
|
||||
<p>已默认你熟悉这些语法——今天讲「为什么」,不讲「怎么写」:</p>
|
||||
<h2>团队建设 / 敏捷 Scrum / C++ 企业应用 <span class="tag tag-lecture">讲</span></h2>
|
||||
<ul>
|
||||
<li>namespace 的作用域本质</li>
|
||||
<li>函数重载的决议机制</li>
|
||||
<li>const 的本质 vs #define</li>
|
||||
<li>引用的本质(语法糖视角)</li>
|
||||
<li>三目运算符可否作左值 —— 纯版本差异</li>
|
||||
<li>团队建设与文化、实训安排与考评要求</li>
|
||||
<li>项目开发过程管理全景(JIRA / Git / OASIS / Wiki)</li>
|
||||
<li>敏捷 Scrum 项目开发管理</li>
|
||||
<li>C++ 在企业中的应用</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p class="module-badge">强化点 1 / 5</p>
|
||||
<h3>namespace 为什么不能定义在函数体内 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
namespace A{
|
||||
int a = 10;
|
||||
}
|
||||
namespace B{
|
||||
int a = 20;
|
||||
}
|
||||
void test(){
|
||||
cout << "A::a : " << A::a << endl;
|
||||
cout << "B::a : " << B::a << endl;
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>作用域是「文件级」概念,不是「语句顺序」——wiki 原文把 namespace
|
||||
写进了函数体内,编译不过;出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch03/namespace_syntax.cpp">p01/ch03/namespace_syntax.cpp</a>,
|
||||
修正记录 <span class="filelink">ERRATA.md [58954439:3/6/7]</span></small></p>
|
||||
<h3>实习实训服务流程 <span class="tag tag-lecture">讲</span></h3>
|
||||
<img src="../../images/training_service_flow.png" style="max-height:480px;">
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a>
|
||||
「六、实习实训服务流程」——需求分析→需求评审→项目研发/代码实现→阶段评审→
|
||||
项目测试/交付,这就是接下来 Day6–10 项目实作要走的完整流程。</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p class="module-badge">强化点 2 / 5</p>
|
||||
<h3>函数重载:二义性从哪来 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
void MyFunc(string b){ cout << "b: " << b << endl; }
|
||||
// 函数重载碰上默认参数
|
||||
void MyFunc(string b, int a = 10){ cout << "a: " << a << " b:" << b << endl; }
|
||||
|
||||
int main(){
|
||||
// MyFunc("hello"); // 二义:两个重载同等匹配
|
||||
MyFunc("hello", 20); // 显式给第二参才能消歧义
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>重载决议只看「能否唯一确定最佳匹配」,默认参数不参与决议——
|
||||
出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch03/function_overload_basics_2.cpp">p01/ch03/function_overload_basics_2.cpp</a>,
|
||||
<span class="filelink">ERRATA.md [58954439:42] ambiguous-overload</span></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p class="module-badge">强化点 3 / 5</p>
|
||||
<h3>const 的本质 vs #define <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
const int constA = 10;
|
||||
int main(){
|
||||
int* p = (int*)&constA;
|
||||
*p = 200; // 绕过 const 的“建议性”检查——编译期常量 ≠ 只读内存
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>const 是「编译期类型检查」,不是「运行期内存保护」;#define 只是文本替换,
|
||||
没有类型、没有作用域。出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch03/const_diff_summary.cpp">p01/ch03/const_diff_summary.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p class="module-badge">强化点 4 / 5</p>
|
||||
<h3>引用的本质 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
void testFunc(int& ref){
|
||||
ref = 100; // 编译器内部转换为 *ref = 100
|
||||
}
|
||||
int main(){
|
||||
int a = 10;
|
||||
int& aRef = a; // 自动转换为 int* const aRef = &a —— 这也是引用必须初始化的原因
|
||||
aRef = 20; // 内部转换为 *aRef = 20
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>把引用当成「自动解引用的 const 指针」来理解,很多“为什么引用不能重新绑定”
|
||||
的疑惑会自然消失。出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch03/reference_essence.cpp">p01/ch03/reference_essence.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p class="module-badge">强化点 5 / 5 · 版本差异</p>
|
||||
<h3>三目运算符能否作左值 —— 纯版本问题 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
// C(p01/ch03/ternary_operator.c):三目表达式返回右值
|
||||
(a > b ? a : b) = 100; // C 中非法
|
||||
|
||||
// C++(p01/ch03/ternary_operator.cpp):三目表达式返回左值引用
|
||||
(a > b ? a : b) = 100; // C++ 中合法,b 会被改写
|
||||
</code></pre>
|
||||
<p><small>现场演示:<code class="filelink">tools/std_probe.py --lang c --file
|
||||
p01/ch03/ternary_operator.c --standards c89,c99,c11,c17 --pedantic-errors</code>
|
||||
—— 对照 <span class="filelink">docs/VERSION_NOTES.md §2</span></small></p>
|
||||
<h3>质量保证:四平台一标准 <span class="tag tag-lecture">讲</span></h3>
|
||||
<img src="../../images/quality_four_platforms.png" style="max-height:420px;">
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a>
|
||||
「七、质量保证」——JIRA(任务跟踪)+ Git(版本管理)+ Wiki 知识库 +
|
||||
OASIS 绿洲实训管理平台,是接下来项目实作阶段的四个协作工具。</small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 A:练 ===================== -->
|
||||
<!-- ===================== 模块 A:讨论(无编码) ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 练习 95min</p>
|
||||
<h2>动手:ch03 强化练习 <span class="tag tag-practice">练</span></h2>
|
||||
<p>目录:<a class="filelink" target="_blank" href="../../../p01/ch03">p01/ch03/</a>
|
||||
(47 个源文件,本节任务只挑一部分,其余留作参考自学)</p>
|
||||
<p class="module-badge">模块 A · 讨论 95min(分组,非编码)</p>
|
||||
<h2>动手:分组讨论 <span class="tag tag-discuss">议</span></h2>
|
||||
<p><small>今天上午没有编码练习——先把协作流程和项目方向定下来,Day6–10
|
||||
的项目实作会真的用到今天讨论的结论。</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · 逐一验证 <span class="tag tag-practice">练</span></h4>
|
||||
<p>从 <code class="filelink">p01/ch03</code> 挑 8–10 个文件(namespace / reference /
|
||||
overload / const 系列),逐个执行:</p>
|
||||
<pre><code class="bash" data-trim>tools/run.sh p1c03_03 # namespace_syntax
|
||||
tools/run.sh p1c03_33 # reference_essence
|
||||
tools/run.sh p1c03_43 # function_overload_basics_2
|
||||
# ……自行在 build/ 里用 ninja -t targets 或 cmake --build 找到其余目标名</code></pre>
|
||||
<p>对照 <code class="filelink">docs/ERRATA.md</code> 对应条目,写一句话解释
|
||||
「wiki 原文错在哪、现在为什么对」。</p>
|
||||
<div class="task-card discuss-card">
|
||||
<h4>任务卡 1 · 协作分工 <span class="tag tag-discuss">议</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>结合服务流程图,说清楚本组打算怎么分工使用 Jira(任务跟踪)+ Git(代码版本管理)</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>每组提交一句话分工说明(谁负责建 Jira 看板 / 谁负责 Git 仓库初始化),能在图上指出对应环节</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd>上一节"实习实训服务流程"图 ·
|
||||
<a class="filelink" target="_blank" href="../../TRAINING_PLAN_2026.md">TRAINING_PLAN_2026.md 「七、质量保证 (一)(二)」</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · 版本探针实操 <span class="tag tag-practice">练</span></h4>
|
||||
<pre><code class="bash" data-trim>tools/std_probe.py --lang c --file p01/ch03/ternary_operator.c \
|
||||
--standards c89,c99,c11,c17 --pedantic-errors</code></pre>
|
||||
<p>记录哪个标准报错、哪个不报错,对照
|
||||
<code class="filelink">docs/VERSION_NOTES.md §2</code> 的结论,理解“同一段代码
|
||||
在不同标准下含义不同”。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 3 · 拔高(选做) <span class="tag tag-practice">练</span></h4>
|
||||
<p>阅读 <code class="filelink">docs/ERRATA.md [58954439:42]</code>,解释生成脚本的
|
||||
去重逻辑为什么会把重载函数改名导致二义性——这是一次「工具误伤正确代码」的真实案例,
|
||||
训练读 diff、读生成器逻辑的能力。</p>
|
||||
<p><small>参考:<a class="filelink" target="_blank"
|
||||
href="../../../tools/gen_part1.py">tools/gen_part1.py</a> 中 <code>dedupe</code> 相关逻辑</small></p>
|
||||
<div class="task-card discuss-card">
|
||||
<h4>任务卡 2 · 选定项目方向 <span class="tag tag-discuss">议</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>选定实训期间打算做的项目方向(Linux C 方向,或 C++/Qt 方向的备选商业项目之一)</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>每组报出项目名称 + 一句话说明选择理由</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../TRAINING_PLAN_2026.md">TRAINING_PLAN_2026.md 「八、备选商业项目介绍」</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -177,107 +104,74 @@ tools/run.sh p1c03_43 # function_overload_basics_2
|
||||
<!-- ===================== 模块 B:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 讲授 35min</p>
|
||||
<h2>类和对象基础 <span class="tag tag-lecture">讲</span></h2>
|
||||
<ul>
|
||||
<li>封装:C 结构体 vs C++ 类</li>
|
||||
<li>构造 / 析构的调用时机</li>
|
||||
<li>深拷贝 vs 浅拷贝</li>
|
||||
<li>静态成员与单例</li>
|
||||
</ul>
|
||||
<p class="module-badge">模块 B · 讲授 35min(巡礼式,压缩讲解)</p>
|
||||
<h2>C++ 回顾 / CMake / Vcpkg <span class="tag tag-lecture">讲</span></h2>
|
||||
<p><small>只挑「学过但容易讲不透」的点,不逐章展开——方案书官方定位是
|
||||
"回顾和提升",深度让位于 Day2–5 的 Qt 内容。</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>封装:同一个问题,两种语言写法 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="c" data-trim data-noescape>
|
||||
// C:结构体没有访问控制,甚至类型都保护不了
|
||||
typedef struct _Person{ char name[64]; int age; } Person;
|
||||
typedef struct _Aninal{ char name[64]; int age; int type; } Ainmal;
|
||||
|
||||
void AnimalEat(Ainmal* animal){ printf("%s在吃动物吃的饭!\n", animal->name); }
|
||||
|
||||
int main(){
|
||||
Person person; strcpy(person.name, "小明"); person.age = 30;
|
||||
AnimalEat(&person); // 把 Person* 传给期望 Ainmal* 的函数——编译器毫无察觉
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>对照:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s01/class_encapsulation.c">class_encapsulation.c</a> vs
|
||||
<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s01/class_encapsulation.cpp">class_encapsulation.cpp</a>
|
||||
(public/protected/private 三级访问控制)</small></p>
|
||||
<h3>namespace 作用域 <span class="tag tag-lecture">讲</span></h3>
|
||||
<p>"作用域"不是"文件顺序"——很多人误以为 namespace 只是给名字加前缀,
|
||||
实际上它定义的是一段可以被跨文件延续、跨块合并的作用域。</p>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch03/namespace_syntax.cpp">p01/ch03/namespace_syntax.cpp</a>,
|
||||
对照 <a class="filelink" target="_blank" href="../../ERRATA.md">docs/ERRATA.md</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>构造 / 析构的调用时机 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>函数重载决议 <span class="tag tag-lecture">讲</span></h3>
|
||||
<p>编译器靠"名字改写"(name mangling)把重载函数变成不同的符号;二义性
|
||||
往往不是代码写错了,而是重载集合里存在两个"一样好"的候选。</p>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch03/function_overload_mechanism.cpp">p01/ch03/function_overload_mechanism.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>引用的本质 <span class="tag tag-lecture">讲</span></h3>
|
||||
<p>把引用理解成"自动解引用的 const 指针"——这个实现视角能解释引用为什么
|
||||
必须初始化、为什么不能重新绑定。</p>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch03/reference_essence.cpp">p01/ch03/reference_essence.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>虚析构与内存泄漏 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Person{
|
||||
public:
|
||||
Person(){
|
||||
cout << "构造函数调用!" << endl;
|
||||
pName = (char*)malloc(sizeof("John"));
|
||||
strcpy(pName, "John");
|
||||
}
|
||||
~Person(){
|
||||
cout << "析构函数调用!" << endl;
|
||||
if (pName != NULL){ free(pName); pName = NULL; }
|
||||
}
|
||||
private:
|
||||
char* pName;
|
||||
};
|
||||
</code></pre>
|
||||
<p><small>强化点:wiki 原文这一串例子普遍漏写 <code>#include <cstring></code>
|
||||
(用到 strcpy 却不声明)——出处 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s03/ctor_and_dtor.cpp">p01/ch04/s03/ctor_and_dtor.cpp</a>,
|
||||
<code class="filelink">ERRATA.md [58954451:0..13] missing-include</code>:编译器不报错,
|
||||
但换个平台/换个头文件包含顺序就可能炸——这类隐式依赖正是“强化”该抓的细节。</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>深拷贝 vs 浅拷贝 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Person{
|
||||
public:
|
||||
Person(char* name, int age){
|
||||
pName = (char*)malloc(strlen(name) + 1);
|
||||
strcpy(pName, name);
|
||||
}
|
||||
// 自己实现拷贝构造函数 —— 深拷贝
|
||||
Person(const Person& person){
|
||||
pName = (char*)malloc(strlen(person.pName) + 1);
|
||||
strcpy(pName, person.pName);
|
||||
}
|
||||
~Person(){ if (pName != NULL) free(pName); }
|
||||
private:
|
||||
char* pName;
|
||||
};
|
||||
void test(){
|
||||
Person p1("Edward", 30);
|
||||
Person p2 = p1; // 不写拷贝构造 → 默认浅拷贝 → 两个指针指向同一块内存 → 析构两次 double free
|
||||
}
|
||||
Base* p = new Derived();
|
||||
delete p; // 如果 ~Base() 不是 virtual,~Derived() 不会被调用——经典泄漏
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s03/deep_vs_shallow_copy.cpp">p01/ch04/s03/deep_vs_shallow_copy.cpp</a></small></p>
|
||||
href="../../../p01/ch04/s08/virtual_dtor_purpose.cpp">p01/ch04/s08/virtual_dtor_purpose.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>静态成员与单例 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Printer{
|
||||
public:
|
||||
static Printer* getInstance(){ return pPrinter; }
|
||||
void PrintText(string text){ cout << text << endl; mTimes++; }
|
||||
private:
|
||||
Printer(){ mTimes = 0; }
|
||||
Printer(const Printer&){} // 禁止拷贝
|
||||
private:
|
||||
static Printer* pPrinter;
|
||||
int mTimes;
|
||||
};
|
||||
Printer* Printer::pPrinter = new Printer; // 类外初始化静态成员
|
||||
</code></pre>
|
||||
<h3>模板与 STL 速览 <span class="tag tag-lecture">讲</span></h3>
|
||||
<p>只点名,不展开:泛型编程(函数模板/类模板)+ 容器/迭代器,是 Day6–10
|
||||
项目实作里会大量用到的基础设施。</p>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s03/static_singleton.cpp">p01/ch04/s03/static_singleton.cpp</a></small></p>
|
||||
href="../../../p01/ch05/class_template_basics.cpp">p01/ch05/class_template_basics.cpp</a>、
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch09/s03/vector_iterator.cpp">p01/ch09/s03/vector_iterator.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>CMake:用本仓库当教材 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="bash" data-trim>cmake -S . -B build -G Ninja -DBUILD_QT_PART=OFF -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build # 144 个目标全绿</code></pre>
|
||||
<p><small>出处:仓库根 <a class="filelink" target="_blank" href="../../../CMakeLists.txt">CMakeLists.txt</a>——
|
||||
<code>option(BUILD_QT_PART)</code> 控制是否编译 Qt 部分,每个示例一个
|
||||
<code>add_executable</code> 目标,这就是"一文件一目标"约定的由来。</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Vcpkg 包管理器(概念,无演示) <span class="tag tag-lecture">讲</span></h3>
|
||||
<p>C++ 生态的包管理器,解决"第三方库怎么装、怎么被 CMake 找到"的问题。</p>
|
||||
<div class="verify-box">
|
||||
<h4>缺口说明</h4>
|
||||
<p>本仓库用的是"隔离 Qt 手动装"(<code>tools/install_qt_host.sh</code>)
|
||||
而非 vcpkg,两种思路都要认识;本仓库暂无 vcpkg 可运行示例——安装真实
|
||||
vcpkg 工具链超出最小示例范畴,此处仅作概念介绍。</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -285,37 +179,50 @@ Printer* Printer::pPrinter = new Printer; // 类外初始化静态成员
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 练习 95min</p>
|
||||
<h2>动手:类和对象 <span class="tag tag-practice">练</span></h2>
|
||||
<h2>动手:走一遍真实构建流程 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · 扩展一个类 <span class="tag tag-practice">练</span></h4>
|
||||
<p>以 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s02/point_and_circle.cpp">point_and_circle.cpp</a> 或
|
||||
<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s02/cube_class_design.cpp">cube_class_design.cpp</a> 为起点,
|
||||
新增一个成员函数(如计算面积 / 体积),编译运行验证输出正确。</p>
|
||||
<h4>任务卡 1 · 走一遍真实构建流程 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>独立完成本仓库 Part1 的配置+构建,不看着教师操作</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd><code>cmake -S . -B build -G Ninja -DBUILD_QT_PART=OFF</code> 配置成功;
|
||||
<code>cmake --build build</code> 144 个目标全绿;能说出 <code>option(BUILD_QT_PART)</code> 的作用</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../CMakeLists.txt">仓库根 CMakeLists.txt</a> ·
|
||||
<span class="ext-ref">CMake 官方文档 Tutorial(选读·需联网)</span></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · 复现并修复浅拷贝崩溃 <span class="tag tag-practice">练</span></h4>
|
||||
<p>修改 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s03/deep_vs_shallow_copy.cpp">deep_vs_shallow_copy.cpp</a>:
|
||||
故意删掉拷贝构造函数触发浅拷贝,用 <code class="filelink">tools/run.sh</code>
|
||||
观察 double free / 崩溃或异常输出,再恢复深拷贝对比。</p>
|
||||
<h4>任务卡 2 · 修一个真实的坑 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>从 <a class="filelink" target="_blank" href="../../ERRATA.md">docs/ERRATA.md</a>
|
||||
挑 2 条记录,理解"wiki 原文错在哪、现在的写法为什么对"</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>能用一句话向同组同学解释清楚,不能只念代码</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../ERRATA.md">docs/ERRATA.md</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 3 · 独立实现单例 <span class="tag tag-practice">练</span></h4>
|
||||
<p>参照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s03/static_singleton.cpp">static_singleton.cpp</a>,
|
||||
独立写一个单例类(拷贝构造设为私有 / delete,验证多次 <code>getInstance()</code>
|
||||
返回同一地址)。</p>
|
||||
<h4>任务卡 3(选做)· 版本探针 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>用 <code>tools/std_probe.py</code> 验证一个标准版本差异点(如三目运算符可否作左值)</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>跑出至少一个"该标准报错、另一个标准不报错"的对比结果,对照
|
||||
<a class="filelink" target="_blank" href="../../VERSION_NOTES.md">docs/VERSION_NOTES.md</a> §2</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../tools/std_probe.py">tools/std_probe.py</a> ·
|
||||
<a class="filelink" target="_blank" href="../../VERSION_NOTES.md">docs/VERSION_NOTES.md</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -324,14 +231,13 @@ Printer* Printer::pPrinter = new Printer; // 类外初始化静态成员
|
||||
<section>
|
||||
<h2>当日产出验证</h2>
|
||||
<div class="verify-box">
|
||||
<h4>验收标准</h4>
|
||||
<pre><code class="bash" data-trim>cmake --build build --target all_p01_ch03
|
||||
cmake --build build --target all_p01_ch04_s03</code></pre>
|
||||
<p>全绿;至少完成 1 个「浅拷贝崩溃复现 + 修复对比」。</p>
|
||||
<h4>验收</h4>
|
||||
<pre><code class="bash" data-trim>cmake --build build --target all # Part1,144 个目标全绿</code></pre>
|
||||
<p>每组提交一句话分工说明 + 至少 1 条 ERRATA 坑点讲解。</p>
|
||||
</div>
|
||||
<p class="footer-note"><small>明日预告:Day 2 —— OOP 进阶(对象模型 / 友元 /
|
||||
运算符重载 / 继承 / 多态)</small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a></p>
|
||||
<p class="footer-note"><small>明天 Day2 进入 Qt:项目结构 / 信号槽 / 控件 / 布局与 QSS ——
|
||||
大纲:<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a> · <a class="filelink" href="day2.html">下一天 →</a></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@@ -341,11 +247,7 @@ cmake --build build --target all_p01_ch04_s03</code></pre>
|
||||
<script src="reveal/plugin/highlight/highlight.js"></script>
|
||||
<script>
|
||||
Reveal.initialize({
|
||||
hash: true,
|
||||
controls: true,
|
||||
progress: true,
|
||||
center: true,
|
||||
slideNumber: true,
|
||||
hash: true, controls: true, progress: true, center: true, slideNumber: true,
|
||||
plugins: [ RevealHighlight ]
|
||||
});
|
||||
</script>
|
||||
|
||||
+161
-195
@@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Day 2 · OOP 进阶</title>
|
||||
<title>Day 2 · Qt 开发环境/信号槽/基础控件/布局与 QSS</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="reveal/reveal.css">
|
||||
<link rel="stylesheet" href="reveal/theme.css">
|
||||
@@ -15,96 +15,89 @@
|
||||
|
||||
<section>
|
||||
<h1>Day 2</h1>
|
||||
<h3>OOP 进阶</h3>
|
||||
<p class="module-badge">Qt 方向实训 · 第 2 / 4 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<h3>Qt 开发环境 / 信号槽 / 基础控件 / 布局与 QSS</h3>
|
||||
<p class="module-badge">C++/Qt 方向实训 · 第 2 / 5 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<ul>
|
||||
<li>模块 A(3 课时):对象模型 / 友元 / 运算符重载</li>
|
||||
<li>模块 B(3 课时):继承与多态</li>
|
||||
<li>模块 A(3 课时):项目结构 / 第一个程序 / 信号槽</li>
|
||||
<li>模块 B(3 课时):常用控件 / 布局管理 / QSS 界面美化 → 计算器案例</li>
|
||||
</ul>
|
||||
<p><small>回顾:Day1 已建立类/构造析构/深浅拷贝的直觉——今天在这个基础上深挖机制</small></p>
|
||||
<div class="verify-box" style="font-size:0.5em;">
|
||||
<h4>开始前准备(一次性)</h4>
|
||||
<pre><code class="bash" data-trim>tools/install_qt_host.sh
|
||||
cmake -S . -B build -G Ninja -DBUILD_QT_PART=ON \
|
||||
-DCMAKE_PREFIX_PATH=$PWD/.qt514/5.14.2/gcc_64 -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 A:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 讲授 35min</p>
|
||||
<h2>对象模型 / 友元 / 运算符重载 <span class="tag tag-lecture">讲</span></h2>
|
||||
<h2>项目结构 / 信号槽 <span class="tag tag-lecture">讲</span></h2>
|
||||
<ul>
|
||||
<li>this 指针到底解决什么问题</li>
|
||||
<li>友元:突破封装的代价与取舍</li>
|
||||
<li>运算符重载:=、==、[]、()、++/-- 全景</li>
|
||||
<li>最小 Qt 程序结构</li>
|
||||
<li>控件创建与显示</li>
|
||||
<li>内置 / 自定义 / lambda 信号槽</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>this 指针:区分同名 + 隐式参数 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>最小 Qt 程序:QApplication + exec() <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Person{
|
||||
class Widget : public QWidget {
|
||||
Q_OBJECT // 用到信号槽/属性系统的类都要有这个宏(触发 moc 代码生成)
|
||||
public:
|
||||
Person(string name, int age){
|
||||
this->name = name; // 形参名与成员名相同,靠 this-> 区分
|
||||
this->age = age;
|
||||
}
|
||||
// 成员函数隐含的第一个参数就是 this —— operator= 也是这样实现的
|
||||
Person PersonPlusPerson(Person& person){
|
||||
string newname = this->name + person.name;
|
||||
int newage = this->age + person.age;
|
||||
return Person(newname, newage);
|
||||
}
|
||||
Widget(QWidget* parent = nullptr) : QWidget(parent) {}
|
||||
};
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s04/this_pointer.cpp">p01/ch04/s04/this_pointer.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>友元:全局函数 / 成员函数 / 整个类 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Building{
|
||||
friend void CleanBedRoom(Building& building); // 友元全局函数
|
||||
friend class MyFriend; // 友元类——MyFriend 的所有成员都能访问私有区
|
||||
public:
|
||||
Building();
|
||||
private:
|
||||
string mBedroom;
|
||||
};
|
||||
</code></pre>
|
||||
<p><small>友元是「单向」「不可传递」「不可继承」的特权授予——用得越多,封装边界越模糊,
|
||||
权衡取舍要讲清楚。出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s05/friend_syntax.cpp">p01/ch04/s05/friend_syntax.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>赋值运算符重载:与拷贝构造的分工 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
Person2& operator=(const Person2& person){
|
||||
// 关键:当前对象已存在,pName 可能已指向堆内存——必须先释放,否则内存泄漏
|
||||
if (this->pName != NULL){ delete[] this->pName; }
|
||||
this->pName = new char[strlen(person.pName) + 1];
|
||||
strcpy(this->pName, person.pName);
|
||||
return *this; // 返回引用才能支持 a = b = c 连续赋值
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>“初始化用拷贝构造,已存在对象再赋值用 operator=”——这条分界线是本节强化重点。
|
||||
出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s06/overload_assignment.cpp">p01/ch04/s06/overload_assignment.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>前置/后置 ++ 的重载差异 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
Complex& operator++(){ // 前置++:改自身,返回引用
|
||||
mA++; mB++;
|
||||
return *this;
|
||||
}
|
||||
Complex operator++(int){ // 后置++:占位 int 参数只是为了和前置区分重载签名
|
||||
Complex temp = *this; // 必须先拷贝旧值
|
||||
mA++; mB++;
|
||||
return temp; // 返回值(而非引用)——所以后置++开销更大
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv); // 整个程序唯一的 QApplication
|
||||
Widget w;
|
||||
w.show();
|
||||
return app.exec(); // 进入事件循环,程序在这里“活着”
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s06/overload_increment_decrement.cpp">p01/ch04/s06/overload_increment_decrement.cpp</a></small></p>
|
||||
href="../../../p03/ch02/minimal_qt_app.cpp">p03/ch02/minimal_qt_app.cpp</a>(注意仓库版本额外注入了
|
||||
<code>QTimer::singleShot</code> 200ms 自动退出,便于离屏自动化验证,真实项目不需要这一行)</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>控件创建:两种写法 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
// 第一种:先构造再设属性
|
||||
QPushButton* btn = new QPushButton;
|
||||
btn->setParent(this);
|
||||
btn->setText(QStringLiteral("德玛西亚"));
|
||||
btn->move(100, 100);
|
||||
|
||||
// 第二种:构造时指定文本与父亲
|
||||
QPushButton* btn2 = new QPushButton(QStringLiteral("孙悟空"), this);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch03/button_creation.cpp">p03/ch03/button_creation.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>信号槽:内置 / 自定义 / 重载消歧义 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Teacher : public QObject {
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void hungry(); // 无参信号
|
||||
void hungry(QString food); // 带参信号(重载)
|
||||
public slots:
|
||||
void classIsOver(){ emit hungry(); emit hungry(QString("汉堡")); }
|
||||
};
|
||||
// 连接重载信号时必须用 QOverload 显式指定版本
|
||||
connect(t, QOverload<>::of(&Teacher::hungry), s, QOverload<>::of(&Student::treat));
|
||||
connect(t, QOverload<QString>::of(&Teacher::hungry), s, QOverload<QString>::of(&Student::treat));
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch04/custom_signal_slot.cpp">p03/ch04/custom_signal_slot.cpp</a>;
|
||||
lambda 槽 + 值捕获/引用捕获对比见
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch04/lambda_signal_slot.cpp">lambda_signal_slot.cpp</a></small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -112,32 +105,50 @@ Complex operator++(int){ // 后置++:占位 int 参数只是为了和前
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 练习 95min</p>
|
||||
<h2>动手:运算符重载 <span class="tag tag-practice">练</span></h2>
|
||||
<h2>动手:第一个 Qt 程序 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · operator= + operator== <span class="tag tag-practice">练</span></h4>
|
||||
<p>给 Day1 写的类(或新写一个含指针成员的类)实现 <code>operator=</code>(注意先
|
||||
释放旧资源)与 <code>operator==</code>,参照
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s06/overload_assignment.cpp">overload_assignment.cpp</a>、
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s06/overload_equality.cpp">overload_equality.cpp</a>。</p>
|
||||
<h4>任务卡 1 · 跑通最小程序 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>编译运行 <code>minimal_qt_app</code> 与 <code>button_creation</code></dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd><code>QT_QPA_PLATFORM=offscreen tools/run_qt.sh minimal_qt_app</code> 与
|
||||
<code>button_creation</code> 均退出码为 0,无崩溃</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch02/minimal_qt_app.cpp">p03/ch02/minimal_qt_app.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch03/button_creation.cpp">p03/ch03/button_creation.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · operator[] 边界检查 <span class="tag tag-practice">练</span></h4>
|
||||
<p>实现一个重载了 <code>operator[]</code> 的简单容器类(固定大小数组包装),
|
||||
越界访问要有明确行为(断言或抛异常),参照
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s06/overload_pointer_operator.cpp">overload_pointer_operator.cpp</a> 的思路。</p>
|
||||
<h4>任务卡 2 · 自定义信号槽改文本 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>给一个按钮连接自定义信号槽:点击后改变一个 <code>QLabel</code> 的文本内容</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>编译通过;离屏或有 X 环境下点击/模拟点击后 <code>QLabel</code> 文本确实改变;
|
||||
用 <code>connect</code> 语法而非直接函数调用</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch04/custom_signal_slot.cpp">custom_signal_slot.cpp</a>(主参考)·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch04/lambda_signal_slot.cpp">lambda_signal_slot.cpp</a>(lambda 槽写法对比)</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 3 · 选做:友元实现 operator<< <span class="tag tag-practice">练</span></h4>
|
||||
<p>参照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s06/overload_with_friend.cpp">overload_with_friend.cpp</a>,
|
||||
为你的类实现 <code>friend ostream& operator<<</code>,讨论「为什么流输出运算符
|
||||
通常不能写成成员函数」(提示:左操作数是 ostream,不是本类对象)。</p>
|
||||
<h4>任务卡 3(选做)· 重载信号消歧义 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>仿 <code>builtin_signal_slot.cpp</code>,给一个类定义两个同名不同参的信号,用
|
||||
<code>QOverload</code> 消歧义连接</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>两个重载信号都能各自触发对应的槽</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch04/builtin_signal_slot.cpp">p03/ch04/builtin_signal_slot.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -146,137 +157,91 @@ Complex operator++(int){ // 后置++:占位 int 参数只是为了和前
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 讲授 35min</p>
|
||||
<h2>继承与多态 <span class="tag tag-lecture">讲</span></h2>
|
||||
<ul>
|
||||
<li>继承中构造/析构的顺序</li>
|
||||
<li>同名成员遮蔽 vs 重写</li>
|
||||
<li>虚继承解决菱形问题</li>
|
||||
<li>upcast 裁切问题 → virtual 解决</li>
|
||||
<li>纯虚函数/抽象类、虚析构的意义</li>
|
||||
</ul>
|
||||
<h2>常用控件 / 布局管理 / QSS 美化 <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>构造先基类后派生类,析构相反 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>常用控件:QLabel 显示图片 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class A{ public: A(){cout<<"A构造\n";} ~A(){cout<<"A析构\n";} };
|
||||
class B : public A{ public: B(){cout<<"B构造\n";} ~B(){cout<<"B析构\n";} };
|
||||
class C : public B{ public: C(){cout<<"C构造\n";} ~C(){cout<<"C析构\n";} };
|
||||
|
||||
int main(){ C c; }
|
||||
// 输出顺序:A构造 → B构造 → C构造 → C析构 → B析构 → A析构
|
||||
QPixmap pixmap;
|
||||
pixmap.load(":/Image/boat.jpg"); // 从 Qt 资源系统(.qrc)加载
|
||||
QLabel* label = new QLabel(this);
|
||||
label->setPixmap(pixmap);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/inheritance_ctor_dtor_order.cpp">p01/ch04/s07/inheritance_ctor_dtor_order.cpp</a></small></p>
|
||||
href="../../../p03/ch08/label_pixmap.cpp">p03/ch08/label_pixmap.cpp</a>;
|
||||
HTML 富文本标签见 <a class="filelink" target="_blank" href="../../../p03/ch08/label_text_html.cpp">label_text_html.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>同名成员:默认遮蔽,需类名限定 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>自定义控件与布局:QHBoxLayout <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Base{ public: Base():mParam(0){} int mParam; };
|
||||
class Derived : public Base{
|
||||
public:
|
||||
Derived():mParam(10){}
|
||||
void Print(){
|
||||
cout << Base::mParam << endl; // 必须类名限定才能访问被遮蔽的基类成员
|
||||
cout << mParam << endl; // 默认访问派生类自己的
|
||||
}
|
||||
int mParam;
|
||||
};
|
||||
QSpinBox* spin = new QSpinBox(this);
|
||||
QSlider* slider = new QSlider(Qt::Horizontal, this);
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
layout->addWidget(spin);
|
||||
layout->addWidget(slider);
|
||||
// spin 变化 → slider 跟随,slider 变化 → spin 跟随
|
||||
connect(spin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), slider, &QSlider::setValue);
|
||||
connect(slider, &QSlider::valueChanged, spin, &QSpinBox::setValue);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/same_name_members.cpp">p01/ch04/s07/same_name_members.cpp</a></small></p>
|
||||
href="../../../p03/ch08/custom_widget.cpp">p03/ch08/custom_widget.cpp</a>——布局管理器负责排布,
|
||||
不用手算控件坐标。</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>菱形继承 → 虚继承 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>QSS 界面美化(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class BigBase{ public: int mParam; };
|
||||
class Base1 : public BigBase{};
|
||||
class Base2 : public BigBase{};
|
||||
class Derived : public Base1, public Base2{};
|
||||
// derived.mParam 二义——两份 BigBase 子对象;须写 derived.Base1::mParam 消歧义
|
||||
// 虚继承(public virtual BigBase)能让两条路径共享同一份 BigBase 子对象
|
||||
okBtn->setObjectName("okButton");
|
||||
setStyleSheet(
|
||||
"QPushButton#okButton { background-color: #2f855a; border-radius: 6px; color: white; }"
|
||||
"QPushButton#okButton:hover { background-color: #276749; }"
|
||||
);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/diamond_virtual_inheritance.cpp">diamond_virtual_inheritance.cpp</a>
|
||||
及 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/diamond_virtual_inheritance_2.cpp">_2.cpp</a>(虚继承版本对比)</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>upcast 裁切问题:不加 virtual 就没有多态 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
// 问题版:Animal::speak_1 不是 virtual
|
||||
class Animal{ public: void speak_1(){ cout<<"动物在唱歌...\n"; } };
|
||||
class Dog : public Animal{ public: void speak_1(){ cout<<"小狗在唱歌...\n"; } };
|
||||
void DoBussiness(Animal& animal){ animal.speak_1(); }
|
||||
void test(){ Dog dog; DoBussiness(dog); } // 输出「动物在唱歌」——静态绑定,不是想要的结果
|
||||
|
||||
// 解决版:都加 virtual → 动态绑定,输出「小狗在唱歌」
|
||||
</code></pre>
|
||||
<p><small>问题版出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s08/upcast_problem.cpp">upcast_problem.cpp</a>;解决版:
|
||||
<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s08/virtual_function_solution.cpp">virtual_function_solution.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>纯虚函数、抽象类、虚析构的意义 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class People{
|
||||
public:
|
||||
virtual void showName() = 0; // 纯虚函数 → People 是抽象类,不能实例化
|
||||
virtual ~People(){ cout<<"析构 People\n"; } // 虚析构:基类指针 delete 派生类对象时能正确调用派生类析构
|
||||
};
|
||||
class Worker : public People{
|
||||
public:
|
||||
virtual void showName(){ /* ... */ }
|
||||
~Worker(){ cout<<"析构 Worker\n"; delete[] pName; }
|
||||
private:
|
||||
char* pName;
|
||||
};
|
||||
People* p = new Worker;
|
||||
delete p; // 若 ~People() 不是 virtual,只会调用 ~People,Worker::pName 泄漏
|
||||
</code></pre>
|
||||
<p><small>强化点:<code class="filelink">ERRATA.md [58954469:5] abstract-instantiation</code>
|
||||
——wiki 原文一度把带纯虚析构的类直接实例化,这是编译错误。出处:
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s08/pure_virtual_dtor.cpp">pure_virtual_dtor.cpp</a>、
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch04/s08/virtual_dtor_purpose.cpp">virtual_dtor_purpose.cpp</a></small></p>
|
||||
href="../examples/qss_styling/qt_qss_styling.cpp">docs/teaching/examples/qss_styling/qt_qss_styling.cpp</a>
|
||||
(<a class="filelink" target="_blank" href="../examples/qss_styling/README.md">配套 README</a>)——
|
||||
仓库 <code>p03/</code> 的 wiki 抓取内容没有 QSS,这是本轮补写的教学补充示例,已离屏验证跑通。</small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:练 ===================== -->
|
||||
<!-- ===================== 模块 B:练(计算器案例) ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 练习 95min</p>
|
||||
<h2>动手:继承与多态 <span class="tag tag-practice">练</span></h2>
|
||||
<p class="module-badge">模块 B · 练习 95min(计算器案例,练习任务,非预建方案)</p>
|
||||
<h2>动手:做一个计算器 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · 复现并修复 upcast 裁切 <span class="tag tag-practice">练</span></h4>
|
||||
<p>先跑 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s08/upcast_problem.cpp">upcast_problem.cpp</a> 观察输出,
|
||||
再手动加 <code>virtual</code> 关键字重新编译,对比两次输出差异,写清楚“静态绑定 vs 动态绑定”
|
||||
各自发生在什么时候。</p>
|
||||
<h4>任务卡 1 · 计算器雏形 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>组合 <code>button_creation.cpp</code>(数字/运算符按钮)+ <code>custom_signal_slot.cpp</code>
|
||||
(点击信号槽)+ <code>custom_widget.cpp</code>(布局排布按钮),做出一个能加减乘除的最小计算器</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>至少支持两个数 + 一种运算符的计算并在 <code>QLabel</code>/<code>QLineEdit</code> 上正确显示结果;
|
||||
界面不重叠、按钮可点击</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch03/button_creation.cpp">button_creation.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch04/custom_signal_slot.cpp">custom_signal_slot.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch08/custom_widget.cpp">custom_widget.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · 基类指针管理派生类对象 <span class="tag tag-practice">练</span></h4>
|
||||
<p>设计一个含虚析构的基类 + 2 个派生类(各自持有一个堆内存成员),用基类指针数组
|
||||
管理多个对象,逐个 <code>delete</code>,通过析构函数里的打印验证:(1) 调用的是派生类
|
||||
析构 (2) 没有内存泄漏(可用打印计数或 valgrind 验证)。</p>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 3 · 选做:普通继承 vs 虚继承对比 <span class="tag tag-practice">练</span></h4>
|
||||
<p>对照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/diamond_virtual_inheritance.cpp">diamond_virtual_inheritance.cpp</a>
|
||||
与 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch04/s07/diamond_virtual_inheritance_2.cpp">_2.cpp</a>,
|
||||
打印 <code>sizeof(Derived)</code> 并解释两种继承方式下对象体积差异的原因。</p>
|
||||
<h4>任务卡 2 · 用 QSS 美化计算器 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>给任务卡 1 的计算器套一份 QSS 样式(数字键/运算键/等号键区分颜色)</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>至少 2 类按钮有不同的 <code>background-color</code>;至少 1 个按钮有
|
||||
<code>:hover</code> 或 <code>:pressed</code> 效果</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../examples/qss_styling/qt_qss_styling.cpp">qt_qss_styling.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../examples/qss_styling/README.md">README</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -285,15 +250,16 @@ delete p; // 若 ~People() 不是 virtual,只会调用 ~People,Worker::pNa
|
||||
<section>
|
||||
<h2>当日产出验证</h2>
|
||||
<div class="verify-box">
|
||||
<h4>验收标准</h4>
|
||||
<pre><code class="bash" data-trim>cmake --build build --target all_p01_ch04_s06
|
||||
cmake --build build --target all_p01_ch04_s07
|
||||
cmake --build build --target all_p01_ch04_s08</code></pre>
|
||||
<p>全绿;产出「基类指针管理派生类对象、正确释放」的可运行小程序。</p>
|
||||
<h4>现场演示</h4>
|
||||
<pre><code class="bash" data-trim>QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
|
||||
# 有 X 环境:去掉 QT_QPA_PLATFORM=offscreen 直接看窗口</code></pre>
|
||||
<p>现场演示计算器,至少完成一次正确运算 + QSS 美化。</p>
|
||||
</div>
|
||||
<p class="footer-note"><small>明日预告:Day 3 —— 泛型与标准库(模板 / 类型转换 /
|
||||
异常 / IO 流 / STL)</small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a></p>
|
||||
<p class="footer-note"><small>明天 Day3:高级控件 Model/View / 事件机制 / 文件 IO 与 JSON ——
|
||||
大纲:<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a> ·
|
||||
<a class="filelink" href="day1.html">← 上一天</a> ·
|
||||
<a class="filelink" href="day3.html">下一天 →</a></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
+129
-160
@@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Day 3 · 泛型与标准库</title>
|
||||
<title>Day 3 · 高级控件 Model/View / 事件机制 / 文件IO与JSON</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="reveal/reveal.css">
|
||||
<link rel="stylesheet" href="reveal/theme.css">
|
||||
@@ -15,11 +15,11 @@
|
||||
|
||||
<section>
|
||||
<h1>Day 3</h1>
|
||||
<h3>泛型与标准库</h3>
|
||||
<p class="module-badge">Qt 方向实训 · 第 3 / 4 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<h3>高级控件 Model/View / 事件机制 / 文件 IO 与 JSON</h3>
|
||||
<p class="module-badge">C++/Qt 方向实训 · 第 3 / 5 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<ul>
|
||||
<li>模块 A(3 课时):模板 + 类型转换</li>
|
||||
<li>模块 B(3 课时):异常 + IO 流 + STL</li>
|
||||
<li>模块 A(3 课时):Model/View 架构 / 事件机制</li>
|
||||
<li>模块 B(3 课时):文件 IO / JSON 解析 → 本地数据存储案例</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -27,49 +27,55 @@
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 讲授 35min</p>
|
||||
<h2>模板 + 类型转换 <span class="tag tag-lecture">讲</span></h2>
|
||||
<ul>
|
||||
<li>函数模板 vs 类模板</li>
|
||||
<li>模板的局限性</li>
|
||||
<li>四种显式类型转换(static / dynamic / const_cast)</li>
|
||||
</ul>
|
||||
<h2>Model/View 架构 / 事件机制 <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>类模板:类型不能自动推导 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>Model/View 模型视图架构(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
template<class NameType, class AgeType>
|
||||
class Person{
|
||||
// 模型:只管数据
|
||||
auto* model = new QStandardItemModel(this);
|
||||
model->appendRow(new QStandardItem(QStringLiteral("需求分析")));
|
||||
|
||||
// 视图:只管展示,不持有数据
|
||||
auto* view = new QListView(this);
|
||||
view->setModel(model);
|
||||
|
||||
// 选中状态变化 → 通知外部,模型和视图之间没有直接耦合
|
||||
connect(view->selectionModel(), &QItemSelectionModel::currentChanged, ...);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../examples/model_view/qt_model_view_basics.cpp">docs/teaching/examples/model_view/qt_model_view_basics.cpp</a>
|
||||
(<a class="filelink" target="_blank" href="../examples/model_view/README.md">配套 README</a>)——
|
||||
仓库 <code>p03/</code> 的 wiki 抓取内容没有 Model/View,这是本轮补写的教学补充示例,已离屏验证跑通。</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>事件循环机制(复用 Day2 概念) <span class="tag tag-lecture">讲</span></h3>
|
||||
<p><code>app.exec()</code> 进入的就是事件循环——鼠标点击、键盘输入、定时器
|
||||
触发都会变成事件,被塞进这个循环里依次分发。今天讲的"事件过滤器"就是在
|
||||
事件被分发给控件之前,插一个"拦截点"。</p>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch02/minimal_qt_app.cpp">p03/ch02/minimal_qt_app.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>自定义事件处理 / 事件过滤器 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class EventLabel : public QLabel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Person(NameType name, AgeType age){ mName = name; mAge = age; }
|
||||
void showPerson(){ cout << mName << " " << mAge << endl; }
|
||||
NameType mName; AgeType mAge;
|
||||
explicit EventLabel(QWidget* p=nullptr):QLabel(p){ setMouseTracking(true); }
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent* event) override {
|
||||
setText(QString("Move: (%1, %2)").arg(event->x()).arg(event->y()));
|
||||
}
|
||||
};
|
||||
|
||||
// Person P1("德玛西亚", 18); // 非法——类模板不支持类型自动推导
|
||||
Person<string, int> P1("德玛西亚", 18); // 必须显式写出模板实参
|
||||
</code></pre>
|
||||
<p><small>与函数模板最大的区别:函数模板能从实参推导类型,类模板不能——这是常见误用点。
|
||||
出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch05/class_template_basics.cpp">p01/ch05/class_template_basics.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>dynamic_cast:运行期检查的向下转型 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Animal { public: virtual void ShowName() = 0; }; // 必须有虚函数(多态类型)
|
||||
class Dog : public Animal { virtual void ShowName(){ /*...*/ } };
|
||||
class Other { public: void PrintSomething(){} }; // 与 Animal 无继承关系
|
||||
|
||||
Dog* dog01 = new Dog;
|
||||
Animal* animal02 = dynamic_cast<Animal*>(dog01); // 子类→父类,合法(其实用不着 dynamic_cast)
|
||||
|
||||
// Dog* dog02 = dynamic_cast<Dog*>(animal01); // 父类→子类:运行期检查,失败返回 nullptr
|
||||
// Animal* x = dynamic_cast<Animal*>(other); // 无继承关系:编译期报错
|
||||
</code></pre>
|
||||
<p><small>dynamic_cast 依赖 RTTI,只对多态类型(含虚函数)生效——这是与 static_cast
|
||||
最本质的区别。出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch06/dynamic_cast_demo.cpp">p01/ch06/dynamic_cast_demo.cpp</a></small></p>
|
||||
<p><small>重写 <code>xxxEvent</code> 是 Qt 事件处理的主线之一(另一条线是事件过滤器
|
||||
<code>installEventFilter</code>)。出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch09/mouse_event.cpp">p03/ch09/mouse_event.cpp</a>;
|
||||
事件过滤器见 <a class="filelink" target="_blank" href="../../../p03/ch09/event_filter_mainwindow.cpp">event_filter_mainwindow.cpp</a></small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -77,34 +83,35 @@ Animal* animal02 = dynamic_cast<Animal*>(dog01); // 子类→父类,
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 练习 95min</p>
|
||||
<h2>动手:模板与类型转换 <span class="tag tag-practice">练</span></h2>
|
||||
<h2>动手:Model/View + 事件 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · 写一个通用容器模板 <span class="tag tag-practice">练</span></h4>
|
||||
<p>仿照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch05/class_template_basics.cpp">class_template_basics.cpp</a>,
|
||||
写一个 <code>Pair<T1, T2></code> 或简易 <code>Stack<T></code>(push/pop/top,
|
||||
栈空时的行为要明确定义)。</p>
|
||||
<h4>任务卡 1 · Model/View 换视图 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>把 <code>qt_model_view_basics.cpp</code> 里的 <code>QListView</code> 换成
|
||||
<code>QTableView</code>,模型代码不改</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>编译通过,界面正常展示同一份数据,证明"同一个模型可以喂给不同视图"</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../examples/model_view/qt_model_view_basics.cpp">qt_model_view_basics.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../examples/model_view/README.md">README</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · dynamic_cast 安全向下转型 <span class="tag tag-practice">练</span></h4>
|
||||
<p>用 Day2 写的多态类层次(基类指针数组),对每个元素做
|
||||
<code>dynamic_cast</code> 判断具体类型,转型失败(返回 nullptr)的分支要写出来,
|
||||
不能假设转型一定成功。参照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch06/dynamic_cast_demo.cpp">dynamic_cast_demo.cpp</a>。</p>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 3 · 选做:类模板的派生关系 <span class="tag tag-practice">练</span></h4>
|
||||
<p>阅读 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch05/class_template_derives_class.cpp">class_template_derives_class.cpp</a>
|
||||
与 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch05/class_template_derives_template.cpp">class_template_derives_template.cpp</a>,
|
||||
写一句话总结「普通类继承类模板」与「类模板继承类模板」在写法上的区别。</p>
|
||||
<h4>任务卡 2 · 事件过滤器实操 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>仿 <code>event_filter_mainwindow.cpp</code>,给一个控件装事件过滤器,拦截鼠标按下事件并打印坐标</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>点击控件后终端/调试输出能看到坐标信息</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch09/event_filter_mainwindow.cpp">event_filter_mainwindow.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch09/mouse_event.cpp">mouse_event.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -112,119 +119,82 @@ Animal* animal02 = dynamic_cast<Animal*>(dog01); // 子类→父类,
|
||||
<!-- ===================== 模块 B:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 讲授 35min(三块内容合并速览)</p>
|
||||
<h2>异常 + IO 流 + STL <span class="tag tag-lecture">讲</span></h2>
|
||||
<ul>
|
||||
<li>异常:栈解旋、自定义异常类</li>
|
||||
<li>文件 IO:文本 vs 二进制</li>
|
||||
<li>STL:容器 + 迭代器 + 算法 + 函数对象</li>
|
||||
</ul>
|
||||
<p class="module-badge">模块 B · 讲授 35min</p>
|
||||
<h2>文件 IO / JSON 解析 <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>栈解旋(stack unwinding) <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>文件系统:QFile 读文本 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Person{
|
||||
public:
|
||||
Person(string name){ mName = name; cout << mName << "对象被创建!\n"; }
|
||||
~Person(){ cout << mName << "对象被析构!\n"; }
|
||||
string mName;
|
||||
};
|
||||
void TestFunction(){
|
||||
Person p1("aaa"), p2("bbb"), p3("ccc");
|
||||
throw 10; // 抛出异常时,p3/p2/p1 按逆序自动析构——这就是「栈解旋」
|
||||
}
|
||||
int main(){ try{ TestFunction(); } catch(...){ cout<<"异常被捕获!\n"; } }
|
||||
QFile file("in.txt");
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "打开失败"; return -1; }
|
||||
while (!file.atEnd()) { qDebug() << file.readLine(); }
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch07/stack_unwinding.cpp">p01/ch07/stack_unwinding.cpp</a></small></p>
|
||||
href="../../../p03/ch11/qfile_basic.cpp">p03/ch11/qfile_basic.cpp</a>;结构化读写见
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch11/qtextstream_io.cpp">qtextstream_io.cpp</a>、
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch11/qdatastream_io.cpp">qdatastream_io.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>自定义异常类:继承 std::exception <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>JSON 解析与写入(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class MyOutOfRange : public exception{
|
||||
public:
|
||||
MyOutOfRange(const string errorInfo){ m_Error = errorInfo; }
|
||||
virtual const char* what() const noexcept { return m_Error.c_str(); }
|
||||
string m_Error;
|
||||
};
|
||||
// 多个 catch 分支各自匹配不同的异常类型(都直接继承 exception,彼此是平级、不是父子)
|
||||
// Person(151) 这里实际只会抛 MyOutOfRange,前两个 catch 分支不会命中——
|
||||
// 写多个 catch 分支是为了同一段代码能同时应对「标准库异常」和「自定义异常」两种来源
|
||||
try{ Person p(151); }
|
||||
catch (out_of_range& e){ cout << e.what(); } // 若改成 throw out_of_range(...) 会走这里
|
||||
catch (MyOutOfRange e){ cout << e.what(); } // 本例实际命中的分支
|
||||
QJsonObject student;
|
||||
student["name"] = QStringLiteral("张三");
|
||||
student["score"] = 92;
|
||||
|
||||
QJsonDocument doc(student);
|
||||
QByteArray bytes = doc.toJson(QJsonDocument::Indented); // 序列化
|
||||
|
||||
QJsonDocument parsed = QJsonDocument::fromJson(bytes); // 反序列化
|
||||
QJsonObject obj = parsed.object();
|
||||
qDebug() << obj["name"].toString() << obj["score"].toInt();
|
||||
</code></pre>
|
||||
<p><small>强化点:catch 分支的匹配顺序只在「类型存在继承关系」时才重要(子类 catch 必须写在
|
||||
父类前面,否则父类分支会先吞掉子类异常);这里三个类型互不继承,顺序其实不影响本例结果。
|
||||
出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch07/custom_exception_class.cpp">p01/ch07/custom_exception_class.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>二进制文件读写 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
ofstream osm("person.txt", ios::out | ios::binary);
|
||||
osm.write((const char*)&p1, sizeof(Person)); // 直接写内存布局,不做任何格式转换
|
||||
osm.close();
|
||||
|
||||
ifstream ism("person.txt", ios::in | ios::binary);
|
||||
Person p3;
|
||||
ism.read((char*)&p3, sizeof(Person)); // 按同样的 sizeof 读回——结构必须一致
|
||||
</code></pre>
|
||||
<p><small>二进制 IO 要求读写两端的结构体内存布局完全一致(含 padding),跨平台/跨编译器
|
||||
不保证兼容。出处:<a class="filelink" target="_blank"
|
||||
href="../../../p01/ch08/binary_file_io.cpp">p01/ch08/binary_file_io.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>STL:容器 + 迭代器 + 算法 + 函数对象 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
// 谓词(函数对象)驱动算法
|
||||
class GreaterThenFive{ public: bool operator()(int num){ return num > 5; } };
|
||||
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterThenFive());
|
||||
|
||||
class MyCompare{ public: bool operator()(int a,int b){ return a > b; } };
|
||||
sort(v.begin(), v.end(), MyCompare()); // 用函数对象改变算法策略——从小到大 → 从大到小
|
||||
</code></pre>
|
||||
<p><small>STL 的核心思想:容器负责存储,算法负责操作,迭代器是两者之间的胶水,
|
||||
函数对象(谓词)负责定制算法行为。出处:
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch09/s03/vector_iterator.cpp">p01/ch09/s03/vector_iterator.cpp</a>、
|
||||
<a class="filelink" target="_blank" href="../../../p01/ch09/s04/predicate.cpp">p01/ch09/s04/predicate.cpp</a></small></p>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../examples/json_io/qt_json_parse_write.cpp">docs/teaching/examples/json_io/qt_json_parse_write.cpp</a>
|
||||
(<a class="filelink" target="_blank" href="../examples/json_io/README.md">配套 README</a>)——
|
||||
仓库 <code>p03/ch11</code> 的 wiki 抓取内容只有 QFile/QTextStream/QDataStream,没有 JSON,
|
||||
这是本轮补写的教学补充示例;不需要 GUI,<code>tools/run_qt.sh qt_json_parse_write</code>
|
||||
直接跑即可,不需要 <code>QT_QPA_PLATFORM=offscreen</code>。</small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:练 ===================== -->
|
||||
<!-- ===================== 模块 B:练(本地数据存储案例) ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 练习 95min(任选其二,第三个留参考)</p>
|
||||
<h2>动手:异常 / STL / 文件 IO <span class="tag tag-practice">练</span></h2>
|
||||
<p class="module-badge">模块 B · 练习 95min(本地数据存储案例,练习任务,非预建方案)</p>
|
||||
<h2>动手:本地数据存储小工具 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · 给类加异常处理 <span class="tag tag-practice">练</span></h4>
|
||||
<p>给 Day1/Day2 写的类加一个自定义异常类(继承 <code>std::exception</code>),
|
||||
非法参数(如负数年龄)抛出异常,调用方 <code>catch</code> 后打印信息、程序不崩溃。
|
||||
参照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch07/custom_exception_class.cpp">custom_exception_class.cpp</a>。</p>
|
||||
<h4>任务卡 1 · JSON 落盘 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>把 <code>qt_json_parse_write.cpp</code> 的 JSON 数据实际写入一个文件(结合
|
||||
<code>qfile_basic.cpp</code> 的 <code>QFile</code> 写法),再读回来解析校验</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>程序运行后磁盘上生成一个 <code>.json</code> 文件,内容可读;重新运行程序能正确解析出
|
||||
之前写入的数据</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../examples/json_io/qt_json_parse_write.cpp">qt_json_parse_write.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../examples/json_io/README.md">README</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch11/qfile_basic.cpp">qfile_basic.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · vector + 算法解决实际问题 <span class="tag tag-practice">练</span></h4>
|
||||
<p>用 <code>vector</code> 存一组「学生成绩」,用 <code>sort</code> +
|
||||
自定义谓词按成绩排序,再用 <code>find_if</code> 找出所有不及格的学生并打印。
|
||||
参照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch09/s04/predicate.cpp">predicate.cpp</a>。</p>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 3 · 结构体数组的二进制读写 <span class="tag tag-practice">练</span></h4>
|
||||
<p>参照 <a class="filelink" target="_blank"
|
||||
href="../../../p01/ch08/binary_file_io.cpp">binary_file_io.cpp</a>,把一个结构体数组
|
||||
(如 5 个学生记录)写入二进制文件,再读出并逐条打印校验,确认与写入前一致。</p>
|
||||
<h4>任务卡 2 · 本地数据存储小工具 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>设计一个"学员信息"结构(姓名/成绩/标签),支持增加一条记录并追加保存到 JSON 文件,
|
||||
重启程序后能读出之前保存的全部记录</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>至少能存 2 条以上记录并在重启后完整读回;数据字段不丢失</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../examples/json_io/">docs/teaching/examples/json_io/</a> ·
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch11/qtextstream_io.cpp">qtextstream_io.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -233,15 +203,14 @@ sort(v.begin(), v.end(), MyCompare()); // 用函数对象改变算法策略—
|
||||
<section>
|
||||
<h2>当日产出验证</h2>
|
||||
<div class="verify-box">
|
||||
<h4>验收标准</h4>
|
||||
<pre><code class="bash" data-trim>cmake --build build --target all_p01_ch05
|
||||
cmake --build build --target all_p01_ch07
|
||||
cmake --build build --target all_p01_ch09_s04</code></pre>
|
||||
<p>全绿;至少 1 个模板类 + 1 个用 STL 容器/算法解决实际小问题的程序。</p>
|
||||
<h4>现场演示</h4>
|
||||
<p>现场演示本地数据存储小工具,验证"写入 → 重启 → 读回"完整闭环。</p>
|
||||
</div>
|
||||
<p class="footer-note"><small>明日预告:Day 4 —— Qt 桌面全景(1 天快速建立骨架认知
|
||||
+ 动手做一个可跑的小工具)</small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a></p>
|
||||
<p class="footer-note"><small>明天 Day4:2D 图形绘制 / 多线程 / 动画框架与状态机 ——
|
||||
大纲:<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a> ·
|
||||
<a class="filelink" href="day2.html">← 上一天</a> ·
|
||||
<a class="filelink" href="day4.html">下一天 →</a></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
+138
-223
@@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Day 4 · Qt 桌面全景</title>
|
||||
<title>Day 4 · 2D绘图/多线程/动画框架与状态机</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="reveal/reveal.css">
|
||||
<link rel="stylesheet" href="reveal/theme.css">
|
||||
@@ -15,198 +15,19 @@
|
||||
|
||||
<section>
|
||||
<h1>Day 4</h1>
|
||||
<h3>Qt 桌面全景</h3>
|
||||
<p class="module-badge">Qt 方向实训 · 第 4 / 4 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<h3>2D 图形绘制 / 多线程 / 动画框架与状态机</h3>
|
||||
<p class="module-badge">C++/Qt 方向实训 · 第 4 / 5 天 · 讲:练 ≈ 1:2.7</p>
|
||||
<ul>
|
||||
<li>模块 A(3 课时):项目结构 / 第一个程序 / 信号槽 / 主窗口</li>
|
||||
<li>模块 B(3 课时):对话框 / 控件 / 事件 / 绘图 / 文件(速览 + 分组选做)</li>
|
||||
<li>模块 A(3 课时):2D 绘图 / 多线程</li>
|
||||
<li>模块 B(3 课时):动画框架 / 状态机 → 交互式小游戏案例</li>
|
||||
</ul>
|
||||
<p><small>定位声明:Day1–3 是「强化已知」,今天是「Qt 零基础入门」——讲授压缩为
|
||||
骨架级讲解,练习是「跟着例子改」而不是从零写。9 个章节、29 个目标不追求全覆盖。</small></p>
|
||||
<div class="verify-box" style="font-size:0.5em;">
|
||||
<h4>开始前准备(一次性)</h4>
|
||||
<pre><code class="bash" data-trim>tools/install_qt_host.sh
|
||||
cmake -S . -B build -G Ninja -DBUILD_QT_PART=ON \
|
||||
-DCMAKE_PREFIX_PATH=$PWD/.qt514/5.14.2/gcc_64 -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 A:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 讲授 35min</p>
|
||||
<h2>项目结构 / 信号槽 / 主窗口 <span class="tag tag-lecture">讲</span></h2>
|
||||
<ul>
|
||||
<li>最小 Qt 程序结构</li>
|
||||
<li>控件创建与显示</li>
|
||||
<li>内置 / 自定义 / lambda 信号槽</li>
|
||||
<li>QMainWindow 中心部件</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>最小 Qt 程序:QApplication + exec() <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Widget : public QWidget {
|
||||
Q_OBJECT // 用到信号槽/属性系统的类都要有这个宏(触发 moc 代码生成)
|
||||
public:
|
||||
Widget(QWidget* parent = nullptr) : QWidget(parent) {}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv); // 整个程序唯一的 QApplication
|
||||
Widget w;
|
||||
w.show();
|
||||
return app.exec(); // 进入事件循环,程序在这里“活着”
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch02/minimal_qt_app.cpp">p03/ch02/minimal_qt_app.cpp</a>(注意仓库版本额外注入了
|
||||
<code>QTimer::singleShot</code> 200ms 自动退出,便于离屏自动化验证,真实项目不需要这一行)</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>控件创建:两种写法 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
// 第一种:先构造再设属性
|
||||
QPushButton* btn = new QPushButton;
|
||||
btn->setParent(this);
|
||||
btn->setText(QStringLiteral("德玛西亚"));
|
||||
btn->move(100, 100);
|
||||
|
||||
// 第二种:构造时指定文本与父亲
|
||||
QPushButton* btn2 = new QPushButton(QStringLiteral("孙悟空"), this);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch03/button_creation.cpp">p03/ch03/button_creation.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>信号槽:内置 / 自定义 / 重载消歧义 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class Teacher : public QObject {
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void hungry(); // 无参信号
|
||||
void hungry(QString food); // 带参信号(重载)
|
||||
public slots:
|
||||
void classIsOver(){ emit hungry(); emit hungry(QString("汉堡")); }
|
||||
};
|
||||
// 连接重载信号时必须用 QOverload 显式指定版本
|
||||
connect(t, QOverload<>::of(&Teacher::hungry), s, QOverload<>::of(&Student::treat));
|
||||
connect(t, QOverload<QString>::of(&Teacher::hungry), s, QOverload<QString>::of(&Student::treat));
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch04/custom_signal_slot.cpp">p03/ch04/custom_signal_slot.cpp</a>;
|
||||
lambda 槽 + 值捕获/引用捕获对比见
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch04/lambda_signal_slot.cpp">lambda_signal_slot.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>QMainWindow:中心部件 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class DemoMainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DemoMainWindow(){
|
||||
QTextEdit* edit = new QTextEdit(this);
|
||||
setCentralWidget(edit); // QMainWindow 有固定布局:菜单栏/工具栏/中心部件/停靠窗口/状态栏
|
||||
}
|
||||
};
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch05/central_widget.cpp">p03/ch05/central_widget.cpp</a>;停靠窗口见
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch05/dock_widget.cpp">dock_widget.cpp</a></small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 A:练 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 练习 95min</p>
|
||||
<h2>动手:第一个 Qt 程序 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · 跑通最小程序(离屏验证) <span class="tag tag-practice">练</span></h4>
|
||||
<pre><code class="bash" data-trim>QT_QPA_PLATFORM=offscreen tools/run_qt.sh minimal_qt_app
|
||||
QT_QPA_PLATFORM=offscreen tools/run_qt.sh button_creation</code></pre>
|
||||
<p>有 X 环境的同学可去掉 <code>QT_QPA_PLATFORM=offscreen</code> 直接看窗口弹出(200ms 后自动退出)。</p>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · 自定义信号槽改文本 <span class="tag tag-practice">练</span></h4>
|
||||
<p>仿 <a class="filelink" target="_blank"
|
||||
href="../../../p03/ch04/custom_signal_slot.cpp">custom_signal_slot.cpp</a>,
|
||||
给一个按钮连接自定义信号槽:点击后改变一个 <code>QLabel</code> 的文本内容。</p>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 3 · 窗口骨架 <span class="tag tag-practice">练</span></h4>
|
||||
<p>用 <code>QMainWindow</code> + <a class="filelink" target="_blank"
|
||||
href="../../../p03/ch05/dock_widget.cpp">dock_widget.cpp</a> 搭一个「中心区 + 侧边栏」
|
||||
的窗口骨架。</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 讲授 35min(五个方向各约 7min 速览)</p>
|
||||
<h2>对话框 / 控件 / 事件 / 绘图 / 文件 <span class="tag tag-lecture">讲</span></h2>
|
||||
<p><small>只求「这个方向能做什么」的骨架认知,不逐行讲解</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>对话框:文件对话框 + QTextStream <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
void openFile() {
|
||||
QString path = QFileDialog::getOpenFileName(this, QStringLiteral("打开文件"));
|
||||
if (path.isEmpty()) return;
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { /* 提示失败 */ return; }
|
||||
QTextStream in(&file);
|
||||
QString text = in.readAll();
|
||||
QMessageBox::information(this, QStringLiteral("内容"), text);
|
||||
}
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch06/file_dialog.cpp">p03/ch06/file_dialog.cpp</a>;模态/非模态对话框见
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch06/modal_dialog.cpp">modal_dialog.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>常用控件:QLabel 显示图片 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
QPixmap pixmap;
|
||||
pixmap.load(":/Image/boat.jpg"); // 从 Qt 资源系统(.qrc)加载
|
||||
QLabel* label = new QLabel(this);
|
||||
label->setPixmap(pixmap);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch08/label_pixmap.cpp">p03/ch08/label_pixmap.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>事件机制:重写鼠标事件 <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
class EventLabel : public QLabel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EventLabel(QWidget* p=nullptr):QLabel(p){ setMouseTracking(true); }
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent* event) override {
|
||||
setText(QString("Move: (%1, %2)").arg(event->x()).arg(event->y()));
|
||||
}
|
||||
};
|
||||
</code></pre>
|
||||
<p><small>重写 <code>xxxEvent</code> 是 Qt 事件处理的主线之一(另一条线是事件过滤器
|
||||
<code>installEventFilter</code>)。出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch09/mouse_event.cpp">p03/ch09/mouse_event.cpp</a></small></p>
|
||||
<h2>2D 绘图 / 多线程 <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -221,74 +42,168 @@ void paintEvent(QPaintEvent*) override {
|
||||
</code></pre>
|
||||
<p><small>所有绘制都发生在 <code>paintEvent</code> 里,由 Qt 在需要重绘时自动调用。
|
||||
出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch10/qpainter_basic.cpp">p03/ch10/qpainter_basic.cpp</a></small></p>
|
||||
href="../../../p03/ch10/qpainter_basic.cpp">p03/ch10/qpainter_basic.cpp</a>;绘图设备
|
||||
(<code>QImage</code>/<code>QPixmap</code>/<code>QPicture</code>)见
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch10/paint_device_qimage.cpp">paint_device_qimage.cpp</a>、
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch10/paint_device_pixmaps.cpp">paint_device_pixmaps.cpp</a></small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>文件系统:QFile 读文本 <span class="tag tag-lecture">讲</span></h3>
|
||||
<h3>QThread 多线程(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
QFile file("in.txt");
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qDebug() << "打开失败"; return -1; }
|
||||
while (!file.atEnd()) { qDebug() << file.readLine(); }
|
||||
|
||||
QFileInfo info(file);
|
||||
qDebug() << info.baseName() << info.suffix();
|
||||
class Worker : public QObject {
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
void process() {
|
||||
qint64 sum = 0;
|
||||
for (int i = 1; i <= 1000000; ++i) sum += i;
|
||||
emit resultReady(sum); // 排队连接自动把结果带回主线程
|
||||
}
|
||||
signals:
|
||||
void resultReady(qint64 result);
|
||||
};
|
||||
worker.moveToThread(&workerThread); // 官方推荐写法:worker-object + moveToThread
|
||||
connect(&workerThread, &QThread::started, &worker, &Worker::process);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../../p03/ch11/qfile_basic.cpp">p03/ch11/qfile_basic.cpp</a>;结构化读写见
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch11/qtextstream_io.cpp">qtextstream_io.cpp</a>、
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch11/qdatastream_io.cpp">qdatastream_io.cpp</a></small></p>
|
||||
href="../examples/qthread_worker/qt_thread_worker.cpp">docs/teaching/examples/qthread_worker/qt_thread_worker.cpp</a>
|
||||
(<a class="filelink" target="_blank" href="../examples/qthread_worker/README.md">配套 README</a>)——
|
||||
仓库 <code>p03/</code> 的 wiki 抓取内容没有多线程,这是本轮补写的教学补充示例,已验证跑通。</small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:练(分组三选一) ===================== -->
|
||||
<!-- ===================== 模块 A:练 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 练习 95min(三选一,分组完成)</p>
|
||||
<h2>动手:做一个可跑的小工具 <span class="tag tag-practice">练</span></h2>
|
||||
<p class="module-badge">模块 A · 练习 95min</p>
|
||||
<h2>动手:绘图 + 多线程 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · 画一个简单图形界面 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>仿 <code>qpainter_basic.cpp</code>,在 <code>paintEvent</code> 里画一个组合图形
|
||||
(如棋盘格/仪表盘刻度)</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>编译通过,离屏或 X 环境下能看到图形正确绘制(可截图验证)</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../../../p03/ch10/qpainter_basic.cpp">p03/ch10/qpainter_basic.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 2 · 感受多线程不卡界面 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>把 <code>qt_thread_worker.cpp</code> 里的耗时计算换成一个更长的循环或
|
||||
<code>QThread::msleep</code>,观察主线程/事件循环没有被卡住</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>能说清楚"如果把这段计算直接放主线程会发生什么"(界面无响应),并解释
|
||||
<code>moveToThread</code> 起的作用</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../examples/qthread_worker/qt_thread_worker.cpp">qt_thread_worker.cpp</a> ·
|
||||
<a class="filelink" target="_blank" href="../examples/qthread_worker/README.md">README</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 讲授 35min</p>
|
||||
<h2>动画框架 / 状态机 <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>QPropertyAnimation 动画(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
auto* anim = new QPropertyAnimation(button, "geometry", &window);
|
||||
anim->setDuration(800);
|
||||
anim->setStartValue(QRect(10, 10, 100, 30));
|
||||
anim->setEndValue(QRect(180, 140, 120, 40));
|
||||
anim->setEasingCurve(QEasingCurve::OutBounce);
|
||||
anim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../examples/property_animation/qt_property_animation.cpp">docs/teaching/examples/property_animation/qt_property_animation.cpp</a>
|
||||
(<a class="filelink" target="_blank" href="../examples/property_animation/README.md">配套 README</a>)</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>QStateMachine 状态机(教学补充) <span class="tag tag-lecture">讲</span></h3>
|
||||
<pre><code class="cpp" data-trim data-noescape>
|
||||
auto* redState = new QState(&machine);
|
||||
auto* greenState = new QState(&machine);
|
||||
redState->assignProperty(&button, "text", QStringLiteral("红灯:禁止通行"));
|
||||
greenState->assignProperty(&button, "text", QStringLiteral("绿灯:允许通行"));
|
||||
redState->addTransition(&button, &QPushButton::clicked, greenState);
|
||||
greenState->addTransition(&button, &QPushButton::clicked, redState);
|
||||
machine.setInitialState(redState);
|
||||
machine.start();
|
||||
</code></pre>
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../examples/state_machine/qt_state_machine.cpp">docs/teaching/examples/state_machine/qt_state_machine.cpp</a>
|
||||
(<a class="filelink" target="_blank" href="../examples/state_machine/README.md">配套 README</a>)——
|
||||
<code>QStateMachine</code>/<code>QState</code> 属于 QtCore,不需要额外模块。以上两个
|
||||
(动画、状态机)都是仓库 <code>p03/</code> 原本没有的教学补充示例,已离屏验证跑通。</small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:练(交互式小游戏案例,选做) ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 练习 95min(交互式小游戏案例,练习任务,选做,非预建方案)</p>
|
||||
<h2>动手:动画 + 状态机小游戏 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>方案 A · 图片查看器雏形 <span class="tag tag-practice">练</span></h4>
|
||||
<p>结合 <a class="filelink" target="_blank"
|
||||
href="../../../p03/ch08/label_pixmap.cpp">label_pixmap.cpp</a> +
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch06/file_dialog.cpp">file_dialog.cpp</a>:
|
||||
用文件对话框选一张图片,加载到 <code>QLabel</code> 显示。</p>
|
||||
<h4>任务卡 1 · 动画 + 状态机组合 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>把动画和状态机组合到同一个窗口——例如按钮在"红灯"状态时禁止移动,"绿灯"状态时
|
||||
点击才触发动画</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>状态切换正确(红灯不触发动画,绿灯触发动画);动画播放完整不报错</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd><a class="filelink" target="_blank" href="../examples/property_animation/">property_animation/</a> ·
|
||||
<a class="filelink" target="_blank" href="../examples/state_machine/">state_machine/</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>方案 B · 简易绘图板 <span class="tag tag-practice">练</span></h4>
|
||||
<p>结合 <a class="filelink" target="_blank"
|
||||
href="../../../p03/ch10/qpainter_basic.cpp">qpainter_basic.cpp</a> +
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch09/mouse_event.cpp">mouse_event.cpp</a>:
|
||||
监听鼠标拖动事件,记录轨迹点,在 <code>paintEvent</code> 里用 <code>QPainter</code> 画出轨迹。</p>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>方案 C · 文本文件小工具 <span class="tag tag-practice">练</span></h4>
|
||||
<p>结合 <a class="filelink" target="_blank"
|
||||
href="../../../p03/ch11/qfile_basic.cpp">qfile_basic.cpp</a> +
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch11/qtextstream_io.cpp">qtextstream_io.cpp</a>:
|
||||
读一个文本文件,统计行数/字符数,把统计结果写回一个新文件。</p>
|
||||
<h4>任务卡 2(选做)· 简易反应力小游戏 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>结合任务卡 1 + 鼠标事件(Day3 <code>mouse_event.cpp</code>)+ 多线程计时
|
||||
(Day4 <code>qt_thread_worker.cpp</code>),做一个"状态变化后限时点击"的最小交互游戏</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>游戏至少有"开始→进行中→结束"三个可辨识状态,计时或计分逻辑不崩溃</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd>上述三个教学补充示例 +
|
||||
<a class="filelink" target="_blank" href="../../../p03/ch09/mouse_event.cpp">mouse_event.cpp</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 收尾 ===================== -->
|
||||
<section>
|
||||
<h2>当日产出验证 · 实训总验收</h2>
|
||||
<h2>当日产出验证</h2>
|
||||
<div class="verify-box">
|
||||
<h4>现场演示</h4>
|
||||
<pre><code class="bash" data-trim>QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
|
||||
# 有 X 环境:去掉 QT_QPA_PLATFORM=offscreen 直接看窗口</code></pre>
|
||||
<p>每组演示自己选做的方案(A/B/C 三选一),教师验收「编译通过 + 交互符合预期」。</p>
|
||||
<p>分组演示动画+状态机组合小工具(任务卡 2 选做,量力而行)。</p>
|
||||
</div>
|
||||
<p class="footer-note"><small>4 天实训到此结束——大纲:
|
||||
<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a></p>
|
||||
<p class="footer-note"><small>明天 Day5:团队协同开发 / 综合案例复盘 / OpenCV(可选自学) ——
|
||||
大纲:<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a> ·
|
||||
<a class="filelink" href="day3.html">← 上一天</a> ·
|
||||
<a class="filelink" href="day5.html">下一天 →</a></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Day 5 · 团队协同开发/综合案例复盘/OpenCV可选</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="reveal/reveal.css">
|
||||
<link rel="stylesheet" href="reveal/theme.css">
|
||||
<link rel="stylesheet" href="reveal/plugin/highlight/monokai.css">
|
||||
<link rel="stylesheet" href="custom.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="reveal">
|
||||
<div class="slides">
|
||||
|
||||
<section>
|
||||
<h1>Day 5</h1>
|
||||
<h3>团队协同开发 / 综合案例复盘 / OpenCV(可选自学)</h3>
|
||||
<p class="module-badge">C++/Qt 方向实训 · 第 5 / 5 天 · 最后一天课堂内容</p>
|
||||
<ul>
|
||||
<li>模块 A(3 课时):团队协同开发(概念讲解)</li>
|
||||
<li>模块 B(3 课时):综合案例复盘 + OpenCV/AI(可选,自学指针)</li>
|
||||
</ul>
|
||||
<p><small>今天结束后 Day6–10 是纯项目实作(学员编码 + 教员指导 + 成果展示),
|
||||
不再有课堂讲授——今天的分组结论会直接带到项目实作阶段。</small></p>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 A:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 讲授 35min</p>
|
||||
<h2>团队协同开发 <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>质量保证:四平台一标准(复习) <span class="tag tag-lecture">讲</span></h3>
|
||||
<img src="../../images/quality_four_platforms.png" style="max-height:420px;">
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a>
|
||||
「七、质量保证 (二) Git」——接下来 Day6–10 项目实作,每人每完成一个任务都要提交代码
|
||||
到 Git 并写清任务描述。</small></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>标准化考核标准 <span class="tag tag-lecture">讲</span></h3>
|
||||
<img src="../../images/assessment_standard.png" style="max-height:480px;">
|
||||
<p><small>出处:<a class="filelink" target="_blank"
|
||||
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a>
|
||||
「七、(五) 标准化的考核标准」——项目管理评估(40%) + 项目技术评估(30%) +
|
||||
职业素养评估(30%),Day6–10 项目实作最终按这个标准打分。</small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 A:讨论 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 A · 讨论 95min</p>
|
||||
<h2>动手:分工复盘 <span class="tag tag-discuss">议</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card discuss-card">
|
||||
<h4>任务卡 1 · 分工复盘 <span class="tag tag-discuss">议</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>结合考核标准图(项目管理评估/项目技术评估/职业素养评估三大块),对照 Day1
|
||||
任务卡 1 定下的分工,检查执行情况</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>每组能说出至少 1 项做得好的、1 项需要改进的</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd>上一节"标准化考核标准"图 · Day1 任务卡 1 的分工结论</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:讲 ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 讲授 35min</p>
|
||||
<h2>综合案例复盘 + OpenCV/AI(可选) <span class="tag tag-lecture">讲</span></h2>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Day2–4 案例回顾 <span class="tag tag-lecture">讲</span></h3>
|
||||
<p>请 1–2 组现场展示计算器 / 本地数据存储 / 动画状态机小游戏,其他组观摩提问。</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>OpenCV 与 AI 机器学习(可选,自学指针) <span class="tag tag-lecture">讲</span></h3>
|
||||
<div class="verify-box">
|
||||
<h4>缺口说明</h4>
|
||||
<p>方案书原文对本项标注"可选择",属于非必修拓展。引入 OpenCV 会新增一条完整的
|
||||
三方库工具链,本仓库暂不提供可运行示例。如果感兴趣,自学方向是
|
||||
<code>cv::CascadeClassifier</code> 人脸检测入门。</p>
|
||||
</div>
|
||||
<p><small><span class="ext-ref">OpenCV 官方教程(doc.opencv.org,选读·需联网)</span></small></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 模块 B:练(综合演练) ===================== -->
|
||||
<section>
|
||||
<section>
|
||||
<p class="module-badge">模块 B · 练习 95min(综合演练)</p>
|
||||
<h2>动手:三选一深化 <span class="tag tag-practice">练</span></h2>
|
||||
</section>
|
||||
<section>
|
||||
<div class="task-card">
|
||||
<h4>任务卡 1 · 三选一深化 <span class="tag tag-practice">练</span></h4>
|
||||
<dl class="task-spec">
|
||||
<dt>目标</dt>
|
||||
<dd>从计算器 / 本地数据存储 / 动画状态机小游戏中选一个继续深化(加一个新功能)</dd>
|
||||
<dt>验收标准</dt>
|
||||
<dd>现场演示新增功能正常工作,编译无报错</dd>
|
||||
<dt>基础知识 · 参考</dt>
|
||||
<dd>回顾 Day2–4 对应的教学补充示例与 README:
|
||||
<a class="filelink" target="_blank" href="../examples/qss_styling/README.md">qss_styling</a> ·
|
||||
<a class="filelink" target="_blank" href="../examples/json_io/README.md">json_io</a> ·
|
||||
<a class="filelink" target="_blank" href="../examples/property_animation/README.md">property_animation</a> ·
|
||||
<a class="filelink" target="_blank" href="../examples/state_machine/README.md">state_machine</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ===================== 收尾 ===================== -->
|
||||
<section>
|
||||
<h2>当日产出验证 · Day1–5 阶段总验收</h2>
|
||||
<div class="verify-box">
|
||||
<h4>现场演示</h4>
|
||||
<p>每组现场演示深化后的案例,教师按考核标准图三大块给出反馈,为 Day6–10
|
||||
的正式项目实作分组定方向。</p>
|
||||
</div>
|
||||
<p class="footer-note"><small>Day1–5 课堂教学到此结束,Day6–10 进入项目实作——大纲:
|
||||
<a class="filelink" href="../OUTLINE.md">docs/teaching/OUTLINE.md</a></small></p>
|
||||
<p><a class="filelink" href="index.html">返回目录</a> ·
|
||||
<a class="filelink" href="day4.html">← 上一天</a></p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="reveal/reveal.js"></script>
|
||||
<script src="reveal/plugin/highlight/highlight.js"></script>
|
||||
<script>
|
||||
Reveal.initialize({
|
||||
hash: true, controls: true, progress: true, center: true, slideNumber: true,
|
||||
plugins: [ RevealHighlight ]
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Qt 方向实训 · 4 天教案幻灯片</title>
|
||||
<title>C++/Qt 方向实训 · 5 天教案幻灯片</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body { font-family: -apple-system, "Helvetica Neue", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
@@ -17,19 +17,23 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>川大 C++/LinuxC 课程「02. Qt 方向」—— 4 天实训幻灯片</h1>
|
||||
<p class="desc">配套大纲:<code>docs/teaching/OUTLINE.md</code>。
|
||||
<h1>川大 C++/LinuxC 课程「02. Qt 方向」—— 5 天实训幻灯片</h1>
|
||||
<p class="desc">对齐官方《四川大学软件学院 2026 年夏 C/C++ 项目实习实训方案书 V1.2》
|
||||
「五、执行计划 (二) C++方向」Day1–5(Day6–10 是纯项目实作,无课堂内容,不在本教案范围)。
|
||||
配套大纲:<code>docs/teaching/OUTLINE.md</code>;方案书全文:<code>docs/TRAINING_PLAN_2026.md</code>。
|
||||
离线可用(reveal.js 已 vendor 进仓库);代码链接需通过本地 HTTP 服务打开才能正常跳转,
|
||||
见仓库 <code>docs/teaching/OUTLINE.md</code> 「材料与播放方式」一节。</p>
|
||||
<ul>
|
||||
<li><a class="day" href="day1.html">Day 1 · C++ 对 C 的扩展 + 类和对象基础</a>
|
||||
<div class="desc">p01/ch02、p01/ch03 强化精讲 → p01/ch04/s01–s03 封装/构造析构</div></li>
|
||||
<li><a class="day" href="day2.html">Day 2 · OOP 进阶</a>
|
||||
<div class="desc">p01/ch04/s04–s06 对象模型/友元/运算符重载 → p01/ch04/s07–s08 继承/多态</div></li>
|
||||
<li><a class="day" href="day3.html">Day 3 · 泛型与标准库</a>
|
||||
<div class="desc">p01/ch05 模板 + p01/ch06 类型转换 → p01/ch07 异常 + p01/ch08 IO流 + p01/ch09 STL</div></li>
|
||||
<li><a class="day" href="day4.html">Day 4 · Qt 桌面全景</a>
|
||||
<div class="desc">p03/ch02–ch05 项目/信号槽/主窗口 → p03/ch06、ch08–ch11 对话框/控件/事件/绘图/文件</div></li>
|
||||
<li><a class="day" href="day1.html">Day 1 · 开训 + C++ 回顾/工具链</a>
|
||||
<div class="desc">团队建设/Scrum/C++企业应用(讨论,无编码) → C++精选回顾 + CMake实操 + Vcpkg概念</div></li>
|
||||
<li><a class="day" href="day2.html">Day 2 · Qt 开发环境/信号槽/基础控件/布局与 QSS</a>
|
||||
<div class="desc">p03/ch02–04 项目结构/信号槽 → p03/ch08 控件/布局 + QSS新示例 → 计算器案例</div></li>
|
||||
<li><a class="day" href="day3.html">Day 3 · 高级控件 Model/View / 事件机制 / 文件IO与JSON</a>
|
||||
<div class="desc">Model/View新示例 + p03/ch09 事件拦截 → p03/ch11 文件IO + JSON新示例 → 本地数据存储案例</div></li>
|
||||
<li><a class="day" href="day4.html">Day 4 · 2D绘图/多线程/动画框架与状态机</a>
|
||||
<div class="desc">p03/ch10 2D绘图 + QThread新示例 → 动画+状态机新示例 → 交互式小游戏案例</div></li>
|
||||
<li><a class="day" href="day5.html">Day 5 · 团队协同开发/综合案例复盘/OpenCV可选</a>
|
||||
<div class="desc">团队协同开发(概念,用方案书图) → 综合案例复盘 + OpenCV/AI自学指针</div></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user