Compare commits

...

5 Commits

Author SHA1 Message Date
张宗平 6640baffae 堂前测试重做:C++98 双平行卷(A/B)+ 三级难度梯度
原版 10 题单卷难度近似、缺梯度、无平行卷,区分度不足。新版改为两份等价
平行卷(防抄袭 / 可重测),按 L1 初级 / L2 中级 / L3 进阶三级分布,补齐
对象切片、菱形继承二义、const 与指针等真正能筛出高阶学员的考点;严格限定
C++98,不涉及现代特性。所有代码题输出经 g++ -std=c++98 -pedantic-errors
实测核实,修正了 B 卷第 2 题链式 << 求值顺序未定义的缺陷(改为三行输出)。
2026-07-04 13:31:33 +08:00
张宗平 9765865d13 Day1 C++ 回顾补一小节:C++11/14/17/20+ 新标准巡礼
原「C++ 环境搭建/高级程序回顾」只覆盖语言老知识点(namespace/重载/引用/
虚析构/模板),没有让学员感受到 C++ 这些年一直在大幅进化。新增一个概念性
知识点(无配套代码,同 Vcpkg 那一条的先例):按标准列出 C++11→20 的核心
新特性(auto/智能指针/移动语义/lambda → concepts/ranges/协程/模块),
并提醒"新标准≠生产环境能直接用",挂钩本仓库自身的 C++17 基线选择和
tools/std_probe.py。OUTLINE.md 表格行/核心概念条目与 day1.html 新增幻灯片
一一对应。
2026-07-02 16:41:48 +08:00
张宗平 a6743fcea0 每个「讲」知识点补齐核心概念 + 常见的坑,幻灯片与大纲同步
此前一版「讲」部分基本只是代码演示(贴代码 + 一句出处说明),没有讲透
知识点本身的原理。改为覆盖清单原则:每个知识点在大纲里至少固定两项——
核心概念(是什么/为什么,不只是怎么写)+ 常见的坑(注意事项/误区),
课堂实际讲解深度按学员反馈现场调整,但大纲必须列全,以免漏讲或退化成
纯代码演示。

- custom.css 新增 .concept-box/.pitfall-box 卡片样式
- day1–5.html 每个知识点前插入对应卡片,内容与 OUTLINE.md 的覆盖清单一一对应
2026-07-02 16:25:46 +08:00
张宗平 ee902fb1e0 幻灯片 index.html 里的 markdown 引用补上可点击链接
配套大纲、方案书全文此前只是纯文本 <code> 提及,无法点击跳转;改成
<a> 链接(相对路径),配合 serve.py 的 .md 自动渲染即可正常打开。
2026-07-02 16:24:50 +08:00
张宗平 1f66098277 Qt 示例窗口默认不再自动退出,仅离屏验证时自动退出
p03/ 生成产物与 docs/teaching/examples/ 手工示例里的 QTimer::singleShot
自动退出逻辑,从"始终 200ms 后退出"改为"仅当 QT_QPA_PLATFORM=offscreen
时才退出"。正常运行(有显示环境)窗口保持打开,需手动关闭;离屏自动化
验证(tools/run_qt.sh + QT_QPA_PLATFORM=offscreen)行为不变。

p03/ 改动来自重新生成 tools/gen_part3.py(AUTO_QUIT 常量加 qgetenv 判断),
未手改生成产物。QCoreApplication 控制台风格示例(qfile_basic 等)本就
执行完即退出,不受影响。
2026-07-02 16:14:49 +08:00
44 changed files with 1282 additions and 220 deletions
+2 -1
View File
@@ -64,7 +64,8 @@ QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
> **隔离验证**`ldd build/p03/.../<target>` 显示所有 Qt 库只解析到 `.qt514/`
> 零系统 Qt 引用。顶层 `CMakeLists.txt` 有版本断言——误连系统 Qt 5.15 会 `FATAL_ERROR`。
> 每个 Qt GUI 目标注入 `QTimer::singleShot` 让程序 200ms 后自动退出,便于离屏验证。
> 每个 Qt GUI 目标注入 `QTimer::singleShot`,仅当 `QT_QPA_PLATFORM=offscreen` 时才在
> 200ms 后自动退出,便于离屏验证;正常运行窗口保持打开,不自动退出。
## 目录命名(深缩写)
+127
View File
@@ -40,6 +40,15 @@
- 如果后续需要这两项也做成可运行示例,请明确提出——这会涉及新增外部依赖,
需要单独确认。
**核心概念覆盖原则**(本轮追加):此前一版的"讲"部分基本只是代码演示(贴代码 + 一句出处
说明),没有讲透知识点本身的原理。本轮改为:**每个"讲"知识点在大纲里至少固定两项——核心
概念(是什么/为什么,不只是怎么写)+ 常见的坑(注意事项/误区)**,随知识点内联在下面各天
的表格后。这是**覆盖清单**而非固定讲稿:课堂实际讲解深度按学员反馈现场调整(PRETEST 摸底
好、进度快,可以点到为止;反馈吃力,可以就某个概念多展开几分钟),但大纲必须列全这些点,
以免讲课时漏掉、或降级回退到纯代码演示。幻灯片 `day1.html``day5.html` 每个知识点前都配了
对应的"核心概念/常见的坑"卡片(`custom.css` 里的 `.concept-box`/`.pitfall-box`),大纲里的
条目和幻灯片内容一一对应,改一处要同步改另一处。
## 1. 总体目标
1. 学员能说出 C++ 相对 C 的关键扩展机制在**哪个标准版本**成立/失效(复习向,
@@ -92,6 +101,15 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| 敏捷 Scrum 项目开发管理 | 同上「服务流程图」里的迭代环节(需求分析→需求评审→项目研发→阶段评审→项目测试) |
| C++ 在企业中的应用 | 口头介绍,结合往届案例(如有) |
**核心概念与常见误区**
- **实习实训服务流程**:概念——需求分析→需求评审→项目研发/代码实现→阶段评审→项目测试/交付,
每环节都有明确"评审"节点把关;误区——把需求分析当走过场,跳过评审直接编码,后期返工成本
远高于前期对齐需求的成本。
- **四平台一标准**:概念——JIRA/Git/Wiki/OASIS 四个工具分别对应"谁在做什么/代码怎么变化/
知识怎么沉淀/进度怎么被看到"四个维度,缺一个都有协作盲区;误区——只用 Git 不用 Jira,代码
有版本历史但任务分工和进度没有可见记录。
**讨论(95min,分组,非编码)**
- **任务卡 1**
@@ -114,9 +132,34 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| 引用的本质 | `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` | 泛型编程 + 容器/迭代器,只点名不展开 |
| C++11/14/17/20+ 新标准巡礼(概念,无演示) | 无仓库代码 | 让学员看到 C++ 不是停滞的语言:auto/智能指针/移动语义/lambda(11) → concepts/ranges/协程(20),每 3 年一次实打实的能力升级 |
| CMake 项目构建 | 本仓库 `CMakeLists.txt``p03/CMakeLists.txt` | 用真实项目当教材:`option(BUILD_QT_PART)``add_subdirectory`、每个示例一个 `add_executable` 目标 |
| Vcpkg 包管理器(概念,无演示) | 无仓库代码 | C++ 生态的包管理器,解决"第三方库怎么装、怎么被 CMake 找到"的问题;本仓库用的是"隔离 Qt 手动装"而非 vcpkg,两种思路都要认识 |
**核心概念与常见的坑**(每个知识点至少覆盖这两项,课堂讲解深度按学员反馈调整;对应幻灯片
`day1.html` 每个知识点前的"核心概念/常见的坑"卡片):
- **namespace 作用域**:概念——作用域可跨文件延续、跨块合并,不是"文件顺序";坑——头文件里
`using namespace std;` 会把命名空间污染带给所有 include 它的地方。
- **函数重载决议**:概念——编译器靠名字改写(name mangling)区分重载,二义性源于"同样好"的
候选而非代码写错;坑——隐式类型转换会扩大候选集,可能触发意料之外的重载版本。
- **引用的本质**:概念——"自动解引用的 const 指针"这一视角能解释必须初始化、不能重新绑定;
坑——返回局部变量的引用,函数结束后变成悬空引用。
- **虚析构与内存泄漏**:概念——基类指针 delete 派生类对象时,只有虚析构才会走虚函数表调用到
派生类析构;坑——只要类可能被继承+可能被基类指针删除,就该把析构声明为 virtual。
- **模板与 STL 速览**:概念——泛型编程让代码脱离具体类型复用,迭代器是容器与算法之间的统一
接口;坑——容器扩容/删除元素后旧迭代器可能失效,继续用是未定义行为。
- **C++11/14/17/20+ 新标准巡礼**:概念——C++11auto/智能指针/移动语义/lambda/语言级线程)
→ C++14(泛型 lambda/返回类型推导)→ C++17(结构化绑定/if-init/optional·variant·string_view/
filesystem)→ C++20concepts/ranges/协程/模块/三路比较),每个标准都是实打实的能力升级,
不是小修小补;坑——新标准不等于现在能用,生产环境编译器/CI 常年落后最新标准 1–2 个版本,
用新特性前先确认目标工具链 `-std=` 支持到哪(可用 `tools/std_probe.py` 自己验证)。
- **CMake 项目构建**:概念——CMake 是"生成构建系统的工具"而非构建系统本身,`-S -B` 生成、
`--build` 才是真正编译;坑——改了生成器相关配置(换生成器/清缓存)后要重新走一次 `-S -B`
- **Vcpkg 包管理器**:概念——一次 `vcpkg install` 配合 CMake toolchain 文件,`find_package`
即可自动定位头文件和库;坑——不是所有第三方库都必须用包管理器装,本仓库选择"隔离 Qt 手动装"
是精确控制版本/路径的另一种取舍,两种思路都要认识。
**练(95min**
- **任务卡 1 · 走一遍真实构建流程**
@@ -149,6 +192,19 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| 控件创建与显示 | `p03/ch03/button_creation.cpp` |
| 内置 / 自定义 / lambda 信号槽 | `p03/ch04/builtin_signal_slot.cpp``custom_signal_slot.cpp``lambda_signal_slot.cpp` |
**核心概念与常见的坑**(对应幻灯片 `day2.html` 每个知识点前的"核心概念/常见的坑"卡片):
- **最小 Qt 程序结构**:概念——`QApplication`是唯一的事件循环拥有者,`app.exec()`阻塞进入
事件循环分发系统事件;`Q_OBJECT`触发 moc 生成信号槽/属性系统所需的元对象代码;坑——一个
进程只能有一个 `QApplication`;忘记 `exec()` 窗口一闪而过;用了信号槽却忘记 `Q_OBJECT`
会在链接阶段报 `undefined reference` 且报错不提示"缺宏"。
- **控件创建与显示**:概念——QObject 对象树父子所有权模型,parent 销毁会自动 delete 所有
子对象;坑——忘记传 parent 造成真实内存泄漏;`move()` 手动定位的控件一旦套上布局会被覆盖。
- **信号槽(内置/自定义/lambda)**:概念——观察者模式的 Qt 实现,发送者/接收者彻底解耦;
新语法 `&Class::signal` 编译期类型检查,旧字符串语法 `SIGNAL()/SLOT()` 运行时检查;连接
方式分 Direct/Queued/Auto;坑——旧语法写错不报编译错误只是运行时静默连接失败;重载信号
不加 `QOverload` 消歧义会编译报错。
**练(95min**
- **任务卡 1 · 跑通最小程序**
@@ -174,6 +230,18 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| 自定义控件与布局(`QHBoxLayout` | `p03/ch08/custom_widget.cpp` |
| **QSS 界面美化(教学补充)** | `docs/teaching/examples/qss_styling/qt_qss_styling.cpp` |
**核心概念与常见的坑**
- **资源系统 .qrc**:概念——`:/...` 路径是 Qt 资源系统,资源在编译期打包进可执行文件,运行时
不依赖外部文件;坑——`.qrc` 路径与代码引用不一致会运行时静默失败(不报编译错误);忘记接入
CMake 的 `qt5_add_resources`/AUTORCC 资源系统不生效。
- **布局管理(QHBoxLayout)**:概念——布局管理器接管子控件位置/大小计算,随窗口缩放自动重排;
布局对象也遵循 QObject 父子所有权;坑——同一控件不能属于两个布局;布局设置后又用
`setGeometry`/`move` 手动改位置会被下次刷新覆盖。
- **QSS 界面美化**:概念——语法脱胎于 CSS(类型/ID 选择器、伪状态)+ 层叠继承规则,作用对象
是 QWidget 树而非 DOM;坑——ID 选择器必须先 `setObjectName``setStyleSheet` 会级联影响
所有子控件,范围失控是最容易踩的坑。
**练(95min,计算器案例——练习任务,非预建方案)**
- **任务卡 1 · 计算器雏形**
@@ -203,6 +271,18 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| 自定义事件处理 / 事件过滤器 | `p03/ch09/event_override_customwidget.cpp``event_filter_mainwindow.cpp` |
| 鼠标事件 | `p03/ch09/mouse_event.cpp` |
**核心概念与常见的坑**(对应幻灯片 `day3.html`):
- **Model/View 架构**:概念——数据(`QAbstractItemModel`)与展示(`QListView`/`QTableView`
彻底拆开,同一模型可喂给多个视图;`QModelIndex` + 角色(`Qt::DisplayRole`等)统一寻址;
坑——改模型数据不发 `dataChanged`/`beginInsertRows` 信号,视图不会自动刷新。
- **事件循环:事件 vs 信号**:概念——`app.exec()` 事件循环分发 `QEvent`(复用 Day2);事件是
框架推给控件的通知,信号槽是对象间主动通知,两者不同机制;坑——主线程做耗时操作会卡住整个
事件循环(呼应 Day4 QThread)。
- **自定义事件处理 / 事件过滤器**:概念——两条拦截路径:重写 `xxxEvent()`(只影响自己)或
`installEventFilter()`(统一拦截多个控件);事件有 `accept()`/`ignore()` 状态决定是否继续
传播;坑——重写事件函数忘记调用基类实现,会丢失控件默认行为。
**练(95min**
- **任务卡 1 · Model/View 换视图**
@@ -224,6 +304,16 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| 结构化二进制读写 | `p03/ch11/qdatastream_io.cpp` |
| **JSON 解析与写入(教学补充)** | `docs/teaching/examples/json_io/qt_json_parse_write.cpp` |
**核心概念与常见的坑**
- **QIODevice 统一读写抽象**:概念——`QFile`/`QTextStream`/`QDataStream` 都建立在 `QIODevice`
统一接口上,之后遇到网络/串口等设备也是同一套接口;`QTextStream`面向文本行、`QDataStream`
面向结构化二进制;坑——忘记检查 `open()` 返回值会静默失败;混淆文本/二进制模式会破坏内容。
- **JSON 序列化/反序列化**:概念——序列化/反序列化是一对互逆操作,正确性标准是"写出去再读
回来数据一致"round-trip);`QJsonValue` 是能装多种类型的变体;坑——不检查
`QJsonParseError` 会让格式错误的输入静默变成空文档;类型不匹配 `toInt()`/`toString()`
不报错只返回默认值。
**练(95min,本地数据存储案例——练习任务,非预建方案)**
- **任务卡 1 · JSON 落盘**
@@ -251,6 +341,16 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| 绘图设备(`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` |
**核心概念与常见的坑**(对应幻灯片 `day4.html`):
- **QPainter 绘图**:概念——Qt 绘制是框架驱动而非"想画就画",所有绘制只能发生在
`paintEvent` 里,由 Qt 按需自动调用;主动触发重绘要用 `update()`(异步排队);坑——在
`paintEvent` 之外直接画东西是未定义行为;数据变了忘记 `update()`界面不会自动重画。
- **QThread 多线程**:概念——线程亲和性(每个 QObject 归属于创建/`moveToThread`到的线程,
GUI 控件必须归属主线程);官方推荐 worker-object 模式而非继承 QThread;跨线程信号槽自动
变 Queued,无需手动加锁;坑——在工作线程直接操作 GUI 控件是未定义行为/崩溃常见来源;
`moveToThread`要在`connect``start()`之前完成。
**练(95min**
- **任务卡 1 · 画一个简单图形界面**
@@ -271,6 +371,17 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| **QPropertyAnimation 动画(教学补充)** | `docs/teaching/examples/property_animation/qt_property_animation.cpp` |
| **QStateMachine 状态机(教学补充)** | `docs/teaching/examples/state_machine/qt_state_machine.cpp` |
**核心概念与常见的坑**
- **QPropertyAnimation 动画**:概念——动画建立在属性系统(`Q_PROPERTY`,同样靠 moc 生成)
上,本质是"用缓动曲线在一段时间内不断调用属性的 setter",任何 `Q_PROPERTY` 都能被动画驱动;
坑——属性必须是真正的 `Q_PROPERTY`,随手传错属性名不会报错只是什么也不发生;动画作用对象
在播放完成前被销毁会导致悬空访问。
- **QStateMachine 状态机**:概念——让"状态"成为一等对象(`QState`),替代一堆 bool 标志位加
if-else;状态转移(`addTransition`)本质是信号触发的转移,与信号槽自然衔接;坑——忘记
`setInitialState``start()`,状态机对象存在但从未真正运行;状态未覆盖所有触发条件会导致
"卡住"。
**练(95min,交互式小游戏案例——练习任务,选做,非预建方案)**
- **任务卡 1 · 动画 + 状态机组合**
@@ -299,6 +410,14 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| 标准化考核标准 | `docs/images/assessment_standard.png` |
| 代码团队协同开发实操建议 | `docs/TRAINING_PLAN_2026.md` 「七、质量保证 (二) Git」 |
**核心概念与常见误区**(对应幻灯片 `day5.html`):
- **Git 提交习惯**:概念——每完成一个任务都提交代码并写清任务描述,提交粒度对应 Jira 任务
粒度,commit 信息是可追溯记录;误区——攒几天代码一次性提交、commit 信息写"update",历史
记录失去可追溯性。
- **标准化考核标准**:概念——项目管理评估(40%)+项目技术评估(30%)+职业素养评估(30%),技术
只占三成;误区——只顾埋头写代码、忽略项目管理和职业素养维度,另外 70% 权重被拉低。
**讨论(95min**
- **任务卡 1 · 分工复盘**
@@ -315,6 +434,14 @@ Day25 讲:练 ≈ 1:2.7Day1 模块 A 是讨论型任务(不追求"练"
| Day2–4 案例回顾 | 请 1–2 组现场展示计算器/本地存储/动画状态机小游戏 |
| OpenCV 与 AI 机器学习(可选,自学指针,无配套代码) | 方案书原文标注"可选择";如学员感兴趣,自学方向为 `cv::CascadeClassifier` 人脸检测入门,<span class="ext-ref">OpenCV 官方教程(需联网,选读)</span> |
**核心概念与常见误区**
- **案例回顾**:概念——代码走查(code review)是团队协作常规环节,讲清"为什么这么设计"比
"功能能跑"更能体现对 Day2–4 核心概念的理解;误区——只演示能跑不讲为什么,观摩组提不出有效
问题,复盘失去巩固概念的作用。
- **OpenCV 与 AI(可选)**:概念——OpenCV 是独立于 Qt 的计算机视觉库,Qt 管界面/交互、
OpenCV 管图像算法,两者通过 `cv::Mat``QImage` 转换配合。
**练(95min,综合演练)**
- **任务卡 1 · 三选一深化**
+323 -86
View File
@@ -1,56 +1,108 @@
# C++ 基础摸底小测验(10 题,预计 10 分钟)
# C++ 基础摸底小测验(10 分钟 / 10 题 · 两份等价平行卷
> 用途:验证 `docs/teaching/OUTLINE.md` §0 里"学员已掌握 C++ 基础语法与 OOP
> 概念"这条假设是否成立。建议在 **Day1 模块 B 开始前**(C++ 回顾环节之前)
> 发给学员,闭卷、不查资料,10 分钟内独立完成。
> **用途**:验证 [`OUTLINE.md`](OUTLINE.md) §0 里"学员已掌握 C++ 基础语法与 OOP 概念"这条假设
> 是否成立。建议在 **Day1 模块 B 开始前**(C++ 回顾环节之前)发给学员,闭卷、不查资料,
> **10 分钟内独立完成**
>
> 题型:选择题 + 代码阅读(判断输出/找 bug)+ 1 道简答,覆盖 Day1 回顾要点
> 涉及的引用、重载、构造析构顺序、虚函数多态、运算符重载、模板、STL、
> const、深浅拷贝九个知识点
> **本卷语言基线 = C++ 98**:刻意不涉及任何 C++11/14/17/20+ 现代特性(auto / 智能指针 /
> 移动语义 / lambda / override / 列表初始化等)。目的是先把"经典 C++ / OOP 这块根基本身
> 是否扎实"测清楚——这是 Qt 课程 C++ 部分的真正地基;现代特性的回顾放在测验之后、随堂进行
>
> **两份平行卷(A / B)**:覆盖**相同的知识点梯度、相同的难度分布**,题目平行但不雷同,
> 用于以下场景:
> - **防抄袭**:相邻座席发不同卷(A/B 交错);
> - **重测**:对摸底结果存疑的学员,换卷再测一次,两次难度可比;
> - **平行对照**:A 卷统一当堂测、B 卷留作补测/作业,便于横向比较。
>
> **三级难度分布(每份卷 10 题)**:
>
> | 层级 | 题号 | 能力定位 | 答错意味着 |
> |---|---|---|---|
> | **L1 初级**(3 题)| 1–3 | 记得住、读得懂基础语法 | 根基明显不足,Day1 需回退到语法讲解 |
> | **L2 中级**4 题)| 4–7 | OOP 三大特性、内存模型有概念 | 概念有但没吃透,讲课时在对应点加时 |
> | **L3 进阶**3 题)| 8–10 | 多态细节、继承陷阱、能分析真题 | 达到"高阶"假设,可全速推进 |
>
> 题型:**选择题 + 代码阅读(判断输出/找 bug)**。每题标注建议用时,加起来约 10 分钟。
---
## 第 1 题(选择,30 秒)
# A 卷
下列关于"引用"和"指针"的说法,**错误**的是?
## 【L1 · 第 1 题】选择(30 秒)· 引用基础
A. 引用必须在定义时初始化
B. 引用可以为 `nullptr`
C. 引用一旦绑定就不能再指向其他对象
D. 函数参数用引用可以避免拷贝开销
下列关于"引用(reference"和"指针(pointer"的说法,**错误**的是?
## 第 2 题(代码阅读,1 分钟)
- A. 引用必须在定义时初始化
- B. 引用可以绑定到 `nullptr`
- C. 引用一旦绑定到一个对象,就不能再改绑到另一个对象
- D. 函数参数用引用传递,可以避免实参被拷贝
## 【L1 · 第 2 题】代码阅读(45 秒)· static 局部变量
```cpp
void f(int x) { std::cout << "int " << x; }
void f(double x) { std::cout << "double " << x; }
int main() { f(3.0f); }
#include <iostream>
int counter() {
static int n = 0; // 只在第一次进入时初始化一次
return ++n;
}
int main() {
std::cout << counter() << counter() << counter();
}
```
这段代码输出什么?
请写出程序的输出。
## 第 3 题(选择,30 秒)
## 【L1 · 第 3 题】代码阅读(45 秒)· 值传递 vs 引用传递
```cpp
#include <iostream>
void f(int x) { x = x + 1; } // 形参 x 是实参的副本
int main() {
int n = 10;
f(n);
std::cout << n;
}
```
请写出程序的输出。
## 【L2 · 第 4 题】代码阅读(1 分钟)· 函数重载决议
```cpp
#include <iostream>
void h(long) { std::cout << "L"; }
void h(double) { std::cout << "D"; }
int main() { h(5); }
```
这段代码会发生什么?(编译通过则写出输出;不能编译则说明原因)
## 【L2 · 第 5 题】选择(45 秒)· 虚函数与多态
关于虚函数,下列说法**正确**的是?
A. 基类析构函数不加 `virtual`,通过基类指针 `delete` 派生类对象一定不会有问题
B. 构造函数可以虚函数
C. 纯虚函数所在的类不能被直接实例化
D. 派生类必须重写基类每一个虚函数
- A. 只要基类析构函数不加 `virtual`,通过基类指针 `delete` 派生类对象一定有问题
- B. 构造函数可以声明为虚函数
- C. 含有纯虚函数`= 0`)的抽象类不能被直接实例化
- D. 派生类必须重写基类中的**每一个**虚函数,否则编译报错
## 第 4代码阅读1 分钟)
## 【L2 ·6代码阅读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; }
#include <iostream>
struct M { M(){ std::cout<<"M"; } ~M(){ std::cout<<"~M"; } };
struct Base { Base(){ std::cout<<"B"; } ~Base(){ std::cout<<"~B"; } };
struct Derived : Base {
M m; // 成员对象
Derived(){ std::cout<<"D"; }
~Derived(){ std::cout<<"~D"; }
};
int main(){ Derived d; }
```
请写出输出的完整顺序。
请写出构造与析构的完整输出顺序。
## 第 5找 bug1.5 分钟)
## 【L2 ·7找 bug1.5 分钟)· 浅拷贝与 double free
```cpp
class Buffer {
@@ -63,94 +115,279 @@ public:
void use() {
Buffer a(10);
Buffer b = a; // 用 a 拷贝构造 b
} // a、b 离开作用域
```
`use()` 运行到结束时会发生什么问题?请说明原因。
## 【L3 · 第 8 题】代码阅读(1 分钟)· 对象切片
```cpp
#include <iostream>
struct Base {
virtual const char* name() const { return "Base"; }
};
struct Derived : Base {
const char* name() const { return "Derived"; } // 重写虚函数
};
void print(Base b) { // 注意:按值传递,不是引用/指针
std::cout << b.name();
}
int main() {
Derived d;
print(d);
}
```
`use()` 函数运行到结束时会发生什么问题?请说明原因
请写出程序的输出,并解释为什么不是 `Derived`
## 第 6选择30 秒)
## 【L3 ·9选择1 分钟)· const 与指针
关于函数模板,下列说法**正确**的是?
下列声明中,表示"**指针本身是常量**(不能再指向别的地址),但指针指向的数据可以修改"的是?
A. 模板函数一旦定义,编译器就会为所有可能的类型生成代码
B. 编译器会根据调用时的实参类型自动推导模板参数,并生成对应的实例
C. 模板函数不能被重载
D. 调用模板函数时必须显式指定所有模板参数
- A. `const char* p`
- B. `char const* p`
- C. `char* const p`
- D. `const char* const p`
## 第 7代码阅读1 分钟)
## 【L3 · 第 10代码阅读1.5 分钟)· 多重继承的二义性
```cpp
std::vector<int> v = {3, 1, 4, 1, 5};
for (int x : v) {
if (x % 2 == 0) std::cout << x;
class Base { public: int value; };
class Left : public Base {}; // 普通继承
class Right : public Base {}; // 普通继承
class Derived : public Left, public Right {};
int main() {
Derived d;
d.value = 10; //
}
```
这段代码输出什么?
`d.value = 10;` 这一行能编译通过吗?如果可以,`value` 来自哪条继承链路?如果不能,请说明原因。
## 第 8 题(选择,30 秒)
---
---
下列哪个函数声明表示"这个成员函数不会修改对象的成员变量"?
# B 卷
A. `void print()`
B. `void print() const`
C. `const void print()`
D. `void const print()`
> 与 A 卷一一对应、考点相同、难度相当的平行卷。
## 第 9(代码阅读,1 分钟)
## 【L1 ·1】选择(30 秒)· 指针基础
下列关于"指针"的说法,**错误**的是?
- A. 指针可以不初始化(此时其值未定义)
- B. 指针可以在生命周期内先后指向不同的对象
- C. 指针变量本身在内存中也占用存储空间
- D. 用 `delete` 释放一个指针后,该指针自动变成空指针 `nullptr`
## 【L1 · 第 2 题】代码阅读(45 秒)· static 局部变量
```cpp
class Point {
#include <iostream>
int tick() {
static int n = 100; // 只在第一次进入时初始化一次
return n--;
}
int main() {
std::cout << tick() << "\n";
std::cout << tick() << "\n";
std::cout << tick() << "\n";
}
```
请写出程序的输出(共三行)。
## 【L1 · 第 3 题】代码阅读(45 秒)· 值传递 vs 引用传递
```cpp
#include <iostream>
void f(int& x) { x = x + 1; } // 形参 x 是实参的别名
int main() {
int n = 10;
f(n);
std::cout << n;
}
```
请写出程序的输出。
## 【L2 · 第 4 题】代码阅读(1 分钟)· 函数重载决议
```cpp
#include <iostream>
void h(short) { std::cout << "S"; }
void h(float) { std::cout << "F"; }
int main() { h(7); }
```
这段代码会发生什么?(编译通过则写出输出;不能编译则说明原因)
## 【L2 · 第 5 题】选择(45 秒)· 虚函数与多态
关于虚函数,下列说法**正确**的是?
- A. 基类把析构函数声明为 `virtual`,是为保证 `delete 基类指针` 时派生类部分也能被正确析构
- B. 在构造函数里调用虚函数,会触发派生类的虚函数版本
- C. 派生类重写基类虚函数时,可以任意改变返回类型,不受限制
- D. 一个类只要有虚函数,编译器就**不会**为它生成虚函数表(vtable)
## 【L2 · 第 6 题】代码阅读(1 分钟)· 构造与析构顺序
```cpp
#include <iostream>
struct N { N(){ std::cout<<"1"; } ~N(){ std::cout<<"4"; } };
struct A { A(){ std::cout<<"2"; } ~A(){ std::cout<<"3"; } };
struct C : A {
N n; // 成员对象
C(){ std::cout<<"5"; }
~C(){ std::cout<<"6"; }
};
int main(){ C c; }
```
请写出构造与析构的完整输出顺序。
## 【L2 · 第 7 题】找 bug1.5 分钟)· 浅拷贝与 double free
```cpp
class Stack {
int* data;
int size;
public:
int x, y;
Point operator+(const Point& other) const {
return Point{x + other.x, y + other.y};
}
Stack(int n) : size(n) { data = new int[n]; }
~Stack() { delete[] data; }
};
Point p1{1, 2}, p2{3, 4};
Point p3 = p1 + p2;
std::cout << p3.x << "," << p3.y;
void use() {
Stack a(10);
Stack b = a; // 用 a 拷贝构造 b
} // a、b 离开作用域
```
这段代码输出什么?
`use()` 运行到结束时会发生什么问题?请说明原因。
## 第 10 题(简答,1.5 分钟)
## 【L3 · 第 8 题】代码阅读(1 分钟)· 对象切片
用一两句话说明"深拷贝"和"浅拷贝"的区别,以及为什么一个类里如果有指针成员、
又没有自己实现拷贝构造函数,可能会引发问题。
```cpp
#include <iostream>
struct Animal {
virtual const char* sound() const { return "..."; }
};
struct Dog : Animal {
const char* sound() const { return "Woof"; } // 重写虚函数
};
const char* record(Animal a) { // 注意:按值传递,不是引用/指针
return a.sound();
}
int main() {
Dog d;
std::cout << record(d);
}
```
请写出程序的输出,并解释为什么不是 `Woof`
## 【L3 · 第 9 题】选择(1 分钟)· const 与指针
下列声明中,表示"**指针指向的数据是常量**(不能通过该指针改数据),但指针本身可以指向别的地址"的是?(本题有两个等价正确选项,全选才得分)
- A. `const char* p`
- B. `char const* p`
- C. `char* const p`
- D. `const char* const p`
## 【L3 · 第 10 题】代码阅读(1.5 分钟)· 多重继承的二义性
```cpp
class Root { public: int id; };
class A : public Root {}; // 普通继承
class B : public Root {}; // 普通继承
class Final : public A, public B {};
int main() {
Final f;
f.id = 5; //
}
```
`f.id = 5;` 这一行能编译通过吗?如果可以,`id` 来自哪条继承链路?如果不能,请说明原因。
---
---
## 答案与评分(教师用,建议测验时不发给学员)
| 题号 | 答案 | 对应知识点 · 出处 |
> 所有"代码阅读 / 找 bug"题的输出与编译行为,均已用 **g++ 13.3 在 `-std=c++98
> -pedantic-errors -Wall` 严格模式下编译运行核实**,非凭记忆作答。
### A 卷答案
| 题号 | 答案 | 考查点 · 课程出处 |
|---|---|---|
| 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` |
| L1·1 | **B**(引用不能为空,这是它和指针的关键区别之一——引用是已绑定对象的别名,不存在"空引用" | 引用的本质 · `p01/ch03/reference_essence.cpp` |
| L1·2 | `123``static` 局部变量只初始化一次,之后跨调用保持值;三次调用依次返回 1、2、3) | static 与存储期 · `p01/ch04/s04` |
| L1·3 | `10`(值传递:`f` 内改的是 `n` 的副本,函数返回后副本销毁,`n` 不变 | 函数参数传递 · `p01/ch03` |
| L2·4 | **编译失败**`h(5)``int` 既要转 `long` 又要转 `double`,两个都是"标准转换",优先级相同 → 重载二义(`ambiguous`)。必须显式写 `h(5L)``h(5.0)` 消歧义 | 函数重载决议 · `p01/ch03/function_overload_mechanism.cpp` |
| L2·5 | **C**(A 错:非虚析构时派生类部分不会被析构,是经典内存泄漏;B 错:构造函数不能是虚函数;D 错:不重写就继承基类实现,只有纯虚函数才"必须"重写) | 虚函数与抽象类 · `p01/ch04/s08/virtual_dtor_purpose.cpp` |
| L2·6 | `BMD~D~M~B`(构造:基类 `B` → 成员 `M` → 派生类体 `D`;析构完全相反:派生类 `~D` → 成员 `~M` → 基类 `~B` | 继承+成员的构造析构顺序 · `p01/ch04/s07/inheritance_ctor_dtor_order.cpp` |
| L2·7 | **浅拷贝导致 double free**:编译器合成的默认拷贝构造是浅拷贝,`b.data``a.data` 指向**同一块** `new[]` 内存;`use()` 结束时 `~Buffer()` 被调用两次,对同一块内存 `delete[]` 两次 → 行为未定义,通常崩溃。修复:自定义拷贝构造(深拷贝),或禁用拷贝 | 深拷贝 vs 浅拷贝 · `p01/ch04/s03/deep_vs_shallow_copy.cpp` |
| L3·8 | 输出 `Base``print(Base b)` 形参**按值接收**`Derived` 对象被**切片(slicing)**:只拷贝了 `Base` 子对象,派生类新增部分被截断,`b` 是个货真价实的 `Base`,虚函数自然调到 `Base::name`。要让多态生效,形参须为 `Base&`/`Base*` | 对象切片 · `p01/ch04/s08` |
| L3·9 | **C**`char* const p` —— `const` 修饰 `p` 本身,故指针不可改指向;`*p` 未被 const 修饰,数据可改。A/B 是"指向 const 的指针"D 两边都 const | const 与指针 · `p01/ch03` |
| L3·10 | **编译失败**`d.value` 二义(`ambiguous`)。`Derived``Left``Right` 两条路径各继承了一份 `Base::value`,编译器无法判断要访问哪一个。修复:①用 `d.Left::value` 显式限定;②改用**虚继承** `class Left : virtual public Base`,让两条路径共享同一个 `Base` 子对象 | 多重继承二义性与虚继承 · `p01/ch04/s07` |
### 评分建议
### B 卷答案
- **810 题正确**:具备 `OUTLINE.md` §0 假设的基础,按 Day1 模块 B 原定的
"巡礼式回顾"节奏推进即可。
- **5–7 题正确**:部分薄弱点——对照上表找出学员集中出错的知识点(比如普遍
在第 4/5 题失分,说明构造析构顺序和深浅拷贝没吃透),讲课时在对应知识点
多花 5–10 分钟,其余部分维持原节奏。
- **0–4 题正确**:明显达不到"已掌握 C++ 基础语法与 OOP 概念"的假设——按
`OUTLINE.md` §0 里的预案,Day1 模块 B 的讲授部分退回到语法讲解,练习任务
保持不变但预期完成度应下调,不需要重排整个教案结构。
| 题号 | 答案 | 考查点 · 课程出处 |
|---|---|---|
| L1·1 | **D**`delete` 只释放指针指向的内存,**不改动指针本身**——它仍保存原地址,成为"悬空指针(dangling)";继续解引用是未定义行为。这是 C++98 没有 `nullptr` 时代的经典坑) | 指针与内存管理 · `p01/ch04/s03` |
| L1·2 | 三行依次输出 **`100` / `99` / `98`**`static` 局部变量只初始化一次、跨调用保持值;`n--` 先返回当前值再自减)。三行分开打印,无求值顺序歧义 | static 与存储期 · `p01/ch04/s04` |
| L1·3 | `11`(引用传递:`x``n` 的别名,`x=x+1` 直接改 `n` | 函数参数传递 · `p01/ch03` |
| L2·4 | **编译失败**`h(7)``int` 既要转 `short` 又要转 `float`,两个都是标准转换、优先级相同 → 重载二义(`ambiguous`)。需写 `h((short)7)``h(7.0f)` 消歧义 | 函数重载决议 · `p01/ch03/function_overload_mechanism.cpp` |
| L2·5 | **A**(A 正确,这正是虚析构存在的意义;B 错:构造期间虚表尚未就绪/对象类型还是基类,调用基类版本;C 错:返回类型必须协变或相同,不能"任意";D 错:有虚函数才会生成 vtable) | 虚函数与抽象类 · `p01/ch04/s08/virtual_dtor_purpose.cpp` |
| L2·6 | `215643`(构造:基类 `2` → 成员 `1` → 派生类 `5`;析构相反:`6``4``3` | 继承+成员的构造析构顺序 · `p01/ch04/s07/inheritance_ctor_dtor_order.cpp` |
| L2·7 | **浅拷贝导致 double free**:默认拷贝构造浅拷贝,`b.data``a.data` 同指一块 `new[]``use()` 结束时析构两次 → double free / 未定义行为。修复同 A 卷第 7 题 | 深拷贝 vs 浅拷贝 · `p01/ch04/s03/deep_vs_shallow_copy.cpp` |
| L3·8 | 输出 `...``record(Animal a)` 按值接收 → **对象切片**`Dog` 部分被截断,`a` 是个 `Animal`,调到 `Animal::sound`。要听到 `Woof`,形参须为 `Animal&`/`Animal*` | 对象切片 · `p01/ch04/s08` |
| L3·9 | **A 和 B**`const char* p``char const* p` 写法不同但完全等价——`const` 修饰的是 `*` 的左边,即指向的数据;指针本身可改。C 是 const 指针,D 两边都 const。**全选 A、B 才得分** | const 与指针 · `p01/ch03` |
| L3·10 | **编译失败**`f.id` 二义(`ambiguous`)。`Final``A``B` 两条路径各继承一份 `Root::id`,编译器无法抉择。修复:①`f.A::id` 显式限定;②虚继承 `class A : virtual public Root` | 多重继承二义性与虚继承 · `p01/ch04/s07` |
> **关于 B 卷第 2 题的设计说明**:原稿曾写成单行链式 `std::cout << tick() << tick() << tick();`
> 但该写法的**输出顺序依赖 `<<` 的求值顺序,而 C++ 未规定该顺序**unspecified behavior),
> 不同编译器可能拼出 `1009998`、`9899100` 等不同结果——会让"标准答案"本身不稳定。现稿改为
> 三行分开打印,输出确定无歧义,更适合做闭卷摸底的客观题。
### 评分与分层预案
**计分规则**:每题 1 分,满分 10 分。选择/代码阅读答对即得分;"找 bug / 解释"题(A·7、A·8、A·10、
B·7、B·8、B·10)只要**说对核心机制**(浅拷贝/double free、对象切片、二义性)即得分,不苛求
措辞。B 卷第 2 题按上述概念核分说明处理。
**分层与教学预案**(对应 `OUTLINE.md` §0):
| 分段 | 占比解读 | 指导动作 |
|---|---|---|
| **810 分** | 达到"已掌握 C++ 基础与 OOP 概念"假设 | Day1 模块 B 按原"巡礼式"节奏推进,现代特性(C++11+)正常引入 |
| **57 分** | 部分薄弱——对照上表找出**集中失分的层级** | **L1 失分多**(Q1–3):回退到基础语法讲解;**L2 失分多**(Q4–7):在构造析构/浅拷贝/重载决议处加时 5–10 分钟;**L3 失分多**(Q8–10):说明学员 OOP 基础尚可,多态/继承陷阱课上点到为止、留自学 |
| **04 分** | 明显达不到假设 | 按 `OUTLINE.md` §0 预案,Day1 模块 B **整体退回语法讲解**,练习任务保留但预期完成度下调 |
**逐题正确率用法**(大规模班级):统计每题正确率,**低于 50% 的题**对应的知识点在 Day1
模块 B 里适当加时;无需逐份细看。
### 与旧版的差异(为什么重做)
此前一版 PRETEST(10 题、单卷)的主要不足:
1. **缺梯度**——10 题难度近似,集中在"概念识别"层面,区分不出"学过"和"真正懂了"
2. **无平行卷**——单卷无法防抄袭、无法平行重测;
3. **进阶层薄弱**——没有对象切片、菱形继承这类真正能筛出"高阶"学员的题,全做对与做对 7 题
的学员水平差异,旧卷测不出来。
新版改进:**三级梯度(L1/L2/L3)+ 两份等价平行卷(A/B)+ 每题标注考查点与课程出处**,
既能给出每层级单独的掌握情况,又支持防抄袭与平行重测。
## 使用方式
- 建议纸质或线上表单均可,闭卷、不查资料、不讨论,10 分钟收卷;
- 只用于摸底分层,不计入 `docs/TRAINING_PLAN_2026.md` 的正式考核成绩
- 大规模学员时可以只统计"每题正确率"而不用逐份细看,正确率低于 50% 的题
对应知识点在 Day1 模块 B 里适当加时。
- 纸质或线上表单均可,闭卷、不查资料、不讨论,**10 分钟收卷**
- 两份平行卷按需使用:相邻座席 A/B 交错防抄袭;或 A 卷当堂测、B 卷补测/作业
- 只用于摸底分层,**不计入** `docs/TRAINING_PLAN_2026.md` 的正式考核成绩;
- 大规模班级可只统计"每题正确率",正确率低于 50% 的题对应知识点在 Day1 模块 B 里适当加时。
@@ -3,7 +3,7 @@
// 用途:方案书「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
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_model_view_basics(自动退出)
// ============================================================================
#include <QApplication>
@@ -53,8 +53,11 @@ int main(int argc, char* argv[]) {
ModelViewDemo w;
w.resize(280, 220);
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
@@ -3,7 +3,7 @@
// 用途:方案书「C++方向 Day4」要求的 QPropertyAnimation 动画框架,仓库 p03/
// 原有 wiki 抓取内容未覆盖这一点,故补一个最小示例。
// 配套文档:docs/teaching/examples/property_animation/README.md
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_property_animation
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_property_animation(自动退出)
// ============================================================================
#include <QApplication>
@@ -32,7 +32,10 @@ int main(int argc, char* argv[]) {
anim->start(QAbstractAnimation::DeleteWhenStopped);
window.show();
// 离屏/自动化验证:动画时长 800ms额外等 300ms 确保播放完再退出
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen)自动退出:动画时长 800ms
// 额外等 300ms 确保播放完再退出;正常运行窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(1100, &app, &QApplication::quit);
}
return app.exec();
}
@@ -3,7 +3,7 @@
// 用途:方案书「C++方向 Day2」要求的 QSS 界面美化,仓库 p03/ 原有 wiki 抓取内容
// 未覆盖这一点,故补一个最小示例。
// 配套文档:docs/teaching/examples/qss_styling/README.md
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_qss_styling
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_qss_styling(自动退出)
// ============================================================================
#include <QApplication>
@@ -50,8 +50,11 @@ int main(int argc, char* argv[]) {
StyledPanel panel;
panel.resize(240, 160);
panel.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
@@ -4,7 +4,7 @@
// 不需要额外模块),仓库 p03/ 原有 wiki 抓取内容未覆盖这一点,故补一个
// 最小示例:一个“红灯/绿灯”两态按钮,点击在两态间切换。
// 配套文档:docs/teaching/examples/state_machine/README.md
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_state_machine
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_state_machine(自动退出)
// ============================================================================
#include <QApplication>
@@ -39,9 +39,12 @@ int main(int argc, char* argv[]) {
button.show();
// 离屏/自动化验证:100ms 后模拟一次点击red -> green),
// 再等 200ms 确认状态机正常处理完过渡后自动退出
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen100ms 后模拟一次点击
// red -> green),再等 200ms 确认状态机正常处理完过渡后自动退出
// 正常运行(有显示环境)需用户手动点击按钮切换状态,窗口保持打开。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(100, &button, [&button]() { button.click(); });
QTimer::singleShot(300, &app, &QApplication::quit);
}
return app.exec();
}
+29
View File
@@ -74,6 +74,35 @@
}
.verify-box h4 { margin: 0 0 6px 0; color: #b7791f; }
/* 每个「讲」知识点的固定两件套:核心概念(是什么/为什么)+ 常见的坑(注意事项) */
.concept-box {
border-left: 6px solid #2b6cb0;
background: #ebf4ff;
padding: 12px 22px;
margin: 10px 0;
border-radius: 4px;
text-align: left;
font-size: 0.58em;
line-height: 1.4;
}
.concept-box h4 { margin: 0 0 6px 0; color: #2b6cb0; }
.concept-box ul { margin: 4px 0 0 20px; padding: 0; }
.concept-box li { margin: 3px 0; }
.pitfall-box {
border-left: 6px solid #c53030;
background: #fff5f5;
padding: 12px 22px;
margin: 10px 0;
border-radius: 4px;
text-align: left;
font-size: 0.58em;
line-height: 1.4;
}
.pitfall-box h4 { margin: 0 0 6px 0; color: #c53030; }
.pitfall-box ul { margin: 4px 0 0 20px; padding: 0; }
.pitfall-box li { margin: 3px 0; }
.reveal pre code { font-size: 0.68em; max-height: 480px; }
.module-badge {
+185 -23
View File
@@ -47,20 +47,52 @@
<section>
<h3>实习实训服务流程 <span class="tag tag-lecture"></span></h3>
<img src="../../images/training_service_flow.png" style="max-height:480px;">
<img src="../../images/training_service_flow.png" style="max-height:520px;">
<p><small>出处:<a class="filelink" target="_blank"
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a>
「六、实习实训服务流程」——需求分析→需求评审→项目研发/代码实现→阶段评审→
项目测试/交付,这就是接下来 Day6–10 项目实作要走的完整流程。</small></p>
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a> 「六、实习实训服务流程」</small></p>
</section>
<section>
<h3>服务流程:核心概念 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>需求分析→需求评审→项目研发/代码实现→阶段评审→项目测试/交付——每个环节都有明确的
"评审"节点把关,不是写完代码就结束,这就是接下来 Day6–10 项目实作要走的完整流程。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见误区</h4>
<ul>
<li>把"需求分析"当成走过场:跳过评审直接开始编码,后期返工成本远高于前期多花时间对齐需求。</li>
</ul>
</div>
</section>
<section>
<h3>质量保证:四平台一标准 <span class="tag tag-lecture"></span></h3>
<img src="../../images/quality_four_platforms.png" style="max-height:420px;">
<img src="../../images/quality_four_platforms.png" style="max-height:520px;">
<p><small>出处:<a class="filelink" target="_blank"
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a>
「七、质量保证」——JIRA(任务跟踪)+ Git(版本管理)+ Wiki 知识库 +
OASIS 绿洲实训管理平台,是接下来项目实作阶段的四个协作工具。</small></p>
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a> 「七、质量保证」</small></p>
</section>
<section>
<h3>四平台一标准:核心概念 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>JIRA(任务跟踪)+ Git(版本管理)+ Wiki 知识库 + OASIS 绿洲实训管理平台,四个工具
分别对应"谁在做什么/代码怎么变化/知识怎么沉淀/进度怎么被看到"四个不同维度,缺一个都会
让协作出现盲区。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见误区</h4>
<ul>
<li>只用 Git 不用 Jira:代码有了版本历史,但"谁负责什么、进度到哪"没有可见记录,
团队协作时容易重复劳动或漏项。</li>
</ul>
</div>
</section>
</section>
@@ -112,8 +144,21 @@
<section>
<h3>namespace 作用域 <span class="tag tag-lecture"></span></h3>
<p>"作用域"不是"文件顺序"——很多人误以为 namespace 只是给名字加前缀,
实际上它定义的是一段可以被跨文件延续、跨块合并的作用域。</p>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>"作用域"不是"文件顺序"——很多人误以为 namespace 只是给名字加前缀,实际上它定义的是
一段可以被跨文件延续、跨块合并的作用域,同一个命名空间可以在多个文件里反复打开、追加内容。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>在头文件里写 <code>using namespace std;</code>:会把整个命名空间污染带给所有
include 这个头文件的地方,容易引发命名冲突,规范做法是只在 .cpp 里用,或干脆不用、
全程写限定名。</li>
</ul>
</div>
<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>
@@ -121,16 +166,40 @@
<section>
<h3>函数重载决议 <span class="tag tag-lecture"></span></h3>
<p>编译器靠"名字改写"name mangling)把重载函数变成不同的符号;二义性
往往不是代码写错了,而是重载集合里存在两个"一样好"的候选。</p>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>编译器靠"名字改写"name mangling)把重载函数变成不同的符号;二义性往往不是代码写
错了,而是重载集合里存在两个"一样好"的候选,编译器无法替你选。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>隐式类型转换会扩大重载候选集——比如 <code>int</code>/<code>double</code> 都能转
<code>float</code> 参数,传一个 <code>long</code> 进去可能触发意料之外的重载版本。</li>
</ul>
</div>
<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>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>把引用理解成"自动解引用的 const 指针"——这个实现视角能解释引用为什么必须初始化、
为什么不能重新绑定到另一个对象。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>返回局部变量的引用:函数结束局部变量已销毁,返回的引用变成悬空引用,后续访问是
未定义行为——和返回局部变量指针是同一类坑。</li>
</ul>
</div>
<p><small>出处:<a class="filelink" target="_blank"
href="../../../p01/ch03/reference_essence.cpp">p01/ch03/reference_essence.cpp</a></small></p>
</section>
@@ -141,36 +210,129 @@
Base* p = new Derived();
delete p; // 如果 ~Base() 不是 virtual~Derived() 不会被调用——经典泄漏
</code></pre>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>通过基类指针 <code>delete</code> 派生类对象时,只有基类析构函数是 <code>virtual</code>
才会走虚函数表找到派生类的析构函数——这是"多态"在析构这个场景下的具体体现。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>只要一个类可能被继承、且可能通过基类指针删除派生对象,就应该把析构函数声明为
<code>virtual</code>——即使当前基类本身没什么要清理的,这是个防御性约定。</li>
</ul>
</div>
<p><small>出处:<a class="filelink" target="_blank"
href="../../../p01/ch04/s08/virtual_dtor_purpose.cpp">p01/ch04/s08/virtual_dtor_purpose.cpp</a></small></p>
</section>
<section>
<h3>模板与 STL 速览 <span class="tag tag-lecture"></span></h3>
<p>只点名,不展开:泛型编程(函数模板/类模板)+ 容器/迭代器,是 Day6–10
项目实作里会大量用到的基础设施。</p>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>只点名,不展开:泛型编程(函数模板/类模板)让同一份代码适配不同类型,容器/迭代器
是 Day6–10 项目实作里会大量用到的基础设施——迭代器是"容器和算法之间的统一接口",这也是
STL 能让算法脱离具体容器类型复用的关键。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>容器扩容/删除元素后,之前保存的迭代器可能失效(如 <code>vector</code> 扩容),
继续用失效的迭代器是未定义行为——遍历中增删元素前要先弄清对应容器的迭代器失效规则。</li>
</ul>
</div>
<p><small>出处:<a class="filelink" target="_blank"
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>C++11/14/17/20+:一门仍在进化的语言 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念——每 3 年一次实打实的能力升级,不是小修小补</h4>
<ul>
<li><b>C++11</b>"现代 C++ 元年"):<code>auto</code> 类型推导、智能指针
<code>unique_ptr</code>/<code>shared_ptr</code>RAII 终结手动 <code>new</code>/<code>delete</code>)、
右值引用 <code>&amp;&amp;</code> + 移动语义(消除不必要的拷贝)、lambda 表达式、
语言级并发 <code>std::thread</code>/<code>mutex</code>/<code>atomic</code>
<code>nullptr</code><code>constexpr</code></li>
<li><b>C++14</b>:泛型 lambda(参数写 <code>auto</code>)、函数返回类型自动推导、
<code>std::make_unique</code> 补齐。</li>
<li><b>C++17</b>:结构化绑定 <code>auto [a,b] = pair</code><code>if</code>/<code>switch</code>
带初始化语句、<code>std::optional</code>/<code>variant</code>/<code>string_view</code>
标准库终于有跨平台的 <code>std::filesystem</code></li>
<li><b>C++20</b>(号称"自 C++11 以来最大的一次变革"):concepts 概念(模板约束,报错从
几百行天书变成一句话)、ranges 范围库(<code>view | filter | transform</code> 管道式组合)、
coroutines 协程(<code>co_await</code> 把异步代码写成同步的样子)、modules 模块(挑战头文件
体系)、<code>&lt;=&gt;</code> 三路比较一行换 6 个运算符。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>新标准 ≠ 现在能用:生产环境的编译器/CI 常年落后最新标准 1–2 个版本,用新特性前先确认
目标工具链 <code>-std=</code> 支持到哪。本仓库出于教学稳定性选 C++17 作为基线(顶层
<code>CMakeLists.txt</code>),可用 <code>tools/std_probe.py</code> 自己验证某特性最早从哪个
标准开始能编译(宿主 gcc 13.3 原生支持到 C++23)。</li>
</ul>
</div>
<div class="verify-box">
<h4>缺口说明</h4>
<p>本节为纯概念巡礼,无对应仓库示例——具体新特性的动手练习超出本次"C++回顾"压缩讲解范畴,
感兴趣可用 <code>tools/std_probe.py</code> + <code>tools/demo_versions/</code> 自行探索
(参见 <a class="filelink" target="_blank" href="../../VERSION_NOTES.md">docs/VERSION_NOTES.md</a>)。</p>
</div>
</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>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>CMake 是"生成构建系统的工具"而不是构建系统本身:<code>cmake -S -B</code>
<code>CMakeLists.txt</code> 生成 Ninja/Makefile 工程,<code>cmake --build</code> 才是
真正编译。<code>option(BUILD_QT_PART)</code> 控制是否编译 Qt 部分,每个示例一个
<code>add_executable</code> 目标,这就是"一文件一目标"约定的由来。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>改了 <code>CMakeLists.txt</code> 却没重新 <code>cmake</code> 配置(只 <code>build</code>):
大多数改动 CMake 会自动侦测重新配置,但换了生成器/清了缓存等情况需要重新走
<code>-S -B</code> 一次。</li>
</ul>
</div>
<p><small>出处:仓库根 <a class="filelink" target="_blank" href="../../../CMakeLists.txt">CMakeLists.txt</a></small></p>
</section>
<section>
<h3>Vcpkg 包管理器(概念,无演示) <span class="tag tag-lecture"></span></h3>
<p>C++ 生态的包管理器,解决"第三方库怎么装、怎么被 CMake 找到"的问题。</p>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>C++ 生态的包管理器,解决"第三方库怎么装、怎么被 CMake 找到"的问题——一次
<code>vcpkg install</code>,配合 CMake 的 toolchain 文件,<code>find_package</code>
就能自动找到头文件和库。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>不是所有第三方库都必须用包管理器装——本仓库用的是"隔离 Qt 手动装"
<code>tools/install_qt_host.sh</code>)而非 vcpkg,两种思路都要认识:包管理器省心但
引入额外工具链,手动隔离装则精确控制版本/路径,各有取舍。</li>
</ul>
</div>
<div class="verify-box">
<h4>缺口说明</h4>
<p>本仓库用的是"隔离 Qt 手动装"<code>tools/install_qt_host.sh</code>
而非 vcpkg,两种思路都要认识;本仓库暂无 vcpkg 可运行示例——安装真实
vcpkg 工具链超出最小示例范畴,此处仅作概念介绍。</p>
<p>本仓库暂无 vcpkg 可运行示例——安装真实 vcpkg 工具链超出最小示例范畴,此处仅作概念介绍。</p>
</div>
</section>
</section>
+138
View File
@@ -42,6 +42,30 @@ cmake --build build</code></pre>
</ul>
</section>
<section>
<h3>最小 Qt 程序:核心概念 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li><code>QApplication</code> 是整个进程唯一的"事件循环拥有者"——<code>app.exec()</code>
阻塞在这里进入事件循环,不断从操作系统取事件(鼠标/键盘/定时器/重绘请求)分发出去,
程序的"活着"就是这个循环在转,不是运行完就退出的普通程序。</li>
<li><code>Q_OBJECT</code> 宏触发 mocmeta-object compiler)在编译前生成额外代码——
信号槽、属性系统、<code>qobject_cast</code> 等运行时反射能力都靠这份生成代码实现,
这是 Qt 自己的预处理步骤,不是普通 C++ 语法。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>一个进程只能有一个 <code>QApplication</code> 实例,创建第二个直接崩溃/未定义行为。</li>
<li>忘记调用 <code>exec()</code>:窗口一闪而过就退出,因为根本没进入事件循环。</li>
<li>类里用了信号槽却忘记写 <code>Q_OBJECT</code>:编译能过,链接阶段报
<code>undefined reference</code>——报错信息完全不提示"缺宏",是新手最常踩的坑。</li>
</ul>
</div>
</section>
<section>
<h3>最小 Qt 程序:QApplication + exec() <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -63,6 +87,28 @@ int main(int argc, char* argv[]) {
<code>QTimer::singleShot</code> 200ms 自动退出,便于离屏自动化验证,真实项目不需要这一行)</small></p>
</section>
<section>
<h3>控件创建:对象树与父子所有权 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>QObject 有一套"对象树"父子所有权模型:给控件传入 <code>parent</code> 后,
parent 被销毁时会自动 <code>delete</code> 所有子对象——这就是为什么 Qt 代码里到处是
<code>new</code> 却几乎不见 <code>delete</code></li>
<li>两种写法(先构造后设属性 / 构造时一次性传参)本质相同,父对象的作用一样。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>忘记传 <code>parent</code>:控件永远不会自动释放(真实内存泄漏),也不会自动出现在
窗口里(没有父容器就没有布局位置)。</li>
<li><code>move()</code> 手动摆放坐标的控件,一旦外层套上布局管理器,手动坐标会被
布局在下次刷新时覆盖——这是"混用绝对定位和布局"最常见的诡异 bug 来源。</li>
</ul>
</div>
</section>
<section>
<h3>控件创建:两种写法 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -79,6 +125,32 @@ QPushButton* btn2 = new QPushButton(QStringLiteral("孙悟空"), this);
href="../../../p03/ch03/button_creation.cpp">p03/ch03/button_creation.cpp</a></small></p>
</section>
<section>
<h3>信号槽:观察者模式 + moc <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>信号槽本质是"观察者模式"的 Qt 实现:发送者不知道、也不关心谁在监听,彻底解耦发送方
和接收方——这和"直接调用某个函数"是完全不同的设计取向。</li>
<li><code>connect()</code> 背后依赖 moc 生成的元对象信息做类型检查:新语法
<code>&Class::signal</code><strong>编译期</strong>检查,字符串旧语法
<code>SIGNAL()/SLOT()</code><strong>运行时</strong>检查,容易悄悄连接失败。</li>
<li>连接方式分 Direct(同线程直接调用)/ Queued(跨线程排队,下次事件循环处理)/
Auto(默认,同线程 Direct、跨线程自动 Queued)——<a class="filelink" target="_blank"
href="day4.html">Day4</a> 讲 QThread 时还会用到。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>旧的字符串语法 <code>SIGNAL(clicked())</code> 函数名/参数类型写错不会编译报错,
只会运行时"连接失败"且经常没有提示——能用新语法就不用旧语法,编译期就能发现问题。</li>
<li>重载信号(如右侧例子的两个 <code>hungry()</code>)不加 <code>QOverload</code>
消歧义,编译器不知道该连哪个重载,直接报错。</li>
</ul>
</div>
</section>
<section>
<h3>信号槽:内置 / 自定义 / 重载消歧义 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -160,6 +232,27 @@ connect(t, QOverload&lt;QString&gt;::of(&Teacher::hungry), s, QOverload&lt;QStri
<h2>常用控件 / 布局管理 / QSS 美化 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>资源系统:.qrc 是什么 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li><code>:/Image/boat.jpg</code> 这种路径不是文件系统路径,是 Qt 资源系统(QRC)——
图片等资源在编译期被打包进可执行文件本身,运行时不依赖外部文件是否存在,这对
"打包发布"很关键。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li><code>.qrc</code> 里资源路径/文件名和代码里 <code>:/...</code> 引用的不一致,会在
运行时静默失败(<code>QPixmap</code> 为空、图不显示),不会报编译错误。</li>
<li>忘记把 <code>.qrc</code> 接入 CMake 的 <code>qt5_add_resources</code>/AUTORCC
资源系统压根不生效。</li>
</ul>
</div>
</section>
<section>
<h3>常用控件:QLabel 显示图片 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -173,6 +266,28 @@ label->setPixmap(pixmap);
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>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>布局管理器(<code>QLayout</code>)接管子控件的位置和大小计算,随窗口缩放自动重新
排布——这是"不要自己算坐标"的根本原因,也是跨平台/跨分辨率 UI 能正确显示的基础。</li>
<li>布局对象本身也遵循 QObject 父子所有权:<code>new QHBoxLayout(this)</code>
把布局设为 <code>this</code> 的布局,同时隐式接管了 <code>addWidget</code> 进来的子控件。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>同一个控件不能同时属于两个布局,重复 <code>addWidget</code> 到不同布局会导致界面
异常或崩溃。</li>
<li>布局设置之后又用 <code>setGeometry</code>/<code>move</code> 手动改控件位置,效果
会被布局在下次刷新时覆盖(呼应上一节"控件创建"的坑)。</li>
</ul>
</div>
</section>
<section>
<h3>自定义控件与布局:QHBoxLayout <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -190,6 +305,29 @@ connect(slider, &QSlider::valueChanged, spin, &QSpinBox::setValue);
不用手算控件坐标。</small></p>
</section>
<section>
<h3>QSS:类 CSS 的样式层叠 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>QSS 语法脱胎于 CSS:选择器(类型选择器 <code>QPushButton</code>、ID 选择器
<code>#okButton</code>、伪状态 <code>:hover</code>/<code>:pressed</code>+ 属性声明,
但作用对象是 QWidget 树而不是 DOM。</li>
<li>样式表遵循层叠规则:作用在父控件上的样式默认会被子控件继承,除非子控件有自己更
具体的规则覆盖。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>用 ID 选择器(<code>#okButton</code>)必须先 <code>setObjectName("okButton")</code>
忘记设置会导致规则完全不生效且没有任何报错。</li>
<li>在某个容器上 <code>setStyleSheet</code> 会级联影响它所有子控件(包括不想改的),
范围失控是 QSS 最容易踩的坑,通常要用更具体的选择器限定范围。</li>
</ul>
</div>
</section>
<section>
<h3>QSS 界面美化(教学补充) <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
+118
View File
@@ -30,6 +30,30 @@
<h2>Model/View 架构 / 事件机制 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>Model/View:数据与展示分离 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>Model/View 把"数据"和"怎么展示数据"彻底拆开:模型(<code>QAbstractItemModel</code>
及其子类)只负责存数据、回答"这个位置是什么",视图(<code>QListView</code>/
<code>QTableView</code>/...)只负责画——同一份模型可以同时喂给多个不同视图。</li>
<li>模型用统一的 <code>QModelIndex</code>(行/列/父项)寻址数据,视图向模型要数据时
还要带上"角色"<code>Qt::DisplayRole</code> 显示文本、<code>Qt::DecorationRole</code>
图标……),同一个位置可以按不同角色返回不同内容。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>直接改模型内部数据却不发信号(<code>dataChanged</code>/<code>beginInsertRows</code>+
<code>endInsertRows</code>),视图不会自动刷新,界面和数据"对不上"。</li>
<li>用了简单的 <code>QStandardItemModel</code> 却又想自定义复杂数据结构:应该继承
<code>QAbstractItemModel</code> 自己实现,而不是硬塞进 Item 模型。</li>
</ul>
</div>
</section>
<section>
<h3>Model/View 模型视图架构(教学补充) <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -50,6 +74,31 @@ connect(view->selectionModel(), &QItemSelectionModel::currentChanged, ...);
仓库 <code>p03/</code> 的 wiki 抓取内容没有 Model/View,这是本轮补写的教学补充示例,已离屏验证跑通。</small></p>
</section>
<section>
<h3>事件循环:事件 vs 信号 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li><code>app.exec()</code> 进入的就是事件循环(复用
<a class="filelink" target="_blank" href="day2.html">Day2</a> 概念)——鼠标点击、
键盘输入、定时器触发都会变成"事件"(<code>QEvent</code>),被塞进这个循环里依次分发给
对应控件。</li>
<li>事件和信号是两条不同的机制:事件是"操作系统/框架推给控件"的通知,走
<code>event()</code>/<code>xxxEvent()</code> 虚函数或事件过滤器;信号槽是"对象之间"
主动通知,走 <code>connect()</code>。控件收到事件后,常见做法就是在事件处理函数里
<code>emit</code> 一个信号,把"底层事件"转成"业务语义通知"。</li>
<li>今天讲的"事件过滤器"就是在事件被分发给控件之前,插一个"拦截点"。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>在事件循环所在的主线程做耗时操作会直接卡住整个界面(无法重绘、按钮点了没反应)——
这也是 <a class="filelink" target="_blank" href="day4.html">Day4</a> 要讲 QThread 的原因。</li>
</ul>
</div>
</section>
<section>
<h3>事件循环机制(复用 Day2 概念) <span class="tag tag-lecture"></span></h3>
<p><code>app.exec()</code> 进入的就是事件循环——鼠标点击、键盘输入、定时器
@@ -59,6 +108,29 @@ connect(view->selectionModel(), &QItemSelectionModel::currentChanged, ...);
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>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>拦截事件有两条路:重写 <code>xxxEvent()</code>(只影响这一个控件自己)或装事件过滤器
<code>installEventFilter()</code>(可以在事件到达目标控件之前,由另一个对象统一拦截,
常用于"一个地方管多个控件"的场景)。</li>
<li>事件对象有 <code>accept()</code>/<code>ignore()</code> 状态:处理完事件要表明"我
吃掉了这个事件"还是"继续往上传",决定事件是否会继续传播给父控件。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>重写 <code>xxxEvent()</code> 时忘记调用基类实现(如 <code>QWidget::mouseMoveEvent</code>),
可能丢失控件本身需要的默认行为。</li>
<li>事件过滤器安装到一个对象上后,如果被观察对象先于过滤器对象销毁(或反过来),要注意
生命周期——Qt 会在对象销毁时自动清理,但过滤逻辑里持有的裸指针不会自动清空。</li>
</ul>
</div>
</section>
<section>
<h3>自定义事件处理 / 事件过滤器 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -123,6 +195,29 @@ protected:
<h2>文件 IO / JSON 解析 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>QIODevice:统一的读写抽象 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li><code>QFile</code>/<code>QTextStream</code>/<code>QDataStream</code> 都建立在
<code>QIODevice</code> 这一统一读写接口上——同样的"打开/读/写/关闭"套路,以后遇到网络
套接字/串口一类设备也是同一套接口,学一次可以迁移到别的设备类型。</li>
<li><code>QTextStream</code> 面向"文本行"(人可读),<code>QDataStream</code> 面向
"结构化二进制"(保留类型信息,程序间高效互传但人不可直接读)——按需求选,不是谁更好。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>忘记检查 <code>open()</code> 的返回值:文件不存在/无权限时静默失败,后续读写在一个
没打开的设备上操作,容易读出空数据而不报错。</li>
<li>混淆文本模式和二进制模式(<code>QIODevice::Text</code>):文本模式下 Qt 会做换行符
转换,用来读写二进制数据会破坏内容。</li>
</ul>
</div>
</section>
<section>
<h3>文件系统:QFile 读文本 <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -136,6 +231,29 @@ while (!file.atEnd()) { qDebug() << file.readLine(); }
<a class="filelink" target="_blank" href="../../../p03/ch11/qdatastream_io.cpp">qdatastream_io.cpp</a></small></p>
</section>
<section>
<h3>JSON:序列化与反序列化的往返 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>JSON 读写的本质是"内存对象 ↔ 文本"的一对互逆操作:序列化(<code>QJsonDocument::toJson</code>
把内存数据变成可存/可传的文本,反序列化(<code>fromJson</code>)再变回来——正确性的标准是
"写出去再读回来,数据要一致"round-trip)。</li>
<li><code>QJsonValue</code> 是一个变体(variant)类型,能装字符串/数字/布尔/数组/对象,
取值时要按预期类型调用 <code>toString()</code>/<code>toInt()</code> 等。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>忘记检查 <code>QJsonDocument::fromJson</code><code>QJsonParseError</code>
文本格式错误时返回一个空文档,后续取值全是默认值,不会崩溃也不会报错,很难定位。</li>
<li>类型不匹配时 <code>toInt()</code>/<code>toString()</code> 等不会抛异常,只会静默
返回默认值(0/空串),排查时容易误以为"数据没写进去"。</li>
</ul>
</div>
</section>
<section>
<h3>JSON 解析与写入(教学补充) <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
+102
View File
@@ -30,6 +30,30 @@
<h2>2D 绘图 / 多线程 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>QPainter:由 Qt 驱动的重绘模型 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>Qt 的绘制是"框架驱动"而非"你想画就画":所有绘制只能发生在 <code>paintEvent</code>
里,由 Qt 在需要重绘时(窗口刚显示、被遮挡后重新露出、你主动请求)自动调用——这和直接式
绘图(想画就立刻画到屏幕)是不同的模型。</li>
<li>控件想主动触发重绘要调用 <code>update()</code>(异步,排队到下次事件循环处理)而不是
直接调用 <code>paintEvent()</code><code>QPainter</code> 只是"在哪个设备上画"的画笔,
画布可以是控件本身,也可以是 <code>QImage</code>/<code>QPixmap</code> 等离屏设备。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li><code>paintEvent</code> 之外的地方直接 <code>new QPainter(this)</code> 画东西:
要么无效要么未定义行为,绘制必须在 <code>paintEvent</code> 回调期间进行。</li>
<li>数据变了却忘记调用 <code>update()</code>:界面不会自动感知"要重画了",看起来像是
"画错了",其实是"根本没重画"。</li>
</ul>
</div>
</section>
<section>
<h3>绘图:QPainter <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -48,6 +72,33 @@ void paintEvent(QPaintEvent*) override {
<a class="filelink" target="_blank" href="../../../p03/ch10/paint_device_pixmaps.cpp">paint_device_pixmaps.cpp</a></small></p>
</section>
<section>
<h3>QThread:线程亲和性与 worker-object <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>"线程亲和性"thread affinity):每个 QObject 都归属于创建它/被
<code>moveToThread</code> 移动到的那个线程,它的槽函数默认在所属线程执行——GUI 控件必须
始终归属主线程,这是 Qt 线程模型的核心约束。</li>
<li>官方推荐的是 worker-object 模式(把耗时逻辑放进一个普通 QObject,
<code>moveToThread</code> 到工作线程),而不是继承 <code>QThread</code> 重写
<code>run()</code>——前者能继续用信号槽通信,后者容易把线程亲和性搞混。</li>
<li>跨线程的信号槽连接会自动变成 Queued(回顾
<a class="filelink" target="_blank" href="day2.html">Day2</a>),结果通过事件循环
安全地"带回"接收者所在线程,不需要手动加锁。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>在工作线程里直接操作 GUI 控件(比如直接 <code>label->setText()</code>):Qt 的 GUI
模块不是线程安全的,这是未定义行为/崩溃的常见来源——正确做法永远是发信号,排队回主线程处理。</li>
<li><code>moveToThread</code> 要在 <code>connect</code><code>thread.start()</code>
之前完成;对象一旦有耗时任务在跑,中途再挪线程会有竞态。</li>
</ul>
</div>
</section>
<section>
<h3>QThread 多线程(教学补充) <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -117,6 +168,32 @@ connect(&workerThread, &QThread::started, &worker, &Worker::process);
<h2>动画框架 / 状态机 <span class="tag tag-lecture"></span></h2>
</section>
<section>
<h3>QPropertyAnimation:动画建立在属性系统上 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>Qt 的属性系统(<code>Q_PROPERTY</code>,同样靠
<a class="filelink" target="_blank" href="day2.html">Day2</a> 讲过的 moc 生成)给每个属性配了
统一的 get/set 接口;动画框架的本质就是"在一段时间内,用缓动曲线不断调用某个属性的
setter",所以任何声明成 <code>Q_PROPERTY</code> 的属性(不只是内置的 geometry/opacity
都能被动画驱动。</li>
<li><code>QEasingCurve</code> 决定的是"数值随时间变化的曲线形状"(线性/回弹/加减速……),
动画本身只是"定时器 + 插值 + 调 setter"的组合,理解这点能解释为什么几乎所有属性都能做动画。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>要动画化的属性必须是真正的 <code>Q_PROPERTY</code>(有 setter),随手传一个字符串
属性名如果对应类没有声明该属性,动画不会报错,只是什么也不会发生。</li>
<li>动画作用的对象在动画播放完成前被销毁会导致悬空访问;
<code>QAbstractAnimation::DeleteWhenStopped</code> 能让动画结束后自动释放自身,但不管
目标对象的生命周期,目标对象仍需自己保证不提前销毁。</li>
</ul>
</div>
</section>
<section>
<h3>QPropertyAnimation 动画(教学补充) <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
@@ -132,6 +209,31 @@ anim->start(QAbstractAnimation::DeleteWhenStopped);
<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>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>状态机解决的是"同一个控件在不同状态下行为/外观不同"的问题——与其用一堆
<code>bool</code> 标志位加 if-else 判断当前处于什么状态,不如让"状态"本身成为一等对象
<code>QState</code>),每个状态自己声明"进入这个状态要怎样"(<code>assignProperty</code>
又用到了 Qt 属性系统)。</li>
<li>状态之间的切换(<code>addTransition</code>)本质是"信号触发的转移"——转移条件就是
一个信号,这让状态机和信号槽自然衔接:任何能发信号的事件都能驱动状态切换。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见的坑</h4>
<ul>
<li>忘记 <code>machine.setInitialState(...)</code> 或忘记调用 <code>machine.start()</code>
状态机对象存在但从未真正运行,所有 <code>assignProperty</code>/<code>addTransition</code>
都不会生效。</li>
<li>某个状态没有覆盖所有可能触发的信号/条件,会导致状态机"卡住"——设计状态机前先把所有
状态和转移条件画成图,比直接写代码更不容易漏。</li>
</ul>
</div>
</section>
<section>
<h3>QStateMachine 状态机(教学补充) <span class="tag tag-lecture"></span></h3>
<pre><code class="cpp" data-trim data-noescape>
+61 -8
View File
@@ -34,20 +34,52 @@
<section>
<h3>质量保证:四平台一标准(复习) <span class="tag tag-lecture"></span></h3>
<img src="../../images/quality_four_platforms.png" style="max-height:420px;">
<img src="../../images/quality_four_platforms.png" style="max-height:520px;">
<p><small>出处:<a class="filelink" target="_blank"
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a>
「七、质量保证 (二) Git」——接下来 Day6–10 项目实作,每人每完成一个任务都要提交代码
到 Git 并写清任务描述。</small></p>
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a> 「七、质量保证 (二) Git」</small></p>
</section>
<section>
<h3>Git 提交习惯:核心概念 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>接下来 Day6–10 项目实作,每人每完成一个任务都要提交代码到 Git 并写清任务描述——
提交粒度对应 Jira 任务粒度,commit 信息就是"这次提交做了什么、为什么"的可追溯记录。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见误区</h4>
<ul>
<li>攒了几天代码一次性提交、commit 信息写"update":历史记录失去可追溯性,出问题时无法
定位是哪次改动引入的。</li>
</ul>
</div>
</section>
<section>
<h3>标准化考核标准 <span class="tag tag-lecture"></span></h3>
<img src="../../images/assessment_standard.png" style="max-height:480px;">
<img src="../../images/assessment_standard.png" style="max-height:520px;">
<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>
href="../../TRAINING_PLAN_2026.md">docs/TRAINING_PLAN_2026.md</a> 「七、(五) 标准化的考核标准」</small></p>
</section>
<section>
<h3>考核标准:核心概念 <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>项目管理评估(40%) + 项目技术评估(30%) + 职业素养评估(30%)——技术实现只占三成,
Day6–10 项目实作最终按这三个维度综合打分,不是"代码能跑就满分"。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见误区</h4>
<ul>
<li>只顾埋头写代码、忽略项目管理和职业素养维度(如任务跟踪是否及时更新、沟通是否
到位):技术分再高,另外 70% 的权重会明显拉低总评。</li>
</ul>
</div>
</section>
</section>
@@ -83,10 +115,31 @@
<section>
<h3>Day24 案例回顾 <span class="tag tag-lecture"></span></h3>
<p>请 1–2 组现场展示计算器 / 本地数据存储 / 动画状态机小游戏,其他组观摩提问。</p>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>代码走查(code review)是团队协作的常规环节——展示时讲清"为什么这么设计"比"功能能跑"
更能体现对 Day2–4 核心概念(对象树/信号槽/Model-View/线程亲和性……)的理解程度。</li>
</ul>
</div>
<div class="pitfall-box">
<h4>常见误区</h4>
<ul>
<li>只演示"能跑"不讲"为什么":观摩组提不出有效问题,复盘就失去了巩固概念的作用。</li>
</ul>
</div>
</section>
<section>
<h3>OpenCV 与 AI 机器学习(可选,自学指针) <span class="tag tag-lecture"></span></h3>
<div class="concept-box">
<h4>核心概念</h4>
<ul>
<li>OpenCV 是独立于 Qt 的计算机视觉库,Qt 负责界面/交互,OpenCV 负责图像算法——两者通过
"内存里的图像数据格式转换"(<code>cv::Mat</code><code>QImage</code>)配合,是很多
桌面视觉类项目的典型组合方式。</li>
</ul>
</div>
<div class="verify-box">
<h4>缺口说明</h4>
<p>方案书原文对本项标注"可选择",属于非必修拓展。引入 OpenCV 会新增一条完整的
+3 -2
View File
@@ -20,9 +20,10 @@
<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>
配套大纲:<a href="../OUTLINE.md"><code>docs/teaching/OUTLINE.md</code></a>
方案书全文:<a href="../../TRAINING_PLAN_2026.md"><code>docs/TRAINING_PLAN_2026.md</code></a>
离线可用(reveal.js 已 vendor 进仓库);代码链接需通过本地 HTTP 服务打开才能正常跳转,
见仓库 <code>docs/teaching/OUTLINE.md</code> 「材料与播放方式」一节。</p>
见仓库 <a href="../OUTLINE.md"><code>docs/teaching/OUTLINE.md</code></a> 「材料与播放方式」一节。</p>
<ul>
<li><a class="day" href="day1.html">Day 1 · 开训 + C++ 回顾/工具链</a>
<div class="desc">团队建设/Scrum/C++企业应用(讨论,无编码) → C++精选回顾 + CMake实操 + Vcpkg概念</div></li>
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954515:0]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -22,8 +22,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
Widget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954525:0]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -36,8 +36,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954527:0]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -28,8 +28,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954527:2]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -60,8 +60,11 @@ public:
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
MyWidget w; w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954527:6]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -34,8 +34,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954529:5]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -25,8 +25,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoMainWindow w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954529:4]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -29,8 +29,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoMainWindow w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954535:12]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -60,8 +60,11 @@ private slots:
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
MainWindow w; w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954535:11]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -47,8 +47,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954535:10]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -39,8 +39,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954535:0]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -28,8 +28,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954535:3]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -29,8 +29,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954535:2]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -28,8 +28,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954535:1]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -28,8 +28,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954540:15]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -36,8 +36,11 @@ public:
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
SmallWidget w; w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954540:movie]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -29,8 +29,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954540:img]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -29,8 +29,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954540:1]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -25,8 +25,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954540:2]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -27,8 +27,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
DemoWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954546:6]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -39,8 +39,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
MainWindow w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954546:2]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -35,8 +35,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
CustomWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954546:0]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -41,8 +41,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
EventLabel w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954548:2]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -36,8 +36,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
PaintWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954548:3]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -36,8 +36,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
PaintWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954548:0]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -32,8 +32,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
PaintedWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+5 -2
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954548:4]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
@@ -41,8 +41,11 @@ int main(int argc, char* argv[]) {
QApplication app(argc, argv);
PaintWidget w;
w.show();
// 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)
// 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;
// 正常运行(有显示环境)窗口保持打开,不自动退出。
if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
QTimer::singleShot(200, &app, &QApplication::quit);
}
return app.exec();
}
+1 -1
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954552:data]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
+1 -1
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954552:0]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QCoreApplication>
+1 -1
View File
@@ -4,7 +4,7 @@
// 原文片段:[58954552:text]
// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication
// 外壳、include、修正笔误)。详见 docs/ERRATA.md。
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)
// ============================================================================
#include <QApplication>
+8 -4
View File
@@ -10,8 +10,9 @@
# 外壳包裹,必要时补所需 QWidget/QMainWindow 子类骨架。
# - 'snippet' 纯签名/单语句/伪代码,不单独建目标,折进各章 README 作文档。
# * 每个目标 = 一个独立 add_executable;开 AUTOMOCQ_OBJECT 类需 moc)。
# * 离屏运行友好:每个目标注入一个 QTimer::singleShot 让程序 200ms 后自动退出
# 便于 QT_QPA_PLATFORM=offscreen 自动化验证(窗口类目标)。
# * 离屏运行友好:每个窗口类目标注入一个 QTimer::singleShot仅当检测到
# QT_QPA_PLATFORM=offscreen 时才在 200ms 后自动退出;正常运行(有显示环境)
# 窗口保持打开,不会自动关闭。
# * 资源依赖(:/images/*.png 等)用脚本生成的纯色占位 png + .qrc 提供,qt5_add_resources。
# * Qt 代码修正(笔误/平台/过时 API)每处 record_erratum 进 docs/ERRATA.md(透明可审计)。
#
@@ -275,8 +276,11 @@ TARGET_DATAFILES = {
# 外壳模板:把一个「片段」包成可运行的 Qt 程序
# ==============================================================================
AUTO_QUIT = (
" // 离屏/自动化验证:200ms 后自动退出(GUI 模式下不阻塞)\n"
" // 仅在离屏自动化验证时(QT_QPA_PLATFORM=offscreen200ms 后自动退出;\n"
" // 正常运行(有显示环境)窗口保持打开,不自动退出。\n"
" if (qgetenv(\"QT_QPA_PLATFORM\") == \"offscreen\") {\n"
" QTimer::singleShot(200, &app, &QApplication::quit);\n"
" }\n"
)
AUTO_QUIT_CORE = (
" QTimer::singleShot(0, &app, &QCoreApplication::quit);\n"
@@ -373,7 +377,7 @@ def header_comment(pid, idx, stem):
f"// 原文片段:[{pid}:{idx}]\n"
"// 说明:本文件由 gen_part3.py 从 wiki 片段生成(必要时补 main/QApplication\n"
"// 外壳、include、修正笔误)。详见 docs/ERRATA.md。\n"
"// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>\n"
"// 离屏验证:QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>(自动退出)\n"
"// ============================================================================\n\n"
)