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

16 KiB
Raw Blame History

教案大纲 —「Qt 方向」4 天实训

配套材料:docs/teaching/slides/day1.htmlday4.htmlreveal.js 交互幻灯片,已 vendor 进仓库,离线可用,见文末「材料与播放方式」)。

0. 受众假设(显式声明,供随时纠偏)

本教案假定学员已掌握 C++ 基础语法与面向对象概念(类、继承、多态等术语不陌生), 但未接触过 Qt。 这是本次问答中用户明确选择的组合:

  • 先修水平:「已熟悉 C++/OOP,只是不了解 Qt」
  • 4 天主线:「3 天 C++ 强化(含 OOP)+ 1 天 Qt 桌面」

两者合起来的落地方式是:Part1C++/OOP)3 天不讲入门语法,改为「强化 / 纠错 / 深挖」定位——重点讲仓库里已经踩过并记录在 docs/ERRATA.md 的真实坑、 docs/VERSION_NOTES.md 里的跨版本反直觉结论,以及对象模型、虚函数表这类「学过但 未必讲透」的机制;Part3Qt)压缩进 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.cppfunction_overload_basics_2.cppERRATA [58954439:42] ambiguous-overload 名字改写(name mangling)与重载集合,为什么会出现二义性
const 的本质 vs #define p01/ch03/const_diff_summary.cppconst_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(版本探针实操):
    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.cppcube_class_design.cpp 为起点, 新增一个成员函数(如计算面积/体积),编译运行验证。
  • 任务卡 2:修改 deep_vs_shallow_copy.cpp,故意触发浅拷贝析构两次释放同一块内存, 用 run.sh 观察崩溃或输出异常,再改回深拷贝对比。
  • 任务卡 3:参照 static_singleton.cpp 独立写一个单例类(禁止拷贝构造)。

当日产出验证cmake --build build --target all_p01_ch03all_p01_ch04_s03 全绿;至少完成 1 个浅拷贝崩溃复现 + 修复对比。


Day 2 — OOP 进阶

模块 A:对象模型 / 友元 / 运算符重载

讲(35min

知识点 文件
this 指针与常量成员函数 p01/ch04/s04/this_pointer.cppconst_member_function.cpp
友元破坏封装的取舍 p01/ch04/s05/friend_syntax.cpp
运算符重载全景(=, ==, [], (), ++/-- p01/ch04/s06/overload_assignment.cppoverload_equality.cppoverload_function_call.cppoverload_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.cppupcast_problem.cpp 用基类指针 delete 派生类对象不加 virtual 析构的内存泄漏演示

练(95min

  • 任务卡 1:跑 upcast_problem.cpp,观察未加 virtual 时的行为,然后手动加上 virtual 关键字重新编译对比输出差异。
  • 任务卡 2:设计一个含虚析构的基类 + 2 个派生类,用基类指针数组管理对象,验证 析构顺序正确、无内存泄漏(可选 valgrind 或简单打印计数)。
  • 任务卡 3(选做):diamond_virtual_inheritance.cppdiamond_virtual_inheritance_2.cpp 对比普通继承 vs 虚继承的成员地址/构造次数。

当日产出验证:一个「基类指针管理派生类对象、正确释放」的可运行小程序。


Day 3 — 泛型与标准库

模块 A:模板 + 类型转换

讲(35min

知识点 文件
函数模板 vs 类模板 p01/ch05/function_template.cppclass_template_basics.cpp
模板的局限性 p01/ch05/template_limitations.cpptemplate_vs_function.cpp
四种显式类型转换 p01/ch06/static_cast_demo.cppdynamic_cast_demo.cppconst_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.cppstack_unwinding.cpp
自定义异常类 / std::exception 体系 p01/ch07/custom_exception_class.cppstd_exception_library.cpp
文件 IO(文本/二进制) p01/ch08/ascii_file_io.cppbinary_file_io.cpp
STL 三大组件速览 p01/ch09/s02/stl_components_case.cpp
常用容器 + 迭代器 p01/ch09/s03/vector_iterator.cpplist_data_structure.cpp
算法 + 函数对象 p01/ch09/s04/predicate.cppfunction_object.cpptraversal_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.cppcustom_signal_slot.cpplambda_signal_slot.cpp
QMainWindow 中心部件与停靠窗口 p03/ch05/central_widget.cppdock_widget.cpp

练(95min

  • 任务卡 1:编译运行 minimal_qt_appbutton_creation(先用离屏模式验证再选做真实窗口):
    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.cppfile_dialog.cppmessage_box_question.cpp
常用控件(图片/文本标签、自定义控件) p03/ch08/label_pixmap.cpplabel_text_html.cppcustom_widget.cpp
事件机制(鼠标事件、事件过滤器) p03/ch09/mouse_event.cppevent_filter_mainwindow.cpp
绘图(QPainter、绘图设备) p03/ch10/qpainter_basic.cpppaint_device_qimage.cpp
文件系统(QFile/QTextStream/QDataStream p03/ch11/qfile_basic.cppqtextstream_io.cppqdatastream_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.htmlday4.htmlindex.html 为入口页。
  • 技术方案:reveal.js 5.2.1(仅 dist/ 核心 + plugin/highlight)已 vendor 进 docs/teaching/slides/reveal/零 CDN 依赖,断网教室也能播放。
  • 播放方式(代码链接用相对路径指向仓库源文件,需要 HTTP 而非 file:// 才能正常跳转):
    cd /home/charles/workspaces/qt-course
    python3 -m http.server 8000
    # 浏览器打开 http://localhost:8000/docs/teaching/slides/index.html
    
  • 幻灯片内所有「文件」链接均为相对本仓库路径的可点击链接,点开后由浏览器直接展示 源码(配合上面的 http.server);教师也可直接在编辑器里打开同一路径带学员精读。