Files
scuc-qt-course/docs/teaching/OUTLINE.md
T
张宗平 b89819d5db 新增 4 天教案大纲 + reveal.js 交互幻灯片(docs/teaching/)
大纲按「3天C++强化(含OOP)+1天Qt桌面」组织,讲:练≈1:2.7;每个知识点直接
链接仓库真实代码文件与 ERRATA/VERSION_NOTES 里记录的具体坑。幻灯片
vendor 了 reveal.js 5.2.1(核心+highlight插件),零 CDN 依赖,配 serve.py
本地起服务(仅绑定 127.0.0.1)供离线播放,代码链接可点开查看源码。
2026-07-01 16:51:12 +08:00

277 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 教案大纲 —「Qt 方向」4 天实训
> 配套材料:`docs/teaching/slides/day1.html` … `day4.html`reveal.js 交互幻灯片,已
> vendor 进仓库,离线可用,见文末「材料与播放方式」)。
## 0. 受众假设(显式声明,供随时纠偏)
**本教案假定学员已掌握 C++ 基础语法与面向对象概念(类、继承、多态等术语不陌生),
但未接触过 Qt。** 这是本次问答中用户明确选择的组合:
- 先修水平:「已熟悉 C++/OOP,只是不了解 Qt」
- 4 天主线:「3 天 C++ 强化(含 OOP)+ 1 天 Qt 桌面」
两者合起来的落地方式是:**Part1(C++/OOP)3 天不讲入门语法,改为「强化 / 纠错 /
深挖」定位**——重点讲仓库里已经踩过并记录在 `docs/ERRATA.md` 的真实坑、
`docs/VERSION_NOTES.md` 里的跨版本反直觉结论,以及对象模型、虚函数表这类「学过但
未必讲透」的机制;**Part3(Qt)压缩进 1 天**,定位是「快速建立骨架认知 + 挑一个
方向动手做出一个可跑的小工具」,不追求覆盖全部 29 个目标。
> 如果实际学员水平达不到「已熟悉 C++/OOP」,Day1–3 的讲授部分需要退回到语法讲解,
> 练习任务保持不变但预期完成度应下调——教师可据此现场调整,无需改讲义结构。
## 1. 总体目标
1. 学员能解释 C++ 相对 C 的关键扩展机制的**原理**(不是背语法),并能指出这些语法在
哪个标准版本才成立/失效。
2. 学员能独立分析并修正一段有缺陷的 C++/Qt 代码(本仓库的 `ERRATA*.md` 就是活教材)。
3. 学员能读懂并扩展一个 Qt 桌面小程序(信号槽、控件、事件、绘图、文件 IO 至少覆盖一类)。
4. 每天结束都有一个**可运行、可验证**的产出(不是抄书面练习)。
## 2. 课时结构(讲:练 ≈ 1:3 的落地方式)
每天 6 课时(45 分钟/课时 ×6 = 270 分钟),分两个模块,模块内部再拆讲/练/答疑:
| 模块 | 时长 | 讲授 | 练习 | 小结/答疑 | 讲:练 |
|---|---|---|---|---|---|
| 模块 A(上午,3 课时) | 135min | 35min | 95min | 5min | ≈1:2.7 |
| 模块 B(下午,3 课时) | 135min | 35min | 95min | 5min | ≈1:2.7 |
全天讲授 70min / 练习 190min,比例 ≈1:2.7,接近题目要求的 1:3;`docs/teaching/slides/`
每天的 HTML 幻灯片按「模块 A 讲 → 模块 A 练习任务卡 → 模块 B 讲 → 模块 B 练习任务卡」
组织,练习任务卡本身就是幻灯片的一部分(放投影上,学员对着做),教师在 95min 内巡场答疑。
## 3. 四天总览
| Day | 主题 | 模块 A | 模块 B |
|---|---|---|---|
| 1 | C++ 对 C 的扩展 + 类和对象基础 | `p01/ch02` `p01/ch03` 强化精讲 | `p01/ch04/s01s03` 封装/构造析构 |
| 2 | OOP 进阶 | `p01/ch04/s04s06` 对象模型/友元/运算符重载 | `p01/ch04/s07s08` 继承/多态 |
| 3 | 泛型与标准库 | `p01/ch05` 模板 + `p01/ch06` 类型转换 | `p01/ch07` 异常 + `p01/ch08` IO流 + `p01/ch09` STL |
| 4 | Qt 桌面全景 | `p03/ch02ch05` 项目/信号槽/主窗口 | `p03/ch06` `p03/ch08ch11` 对话框/控件/事件/绘图/文件(速览+分组选做) |
标注约定:下文表格中 **讲** = 课堂演示讲解,**练** = 学员必须动手完成,**参考** = 不
在课堂展开、留作课后自学(文件仍在仓库里,链接照给)。24 课时不可能覆盖全部 173 个
目标,所有清单都是**精选**而非全集,未点名文件默认按「参考」处理。
---
## Day 1 — C++ 对 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` 挑 810 个文件(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:类和对象基础
**讲(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` |
**练(95min**
- 任务卡 1:给一个自定义类实现 `operator=`(配合深拷贝)+ `operator==`,参照
`overload_assignment.cpp` / `overload_equality.cpp`。
- 任务卡 2:实现一个重载了 `operator[]` 的简单容器类(参照
`overload_pointer_operator.cpp` 的思路),做边界检查。
- 任务卡 3(选做):`overload_with_friend.cpp`——用友元函数实现 `operator<<`
讨论“为什么流输出运算符通常不能是成员函数”。
### 模块 B:继承与多态
**讲(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 析构的内存泄漏演示 |
**练(95min**
- 任务卡 1:跑 `upcast_problem.cpp`,观察未加 `virtual` 时的行为,然后手动加上
`virtual` 关键字重新编译对比输出差异。
- 任务卡 2:设计一个含虚析构的基类 + 2 个派生类,用基类指针数组管理对象,验证
析构顺序正确、无内存泄漏(可选 `valgrind` 或简单打印计数)。
- 任务卡 3(选做):`diamond_virtual_inheritance.cpp` 与 `diamond_virtual_inheritance_2.cpp`
对比普通继承 vs 虚继承的成员地址/构造次数。
**当日产出验证**:一个「基类指针管理派生类对象、正确释放」的可运行小程序。
---
## Day 3 — 泛型与标准库
### 模块 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:项目结构 / 第一个程序 / 信号槽 / 主窗口
**讲(35min**
| 知识点 | 文件 |
|---|---|
| 最小 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` |
**练(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` 搭一个「中心区+侧边栏」的窗口骨架。
### 模块 B:对话框 / 控件 / 事件 / 绘图 / 文件(速览 + 分组选做)
**讲(35min,五个方向各约 7min 速览,只给「这个方向能做什么」的骨架认知)**:
| 方向 | 文件 |
|---|---|
| 对话框(模态/非模态/文件/消息框) | `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` |
**练(95min,三选一,分组完成,组内可互相帮忙)**:
- **方案 A:图片查看器雏形** —— `label_pixmap.cpp` + `file_dialog.cpp`:用文件对话框选图片,
加载到 `QLabel` 显示。
- **方案 B:简易绘图板** —— `qpainter_basic.cpp` + `mouse_event.cpp`:监听鼠标拖动事件,
用 `QPainter` 画出轨迹。
- **方案 C:文本文件小工具** —— `qfile_basic.cpp` + `qtextstream_io.cpp`:读一个文本文件、
统计行数/字符数,写回一个新文件。
**当日产出验证**:每组现场用 `QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>`(或有
X 环境时直接运行)演示自己选做的方案,教师验收「编译通过 + 交互符合预期」。
---
## 4. 验证与考核方式
- 每天结束前 10 分钟(占用小结时间的延伸,或作为课后作业):学员提交当天练习任务卡的
产出文件,用 `cmake --build build` 验证全绿。
- Day4 结束:三选一小工具的现场演示,作为本次实训的最终考核点。
- 教师侧参考答案:所有练习任务的「正确写法」在仓库对应文件里已经存在(生成器产出即
正确版本),可直接用作评分参照;`docs/ERRATA*.md` 记录了每一处修正,可用于讲解
“错在哪、对在哪”。
## 5. 材料与播放方式
- 大纲:本文件 `docs/teaching/OUTLINE.md`。
- 交互幻灯片:`docs/teaching/slides/day1.html` … `day4.html``index.html` 为入口页。
- 技术方案:reveal.js 5.2.1(仅 `dist/` 核心 + `plugin/highlight`)已 vendor 进
`docs/teaching/slides/reveal/`,**零 CDN 依赖**,断网教室也能播放。
- 播放方式(代码链接用相对路径指向仓库源文件,需要 HTTP 而非 `file://` 才能正常跳转):
```bash
cd /home/charles/workspaces/qt-course
python3 -m http.server 8000
# 浏览器打开 http://localhost:8000/docs/teaching/slides/index.html
```
- 幻灯片内所有「文件」链接均为相对本仓库路径的可点击链接,点开后由浏览器直接展示
源码(配合上面的 http.server);教师也可直接在编辑器里打开同一路径带学员精读。