b89819d5db
大纲按「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)供离线播放,代码链接可点开查看源码。
277 lines
16 KiB
Markdown
277 lines
16 KiB
Markdown
# 教案大纲 —「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/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` 对话框/控件/事件/绘图/文件(速览+分组选做) |
|
||
|
||
标注约定:下文表格中 **讲** = 课堂演示讲解,**练** = 学员必须动手完成,**参考** = 不
|
||
在课堂展开、留作课后自学(文件仍在仓库里,链接照给)。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` 挑 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:类和对象基础
|
||
|
||
**讲(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);教师也可直接在编辑器里打开同一路径带学员精读。
|