课程代码仓库初始化:Part1 (144) + Part3 (29) 全量生成与验证

把川大 wiki「02.Qt方向」课程代码整理为 CMake 管理的可编译运行项目。

Part1 C/C++ 强化 (p01/, 144 目标, host gcc/g++ 构建, 全绿):
- 修复全部编译失败:补 <cstring>/<fstream>/<iomanip>/<vector> 等缺失头
  (common_fix + infer_stl_includes 自动检测注入)、修正 pause() 壳注入
  (改检测已转换后的 pause 调用)、namespace 提升出函数体、const 成员函数
  正确性、dedupe 不再破坏函数重载集、K&R 隐式 int / 动态异常规范等
  C++17 移除特性的可编译等价改写。
- 目录采用深缩写 p01/ch04/s03/ (原 part1_cpp/ch04_class_object/03_ctor_dtor)。

Part3 Qt 桌面 (p03/, 29 目标, 隔离 Qt 5.14.2 构建, 全绿):
- gen_part3.py: Qt 外壳模板 (main+QApplication+show, QTimer 自动退出便于
  offscreen 验证)、AUTOMOC + .moc include、rewrite_* 处理散文混入/重载/
  资源依赖、生成占位 png/gif + .qrc + rundata 样例数据。
- 修正 QLable 笔误、全角分号、.→->、Windows 路径、QApplication 阻塞事件
  循环改为 QCoreApplication 等。

任务1 概念/版本验证:
- tools/std_probe.py: 跨 -std= 编译探针 (--pedantic-errors 让已废除特性硬失败)。
- tools/demo_versions/: K&R 隐式 int、三目左值、void* 转换、enum 赋整、
  const 经指针、动态异常规范 共 6 个跨版本演示。
- docs/VERSION_NOTES.md: 每条「原文说法→C17/C++17 是否成立→何版本成立→已验证」。

文档: 顶层 README、docs/SOURCE_PAGES.md (页面→目录映射)、
docs/ERRATA.md (Part1 修正)、docs/ERRATA_part3.md (Part3 修正)。
另修复 tools/run.sh / run_qt.sh 的 ${1:?...} 引号语法 bug。

隔离 Qt 验证: ldd 0 系统 Qt 引用; 173/173 目标全绿; p03 offscreen 运行通过。
This commit is contained in:
张宗平
2026-06-30 14:16:55 +08:00
commit 13b3ebbe14
287 changed files with 16532 additions and 0 deletions
Binary file not shown.
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
/*
* demo_versions/const_via_pointer.c
* --------------------------------------------------------------------------
* 演示:通过指针改写 const 变量 —— C 与 C++ 行为差异。
*
* 来源:wiki 块 [58954439:21]「3.8 三目运算符/C/C++ const 异同」:
* const int a = 10;
* int* p = (int*)&a; // 强转去 const
* *p = 100; // C:可能改写(UB,但常被编译器接受);
* // C++:常量折叠,a 仍可能读出 10(替换式优化)
* 两语言下都属未定义行为(UB),但「观察到的值」常不同:C 多见改写生效,
* C++ 常因常量折叠仍打印原值。
*
* 注:本演示在 -O0 下两者都可能改写;-O2 下 C++ 常量折叠更明显。
*
* 运行:
* tools/std_probe.py --lang c --file tools/demo_versions/const_via_pointer.c
* tools/std_probe.py --lang c++ --file tools/demo_versions/const_via_pointer.c
* --------------------------------------------------------------------------
*/
#include <stdio.h>
int main(void) {
const int a = 10;
int* p = (int*)&a; /* 强转去掉 constUB:试图改写 const 对象) */
*p = 100;
/* 两语言都编译通过;观察 a 的「读出值」差异(依赖优化级别,属 UB) */
printf("a = %d\n", a);
return 0;
}
@@ -0,0 +1,29 @@
/*
* demo_versions/dynamic_exception_spec.cpp
* --------------------------------------------------------------------------
* 演示:动态异常规范 throw(类型...) —— C++17 起被移除(C++11 弃用)。
*
* 来源:wiki 块 [58954474:4]「7.2.4 异常接口声明」原意演示 throw(int,char,char*)
* 与 throw() 异常规范。这些在 C++17 已是语法错误(动态规范被移除)。
*
* 验证:
* g++ -std=c++14 → PASS(动态规范仍合法,仅弃用警告)
* g++ -std=c++17 → ERROR (动态异常规范非法)
*
* 运行:
* tools/std_probe.py --lang c++ --file tools/demo_versions/dynamic_exception_spec.cpp \
* --standards c++11,c++14,c++17,c++20
* --------------------------------------------------------------------------
*/
#include <string>
void may_throw_anything() throw(int, char) { // C++17 移除:动态异常规范
throw 1;
}
void throws_nothing() throw() { // C++17: throw() 等价 noexcept
}
int main() {
return 0;
}
+27
View File
@@ -0,0 +1,27 @@
/*
* demo_versions/enum_assign_int.c
* --------------------------------------------------------------------------
* 演示:枚举与整型的赋值差异 —— C 宽松,C++ 严格。
*
* 来源:wiki 块 [58954439:15]C/C++ 类型严格性对照):
* C 中可 `enum Color c = 1;`(整型隐式转 enum);
* C++ 中需 `enum Color c = (Color)1;` 或 `Color c = static_cast<Color>(1);`。
*
* 验证:
* gcc -std=c17 → PASSint 隐式转 enum 合法)
* g++ -std=c++17 → ERROR invalid conversion from int to Color
*
* 运行:
* tools/std_probe.py --lang c --file tools/demo_versions/enum_assign_int.c
* tools/std_probe.py --lang c++ --file tools/demo_versions/enum_assign_int.c
* --------------------------------------------------------------------------
*/
#include <stdio.h>
enum Color { RED, GREEN, BLUE };
int main(void) {
enum Color c = 1; /* C 合法;C++ 报 invalid conversion from int to Color */
printf("c = %d\n", c);
return 0;
}
+31
View File
@@ -0,0 +1,31 @@
/*
* demo_versions/kr_implicit_int.c
* --------------------------------------------------------------------------
* 演示:K&R 隐式 intC89 允许省略类型)。
*
* 来源:wiki 块 [58954439:14]「3.4 C++中所有的变量和函数都必须有类型」原意是
* 演示 C89 允许「无类型」函数与参数,C99 起被废除。
*
* 验证(用 tools/std_probe.py):
* gcc -std=c89 → PASS(隐式 int 合法)
* gcc -std=c99 → WARNING(隐式 int 已弃用)
* gcc -std=c17 → ERROR (隐式 int 非法)
*
* 运行:tools/std_probe.py --lang c --file tools/demo_versions/kr_implicit_int.c
* --------------------------------------------------------------------------
*/
/* C89:函数返回类型可省略(隐式 int);参数可只写名字(隐式 int)。 */
fun1(i) { /* 等价于 int fun1(int i) */
return i + 1;
}
fun2(p) /* 等价于 int fun2(char* p) —— 但隐式 int 下 p 被当作 int */
char* p;
{
return 0;
}
int main(void) {
return fun1(41);
}
+24
View File
@@ -0,0 +1,24 @@
/*
* demo_versions/malloc_no_cast.c
* --------------------------------------------------------------------------
* 演示:void* 隐式转换差异 —— C 合法,C++ 必须显式转换。
*
* 来源:wiki 块 [58954439:15]C 中 `char* p = malloc(n);` 合法(void* 隐式转任意指针),
* C++ 中 void* 不能隐式转其它指针,必须 `char* p = (char*)malloc(n);`。
*
* 验证:
* gcc -std=c17 → PASSvoid* 隐式转换合法)
* g++ -std=c++17 → ERROR invalid conversion from void* to char*
*
* 运行:
* tools/std_probe.py --lang c --file tools/demo_versions/malloc_no_cast.c
* tools/std_probe.py --lang c++ --file tools/demo_versions/malloc_no_cast.c
* --------------------------------------------------------------------------
*/
#include <stdlib.h>
int main(void) {
char* p = malloc(16); /* C 合法;C++ 报 invalid conversion from void* to char* */
if (p) free(p);
return 0;
}
+26
View File
@@ -0,0 +1,26 @@
/*
* demo_versions/ternary_lvalue.cpp
* --------------------------------------------------------------------------
* 演示:三目运算符 ?: 作为左值 —— C++ 独有,C 不允许。
*
* 来源:wiki 块 [58954439:18]「3.8 三目运算符功能增强」:C 中三目返回值(右值),
* C++ 中三目返回的是「两个操作数中公共类型的引用」(可为左值),故可被赋值。
*
* 验证:
* g++ -std=c++17 → PASS(三目可作左值)
* gcc -std=c17 → ERROR (三目不可作左值,lvalue required as left operand
*
* 运行:
* tools/std_probe.py --lang c++ --file tools/demo_versions/ternary_lvalue.cpp
* tools/std_probe.py --lang c --file tools/demo_versions/ternary_lvalue.cpp
* --------------------------------------------------------------------------
*/
#include <cstdio>
int main() {
int a = 1, b = 2;
/* C++:三目表达式可作为左值被赋值 */
(a > b ? a : b) = 100; // 把较大者赋为 100
printf("a=%d b=%d\n", a, b);
return 0;
}
+34
View File
@@ -0,0 +1,34 @@
# ==============================================================================
# Build-container image for the qt-course repo.
#
# Usage model (see docker_install_qt.sh / docker_build.sh / docker_run_qt.sh):
# 1. Install isolated Qt 5.14.2 INTO THE REPO at .qt514/ (one-off, ~577MB).
# The Qt files live on the host filesystem, mounted into the container.
# 2. Build the Qt part from a container, writing into the mounted build/.
# 3. RUN the compiled Qt apps on the HOST (no container needed) using
# LD_LIBRARY_PATH=.qt514/.../lib — host & container share glibc 2.39,
# so binaries built in the container run natively on the host. Isolation
# from the system Qt 5.15 is by prefix + LD_LIBRARY_PATH, not by the
# container boundary.
#
# This image only needs the build toolchain + aqtinstall. It is small and
# stable (no multi-GB Qt layer baked in; Qt is downloaded into the mount).
# ------------------------------------------------------------------------------
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Shanghai
# Build toolchain matching the host (gcc 13.3, cmake 3.28, ninja) + runtime
# libs so the container can also run GUI examples with X11 forwarding if needed.
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential g++ gcc cmake ninja-build git \
ca-certificates python3 python3-pip python3-venv \
libgl1 libegl1 libxkbcommon0 libdbus-1-3 \
libfontconfig1 libfreetype6 libxcb-cursor0 \
fontconfig xauth x11-xserver-utils \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install --break-system-packages aqtinstall
WORKDIR /workspace
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Build-container: run ONE build pass of the repo (CMake configure + compile),
# mounting the repo so build artifacts land on the host filesystem.
#
# Usage: tools/docker/docker_build.sh [extra cmake args...]
# It builds BOTH part1 (C/C++) and part3 (Qt) unless you pass args to restrict.
# The Qt part is pointed at the isolated .qt514/ toolchain.
#
# After this, run compiled apps on the HOST with tools/run_qt.sh / tools/run.sh.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
IMAGE_TAG="qt-course:builder"
QT_ROOT="/workspace/.qt514/5.14.2/gcc_64"
if [ ! -x "$REPO_ROOT/.qt514/5.14.2/gcc_64/bin/qmake" ]; then
echo ">> Isolated Qt not found at .qt514/. Run tools/docker/docker_install_qt.sh first." >&2
exit 1
fi
docker run --rm \
-v "$REPO_ROOT:/workspace" \
-w /workspace \
"$IMAGE_TAG" \
bash -lc "
set -e
cmake -S . -B build -G Ninja \
-DBUILD_QT_PART=ON \
-DCMAKE_PREFIX_PATH='${QT_ROOT}' \
-DCMAKE_BUILD_TYPE=Debug \
$*
cmake --build build --parallel
"
echo ">> Build done. Run on host: tools/run.sh <target> (Qt targets: tools/run_qt.sh <target>)"
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Build the lightweight build-container image (toolchain + aqtinstall, no Qt baked in).
# Usage: tools/docker/docker_build_image.sh
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
IMAGE_TAG="qt-course:builder"
echo ">> Building image ${IMAGE_TAG} ..."
docker build -t "$IMAGE_TAG" -f "$REPO_ROOT/tools/docker/Dockerfile" "$REPO_ROOT/tools/docker"
echo ">> Done. Next: tools/docker/docker_install_qt.sh (downloads Qt 5.14.2 into .qt514/)"
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Install the ISOLATED Qt 5.14.2 toolchain into the repo at .qt514/ using the
# build container (keeps the host free of aqtinstall deps; Qt files land on the
# host filesystem via the mount so the host can build/run against them later).
#
# Usage: tools/docker/docker_install_qt.sh
# Idempotent: re-running refreshes/repairs the install.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
IMAGE_TAG="qt-course:builder"
QT_DEST="/workspace/.qt514"
QT_VERSION="5.14.2"
if ! docker image inspect "$IMAGE_TAG" >/dev/null 2>&1; then
echo ">> image ${IMAGE_TAG} not found; building it first." >&2
"$REPO_ROOT/tools/docker/docker_build_image.sh"
fi
mkdir -p "$REPO_ROOT/.qt514"
echo ">> Installing Qt ${QT_VERSION} gcc_64 into $REPO_ROOT/.qt514/ (via container)..."
docker run --rm \
-v "$REPO_ROOT:/workspace" \
"$IMAGE_TAG" \
aqt install-qt linux desktop "${QT_VERSION}" gcc_64 -O "${QT_DEST}"
# Verify on the host (no container needed for this read).
QT_ROOT="$REPO_ROOT/.qt514/${QT_VERSION}/gcc_64"
echo ">> Verify (host):"
"$QT_ROOT/bin/qmake" -query QT_VERSION
echo ">> Qt installed at: $QT_ROOT"
echo ">> Now build with: tools/docker/docker_build.sh (or host cmake, see README)"
+213
View File
@@ -0,0 +1,213 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ==============================================================================
# file_name_map.py — 每个 (pageId, section_title) → 英文源文件名词根
#
# 规则(按用户要求):
# * 源文件名用英文,与章节/小节名称匹配(按章节号或语义命名)。
# * 同一小节内有多个源文件时,追加语义后缀区分。
# * 文件名仅含 [a-z0-9_],蛇形命名。
#
# 本表为人工映射,是 gen_part1.py / gen_part3.py 的输入。
# 键 = (pageId, section_title);值 = 英文词根(不含扩展名/前缀)。
# ==============================================================================
FILE_NAME_MAP = {
# ---- ch02 intro (58954438) ----
("58954438", "2.1.1 c++ hello world"): "hello_world",
# ---- ch03 C++对C的扩展 (58954439) ----
("58954439", "3.1 ::作用域运算符"): "scope_resolution_operator",
("58954439", "3.2.2命名空间使用语法"): "namespace_syntax",
("58954439", "3.2.3 using声明"): "using_declaration",
("58954439", "3.2.4 using编译指令"): "using_directive",
("58954439", "3.3 全局变量检测增强"): "global_variable_detection",
("58954439", "3.4 C++中所有的变量和函数都必须有类型"): "strict_function_signatures",
("58954439", "3.5 更严格的类型转换"): "stricter_type_casting",
("58954439", "3.6 struct类型加强"): "struct_enhancement",
("58954439", "3.7 \u201c新增\u201dbool类型关键字"): "bool_keyword",
("58954439", "3.8 三目运算符功能增强"): "ternary_operator",
("58954439", "3.8.2.3 C/C++中const异同总结"): "const_diff_summary",
("58954439", "3.9.3 尽量以const替换#define"): "const_vs_define",
("58954439", "3.10.1 引用基本用法"): "reference_basics",
("58954439", "3.10.2 函数中的引用"): "reference_in_function",
("58954439", "3.10.3 引用的本质"): "reference_essence",
("58954439", "3.10.4 指针引用"): "pointer_reference",
("58954439", "3.10.5 常量引用"): "const_reference",
("58954439", "3.11.2 预处理宏的缺陷"): "macro_pitfalls",
("58954439", "3.11.3.2 类内部的内联函数"): "inline_in_class",
("58954439", "3.12 函数的默认参数"): "default_arguments",
("58954439", "3.13 函数的占位参数"): "placeholder_parameters",
("58954439", "3.14.2.1 函数重载基本语法"): "function_overload_basics",
("58954439", "3.14.2.2 函数重载实现原理"): "function_overload_mechanism",
("58954439", "3.14.3 extern \u201cC\u201d浅析"): "extern_c",
# ---- ch04 class_object ----
# 01 基本概念 (58954448)
("58954448", "4.1.2 类的封装"): "class_encapsulation",
("58954448", "4.1.3 将成员变量设置为private"): "private_members",
# 02 面向对象案例 (58954449)
("58954449", "4.2.1 设计立方体类"): "cube_class_design",
("58954449", "4.2.2 点和圆的关系"): "point_and_circle",
# 03 构造和析构 (58954451)
("58954451", "4.3.2 构造函数和析构函数"): "ctor_and_dtor",
("58954451", "4.3.3 构造函数的分类及调用"): "ctor_classification",
("58954451", "4.3.4 拷贝构造函数的调用时机"): "copy_ctor_timing",
("58954451", "4.3.6 深拷贝和浅拷贝"): "deep_vs_shallow_copy",
("58954451", "4.3.7.1 初始化列表"): "initializer_list",
("58954451", "4.3.7.2 类对象作为成员"): "class_object_member",
("58954451", "4.3.8 explicit关键字"): "explicit_keyword",
("58954451", "4.3.9.2 C动态分配内存方法"): "c_dynamic_alloc",
("58954451", "4.3.9.3 new operator"): "new_operator",
("58954451", "4.3.9.4 delete operator"): "delete_operator",
("58954451", "4.3.9.5 用于数组的new和delete"): "new_delete_array",
("58954451", "4.3.9.6 delete void*可能会出错"): "delete_void_ptr",
("58954451", "4.3.10.1 静态成员变量"): "static_member_variable",
("58954451", "4.3.10.2 静态成员函数"): "static_member_function",
("58954451", "4.3.10.3 const静态成员属性"): "const_static_member",
("58954451", "4.3.10.4 静态成员实现单例模式"): "static_singleton",
# 04 对象模型 (58954456)
("58954456", "4.4.1 成员变量和函数的存储"): "member_storage",
("58954456", "4.4.2.2 this指针的使用"): "this_pointer",
("58954456", "4.4.2.3 const修饰成员函数"): "const_member_function",
("58954456", "4.4.2.4 const修饰对象(常对象)"): "const_object",
# 05 友元 (58954457)
("58954457", "4.5.1 友元语法"): "friend_syntax",
# 06 运算符重载 (58954458)
("58954458", "4.6.2 运算符重载碰上友元函数"): "overload_with_friend",
("58954458", "4.6.4 自增自减(++/--)运算符重载"): "overload_increment_decrement",
("58954458", "4.6.5 指针运算符(*、->)重载"): "overload_pointer_operator",
("58954458", "4.6.6 赋值(=)运算符重载"): "overload_assignment",
("58954458", "4.6.7 等于和不等于(==、!=)运算符重载"): "overload_equality",
("58954458", "4.6.8 函数调用符号()重载"): "overload_function_call",
# 07 继承和派生 (58954461)
("58954461", "4.7.1.1 为什么需要继承"): "why_inheritance",
("58954461", "4.7.2 派生类访问控制"): "derived_access_control",
("58954461", "4.7.3.1 继承中的对象模型"): "inheritance_object_model",
("58954461", "4.7.3.2 对象构造和析构的调用原则"): "inheritance_ctor_dtor_order",
("58954461", "4.7.3 继承中同名成员的处理方法"): "same_name_members",
("58954461", "4.7.5 继承中的静态成员特性"): "static_in_inheritance",
("58954461", "4.7.6.1 多继承概念"): "multiple_inheritance_concept",
("58954461", "4.7.6.2 菱形继承和虚继承"): "diamond_virtual_inheritance",
("58954461", "4.7.6.3 虚继承实现原理"): "virtual_inheritance_mechanism",
# 08 多态 (58954469)
("58954469", "4.8.1 多态基本概念"): "polymorphism_basics",
("58954469", "4.8.2.1 问题抛出"): "upcast_problem",
("58954469", "4.8.2.1 问题解决方案(虚函数,vitual function)"): "virtual_function_solution",
("58954469", "4.8.3 C++如何实现动态绑定"): "dynamic_binding",
("58954469", "4.8.6.1 虚析构函数作用"): "virtual_dtor_purpose",
("58954469", "4.8.6.2 纯虚析构函数"): "pure_virtual_dtor",
("58954469", "4.8.7 重写 重载 重定义"): "override_overload_redefine",
# ---- ch05 模板 (58954472) ----
("58954472", "5.2 函数模板"): "function_template",
("58954472", "5.3 函数模板和普通函数区别"): "template_vs_function",
("58954472", "5.4 函数模板和普通函数在一起调用规则"): "template_call_rules",
("58954472", "5.6模板的局限性"): "template_limitations",
("58954472", "5.7.1 类模板基本概念"): "class_template_basics",
("58954472", "5.7.2 类模板做函数参数"): "class_template_as_param",
("58954472", "5.7.3 类模板派生普通类"): "class_template_derives_class",
("58954472", "5.7.4 类模板派生类模板"): "class_template_derives_template",
("58954472", "5.7.5 类模板类内实现"): "class_template_in_class_impl",
("58954472", "5.7.6 类模板类外实现"): "class_template_out_of_class_impl",
("58954472", "5.7.7 模板类碰到友元函数"): "class_template_friend",
("58954472", "5.8 类模板的应用"): "class_template_application",
# ---- ch06 类型转换 (58954473) ----
("58954473", "6.1 静态转换(static_cast)"): "static_cast_demo",
("58954473", "2.2 动态转换(dynamic_cast)"): "dynamic_cast_demo",
("58954473", "2.3 常量转换(const_cast)"): "const_cast_demo",
# ---- ch07 异常 (58954474) ----
("58954474", "7.1 异常基本概念"): "exception_basics",
("58954474", "7.2.1 异常基本语法"): "exception_syntax",
("58954474", "7.2.2 异常严格类型匹配"): "exception_type_matching",
("58954474", "7.2.3 栈解旋(unwinding)"): "stack_unwinding",
("58954474", "7.2.4 异常接口声明"): "exception_specification",
("58954474", "7.2.5 异常变量生命周期"): "exception_variable_lifetime",
("58954474", "7.2.6 异常的多态使用"): "exception_polymorphism",
("58954474", "7.3.1 标准库介绍"): "std_exception_library",
("58954474", "7.3.2 编写自己的异常类"): "custom_exception_class",
# ---- ch08 IO流 (58954476) ----
("58954476", "8.3 标准输入流"): "standard_input_stream",
("58954476", "8.4.1 字符输出"): "character_output",
("58954476", "8.4.2 格式化输出"): "formatted_output",
("58954476", "8.4.4 C++对ASCII文件的读写操作"): "ascii_file_io",
("58954476", "8.4.5 C++对二进制文件的读写操作"): "binary_file_io",
# ---- ch09 STL ----
# 02 三大组件 (58954487)
("58954487", "2.4 案例"): "stl_components_case",
# 03 常用容器 (58954488)
("58954488", "3.1.2.9 string和c-style字符串转换"): "string_and_c_string",
("58954488", "3.2.2 vector迭代器"): "vector_iterator",
("58954488", "3.2.5.1巧用swap,收缩内存空间"): "vector_swap_shrink",
("58954488", "3.2.5.2 reserve预留空间"): "vector_reserve",
("58954488", "3.6.3 list容器的数据结构"): "list_data_structure",
("58954488", "3.7.2.5 set查找操作"): "set_find_operations",
("58954488", "3.8.3 multimap案例"): "multimap_case",
# 04 常用算法 (58954498)
("58954498", "4.1 函数对象"): "function_object",
("58954498", "4.2 谓词"): "predicate",
("58954498", "4.3 内建函数对象"): "builtin_function_object",
("58954498", "4.3 常用遍历算法"): "traversal_algorithms",
# ======================================================================
# Part 3 — Qt 桌面应用开发 (p03)
# ======================================================================
# ---- ch02 创建Qt项目 (58954515) ----
("58954515", "一个最简单的Qt应用程序"): "minimal_qt_app",
# ---- ch03 第一个Qt小程序 (58954525) ----
("58954525", "3.1 按钮的创建"): "button_creation",
("58954525", "3.2 对象模型(对象树)"): "object_tree_model",
# ---- ch04 信号和槽机制 (58954527) ----
("58954527", "4.1 系统自带的信号和槽"): "builtin_signal_slot",
("58954527", "4.2 自定义信号和槽"): "custom_signal_slot",
("58954527", "4.4 Qt4版本的信号槽写法"): "qt4_signal_slot_style",
("58954527", "4.5 Lambda表达式"): "lambda_signal_slot",
# ---- ch05 QMainWindow (58954529) ----
("58954529", "5.1 菜单栏"): "menubar",
("58954529", "5.3 状态栏"): "statusbar",
("58954529", "5.4 铆接部件"): "dock_widget",
("58954529", "5.5 核心部件(中心部件)"): "central_widget",
("58954529", "5.6 资源文件"): "resource_file",
# ---- ch06 对话框QDialog (58954535) ----
("58954535", "模态对话框"): "modal_dialog",
("58954535", "非模态对话框"): "modeless_dialog",
("58954535", "6.4 消息对话框"): "message_box",
("58954535", "6.5 标准文件对话框"): "file_dialog",
# ---- ch08 常用控件 (58954540) ----
("58954540", "显示文字 (普通文本、html"): "label_text",
("58954540", "显示图片"): "label_pixmap",
("58954540", "显示动画"): "label_movie",
("58954540", "设置/获取内容"): "lineedit_content",
("58954540", "设置显示模式"): "lineedit_echo_mode",
("58954540", "8.4 自定义控件"): "custom_widget",
# ---- ch09 Qt消息机制和事件 (58954546) ----
("58954546", "9.1 事件"): "mouse_event",
("58954546", "9.2 event()"): "event_override",
("58954546", "9.3 事件过滤器"): "event_filter",
("58954546", "9.4 总结"): "event_summary",
# ---- ch10 绘图和绘图设备 (58954548) ----
("58954548", "10.1 QPainter"): "qpainter_basic",
("58954548", "10.2.1 QPixmap、QBitmap、QImage"): "paint_device_pixmaps",
("58954548", "10.2.2 QPicture"): "qpicture_io",
# ---- ch11 文件系统 (58954552) ----
("58954552", "11.1 基本文件操作"): "qfile_basic",
("58954552", "11.2 二进制文件读写"): "qdatastream_io",
("58954552", "11.3 文本文件读写"): "qtextstream_io",
}
def stem_for(pid, section_title):
"""Return the english filename stem for a (pageId, section_title), or None."""
return FILE_NAME_MAP.get((pid, section_title))
+910
View File
@@ -0,0 +1,910 @@
#!/usr/bin/env python3
# ==============================================================================
# gen_part1.py — 生成 Part1C/C++ 强化)的全部源码与 CMakeLists
#
# 策略:解析每页标题层级得到子话题;同一子话题内的代码块(多为 test() 片段)
# 合并为「一个完整小程序」,按教学顺序嵌入并标注出处(// --- 原文片段 N ---)。
# 语言按内容判定(C 块用 .c,C++ 块用 .cpp)。每段代码先经 common_fix 修补
# 平台/笔误,再走本文件中针对个别页/块的 EXACT_FIX。
# ------------------------------------------------------------------------------
import json, os, re, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import gen_sources
from gen_sources import (REPO, WIKI, page_outline, classify_lang, is_complete,
has_windows_pause, has_pause_call, needs_cstring,
infer_stl_includes, slug, ascii_slug, write,
record_erratum, common_fix, PAGE_CONFIG, LANG_OVERRIDE)
from file_name_map import stem_for
# ERRATA is a module-level list in gen_sources that record_erratum mutates in place.
ERRATA = gen_sources.ERRATA
# track section titles we could not map, to report at end
UNMAPPED = []
def src_name_for(pid, sec_title, prefix, idx_in_page):
"""English source-file stem: explicit map → chapter-number fallback → counter."""
stem = stem_for(pid, sec_title)
if stem:
return stem
UNMAPPED.append((pid, sec_title))
# fallback: use the leading chapter number from the title (e.g. "4.3.7.1 ...")
m = re.match(r"(\d+(?:\.\d+)*)", sec_title)
if m:
num = m.group(1).replace(".", "_")
return f"{prefix}_sec_{num}"
return f"{prefix}_t{idx_in_page:02d}"
EXTRACTED = json.load(open(os.path.join(WIKI, "extracted_code.json")))
PAGES = {p["id"]: p for p in json.load(open(os.path.join(WIKI, "all_page_ids.json")))}
# ==============================================================================
# 针对单个代码块的精确修正(结构性/逻辑性错误)。返回修正后的代码。
# 仅对确有错误的块做最小改动,并 record_erratum。
# ==============================================================================
def exact_fix(pid, idx, code):
if pid == "58954439" and idx == 14:
# K&R 隐式 intC99 起非法,C17 无法编译。原意是演示「C89 允许省略类型」
# 并可向无类型参数函数传任意实参。C17 下给出显式类型的等价对照:
# fun1(i) → fun1(int i)
# fun2(i) → fun2(char* i)
# fun3() → fun3(void),原调用 fun3(1,2,"abc") 在 C17 非法 → 改 fun3()
# g(){...} → int g(void){...}
# 原始 C89 形式(含无类型参数)仍由 tools/demo_versions/kr_implicit_int.c
# 在 -std=c89 下跨版本演示。
fixed = code.replace("int fun1(i){", "int fun1(int i){")
fixed = fixed.replace("int fun2(i){", "int fun2(char* i){")
fixed = fixed.replace("int fun3(){ ", "int fun3(void){ ")
fixed = re.sub(r"\ng\(\)\{", "\nint g(void){", fixed)
# main 中 fun3(1, 2, "abc") 在 C17 下是参数过多错误;改为 fun3() 并注释说明
fixed = fixed.replace('fun3(1, 2, "abc");',
'fun3(); // 注意:C89 中 fun3() 可接受任意参数;C17 下 fun3(void) 不接受参数')
record_erratum(pid, idx, "removed-feature",
"K&R 隐式 intint fun1(i)、g(){...})及无类型参数 fun3() 接受任意实参",
"显式 intint fun1(int i)、int g(void){...}),fun2 形参显式为 char*"
"fun3() → fun3(void)、调用改为 fun3()C17 不允许向 (void) 函数传参)",
"隐式 int 与无类型/可变参数在 C99 起被废除,C17 不可编译。原始 C89 形式仍可由 "
"tools/std_probe.py 与 tools/demo_versions/kr_implicit_int.c 在 -std=c89 下演示。")
return fixed
if pid == "58954439" and idx == 38:
# 类定义末尾缺分号
if not code.rstrip().endswith(";"):
fixed = code.rstrip() + ";\n"
record_erratum(pid, idx, "syntax",
"class Person{...}(末尾缺分号)", "class Person{...};",
"C++ 类/结构体定义后必须有分号。")
return fixed
if pid == "58954451" and idx == 8:
# 伪代码:中文散文混入语句。提取可编译对照(new vs malloc),散文转注释。
fixed = (
"// 原文为伪代码对照,演示 new 与 malloc 的等价关系:\n"
"// Person* person = new Person;\n"
"// 相当于:\n"
"// Person* person = (Person*)malloc(sizeof(Person));\n"
"// if(person == NULL){ return 0; }\n"
"// person->Init(); // 构造函数\n"
"// 下面给出可编译的对照程序:\n"
"#include <iostream>\n"
"#include <cstdlib>\n"
"#include <cstring>\n"
"class Person {\n"
"public:\n"
" Person() { std::cout << \"构造函数!\" << std::endl; }\n"
" void Init() { std::cout << \"Init\" << std::endl; }\n"
"};\n"
"int main() {\n"
" Person* person = new Person; // C++: new 自动调用构造函数\n"
" Person* p2 = (Person*)malloc(sizeof(Person));\n"
" if (p2) { new (p2) Person(); } // 对齐 new 的行为(placement new\n"
" delete person;\n"
" if (p2) { p2->~Person(); free(p2); }\n"
" return 0;\n"
"}\n"
)
record_erratum(pid, idx, "pseudo-code",
"new Person / malloc / 构造函数(中文散文混入语句,不可编译)",
"可编译对照程序:new vs malloc+placement new,散文转注释",
"原文是对照式伪代码(含「相当于:」「构造函数」等中文散文),无法编译。"
"改为可编译的 new 与 malloc 行为对照(用 placement new 对齐构造语义)。")
return fixed
if pid == "58954439" and idx in (3, 6, 7):
# wiki 把 namespace 定义 / namespace 作用域成员函数定义 写在 void test(){...}
# 函数体内,这在 C++ 中非法(namespace 必须在全局/命名空间作用域)。把
# namespace 块提升到文件作用域,test() 仅保留对命名空间成员的使用,使示例
# 既可编译又保留原教学意图(演示命名空间的声明与定义分离)。
if idx == 3:
fixed = (
"namespace A{\n\tint a = 10;\n}\n"
"namespace B{\n\tint a = 20;\n}\n"
"void test(){\n"
"\tcout << \"A::a : \" << A::a << endl;\n"
"\tcout << \"B::a : \" << B::a << endl;\n"
"}\n"
)
elif idx == 6:
fixed = (
"namespace MySpace{\n\tvoid func1();\n\tvoid func2(int param);\n}\n"
)
else: # idx == 7
fixed = (
"namespace MySpace{\n\tvoid func1();\n\tvoid func2(int param);\n}\n"
"void MySpace::func1(){\n\tcout << \"MySpace::func1\" << endl;\n"
"}\n"
"void MySpace::func2(int param){\n"
"\tcout << \"MySpace::func2 : \" << param << endl;\n"
"}\n"
"void test(){\n\tMySpace::func1();\n\tMySpace::func2(42);\n}\n"
)
record_erratum(pid, idx, "illegal-in-function",
"namespace 定义 / 命名空间作用域成员函数定义 写在函数体内(void test(){ namespace A{...} }",
"把 namespace 块与成员定义提升到文件作用域,test() 仅使用其成员",
"C++ 不允许在函数体内定义 namespace 或其成员函数;标准要求它们处于全局/"
"命名空间作用域。原 wiki 写法无法编译;提升后保留教学意图。")
return fixed
if pid == "58954439" and idx == 45:
# extern_c_module 的实现文件用 EXIT_SUCCESS 但只 include 了 my_module.h
# 补上 <stdlib.h>。
if "EXIT_SUCCESS" in code and "#include <stdlib.h>" not in code:
fixed = code.replace('#include"my_module.h"',
'#include"my_module.h"\n#include <stdlib.h>')
record_erratum(pid, idx, "missing-include",
'"my_module.h" 引用 EXIT_SUCCESS 但未含 <stdlib.h>',
"补 #include <stdlib.h>",
"EXIT_SUCCESS 定义在 <stdlib.h>。")
return fixed
if pid == "58954439" and idx == 24:
# 该块引用 Person(前面小节定义于另一文件),独立编译为缺失符号。补一个最小
# 的 Person 前置定义(仅含本块用到的 age 成员 + 默认成员初始化,使
# const Person person; 可默认初始化、C++17 合法),使其自洽可编译。
pre = ("class Person { public: int age = 0; };\n")
record_erratum(pid, idx, "missing-symbol",
"const Person 演示引用 Person,但 Person 定义在另一小节(拆分为独立文件后丢失)",
"在本块顶部补一个最小 Person 定义(age 带默认成员初始化,使 const 对象可默认构造)",
"原 wiki 把 Person 定义与 const 演示放在同节、本应同文件;按「示例相对隔离」拆开后"
"本块自洽编译所需的最小前置定义。const 聚合对象需默认成员初始化才能默认构造(C++17)。")
return pre + code
if pid == "58954439" and idx == 41:
# wiki 把 namespace A/B 定义(含重载集 MyFunc)写在函数体内(被 block_classify
# 误判为 statements 包进 block_0(){}),namespace 不能在函数内定义。提升到
# 文件作用域,并在 test() 中调用命名空间内的重载演示调用(与原文重载集意图一致)。
fixed = (
"namespace A{\n"
"\tvoid MyFunc(){ cout << \"无参数!\" << endl; }\n"
"\tvoid MyFunc(int a){ cout << \"a: \" << a << endl; }\n"
"\tvoid MyFunc(string b){ cout << \"b: \" << b << endl; }\n"
"\tvoid MyFunc(int a, string b){ cout << \"a: \" << a << \" b:\" << b << endl;}\n"
"\tvoid MyFunc(string b, int a){ cout << \"a: \" << a << \" b:\" << b << endl;}\n"
"}\n"
"namespace B{\n"
"\tvoid MyFunc(string b, int a){}\n"
"\t//int MyFunc(string b, int a){} //无法重载仅按返回值区分的函数\n"
"}\n"
"void test(){\n"
"\tA::MyFunc();\n"
"\tA::MyFunc(10);\n"
"\tA::MyFunc(\"hello\");\n"
"\tA::MyFunc(10, \"world\");\n"
"\tA::MyFunc(\"world\", 10);\n"
"\tB::MyFunc(\"ok\", 1);\n"
"}\n"
)
record_erratum(pid, idx, "illegal-in-function",
"namespace A/B 定义(含重载集 MyFunc)写在函数体内(void test(){ namespace A{...} }",
"把 namespace 块提升到文件作用域,test() 通过 A::/B:: 调用各重载",
"C++ 不允许在函数体内定义 namespace;标准要求其处于全局/命名空间作用域。"
"原 wiki 写法无法编译;提升后保留重载集演示意图。")
return fixed
if pid == "58954439" and idx == 42:
# 该块演示「函数重载碰上默认参数 → 二义性」。原文 main 里 MyFunc("hello")
# 在两个重载间确实二义,编译器拒绝——但教学意图是「展示二义性」,不是产出
# 可运行错误。注意:MyFunc(string) 与 MyFunc(string,int=10) 中,任何只给一个
# string 实参的调用都二义(强转 string 也无济于事,两候选都同等匹配)。故把
# 单参调用注释为「二义不可调用」,main 只显式调用双参重载,使示例可编译运行
# 且保留「二义性」教学信息。
fixed = (
"void MyFunc(string b){\n"
"\tcout << \"b: \" << b << endl;\n"
"}\n"
"//函数重载碰上默认参数\n"
"void MyFunc(string b, int a = 10){\n"
"\tcout << \"a: \" << a << \" b:\" << b << endl;\n"
"}\n"
"int main(){\n"
"\t// MyFunc(\"hello\"); // 二义:MyFunc(string) 与 MyFunc(string,int=10) 同等匹配\n"
"\t// MyFunc(string(\"hello\")); // 仍二义:强转 string 也无法区分两候选\n"
"\tMyFunc(\"hello\", 20); // 显式给第二参 → 明确选双参重载\n"
"\treturn 0;\n"
"}\n"
)
record_erratum(pid, idx, "ambiguous-overload",
'main 中 MyFunc("hello") 在 MyFunc(string) 与 MyFunc(string,int=10) 间二义,无法编译',
"注释保留二义调用作说明,main 只显式调用双参重载(单参形式因二义不可调用)",
"函数重载遇上默认参数时,编译器无法在两个可行候选间抉择(二义性)。即使强转 "
"std::string 也无法消除——两候选对单个 string 实参同等匹配。原文意在演示此现象,"
"作为可运行示例只能保留可调用的双参形式并注释说明二义点。")
return fixed
if pid == "58954456" and idx == 3:
# const 成员函数 ChangePerson() const 内修改非 mutable 成员 mAgemID 是
# mutable 合法)。const 成员函数不得修改对象逻辑状态——原文 mAge=100 非法。
# 修正:把对 mAge 的修改注释为「非法(演示 const 约束)」,仅保留对 mutable
# mID 的合法修改,并在注释中说明 const 成员函数的语义。
fixed = code.replace(
"\tvoid ChangePerson() const{\n"
"\t\tmAge = 100;\n"
"\t\tmID = 100;\n"
"\t}",
"\tvoid ChangePerson() const{\n"
"\t\t//mAge = 100; // 非法:const 成员函数内不能修改非 mutable 成员\n"
"\t\tmID = 100; // 合法:mID 被 mutable 修饰,const 函数内可改\n"
"\t}")
if fixed != code:
record_erratum(pid, idx, "const-correctness",
"const 成员函数 ChangePerson() const 内修改非 mutable 成员 mAgemAge = 100",
"注释掉对 mAge 的修改,保留对 mutable mID 的修改,并加注释说明",
"const 成员函数承诺不修改对象的逻辑状态,故不得修改非 mutable 数据成员;"
"mID 被 mutable 修饰例外。原写法在 C++17 下编译报错。")
return fixed
if pid == "58954461" and idx == 0:
# wiki 在 class IndexPage 前混入了孤立的中文散文标题词「网页类」(非注释、
# 非代码),编译器报 '网页类' does not name a type。把该散文行转为注释。
if re.search(r"(?m)^[ \t]*网页类[ \t]*$", code):
fixed = re.sub(r"(?m)^([ \t]*)网页类([ \t]*)$",
r"\1// 网页类(原文小标题,转为注释)\2", code)
record_erratum(pid, idx, "prose-in-code",
"class IndexPage 前混入孤立中文散文词「网页类」(非注释非代码)",
"把该散文行转为注释",
"wiki 正文标题词被误纳入代码块,C++ 无法解析;转为注释保留教学上下文。")
return fixed
if pid == "58954469" and idx == 5:
# B 有纯虚析构函数(virtual ~B() = 0;),故 B 是抽象类,不可实例化。原文
# test() 里 B b; 试图构造抽象类对象,编译报错。教学意图正是「B 是抽象类不能
# 实例化」——把 B b; 注释为「非法(演示)」并说明,A a; 保留。
if re.search(r"(?m)^[ \t]*B[ \t]+b[ \t]*;", code):
fixed = re.sub(r"(?m)^([ \t]*)B[ \t]+b[ \t]*;",
r"\1//B b; // 非法:B 含纯虚析构函数,是抽象类,不能实例化对象", code)
record_erratum(pid, idx, "abstract-instantiation",
"test() 中 B b; 试图实例化抽象类 B(B 有纯虚析构函数 virtual ~B()=0",
"注释掉 B b; 并加说明(B 是抽象类不可实例化),A a; 保留",
"含纯虚函数(含纯虚析构函数)的类是抽象类,C++ 禁止创建其对象。原文此处"
"正是演示该约束,但作为可编译示例需把非法构造注释化。")
return fixed
if pid == "58954473" and idx == 2:
# 笔误:注释行 //int b = const_cast<int>(a); 末尾误带了函数闭合花括号 },
# 导致 test01() 未闭合,test02() 被嵌套进 test01() 内 → 函数定义不允许在此。
# 把那个误入注释的 } 移出注释作为 test01() 的正常闭合。
bad = "//int b = const_cast<int>(a); }"
good = ("//int b = const_cast<int>(a); // 不能对非指针或非引用进行 const_cast\n"
"} // end of test01()")
if bad in code:
fixed = code.replace(bad, good)
record_erratum(pid, idx, "syntax",
"//int b = const_cast<int>(a); }(闭合花括号误入注释行)",
"把 } 移出注释作为 test01() 的正常闭合花括号",
"原 wiki 把 test01() 的闭合 } 误写进注释,导致函数体未结束、后续 test02() "
"被嵌套在 test01() 内,C++ 禁止函数内定义函数。")
return fixed
if pid == "58954474" and idx == 4:
# 动态异常规范 throw(int,char,char*) 与 throw() 在 C++17 起被废除(throw()
# 等价 noexcept 已在 C++11 弃用,C++17 移除动态规范)。教学意图是展示「异常
# 规范」概念。给出 C++17 可编译的等价形式:throw()→noexceptthrow(类型...)
# →noexcept(false)(无法精确表达「只抛某类型」,因 C++17 不支持),并注释
# 说明旧规范的语义与废除版本。
fixed = (
"//可抛出所有类型异常\n"
"void TestFunction01(){\n"
"\tthrow 10;\n"
"}\n\n"
"// 【C++17 修正】原文为 throw(int,char,char*) 动态异常规范——C++17 起非法\n"
"// C++11 弃用、C++17 移除)。动态规范无法在现代 C++ 中精确表达「只抛这些类型」,\n"
"// 等价改写为 noexcept(false)(表示可能抛异常)。原文语义见 tools/demo_versions/。\n"
"void TestFunction02() noexcept(false){\n"
"\tstring exception = \"error!\";\n"
"\tthrow exception;\n"
"}\n\n"
"// 【C++17 修正】原文为 throw()(不抛任何异常)→ C++17 等价于 noexcept。\n"
"void TestFunction03() noexcept {\n"
"\t//throw 10; // noexcept 函数抛异常会调用 std::terminate\n"
"}\n\n"
"int main(){\n\n"
"\ttry{\n"
"\t\t//TestFunction01();\n"
"\t\t//TestFunction02();\n"
"\t\t//TestFunction03();\n"
"\t}\n"
"\tcatch (...){\n"
"\t\tcout << \"捕获异常!\" << endl;\n"
"\t}\n\n"
"\treturn EXIT_SUCCESS;\n"
"}\n"
)
record_erratum(pid, idx, "removed-feature",
"动态异常规范 throw(int,char,char*) 与 throw()C++17 已移除)",
"throw() → noexceptthrow(类型...) → noexcept(false),并注释说明旧语义",
"动态异常规范在 C++11 弃用、C++17 移除,C++17 下是语法错误。throw() 的等价物是 "
"noexcept;带类型列表的动态规范无法在现代 C++ 中精确表达,用 noexcept(false) 近似"
"(表示可能抛异常)。旧规范演示见 tools/demo_versions/dynamic_exception_spec.cpp"
"(在 -std=c++14 下编译)。")
return fixed
if pid == "58954472" and idx == 11:
# MyArray 类模板应用:wiki 把「测试代码」用例的裸语句(中文标题词「测试代码:」
# + 文件作用域的 MyArray<int> myArrayInt(10); 及 for 循环、Push_back 调用等)
# 直接铺在类定义之后,没有任何函数包裹 → 全局变量初始化里含 for 循环非法,
# 且中文散文词非法。重写为:保留 MyArray 模板 + Person 类 + 打印函数,把测试
# 驱动语句包进 main(),使示例可编译运行并演示类模板。
fixed = (
"#include <string>\n"
"using std::string;\n\n"
"template<class T>\n"
"class MyArray {\n"
"public:\n"
"\texplicit MyArray(int capacity) {\n"
"\t\tthis->m_Capacity = capacity;\n"
"\t\tthis->m_Size = 0;\n"
"\t\t// 如果T是对象,那么这个对象必须提供默认的构造函数\n"
"\t\tpAddress = new T[this->m_Capacity];\n"
"\t}\n"
"\tMyArray(const MyArray& arr) {\n"
"\t\tthis->m_Capacity = arr.m_Capacity;\n"
"\t\tthis->m_Size = arr.m_Size;\n"
"\t\tthis->pAddress = new T[this->m_Capacity];\n"
"\t\tfor (int i = 0; i < this->m_Size; i++) {\n"
"\t\t\tthis->pAddress[i] = arr.pAddress[i];\n"
"\t\t}\n"
"\t}\n"
"\tT& operator[](int index) { return this->pAddress[index]; }\n"
"\tvoid Push_back(const T& val) {\n"
"\t\tif (this->m_Capacity == this->m_Size) return;\n"
"\t\tthis->pAddress[this->m_Size] = val;\n"
"\t\tthis->m_Size++;\n"
"\t}\n"
"\tvoid Pop_back() { if (this->m_Size > 0) this->m_Size--; }\n"
"\tint getSize() { return this->m_Size; }\n"
"\t~MyArray() {\n"
"\t\tif (this->pAddress != NULL) {\n"
"\t\t\tdelete[] this->pAddress;\n"
"\t\t\tthis->pAddress = NULL;\n"
"\t\t\tthis->m_Capacity = 0;\n"
"\t\t\tthis->m_Size = 0;\n"
"\t\t}\n"
"\t}\n"
"private:\n"
"\tT* pAddress;\n"
"\tint m_Capacity;\n"
"\tint m_Size;\n"
"};\n\n"
"class Person {\n"
"public:\n"
"\tPerson() {}\n"
"\tPerson(string name, int age) { this->mName = name; this->mAge = age; }\n"
"public:\n"
"\tstring mName;\n"
"\tint mAge;\n"
"};\n\n"
"void PrintMyArrayInt(MyArray<int>& arr) {\n"
"\tfor (int i = 0; i < arr.getSize(); i++) {\n"
"\t\tcout << arr[i] << \" \";\n"
"\t}\n"
"\tcout << endl;\n"
"}\n\n"
"void PrintMyPerson(MyArray<Person>& personArr) {\n"
"\tfor (int i = 0; i < personArr.getSize(); i++) {\n"
"\t\tcout << \"姓名:\" << personArr[i].mName << \" 年龄:\" << personArr[i].mAge << endl;\n"
"\t}\n"
"}\n\n"
"// --- 测试代码(原文为文件作用域裸语句,已包进 main 使其可编译运行)---\n"
"int main() {\n"
"\tMyArray<int> myArrayInt(10);\n"
"\tfor (int i = 0; i < 9; i++) {\n"
"\t\tmyArrayInt.Push_back(i);\n"
"\t}\n"
"\tmyArrayInt.Push_back(100);\n"
"\tPrintMyArrayInt(myArrayInt);\n\n"
"\tMyArray<Person> myArrayPerson(10);\n"
"\tPerson p1(\"德玛西亚\", 30);\n"
"\tPerson p2(\"提莫\", 20);\n"
"\tPerson p3(\"孙悟空\", 18);\n"
"\tPerson p4(\"赵信\", 15);\n"
"\tPerson p5(\"赵云\", 24);\n"
"\tmyArrayPerson.Push_back(p1);\n"
"\tmyArrayPerson.Push_back(p2);\n"
"\tmyArrayPerson.Push_back(p3);\n"
"\tmyArrayPerson.Push_back(p4);\n"
"\tmyArrayPerson.Push_back(p5);\n"
"\tPrintMyPerson(myArrayPerson);\n"
"\treturn 0;\n"
"}\n"
)
record_erratum(pid, idx, "prose-in-code",
"「测试代码:」中文散文词 + 文件作用域裸语句(MyArray<int> myArrayInt(10);、"
"for 循环、Push_back 调用等)直接铺在类定义之后,无函数包裹",
"删除散文词,把测试驱动语句包进 main(),保留 MyArray 模板 + Person 类 + 打印函数",
"C++ 不允许在文件作用域直接写可执行语句(如 for 循环、函数调用);中文散文词"
"亦非法。原文是把「使用示例」当散文铺排;重写为可编译运行的单文件程序,保留"
"类模板应用的教学意图。")
return fixed
if pid == "58954439" and idx == 46:
# extern "C" 演示:main 调用 func1()/func2(),但这两个函数在本文件中仅有
# extern 声明(定义在配对的 45 块 MyModule.c)。本文件作为独立可执行目标编译
# 时链接报 undefined reference。修正:在本文件内就地提供 func1/func2 的 extern
# "C" 定义(最小实现),使示例自洽可编译运行;保留原文的 extern "C" 声明教学。
fixed = code + (
"\n// --- 为使本示例作为独立目标可链接,就地提供 func1/func2 的 extern \"C\" 定义 ---\n"
'// (原文仅声明;定义在配对的 MyModule.c。此处补最小实现以自洽编译运行。)\n'
'extern "C" void func1(){ printf("hello world!"); }\n'
'extern "C" int func2(int a, int b){ return a + b; }\n'
)
record_erratum(pid, idx, "missing-definition",
"main 调用 func1()/func2(),但本文件仅有 extern \"C\" 声明(定义在配对的 MyModule.c)",
"在本文件就地补 func1/func2 的 extern \"C\" 定义(最小实现)",
"原 wiki 把声明与定义分属两文件;按「示例相对隔离」拆分为独立目标后,本文件"
"缺少定义导致链接失败。补最小实现使其自洽可运行,保留 extern \"C\" 声明的教学意图。")
return fixed
if pid == "58954474" and idx == 8:
# 自定义异常类 MyOutOfRange::what() 重写 exception::what()。C++11 起
# std::exception::what() 声明为 noexcept,重写版本必须也 noexcept(否则
# 「looser exception specification」编译错)。补 noexcept。
if re.search(r"virtual\s+const\s+char\s*\*\s*what\s*\(\s*\)\s*const\s*\{", code):
fixed = re.sub(r"virtual\s+const\s+char\s*\*\s*what\s*\(\s*\)\s*const\s*\{",
"virtual const char* what() const noexcept {", code)
record_erratum(pid, idx, "override-noexcept",
"MyOutOfRange::what() const 重写 exception::what() 但缺 noexcept",
"补 noexceptvirtual const char* what() const noexcept",
"C++11 起 std::exception::what() 声明为 noexcept;重写函数的异常规范不得"
"比基类更宽松,必须同样 noexcept,否则 C++17 编译报「looser exception "
"specification」。原 wiki 写于旧标准,未带 noexcept。")
return fixed
if pid == "58954488" and idx == 5:
# set 排序仿函数 MyCompare02/03 的 operator() 非 const。C++14 起 set 的比较
# 对象要求「可 const 调用」(comparison object must be invocable as const),
# 非 const operator() 触发 static_assert。给两个 operator() 加 const。
fixed = code.replace(
"bool operator()(int v1,int v2){\n\t\treturn v1 > v2;\n\t}",
"bool operator()(int v1, int v2) const {\n\t\treturn v1 > v2;\n\t}")
fixed = fixed.replace(
"bool operator()(const Person& p1,const Person& p2){\n\t\treturn p1.mAge > p2.mAge;\n\t}",
"bool operator()(const Person& p1, const Person& p2) const {\n\t\treturn p1.mAge > p2.mAge;\n\t}")
if fixed != code:
record_erratum(pid, idx, "const-comparator",
"set 的排序仿函数 MyCompare02/MyCompare03 的 operator() 非 const",
"给两个 operator() 加 const 修饰",
"C++14 起,关联容器(set/map)的比较对象必须可 const 调用(标准要求 "
"Compare 对象满足 invocable as const)。原 wiki 的非 const operator() 在 "
"C++17 libstdc++ 下触发 static_assert。")
return fixed
if pid == "58954488" and idx == 4:
# list 节点结构演示:原文直接访问 MSVC 实现私有的 list 内部成员
# _Nodeptr / _Myhead / _Next / _Myval / _Mysize),这些是 MSVC STL 的
# 实现细节,libstdc++(gcc)没有等价物,无法编译。教学意图是「演示 list 的
# 环形双向链表节点结构」。改写为:用标准接口(迭代器)遍历 list,并用注释
# 图示说明环形链表结构(_Myhead 是哨兵头节点、_Next/_Prev 串联、_Myval 存值),
# 保留教学意图且跨实现可移植。
fixed = (
"#include <iostream>\n"
"#include <list>\n"
"using namespace std;\n\n"
"// 【可移植性说明】原文直接访问 MSVC STL 的 list 私有成员\n"
"// _Nodeptr / _Myhead / _Next / _Mysize / _Myval)来「演示 list 的环形\n"
"// 双向链表结构」。这些是 MSVC 实现细节,libstdc++(gcc)无等价物,无法移植。\n"
"// 下面改用标准迭代器接口遍历,并以注释图示说明 list 的环形双向链表结构:\n"
"//\n"
"// _Myhead (哨兵节点) ──┐\n"
"// ↑ ↓ │ list 内部维护一个哨兵头节点 _Myhead,\n"
"// [_Prev|_Myval|_Next] ←──→ [_Prev|_Myval|_Next] ←──→ ... ──┘\n"
"// 末节点.next = _Myhead_Myhead.prev = 末节点;_Mysize 记录元素个数。\n"
"// 遍历从 _Myhead->_Next 开始,绕一圈回到 _Myhead 即结束。\n\n"
"int main(){\n"
"\tlist<int> myList;\n"
"\tfor (int i = 0; i < 10; i++){\n"
"\t\tmyList.push_back(i);\n"
"\t}\n\n"
"\t// 标准(可移植)方式:用迭代器遍历环形双向链表\n"
"\tcout << \"size = \" << myList.size() << endl;\n"
"\tfor (list<int>::iterator it = myList.begin(); it != myList.end(); it++){\n"
"\t\tcout << \"Node:\" << *it << endl;\n"
"\t}\n\n"
"\treturn EXIT_SUCCESS;\n"
"}\n"
)
record_erratum(pid, idx, "implementation-specific",
"直接访问 MSVC STL 私有成员 _Nodeptr/_Myhead/_Next/_Myval/_Mysize(演示 list 节点结构)",
"改用标准迭代器遍历,以注释图示说明环形双向链表结构",
"MSVC STL 的下划线大写成员是实现细节,libstdc++ 无等价物,跨编译器不可移植。"
"C++ 标准不暴露容器内部节点结构;原写法仅在 MSVC 下可编译。改用标准接口 + 图示"
"注释保留教学意图。")
return fixed
if pid == "58954476" and idx == 4:
# 二进制文件读写:Person 只有 Person(char*,int) 构造,无默认构造,但读取时
# 用 Person p3; 默认构造 → 报错。补一个默认构造(成员带默认值),使二进制读
# 入占位对象合法。同时 char* fileName = "person.txt" 在 C++17 是 const char*
# → char* 的弃用转换(-Wall 报警,本例仅作最小修正)。
if "Person(char* name,int age)" in code and "Person(){}" not in code:
fixed = code.replace(
"class Person{\npublic:\n\tPerson(char* name,int age){",
"class Person{\npublic:\n\tPerson(){} // 补默认构造:二进制读入需先默认构造占位对象\n\tPerson(char* name,int age){")
record_erratum(pid, idx, "missing-default-ctor",
"Person 只有 Person(char*,int) 构造,但二进制读取用 Person p3; 默认构造",
"补一个空的默认构造 Person(){}",
"二进制文件读取需先默认构造一个占位对象再 read() 填充;原 Person 无默认"
"构造,C++17 报「no matching function for call to Person::Person()」。")
return fixed
return code
# ==============================================================================
# C/C++ 控制台程序的合并模板
# ==============================================================================
C_PREAMBLE = (
"#include <stdio.h>\n"
"#include <stdlib.h>\n"
"#include <string.h>\n"
)
CPP_PREAMBLE = (
"#include <iostream>\n"
"using namespace std;\n"
)
# 便携 pause() 壳(仅当某段用到 pause() 时注入;Windows 下原意是等待用户回车)
PAUSE_HELPER = (
"#include <cstdio>\n"
"static inline void pause() { printf(\"按回车键继续...\"); (void)getchar(); }\n"
)
# matches a top-level (column-0) function definition header like "void test01(){"
# or "int fun2(int a, int b){" — captures return-type and function name.
_TOPFN_RE = re.compile(
r"^([A-Za-z_][\w:&<>,\s\*]*?\s+)([A-Za-z_]\w*)\s*\(", re.M)
# a real function definition: optional ret-type, name, "(...)", then a body "{"
_REAL_FN_DEF_RE = re.compile(
r"^([A-Za-z_][\w:&<>,\s\*]*?\s+)?([A-Za-z_]\w*)\s*\([^;]*\)\s*\{", re.M)
def block_classify(code):
"""Classify a fragment's shape so we know how to make it compile.
'program' — has int main(...); kept as-is.
'fndef' — has a top-level function definition with body (e.g. void test(){...}).
'groupdecl' — only global declarations (namespace/class/typedef/using/var defs).
'statements'— loose statements with NO enclosing function (copied from inside
a test() body); must be wrapped to compile.
"""
if re.search(r"\bint\s+main\s*\(", code):
return "program"
if _REAL_FN_DEF_RE.search(code):
return "fndef"
stripped = re.sub(r"//[^\n]*", "", code)
stripped = re.sub(r"/\*.*?\*/", "", stripped, flags=re.S)
lines = [l.strip() for l in stripped.splitlines() if l.strip()]
if not lines:
return "statements"
decl_re = re.compile(
r"^(namespace|class|struct|typedef|using|template|enum|extern|#)\b"
r"|^(int|char|double|float|bool|auto|void|const|static|unsigned|long|short|size_t|wchar_t|string|std::)"
r"[\w:&<>,\*\s]*\s*[A-Za-z_]\w*\s*(=\s*[^;{]*|\([^;{]*\)\s*(=\s*\d+)?)?\s*;"
r"|^(int|char|double|float|bool|auto|void|const|static|size_t|wchar_t|string|std::)"
r"[\w:&<>,\*\s]*?\s+\*?[A-Za-z_]\w*\s*\([^;{]*\)\s*;\s*$")
# 'groupdecl' lines: a declaration line OR a namespace/class/struct/enum block
# boundary line (bare '{', bare '}', or '};' on its own). A bare call like
# printf(...); is NOT a declaration (no leading type) → classify as statements.
return "groupdecl" if all(decl_re.match(l) or l in {"{", "}", "};"} for l in lines) else "statements"
def wrap_statements(code, seq):
"""Wrap a loose-statement fragment in static void func_N(){...} so it compiles."""
if block_classify(code) != "statements":
return code, None
body = "\n".join((" " + l) if l.strip() else l for l in code.splitlines())
fn = f"block_{seq}"
return f"static void {fn}() {{\n{body}\n}}\n", fn
def dedupe_functions(blocks_with_meta):
"""Across the merged blocks, rename duplicated top-level function *definitions*
to <name>_<seq> so the file compiles. The wiki commonly shows several
alternative variants of the same `void test(){...}` under one heading; they
are meant to be read individually, not linked together. We keep all variants
in one file (preserving the teaching progression) but give each a unique name
and call each from main(). Recursion/calls within the same block are renamed
consistently.
CRITICAL: two definitions with the same name but DIFFERENT parameter lists
are legal function *overloads* and must NOT be renamed (renaming them all to
<name>_N would collapse the overload set into redefinitions). We only rename
a duplicate whose (name, param-count) signature already appeared — a true
variant. Returns the (possibly renamed) blocks."""
seen = {} # (fn_name, param_count) -> count of definitions seen so far
out = []
for (idx, code, sec_title) in blocks_with_meta:
# find top-level (col 0) function names *defined* in this block
# (a definition has a body on the same or next line; a call is indented)
defined = []
for m in _TOPFN_RE.finditer(code):
name = m.group(2)
# only treat as definition if the header is at column 0 (not indented call)
if m.start() == 0 or code[m.start()-1] == "\n":
# and it's followed by a body `{` within a few lines (def, not decl)
tail = code[m.end():m.end()+200]
if re.match(r"[^{;]*\{", tail):
# capture the parameter list to count params (overload detection).
# _TOPFN_RE ends right after the opening '(', so m.end() is just
# past '('; grab from there to the matching ')'.
rest = code[m.end():]
paren = re.match(r"([^)]*)\)", rest)
params = paren.group(1) if paren else ""
pcount = 0 if params.strip() == "" or params.strip() == "void" \
else params.count(",") + 1
defined.append((name, pcount))
rename = {} # old_name → new_name (applied by token; renames are unique
# per old name, so overloads sharing a name all map together)
name_seq = {} # name → last assigned seq
for (name, pcount) in defined:
if name == "main":
continue # never rename main; a block with its own main stands alone
if name == "operator":
# operator() / operator+ etc. are overloaded operators, not plain
# functions — renaming them to operator_N breaks the operator.
# They live inside classes anyway and don't collide at file scope.
continue
key = (name, pcount)
cnt = seen.get(key, 0)
# rename only if THIS exact signature (name+paramcount) was seen before —
# a same-name/different-paramcount def is a legal overload, left intact.
if cnt > 0:
seq = name_seq.get(name, 0) + 1
name_seq[name] = seq
rename[name] = f"{name}_{seq}"
seen[key] = cnt + 1
if rename:
# word-boundary replace of each old name with new (only the function
# name token; safe enough for these small teaching snippets)
newcode = code
for old, new in rename.items():
newcode = re.sub(r"\b" + re.escape(old) + r"\b", new, newcode)
code = newcode
out.append((idx, code, sec_title))
return out
def emit_console(target_name, blocks_with_meta, lang, out_dir, src_name=None):
"""Emit one .c/.cpp = one add_executable, merging given (idx,code,section_title)
blocks. Returns (rel_source_path, list of block ids used).
target_name: ASCII-only CMake target name (CMake forbids non-ASCII targets).
src_name: human-readable source filename (may contain CJK); defaults to
a transliteration of target_name."""
# First, rename duplicate function definitions across blocks so the merge
# compiles (each variant gets a unique name; all are called from main).
blocks_with_meta = dedupe_functions(blocks_with_meta)
ext = "c" if lang == "c" else "cpp"
if src_name is None:
src_name = target_name
fname = f"{src_name}.{ext}"
parts = []
# header
title_line = blocks_with_meta[0][2]
parts.append(f"// ============================================================================\n")
parts.append(f"// {target_name}{title_line}\n")
parts.append(f"// 来源:川大 C++/LinuxC wiki「02.Qt方向」\n")
parts.append(f"// 说明:本文件由 gen_part1.py 合并页面中的教学片段而成。每个片段用\n")
parts.append(f"// // --- 原文片段 [pageId:blockIdx] --- 标注出处。\n")
parts.append(f"// 已做最小修正(笔误/平台移植),详见 docs/ERRATA.md。\n")
parts.append(f"// ============================================================================\n\n")
# includes
# NOTE: detect pause() on the already-common_fix-ed code (common_fix converts
# system("pause") → pause()), so has_pause_call sees the converted form.
# Detecting the raw 'system("pause")' literal here would always be False and
# miss the pause() helper injection — the root cause of past link failures.
needs_pause = any(has_pause_call(b[1]) for b in blocks_with_meta)
needs_str = any(needs_cstring(b[1]) for b in blocks_with_meta)
# aggregate STL/IO headers inferred across all merged blocks (sorted, deduped)
stl_hdrs = sorted({h for b in blocks_with_meta for h in infer_stl_includes(b[1])})
if needs_str and "<cstring>" not in stl_hdrs:
stl_hdrs.append("<cstring>")
if lang == "c":
parts.append(C_PREAMBLE)
else:
parts.append(CPP_PREAMBLE)
for h in stl_hdrs:
parts.append(f"#include {h}\n")
if needs_pause:
parts.append("\n" + PAUSE_HELPER)
parts.append("\n")
# body: each block is normalized to a compilable unit before emission.
# 'program' → kept as-is (brings its own main).
# 'fndef' → the test()/fun() definitions are emitted as-is; called from main.
# 'groupdecl' → global decls (namespace/class/typedef/var) emitted at file scope.
# 'statements' → loose statements wrapped in static void block_N(){...} and called from main.
call_fns = []
for n, (idx, code, sec_title) in enumerate(blocks_with_meta):
parts.append(f"// --- 原文片段 [{PAGES_META_PID}:{idx}] {sec_title} ---\n")
body = code.rstrip() + "\n"
try:
cls = block_classify(body)
except Exception:
cls = "fndef" # conservative default
if cls == "statements":
wrapped, fn = wrap_statements(body, n)
if fn:
call_fns.append(fn)
parts.append(wrapped)
else:
parts.append(body)
if cls in ("fndef", "program"):
# only call no-arg DEMO entry points (test* / block_*) from main; any
# internal helpers they call (with params) are called by them.
for m in _REAL_FN_DEF_RE.finditer(body):
name = m.group(2)
if name == "main":
continue
if re.match(r"^(test|block_|demo_|run)", name):
call_fns.append(name)
parts.append("\n")
already_has_main = any(is_complete(b[1]) for b in blocks_with_meta)
call_fns = list(dict.fromkeys(call_fns))
parts.append("// --- main ---\n")
if already_has_main:
pass # rely on a block's own main
elif call_fns:
parts.append("int main() {\n")
for fn in call_fns:
parts.append(f" {fn}();\n")
if needs_pause:
parts.append(" pause();\n")
parts.append(" return 0;\n}\n")
else:
parts.append("int main() {\n return 0;\n}\n")
src_rel = os.path.join(out_dir, fname)
write(os.path.join(REPO, src_rel), "".join(parts))
return src_rel, [b[0] for b in blocks_with_meta]
# global set by main()
PAGES_META_PID = "?"
# ==============================================================================
# 主流程
# ==============================================================================
def main():
global PAGES_META_PID
part1_pages = [pid for pid, cfg in PAGE_CONFIG.items() if cfg["part"] == "p01"]
# collect CMake target entries per chapter dir
targets_by_dir = {} # dir -> list of (target_name, src_rel)
for pid in part1_pages:
cfg = PAGE_CONFIG[pid]
PAGES_META_PID = pid
body = json.load(open(os.path.join(WIKI, "bodies", f"{pid}.json"))) \
.get("body", {}).get("storage", {}).get("value", "")
blocks = EXTRACTED.get(pid, [])
sections = page_outline(body)
out_dir = os.path.join("p01", cfg["dir"])
# Decide language per block (override > classify)
def lang_of(i):
if (pid, i) in LANG_OVERRIDE:
return LANG_OVERRIDE[(pid, i)]
return classify_lang(blocks[i]["code"])
# If a page is almost-all one language, emit per-section merged files in that lang.
# Otherwise emit one file per section using the section's dominant language.
all_langs = [lang_of(i) for i in range(len(blocks))]
dominant = "cpp" if all_langs.count("cpp") >= all_langs.count("c") else "c"
used = [False] * len(blocks)
# --- 特殊处理:58954439:44 与 58954439:45 是 header/impl 对,作为一组两文件目标 ---
if pid == "58954439":
# 44 = MyModule.h, 45 = MyModule.c → 单独目标 p1c03_mymodule
code44 = exact_fix(pid, 44, common_fix(blocks[44]["code"], pid, 44))
code45 = exact_fix(pid, 45, common_fix(blocks[45]["code"], pid, 45))
hdr = os.path.join(out_dir, "my_module.h")
impl = os.path.join(out_dir, "extern_c_module_main.c")
write(os.path.join(REPO, hdr), code44 + "\n")
write(os.path.join(REPO, impl),
f"// {PAGES_META_PID}:45 — MyModule 实现文件(与 my_module.h 配对)\n"
f"// 由 gen_part1.py 成对输出。\n\n"
+ code45.replace('"MyModule.h"', '"my_module.h"').replace('#include"my_module.h"',
'#include"my_module.h"\n#include <stdlib.h>')
+ "\nint main(){ func1(); printf(\"func2=%d\\n\", func2(3,4)); return EXIT_SUCCESS; }\n")
used[44] = used[45] = True
targets_by_dir.setdefault(out_dir, []).append(("p1c03_mymodule", "extern_c_module_main.c"))
# --- 58954439:14 K&R 隐式 int 由通用路径处理(exact_fix 给出 C17 修正版)---
# --- 通用:每个代码块 = 一个独立目标(block-per-target---
# 每个 wiki 代码块都是独立的教学示例(常含同名函数/共享符号的多个变体),
# 合并会导致符号重定义;逐块成文件既保持教学隔离,又避免跨块冲突。
# ASCII target names: CMake forbids non-ASCII target names, so we use a
# per-page counter (prefix_NN). Source filenames are English (mapped via
# file_name_map.py); same section repeated → suffix _2, _3...
tgt_counter = [0]
def next_target(prefix):
tgt_counter[0] += 1
return f"{prefix}_{tgt_counter[0]:02d}"
# Map each block to its owning section title (for naming).
block_sec_title = {}
for sec in sections:
for i in sec["code_indices"]:
if i < len(blocks):
block_sec_title[i] = sec["title"]
for i in range(len(blocks)):
if used[i]:
continue
lang = lang_of(i)
sec_title = block_sec_title.get(i, "(导言)")
# Apply fixes
c0 = common_fix(blocks[i]["code"], pid, i)
c1 = exact_fix(pid, i, c0)
tgt = next_target(cfg["prefix"])
# English source filename stem from the explicit map (with fallback)
src_name = src_name_for(pid, sec_title, cfg["prefix"], i)
# ensure uniqueness within dir (same section name → _2, _3...)
cand = src_name; k = 2
existing_names = {os.path.basename(t[1]) for t in targets_by_dir.get(out_dir, [])}
base_ext = ".cpp" if lang == "cpp" else ".c"
while f"{cand}{base_ext}" in existing_names:
cand = f"{src_name}_{k}"; k += 1
src_name = cand
src_rel, _ = emit_console(tgt, [(i, c1, sec_title)], lang, out_dir, src_name=src_name)
targets_by_dir.setdefault(out_dir, []).append((tgt, src_rel))
used[i] = True
leftover = [i for i in range(len(blocks)) if not used[i]]
if leftover:
print(f"[WARN] {pid} unused blocks: {leftover}", file=sys.stderr)
# --- 写每个 dir 的 CMakeLists ---
write_part1_cmakelists(targets_by_dir)
# --- 写 ERRATA ---
write_errata()
print(f"gen_part1: wrote {sum(len(v) for v in targets_by_dir.values())} targets across {len(targets_by_dir)} dirs.")
def write_part1_cmakelists(targets_by_dir):
# per-dir CMakeLists
for d, targets in targets_by_dir.items():
lines = []
lines.append(f"# Auto-generated by tools/gen_part1.py — do not edit by hand.\n")
lines.append(f"# 章节:{d}\n")
for tgt, src_rel in targets:
src = os.path.basename(src_rel)
lang = "C" if src.endswith(".c") else "CXX"
lines.append(f"add_executable({tgt} {src})\n")
lines.append(f"set_target_properties({tgt} PROPERTIES "
f"C_STANDARD 17 C_STANDARD_REQUIRED ON C_EXTENSIONS OFF)\n")
if lang == "C":
lines.append(f"target_compile_options({tgt} PRIVATE -Wall -Wextra)\n")
else:
lines.append(f"set_target_properties({tgt} PROPERTIES "
f"CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)\n")
lines.append(f"target_compile_options({tgt} PRIVATE -Wall -Wextra)\n")
# a convenience target to build all in this dir
dir_tname = "all_" + d.replace("/", "_").replace("-", "_")
lines.append(f"add_custom_target({dir_tname} DEPENDS {' '.join(t for t,_ in targets)})\n")
write(os.path.join(REPO, d, "CMakeLists.txt"), "".join(lines))
# top-level p01/CMakeLists that includes all sub-dirs
subdirs = sorted(set(d.split("/")[0] if "/" not in d else "/".join(d.split("/")[:2]) for d in targets_by_dir))
# actually include every dir that has targets
seen = set()
includes = []
for d in sorted(targets_by_dir.keys()):
# include the deepest dir that has a CMakeLists we wrote
includes.append(d)
lines = ["# Auto-generated by tools/gen_part1.py\n"]
for d in includes:
rel = os.path.relpath(d, "p01")
lines.append(f"add_subdirectory({rel})\n")
write(os.path.join(REPO, "p01", "CMakeLists.txt"), "".join(lines))
def write_errata():
lines = ["# ERRATA — 代码修正记录\n\n",
"本文件由 `tools/gen_part1.py` 自动生成,记录对 wiki 原始代码块的每一处修正。\n",
"格式:`[pageId:blockIdx]` 类型 → 原内容 → 修正后 → 依据。\n\n"]
if not ERRATA:
lines.append("(无修正)\n")
else:
for (pid, idx, kind, before, after, reason) in ERRATA:
lines.append(f"## [{pid}:{idx}] {kind}\n\n")
lines.append(f"- **原文**`{before}`\n")
lines.append(f"- **修正**`{after}`\n")
lines.append(f"- **依据**{reason}\n\n")
write(os.path.join(REPO, "docs", "ERRATA.md"), "".join(lines))
if __name__ == "__main__":
main()
+1463
View File
File diff suppressed because it is too large Load Diff
+320
View File
@@ -0,0 +1,320 @@
#!/usr/bin/env python3
# ==============================================================================
# gen_sources.py — 把抓取的 wiki 数据生成为课程源码 + CMakeLists(可复现)
#
# 输入 : tools/wiki_data/{extracted_code.json, all_page_ids.json,
# qt_full_tree.json, bodies/*.json}
# 输出 : part1_cpp/**, part3_qt_desktop/** 的 .c/.cpp/.h/.qrc 与 CMakeLists.txt
#
# 设计要点(与计划一致):
# * 按页面标题层级(h1..h6)解析子话题;同一子话题内的代码块(多为 test()
# 片段)合并为「一个完整小程序」,把各片段按教学顺序嵌入,并标注出处。
# * C/C++ 按内容上下文判定:用 gcc -std=c17 或 g++ -std=c++17 构建。
# * 每个完整小程序 = 一个独立 add_executable 目标,可单独构建运行。
#
# 本文件是「骨架/分类/合并」的通用引擎;针对每个页面需要的人工修正
# (笔误、system("pause")、缺 include、伪代码等)通过 OVERRIDES 字典注入,
# 并自动记录到 docs/ERRATA.md,使生成过程透明可审计。
# ------------------------------------------------------------------------------
import json, os, re, glob, sys
from html.parser import HTMLParser
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
WIKI = os.path.join(REPO, "tools", "wiki_data")
# ------------------------------------------------------------------ 标题解析 ----
class OutlineParser(HTMLParser):
"""顺序产出 ('h', level, text) 与 ('code', idx) 事件,定位每个代码块所属标题。"""
def __init__(self):
super().__init__()
self.events = []
self.code_depth = 0
self.code_idx = 0
self.in_code_body = False
self.capture_h = False
self.cur_lvl = None
self.cur_buf = []
def handle_starttag(self, tag, attrs):
ad = dict(attrs)
if tag in ("h1","h2","h3","h4","h5","h6"):
self.capture_h = True; self.cur_lvl = int(tag[1]); self.cur_buf = []
elif tag in ("ac:structured-macro","structured-macro") and (
ad.get("ac:name") == "code" or ad.get("name") == "code"):
self.code_depth += 1
def handle_endtag(self, tag):
if tag in ("h1","h2","h3","h4","h5","h6") and self.capture_h:
txt = re.sub(r"\s+", " ", "".join(self.cur_buf)).strip()
self.events.append(("h", self.cur_lvl, txt))
self.capture_h = False
elif tag in ("ac:structured-macro","structured-macro") and self.code_depth >= 1:
if self.code_depth == 1:
self.events.append(("code", self.code_idx))
self.code_idx += 1
self.code_depth -= 1
def unknown_decl(self, decl):
pass # CDATA handled by the code extractor separately
def handle_data(self, data):
if self.capture_h:
self.cur_buf.append(data)
def handle_entityref(self, name):
if self.capture_h:
self.cur_buf.append({"nbsp": " "}.get(name, f"&{name};"))
def page_outline(body):
"""Return list of {level, title, code_indices} for the deepest headings that
own at least one code block, in document order. Each code block is attached
to the heading under which it physically appears (last heading seen above it
at any level). Headings with no code blocks are dropped from this list but
their text is folded into the preceding owning heading's context."""
if not body:
return []
p = OutlineParser()
p.feed(body)
owning = [] # stack of (level, title) currently open
sections = [] # list of dict
cur_section = None
for ev in p.events:
if ev[0] == "h":
_, lvl, txt = ev
# pop closed headings
while owning and owning[-1][0] >= lvl:
owning.pop()
owning.append((lvl, txt))
cur_section = None # a new heading starts a new (potential) section
else: # code
if cur_section is None:
# build a context title from the heading path
path_titles = [t for (_, t) in owning]
title = path_titles[-1] if path_titles else "(导言)"
cur_section = {"title": title, "path": path_titles, "code_indices": []}
sections.append(cur_section)
cur_section["code_indices"].append(ev[1])
return sections
# ------------------------------------------------------------------ 语言判定 ----
CPP_TOKENS = ["iostream", "std::", "#include <iostream>", "#include<iostream>",
"class ", "template", "namespace", "using namespace", "new ",
"delete ", "std::string", "cout", "cin", "cerr", "vector<",
"string ", "->", "const_cast", "static_cast", "dynamic_cast",
"reinterpret_cast", "operator ", "public:", "private:", "protected:",
"signals:", "slots:", "virtual ", "throw ", "catch ", "try {",
"Q_OBJECT", "QApplication", "QWidget", "emit ", "qDebug", "QFile",
"#include <Q", "#include<Q"]
C_TOKENS = ["printf", "scanf", "puts(", "getchar", "malloc", "free(",
"#include <stdio.h>", "#include<stdio.h>", "#include <stdlib.h>",
"#include <string.h>", "#include <math.h>", "strcpy", "strcat",
"strcmp", "sprintf", "EXIT_SUCCESS"]
def classify_lang(code):
"""Return 'c' | 'cpp' | 'qt' based on content."""
is_qt = any(t in code for t in
["Q_OBJECT","QApplication","QWidget","QMainWindow","QDialog","QPushButton",
"QLabel","#include <Q","#include<Q","emit ","qDebug","QFile","QPainter",
"QMessageBox","QHBoxLayout","QVBoxLayout","QGridLayout","QLineEdit",
"QTextEdit","QComboBox","QEvent","QPaintEvent","QTimer","QAction",
"QMenuBar","QStatusBar","QToolBar","QFileDialog","QTextStream",
"QDataStream","QDir","QFileInfo","QSpinBox","QSlider","QDockWidget",
"QTextEdit","QPixmap","QImage","QPicture","QBitmap","QMovie"])
if is_qt:
return "qt"
is_cpp = any(t in code for t in CPP_TOKENS)
is_c = any(t in code for t in C_TOKENS)
if is_cpp:
return "cpp"
if is_c:
return "c"
return "cpp" # default to C++ when ambiguous (most wiki blocks are C++)
def is_complete(code):
return bool(re.search(r"\bint\s+main\s*\(", code))
def has_windows_pause(code):
return 'system("pause")' in code or 'system( "pause" )' in code
# C 字符串/内存函数(<cstring>/<string.h> 提供)。wiki 的 C++ 片段常直接用 strcpy/
# strlen 等,却未 include 对应头文件(原意依赖教学环境的传递包含)。检测到即补头。
CSTRING_RE = re.compile(
r"\b(strcpy|strncpy|strcat|strncat|strlen|strcmp|strncmp|strchr|strrchr|"
r"strstr|strtok|strerror|strcoll|memcpy|memmove|memset|memcmp|memchr|"
r"sprintf|snprintf)\s*\(")
def needs_cstring(code):
"""True if the fragment calls any <cstring> function and so needs the header."""
return bool(CSTRING_RE.search(code))
def has_pause_call(code):
"""True if the fragment calls pause() (after common_fix converted system("pause"))."""
return bool(re.search(r"\bpause\s*\(", code))
# Map of (header) → list of tokens that, when used unqualified (relying on
# `using namespace std;`), require that header. wiki STL/IO snippets routinely
# omit these includes (they rely on the teaching toolchain's transitive pulls).
# Detection is on bare symbol usage; includes are injected by the emit template.
_STL_HEADER_TOKENS = {
"<vector>": [r"\bvector\b"],
"<list>": [r"\blist\b"],
"<deque>": [r"\bdeque\b"],
"<set>": [r"\bset\s*<", r"\bmultiset\s*<"],
"<map>": [r"\bmap\s*<", r"\bmultimap\s*<"],
"<unordered_map>": [r"\bunordered_map\s*<"],
"<unordered_set>": [r"\bunordered_set\s*<"],
"<stack>": [r"\bstack\s*<"],
"<queue>": [r"\bqueue\s*<", r"\bpriority_queue\s*<"],
"<algorithm>":[r"\bfor_each\b", r"\btransform\b", r"\bfind\b", r"\bfind_if\b",
r"\bsort\b", r"\bcopy\b", r"\bremove\b", r"\bcount\b",
r"\bcount_if\b", r"\baccumulate\b", r"\bfill\b", r"\breplace\b",
r"\bswap\b", r"\breverse\b", r"\bmerge\b", r"\bunique\b",
r"\bbind\b"],
"<functional>":[r"\bfunction\s*<", r"\bplus\b", r"\bminus\b", r"\bmultiplies\b",
r"\bnegate\b", r"\bequal_to\b", r"\bless\b", r"\bgreater\b",
r"\blogical_and\b", r"\bplaceholders::_"],
"<numeric>": [r"\baccumulate\b"],
"<fstream>": [r"\bifstream\b", r"\bofstream\b", r"\bfstream\b"],
"<iomanip>": [r"\bsetw\b", r"\bsetfill\b", r"\bsetiosflags\b", r"\bresetiosflags\b",
r"\bsetbase\b", r"\bsetprecision\b"],
"<string>": [r"\bstd::string\b", r"\bgetline\b"],
"<cmath>": [r"\bsqrt\b", r"\bpow\b", r"\bsin\b", r"\bcos\b", r"\babs\b",
r"\bfabs\b", r"\bfloor\b", r"\bceil\b", r"\blog\b", r"\bexp\b"],
}
def infer_stl_includes(code):
"""Return the sorted list of C++ standard-library headers the fragment needs
but the wiki omitted (detected by symbol usage). Returns [] for C code."""
needed = []
for hdr, pats in _STL_HEADER_TOKENS.items():
if any(re.search(p, code) for p in pats):
needed.append(hdr)
# de-dup & sort for stable output
return sorted(set(needed))
# ------------------------------------------------------------------ 目标命名 ----
def slug(s):
"""Make a filesystem-friendly slug from a heading (keeps CJK for readability
of source filenames)."""
s = re.sub(r"^\d+(\.\d+)*\s*", "", s) # drop leading "3.10.2 "
s = re.sub(r"[(].*?[)]", "", s) # drop parenthetical
s = re.sub(r"[^\w\u4e00-\u9fff]+", "_", s).strip("_")
s = re.sub(r"_+", "_", s)
return s[:40] or "topic"
def ascii_slug(s):
"""ASCII-only slug for CMake target names (CMake forbids non-ASCII targets).
CJK chars are dropped; the result is guaranteed [A-Za-z0-9_]."""
s = re.sub(r"^\d+(\.\d+)*\s*", "", s)
s = re.sub(r"[(].*?[)]", "", s)
# drop anything that's not ASCII word char
s = re.sub(r"[^A-Za-z0-9_]+", "_", s).strip("_")
s = re.sub(r"_+", "_", s)
return s.lower()[:30] or "topic"
# ------------------------------------------------------------------ 输出助手 ----
def write(path, content):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
ERRATA = [] # list of (pageId, blockIdx, kind, before, after, reason)
def record_erratum(pid, idx, kind, before, after, reason):
ERRATA.append((pid, idx, kind, before, after, reason))
# ==============================================================================
# 公共修正:对「所有」part1 代码块都适用的轻量修补(保留原意、可移植)
# ==============================================================================
def common_fix(code, pid, idx):
orig = code
# 0) Normalize non-breaking spaces (U+00A0 NBSP) that the wiki editor injected
# into indented code — these are 'stray \302' compile errors otherwise.
if "\u00a0" in code:
code = code.replace("\u00a0", " ")
# 1) system("pause") → 便携 pause() 壳(仅当出现时;壳函数由模板注入)
if has_windows_pause(code):
code = re.sub(r'system\(\s*"pause"\s*\)', 'pause()', code)
code = re.sub(r'system\(\s*" pause"\s*\)', 'pause()', code)
record_erratum(pid, idx, "platform",
'system("pause")', 'pause()',
"system(\"pause\") 仅 Windows 可用;改用仓库提供的便携 pause() 壳函数 "
"(读一行 stdin 等待回车),行为一致,Linux 可编译运行。")
# 2) 全角分号 → 半角
if "" in code:
code = code.replace("", ";")
record_erratum(pid, idx, "typo", "(全角分号)", ";(半角)",
"全角分号在 C/C++ 中非法,统一替换为半角。")
# 3) 全角引号 → 半角(教学代码常见误输)
for fq, hq in [("", '"'), ("", '"'), ("", "'"), ("", "'")]:
if fq in code:
code = code.replace(fq, hq)
record_erratum(pid, idx, "typo", f"{fq}(全角引号)", f"{hq}(半角)",
"字符串/字符字面量须用半角引号。")
# 4) C 字符串/内存函数缺头:wiki 的 C++ 片段常直接调用 strcpy/strlen/strcmp 等
# 却未 include(依赖教学环境的传递包含)。检测到即补 <cstring>C++)。
# include 由 emit 模板注入;此处只记录 erratum,避免与模板重复插入。
if needs_cstring(code):
record_erratum(pid, idx, "missing-include",
"调用 strcpy/strlen/strcmp 等 C 字符串函数但未 #include <cstring>",
"由生成模板统一补 #include <cstring>",
"C 字符串/内存函数声明在 <cstring>C++/ <string.h>C);"
"原 wiki 代码依赖教学环境传递包含,隔离编译时须显式 include。")
# 5) STL 容器/算法/IO 头缺失:wiki 片段依赖 `using namespace std;` 且假定教学
# 工具链传递包含了 <vector>/<algorithm>/<fstream>/<iomanip> 等。隔离编译时这些
# 符号未声明。检测到的头由 emit 模板注入;此处只记录 erratum。
missing = infer_stl_includes(code)
if missing:
record_erratum(pid, idx, "missing-include",
f"使用 STL/IO 符号(如 vector/for_each/ifstream/setw 等)但未 #include {', '.join(missing)}",
f"由生成模板统一补 #include {' '.join(missing)}",
"原 wiki 代码假定教学环境已传递包含相应标准库头;按「示例相对隔离」独立编译"
"时须显式 include。检测依据为符号使用(见 gen_sources.infer_stl_includes)。")
return code
# ---------------------------------------------------------------- 页面配置表 ----
# 每个页面的目录映射、C/C++ 判定覆盖、特殊修正。键=pageId。
# 目录采用「深缩写」:part 缩写 pNN(顶层不编号)/ 章缩写 chNN / 节缩写 sNN。
# 例:part1_cpp/ch04_class_object/03_ctor_dtor → p01/ch04/s03
# 例:part3_qt_desktop/ch04_signal_slot → p03/ch04
# 单章多节的页:dir 含 sNN 子目录;单章无子节的页:dir 仅到 chNN。
PAGE_CONFIG = {
# ---- Part 1: C++ 强化 ---- (p01)
"58954438": {"part": "p01", "dir": "ch02", "prefix": "p1c02"},
"58954439": {"part": "p01", "dir": "ch03", "prefix": "p1c03"},
"58954448": {"part": "p01", "dir": "ch04/s01", "prefix": "p1c04_01"},
"58954449": {"part": "p01", "dir": "ch04/s02", "prefix": "p1c04_02"},
"58954451": {"part": "p01", "dir": "ch04/s03", "prefix": "p1c04_03"},
"58954456": {"part": "p01", "dir": "ch04/s04", "prefix": "p1c04_04"},
"58954457": {"part": "p01", "dir": "ch04/s05", "prefix": "p1c04_05"},
"58954458": {"part": "p01", "dir": "ch04/s06", "prefix": "p1c04_06"},
"58954461": {"part": "p01", "dir": "ch04/s07", "prefix": "p1c04_07"},
"58954469": {"part": "p01", "dir": "ch04/s08", "prefix": "p1c04_08"},
"58954472": {"part": "p01", "dir": "ch05", "prefix": "p1c05"},
"58954473": {"part": "p01", "dir": "ch06", "prefix": "p1c06"},
"58954474": {"part": "p01", "dir": "ch07", "prefix": "p1c07"},
"58954476": {"part": "p01", "dir": "ch08", "prefix": "p1c08"},
"58954487": {"part": "p01", "dir": "ch09/s02", "prefix": "p1c09_02"},
"58954488": {"part": "p01", "dir": "ch09/s03", "prefix": "p1c09_03"},
"58954498": {"part": "p01", "dir": "ch09/s04", "prefix": "p1c09_04"},
# ---- Part 3: Qt 桌面 ---- (p03)
"58954515": {"part": "p03", "dir": "ch02", "prefix": "p3c02"},
"58954525": {"part": "p03", "dir": "ch03", "prefix": "p3c03"},
"58954527": {"part": "p03", "dir": "ch04", "prefix": "p3c04"},
"58954529": {"part": "p03", "dir": "ch05", "prefix": "p3c05"},
"58954535": {"part": "p03", "dir": "ch06", "prefix": "p3c06"},
"58954540": {"part": "p03", "dir": "ch08", "prefix": "p3c08"},
"58954546": {"part": "p03", "dir": "ch09", "prefix": "p3c09"},
"58954548": {"part": "p03", "dir": "ch10", "prefix": "p3c10"},
"58954552": {"part": "p03", "dir": "ch11", "prefix": "p3c11"},
}
# Per-block language overrides (after manual review of ambiguous cases).
LANG_OVERRIDE = {
("58954439", 13): "c", # tentative def + printf + EXIT_SUCCESS
("58954439", 14): "c", # K&R implicit-int (demo of removed C89 feature)
("58954439", 15): "c", # enum=int + malloc w/o cast (C-only)
("58954439", 18): "c", # ternary rvalue (C side)
("58954439", 21): "c", # const modified via cast pointer
("58954439", 44): "c", # extern "C" guarded header (valid C)
("58954439", 45): "c", # C impl file
("58954448", 0): "c", # C structs + strcpy + Person* to Animal*
}
if __name__ == "__main__":
print("gen_sources: this is the engine module; run gen_part1.py / gen_part3.py.")
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Install the ISOLATED Qt 5.14.2 toolchain into .qt514/ DIRECTLY ON THE HOST
# (no Docker), using a local venv for aqtinstall. The host already has the right
# toolchain (gcc 13.3, cmake 3.28); this just fetches Qt files into the repo.
#
# Usage: tools/install_qt_host.sh
# Prefer this over docker_install_qt.sh when you don't need a container.
# Verified: a binary built against .qt514/ links ONLY to .qt514/ (ldd shows 0
# system-Qt refs) and runs on the host with tools/run_qt.sh.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VENV="$REPO_ROOT/.venv-aqt"
QT_VERSION="5.14.2"
if [ ! -x "$VENV/bin/aqt" ]; then
echo ">> Creating local venv with aqtinstall at $VENV ..."
python3 -m venv "$VENV" --system-site-packages
"$VENV/bin/pip" install --quiet aqtinstall
fi
echo ">> Installing Qt ${QT_VERSION} gcc_64 into $REPO_ROOT/.qt514/ ..."
"$VENV/bin/aqt" install-qt linux desktop "${QT_VERSION}" gcc_64 -O "$REPO_ROOT/.qt514"
QT_ROOT="$REPO_ROOT/.qt514/${QT_VERSION}/gcc_64"
echo ">> Verify:"
"$QT_ROOT/bin/qmake" -query QT_VERSION
echo ">> Qt installed at: $QT_ROOT"
echo ">> Build on host: cmake -S . -B build -G Ninja -DBUILD_QT_PART=ON -DCMAKE_PREFIX_PATH='$QT_ROOT' && cmake --build build"
echo ">> Run Qt targets: tools/run_qt.sh <target>"
Executable
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Run a compiled part1 (C/C++) target on the HOST.
# Usage: tools/run.sh <target> # run ./build/path/<target>
# tools/run.sh <target> [args...] # pass args
# Looks up the target binary under build/ automatically.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
target="${1:?usage: tools/run.sh <target> [args...]}"
shift || true
bin=$(find "$REPO_ROOT/build" -type f -name "$target" -executable 2>/dev/null | head -1) || true
if [ -z "$bin" ]; then
echo "target '$target' not found under $REPO_ROOT/build. Build first." >&2
exit 1
fi
exec "$bin" "$@"
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Run a compiled Qt target on the HOST against the ISOLATED Qt 5.14.2 in .qt514/.
# Usage: tools/run_qt.sh <target> [args...]
# Headless/scriptable: QT_QPA_PLATFORM=offscreen tools/run_qt.sh <target>
# Isolation is by LD_LIBRARY_PATH + Qt plugin path, NOT a container. Verified by
# ldd: linked Qt libs resolve only to .qt514/, zero refs to system /usr/lib Qt5.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
target="${1:?usage: tools/run_qt.sh <target> [args...]}"
shift || true
QT_ROOT="$REPO_ROOT/.qt514/5.14.2/gcc_64"
bin=$(find "$REPO_ROOT/build" -type f -name "$target" -executable 2>/dev/null | head -1) || true
if [ -z "$bin" ]; then
echo "Qt target '$target' not found under $REPO_ROOT/build. Build first." >&2
exit 1
fi
export LD_LIBRARY_PATH="$QT_ROOT/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export QT_PLUGIN_PATH="$QT_ROOT/plugins"
export QT_QPA_PLATFORM_PLUGIN_PATH="$QT_ROOT/plugins/platforms"
exec "$bin" "$@"
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
# ==============================================================================
# std_probe.py — 快速演示「同一段代码在不同语言标准下的支持情况」
#
# 用途(对应任务 1):把一段 C 或 C++ 代码片段,在多个 -std= 下逐个编译,报告
# pass / fail / warnings,从而直观看到「某概念在哪个版本成立」。
#
# 用法:
# tools/std_probe.py --lang c --code 'int main(){ __auto_type x=1; return x; }'
# tools/std_probe.py --lang c++ --file snippet.cpp
# tools/std_probe.py --lang c --file snippet.c --standards c89,c99,c11,c17,c23
#
# 说明:
# * 编译器用宿主机 gcc/g++(本仓库 gcc 13 原生支持全部目标标准)。
# * 仅做语法编译(-fsyntax-only),不链接、不运行,避免副作用。
# * 退出码:全 pass 为 0;任一 fail 为 1(便于脚本/CI 判定)。
# ------------------------------------------------------------------------------
import argparse, os, subprocess, sys, tempfile
# 默认要探测的标准列表(C 与 C++ 各一组,覆盖课程相关版本)
DEFAULTS = {
"c": ["c89", "c99", "c11", "c17", "c23"],
"c++": ["c++98", "c++03", "c++11", "c++14", "c++17", "c++20", "c++23"],
}
def probe(code: str, lang: str, standards, extra_flags=None):
"""Compile `code` under each standard in `standards` (syntax-only). Returns
list of dicts: {std, ok, warnings, errors}."""
compiler = "gcc" if lang == "c" else "g++"
results = []
extra_flags = extra_flags or []
with tempfile.NamedTemporaryFile("w", suffix=(".c" if lang == "c" else ".cpp"),
delete=False) as f:
f.write(code)
src = f.name
try:
for std in standards:
cmd = [compiler, f"-std={std}", "-fsyntax-only", "-Wall", "-Wextra"] + extra_flags + [src]
p = subprocess.run(cmd, capture_output=True, text=True)
combined = (p.stderr or "") + (p.stdout or "")
# categorize lines: gcc/clang warnings vs errors
warns = [l for l in combined.splitlines() if "warning:" in l]
errs = [l for l in combined.splitlines() if "error:" in l]
results.append({
"std": std,
"ok": p.returncode == 0,
"warnings": warns,
"errors": errs,
})
finally:
os.unlink(src)
return results
def main():
ap = argparse.ArgumentParser(description="Probe a C/C++ snippet across -std= versions.")
g = ap.add_mutually_exclusive_group(required=True)
g.add_argument("--code", help="inline code snippet")
g.add_argument("--file", help="path to a source file")
ap.add_argument("--lang", choices=["c", "c++"], required=True,
help="language (selects compiler & default standards)")
ap.add_argument("--standards", default=None,
help="comma-separated -std values (default: course-relevant set)")
ap.add_argument("--show-warnings", action="store_true",
help="print warning lines (default: only summarize)")
ap.add_argument("--pedantic-errors", action="store_true",
help="add -pedantic-errors so removed/deprecated features FAIL hard "
"(gcc otherwise lets many removed features compile as warnings)")
args = ap.parse_args()
code = args.code if args.code else open(args.file).read()
standards = args.standards.split(",") if args.standards else DEFAULTS[args.lang]
extra_flags = ["-pedantic-errors"] if args.pedantic_errors else []
print(f"Compiler : {('gcc' if args.lang=='c' else 'g++')} ({subprocess.run(['gcc','--version'],capture_output=True,text=True).stdout.splitlines()[0]})")
print(f"Language : {args.lang}")
print(f"Standards: {', '.join(standards)}"
+ (" [pedantic-errors ON]" if args.pedantic_errors else ""))
print("-" * 64)
results = probe(code, args.lang, standards, extra_flags=extra_flags)
any_fail = False
for r in results:
flag = "PASS" if r["ok"] else "FAIL"
w = f" {len(r['warnings'])} warning(s)" if r["warnings"] else ""
e = f" {len(r['errors'])} error(s)" if r["errors"] else ""
print(f" -std={r['std']:<8} : {flag}{w}{e}")
if not r["ok"]:
any_fail = True
for line in r["errors"][:3]:
print(f" {line.strip()}")
if args.show_warnings:
for line in r["warnings"][:3]:
print(f" (warn) {line.strip()}")
print("-" * 64)
print("Result : " + ("ALL PASS" if not any_fail else "SOME FAIL"))
return 1 if any_fail else 0
if __name__ == "__main__":
sys.exit(main())
+291
View File
@@ -0,0 +1,291 @@
[
{
"id": "58954436",
"title": "01.C++技术强化及提升",
"path": [
"01.C++技术强化及提升"
]
},
{
"id": "58954437",
"title": "01.C++概述",
"path": [
"01.C++技术强化及提升",
"01.C++概述"
]
},
{
"id": "58954438",
"title": "02.C++初识",
"path": [
"01.C++技术强化及提升",
"02.C++初识"
]
},
{
"id": "58954439",
"title": "03.C++对C的扩展",
"path": [
"01.C++技术强化及提升",
"03.C++对C的扩展"
]
},
{
"id": "58954440",
"title": "04.类和对象",
"path": [
"01.C++技术强化及提升",
"04.类和对象"
]
},
{
"id": "58954448",
"title": "01.类和对象的基本概念",
"path": [
"01.C++技术强化及提升",
"04.类和对象",
"01.类和对象的基本概念"
]
},
{
"id": "58954449",
"title": "02.面向对象程序设计案例",
"path": [
"01.C++技术强化及提升",
"04.类和对象",
"02.面向对象程序设计案例"
]
},
{
"id": "58954451",
"title": "03.对象的构造和析构",
"path": [
"01.C++技术强化及提升",
"04.类和对象",
"03.对象的构造和析构"
]
},
{
"id": "58954456",
"title": "04.C++面向对象模型初探",
"path": [
"01.C++技术强化及提升",
"04.类和对象",
"04.C++面向对象模型初探"
]
},
{
"id": "58954457",
"title": "05.友元",
"path": [
"01.C++技术强化及提升",
"04.类和对象",
"05.友元"
]
},
{
"id": "58954458",
"title": "06.运算符重载",
"path": [
"01.C++技术强化及提升",
"04.类和对象",
"06.运算符重载"
]
},
{
"id": "58954461",
"title": "07.继承和派生",
"path": [
"01.C++技术强化及提升",
"04.类和对象",
"07.继承和派生"
]
},
{
"id": "58954469",
"title": "08.多态",
"path": [
"01.C++技术强化及提升",
"04.类和对象",
"08.多态"
]
},
{
"id": "58954472",
"title": "05.C++模板",
"path": [
"01.C++技术强化及提升",
"05.C++模板"
]
},
{
"id": "58954473",
"title": "06.C++类型转换",
"path": [
"01.C++技术强化及提升",
"06.C++类型转换"
]
},
{
"id": "58954474",
"title": "07.C++异常",
"path": [
"01.C++技术强化及提升",
"07.C++异常"
]
},
{
"id": "58954476",
"title": "08.C++输入输出流",
"path": [
"01.C++技术强化及提升",
"08.C++输入输出流"
]
},
{
"id": "58954485",
"title": "09.STL",
"path": [
"01.C++技术强化及提升",
"09.STL"
]
},
{
"id": "58954486",
"title": "01.STL概论",
"path": [
"01.C++技术强化及提升",
"09.STL",
"01.STL概论"
]
},
{
"id": "58954487",
"title": "02.STL三大组件",
"path": [
"01.C++技术强化及提升",
"09.STL",
"02.STL三大组件"
]
},
{
"id": "58954488",
"title": "03.常用容器",
"path": [
"01.C++技术强化及提升",
"09.STL",
"03.常用容器"
]
},
{
"id": "58954498",
"title": "04.常用算法",
"path": [
"01.C++技术强化及提升",
"09.STL",
"04.常用算法"
]
},
{
"id": "58954500",
"title": "02.QT安装教程(免注册和登陆)",
"path": [
"02.QT安装教程(免注册和登陆)"
]
},
{
"id": "58954512",
"title": "03.Qt桌面应用开发",
"path": [
"03.Qt桌面应用开发"
]
},
{
"id": "58954513",
"title": "1 Qt概述",
"path": [
"03.Qt桌面应用开发",
"1 Qt概述"
]
},
{
"id": "58954515",
"title": "2 创建Qt项目",
"path": [
"03.Qt桌面应用开发",
"2 创建Qt项目"
]
},
{
"id": "58954525",
"title": "3 第一个Qt小程序",
"path": [
"03.Qt桌面应用开发",
"3 第一个Qt小程序"
]
},
{
"id": "58954527",
"title": "4 信号和槽机制",
"path": [
"03.Qt桌面应用开发",
"4 信号和槽机制"
]
},
{
"id": "58954529",
"title": "5 QMainWindow",
"path": [
"03.Qt桌面应用开发",
"5 QMainWindow"
]
},
{
"id": "58954535",
"title": "6 对话框QDialog",
"path": [
"03.Qt桌面应用开发",
"6 对话框QDialog"
]
},
{
"id": "58954536",
"title": "7 布局管理器",
"path": [
"03.Qt桌面应用开发",
"7 布局管理器"
]
},
{
"id": "58954540",
"title": "8 常用控件",
"path": [
"03.Qt桌面应用开发",
"8 常用控件"
]
},
{
"id": "58954546",
"title": "9 Qt消息机制和事件",
"path": [
"03.Qt桌面应用开发",
"9 Qt消息机制和事件"
]
},
{
"id": "58954548",
"title": "10 绘图和绘图设备",
"path": [
"03.Qt桌面应用开发",
"10 绘图和绘图设备"
]
},
{
"id": "58954552",
"title": "11 文件系统",
"path": [
"03.Qt桌面应用开发",
"11 文件系统"
]
}
]
+1
View File
@@ -0,0 +1 @@
{"id":"58954436","type":"page","status":"current","title":"01.C++技术强化及提升","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:24.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954436/version/1"},"_expandable":{"content":"/rest/api/content/58954436"}},"body":{"storage":{"value":"","representation":"storage","_expandable":{"content":"/rest/api/content/58954436"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":0},"_links":{"webui":"/pages/viewpage.action?pageId=58954436","edit":"/pages/resumedraft.action?draftId=58954436","tinyui":"/x/xJKDAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954436"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954436/child","restrictions":"/rest/api/content/58954436/restriction/byOperation","history":"/rest/api/content/58954436/history","ancestors":"","descendants":"/rest/api/content/58954436/descendant","space":"/rest/api/space/2CT"}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"id":"58954440","type":"page","status":"current","title":"04.类和对象","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:24.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954440/version/1"},"_expandable":{"content":"/rest/api/content/58954440"}},"body":{"storage":{"value":"<ac:layout><ac:layout-section ac:type=\"two_right_sidebar\"><ac:layout-cell><p><ac:link><ri:page ri:content-title=\"01.类和对象的基本概念\" /></ac:link></p><p><ac:link><ri:page ri:content-title=\"02.面向对象程序设计案例\" /></ac:link></p><p><ac:link><ri:page ri:content-title=\"03.对象的构造和析构\" /></ac:link></p><p><ac:link><ri:page ri:content-title=\"04.C++面向对象模型初探\" /></ac:link></p><p><ac:link><ri:page ri:content-title=\"05.友元\" /></ac:link></p><p><ac:link><ri:page ri:content-title=\"06.运算符重载\" /></ac:link></p><p><ac:link><ri:page ri:content-title=\"07.继承和派生\" /></ac:link></p><p><ac:link><ri:page ri:content-title=\"08.多态\" /></ac:link></p></ac:layout-cell><ac:layout-cell><p><br /></p></ac:layout-cell></ac:layout-section></ac:layout>","representation":"storage","_expandable":{"content":"/rest/api/content/58954440"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":"none"},"_links":{"webui":"/pages/viewpage.action?pageId=58954440","edit":"/pages/resumedraft.action?draftId=58954440","tinyui":"/x/yJKDAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954440"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954440/child","restrictions":"/rest/api/content/58954440/restriction/byOperation","history":"/rest/api/content/58954440/history","ancestors":"","descendants":"/rest/api/content/58954440/descendant","space":"/rest/api/space/2CT"}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"id":"58954485","type":"page","status":"current","title":"09.STL","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:24.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954485/version/1"},"_expandable":{"content":"/rest/api/content/58954485"}},"body":{"storage":{"value":"","representation":"storage","_expandable":{"content":"/rest/api/content/58954485"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":"none"},"_links":{"webui":"/display/2CT/09.STL","edit":"/pages/resumedraft.action?draftId=58954485","tinyui":"/x/9ZKDAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954485"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954485/child","restrictions":"/rest/api/content/58954485/restriction/byOperation","history":"/rest/api/content/58954485/history","ancestors":"","descendants":"/rest/api/content/58954485/descendant","space":"/rest/api/space/2CT"}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"id":"58954536","type":"page","status":"current","title":"7 布局管理器","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:25.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954536/version/1"},"_expandable":{"content":"/rest/api/content/58954536"}},"body":{"storage":{"value":"<p>所谓 GUI 界面,归根结底,就是一堆组件的叠加。我们创建一个窗口,把按钮放上面,把图标放上面,这样就成了一个界面。在放置时,组件的位置尤其重要。我们必须要指定组件放在哪里,以便窗口能够按照我们需要的方式进行渲染。这就涉及到组件定位的机制。<br />\n<span style=\"color: rgb(255,0,0);\"><strong>Qt 提供了两种组件定位机制:绝对定位和布局定位。</strong></span></p>\n<ul>\n\t<li>绝对定位就是一种最原始的定位方法:给出这个组件的坐标和长宽值。</li>\n</ul>\n\n\n<p>这样,Qt 就知道该把组件放在哪里以及如何设置组件的大小。但是这样做带来的一个问题是,如果用户改变了窗口大小,比如点击最大化按钮或者使用鼠标拖动窗口边缘,采用绝对定位的组件是不会有任何响应的。这也很自然,因为你并没有告诉 Qt,在窗口变化时,组件是否要更新自己以及如何更新。或者,还有更简单的方法:禁止用户改变窗口大小。但这总不是长远之计。</p>\n<ul>\n\t<li>布局定位:你只要把组件放入某一种布局,布局由专门的布局管理器进行管理。当需要调整大小或者位置的时候,Qt 使用对应的布局管理器进行调整。</li>\n</ul>\n\n\n<p>布局定位完美的解决了使用绝对定位的缺陷。<br />\nQt 提供的布局中以下三种是我们最常用的:</p>\n<ul>\n\t<li>QHBoxLayout:按照水平方向从左到右布局;</li>\n\t<li>QVBoxLayout:按照竖直方向从上到下布局;</li>\n\t<li>QGridLayout:在一个网格中进行布局,类似于 HTML 的 table</li>\n</ul>\n\n\n<h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"77a21336-b4b5-4b2a-84b6-80a32d528bfb\"><ac:parameter ac:name=\"\">_Toc466556793</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"2abb1215-0dcc-4e38-b622-dc51eacb0597\"><ac:parameter ac:name=\"\">_Toc471842018</ac:parameter></ac:structured-macro>7.1 系统提供的布局控件</h1>\n<p><ac:image ac:height=\"156\" ac:width=\"298\"><ri:attachment ri:filename=\"worddav18ab88afaab13e557090ff7c7f4b8ce0.png\" /></ac:image><br />\n这4个为系统给我们提供的布局的控件,但是使用起来不是非常的灵活,这里就不详细介绍了。</p>\n<h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"1a671ec2-4963-41a8-a244-1e245b4cab55\"><ac:parameter ac:name=\"\">_Toc471842019</ac:parameter></ac:structured-macro>7.2 利用widget做布局</h1>\n<p>第二种布局方式是利用控件里的widget来做布局,在Containers中<br />\n<ac:image ac:height=\"320\" ac:width=\"279\"><ri:attachment ri:filename=\"worddavb1b05c61db9303f4bd40ca5a09e9f386.png\" /></ac:image><br />\n在widget中的控件可以进行水平、垂直、栅格布局等操作,比较灵活。<br />\n再布局的同时我们需要灵活运用弹簧的特性让我们的布局更加的美观,下面是一个登陆窗口,利用widget可以搭建出如下登陆界面:<br />\n<ac:image ac:height=\"249\" ac:width=\"320\"><ri:attachment ri:filename=\"worddav726822fbe1bdfb789e8987a25380efa3.png\" /></ac:image></p>","representation":"storage","_expandable":{"content":"/rest/api/content/58954536"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":44},"_links":{"webui":"/pages/viewpage.action?pageId=58954536","edit":"/pages/resumedraft.action?draftId=58954536","tinyui":"/x/KJODAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954536"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954536/child","restrictions":"/rest/api/content/58954536/restriction/byOperation","history":"/rest/api/content/58954536/history","ancestors":"","descendants":"/rest/api/content/58954536/descendant","space":"/rest/api/space/2CT"}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+185
View File
@@ -0,0 +1,185 @@
{
"id": "58954499",
"title": "02.Qt方向",
"children": [
{
"id": "58954436",
"title": "01.C++技术强化及提升",
"children": [
{
"id": "58954437",
"title": "01.C++概述",
"children": []
},
{
"id": "58954438",
"title": "02.C++初识",
"children": []
},
{
"id": "58954439",
"title": "03.C++对C的扩展",
"children": []
},
{
"id": "58954440",
"title": "04.类和对象",
"children": [
{
"id": "58954448",
"title": "01.类和对象的基本概念",
"children": []
},
{
"id": "58954449",
"title": "02.面向对象程序设计案例",
"children": []
},
{
"id": "58954451",
"title": "03.对象的构造和析构",
"children": []
},
{
"id": "58954456",
"title": "04.C++面向对象模型初探",
"children": []
},
{
"id": "58954457",
"title": "05.友元",
"children": []
},
{
"id": "58954458",
"title": "06.运算符重载",
"children": []
},
{
"id": "58954461",
"title": "07.继承和派生",
"children": []
},
{
"id": "58954469",
"title": "08.多态",
"children": []
}
]
},
{
"id": "58954472",
"title": "05.C++模板",
"children": []
},
{
"id": "58954473",
"title": "06.C++类型转换",
"children": []
},
{
"id": "58954474",
"title": "07.C++异常",
"children": []
},
{
"id": "58954476",
"title": "08.C++输入输出流",
"children": []
},
{
"id": "58954485",
"title": "09.STL",
"children": [
{
"id": "58954486",
"title": "01.STL概论",
"children": []
},
{
"id": "58954487",
"title": "02.STL三大组件",
"children": []
},
{
"id": "58954488",
"title": "03.常用容器",
"children": []
},
{
"id": "58954498",
"title": "04.常用算法",
"children": []
}
]
}
]
},
{
"id": "58954500",
"title": "02.QT安装教程(免注册和登陆)",
"children": []
},
{
"id": "58954512",
"title": "03.Qt桌面应用开发",
"children": [
{
"id": "58954513",
"title": "1 Qt概述",
"children": []
},
{
"id": "58954515",
"title": "2 创建Qt项目",
"children": []
},
{
"id": "58954525",
"title": "3 第一个Qt小程序",
"children": []
},
{
"id": "58954527",
"title": "4 信号和槽机制",
"children": []
},
{
"id": "58954529",
"title": "5 QMainWindow",
"children": []
},
{
"id": "58954535",
"title": "6 对话框QDialog",
"children": []
},
{
"id": "58954536",
"title": "7 布局管理器",
"children": []
},
{
"id": "58954540",
"title": "8 常用控件",
"children": []
},
{
"id": "58954546",
"title": "9 Qt消息机制和事件",
"children": []
},
{
"id": "58954548",
"title": "10 绘图和绘图设备",
"children": []
},
{
"id": "58954552",
"title": "11 文件系统",
"children": []
}
]
}
]
}