diff --git a/.claude/commands/audit.md b/.claude/commands/audit.md new file mode 100644 index 000000000..435153467 --- /dev/null +++ b/.claude/commands/audit.md @@ -0,0 +1,35 @@ +--- +description: 对一篇文章跑「事实核查 + 严谨性」双审查并产出修正 —— 复用两个公开审查 prompt 的方法论 +argument-hint: "[必填: 待审查文章的相对/绝对路径]" +--- + +# /audit — 文章双审查(事实 + 严谨) + +你是**执行者**:对目标文章做两轮审查,**直接修改文章并产出报告**。 + +## 输入 + +`$ARGUMENTS` —— 待审查文章路径(必填)。为空则停下问用户要路径。 + +## 方法论来源 + +- **事实核查**:[`article-factcheck-audit.md`](../prompts/article-factcheck-audit.md)(人名 / 日期 / 规格 / 引用 / 代码声称行为) +- **严谨性核查**:[`article-rigor-audit.md`](../prompts/article-rigor-audit.md)(无证据的"编译器会优化 / 更快"类断言) + +严格按这两个 prompt 的流程和约束执行,本命令只是把它们串成一次调用。 + +## 流程 + +1. **读全文**,通读目标文章。 +2. **事实轮**:标记需验证声明 → web search 查权威源 → 按 P0–P3 分级。 +3. **严谨轮**:标记无证据的技术 / 性能断言 → 编译验证(见硬约束)。 +4. **修正**:按 factcheck 的三要素(原文摘录 / RefLink / 验证路径)插入 `:::warning` / `:::details`;rigor 轮用真实汇编 / 输出替换空口描述。验证代码写入 `code/volumn_codes/vol{N}/`。 +5. **报告**:输出修正汇总表 + 新增引用 + 新增验证代码 + 验证清单。 + +## 硬约束(行为纪律) + +- **编译只到 `/tmp/`**:`g++ -std=c++XX -O{0,2} -o /tmp/xxx /tmp/xxx.cpp`,运行 `/tmp/xxx` —— **分两次独立 Bash 调用,禁止 `&&` 串联**。 +- **禁止**在项目目录跑 cmake / make / ninja 或建 build 目录。 +- **禁止**把报告 / 日志写到 `/tmp/` 或任何位置 —— 报告直接作为返回文本输出。 +- 只修改目标文章 + 对应 `code/volumn_codes/` 目录。 +- 写作风格遵循 [`writing-style.md`](../style/writing-style.md)。 diff --git a/.claude/commands/explain.md b/.claude/commands/explain.md new file mode 100644 index 000000000..839b618a7 --- /dev/null +++ b/.claude/commands/explain.md @@ -0,0 +1,28 @@ +--- +description: 给 C++ 学习者讲一个概念 —— 先在仓库定位项目怎么讲、再验证、用项目声音讲解(learning track 4 守则固化版) +argument-hint: "[必填: 要讲解的 C++ 概念]" +--- + +# /explain — 给学习者讲一个 C++ 概念 + +受众:懂 Agent、正在学 C++ 的人。你的任务是**当导师**,核心是**别让学习者绕弯**。严格按 [`learning-with-agents.md`](../../.github/learning-with-agents.md) 的四条守则。 + +## 输入 + +`$ARGUMENTS` —— 要讲解的概念(如 `memory_order_acquire`、`std::move`)。为空则停下问。 + +## 流程(四守则) + +1. **定位**:先在**本仓库**找项目怎么讲这个概念 —— 查 `documents/roadmap/index.md`(学习顺序)和各卷 `documents/vol*/index.md`,grep 概念名,读项目已有处理。**别凭通用记忆现编一套**,对齐项目的讲法和进度。 +2. **验证**:凡涉及 C++ 行为断言,先编译实测或查 cppreference 标版本(做法同 `/verify-claim`)。**禁止凭记忆断言。** +3. **讲解**:用**项目声音**讲([`writing-style.md`](../style/writing-style.md) Part 2)—— 讲"为什么"、类比 + 机制拆解、标志性句式、鼓励读者自己跑代码。 +4. **纠偏**:概念若有常见误解,主动点破(查 [`faq.md`](../../.github/faq.md))。 + +## 输出 + +一段项目风格的讲解 + 最小可编译代码(标 C++ 标准)+ 链接:项目对应文章 + cppreference。 + +## 硬约束 + +- 编译验证产物**只到 `/tmp/`**,不跑 cmake / make / build。 +- 讲解里的每条 C++ 行为都要能验证;不确定就说"我验一下"。 diff --git a/.claude/commands/minor.md b/.claude/commands/minor.md new file mode 100644 index 000000000..f9d8de3b2 --- /dev/null +++ b/.claude/commands/minor.md @@ -0,0 +1,59 @@ +--- +description: 讨论一轮 minor 发版打包 — 确认后产出【发版规划总结 + 稳定格式的迭代 Issue】到聊天框(只规划,不执行,不开 issue) +argument-hint: "[可选: 本次发版框定/关注点,留空则由你扫描后建议]" +--- + +# /minor — 发版打包规划(规划者模式) + +你现在是**规划者**,不是执行者。本轮你**不修改任何文件、不建 tag、不碰版本号、不调 `gh`**。 + +## 定位 + +Minor = **发版打包规划**:把自上次 tag 以来累积的全部 patch(所有类型)捆成一次 release,讲清楚这次发版做了什么、到了哪个里程碑。 + +**关键:内容推进本身是 patch 级工作**(每写一段内容都是一个 patch)。minor **不做内容**,只在"攒够了、要发版"时由用户**手工判定**触发,与 patch 类型无关。 + +## 输入 + +`$ARGUMENTS` —— 本次发版的框定或关注点。为空时由你扫描后给出建议。 + +## 流程 + +### 1. 扫描累积与路线 + +- **累积 patch**: 运行 + `git log $(git describe --tags --abbrev=0)..HEAD --oneline` + 列出自上次 tag 以来的全部待打包提交。 +- **路线对照**: 读 `todo/000-project-roadmap.md` + 相关 `todo/01x-vol*.md`,看这批 patch 合起来推进了哪条 next-step。 +- ⚠️ 项目 Python 脚本一律用 `.venv/bin/python`。 + +### 2. 框定本次发版 + +- 把累积 patch 按类型粗分,给出本次 release 的**一句话叙事**。 +- 对照 volume TODO,说清本次发版**到达了哪个内容里程碑**。推进是 patch 做的,minor 只负责"看清楚到了哪"。 +- 若离更完整的 minor 还差一点:建议再补哪 1-2 个 patch(留给后续 `/patch`,不在 minor 里执行)。 +- **保持灵活,不搞死板**:讲明白发版范围即可,不强制验收标准、不强制文件清单。 + +### 3. 提案 → 讨论 → 确认(核心是讨论) + +- 先用**一句话**讲清这次发版要打包什么,作为讨论起点(累积统计、里程碑口头带过,**别堆列表**)。 +- 与用户**来回讨论打磨**,直到用户**明确确认**。 +- **未确认前,不产出 Issue。** + +### 4. 确认后,产出两样东西到聊天框(讨论的最终产物) + +**(a) 本轮确认的发版工作** —— 一句话总结聊定的发版范围与里程碑。 + +**(b) 迭代 Issue(Markdown)** —— 套用 `.claude/templates/iteration-issue.md`(极简版),迭代类型填 minor。用 ` ```markdown ` 代码块包裹。用户微调后**自行**送 GitHub。 + +★ **(a)(b) 都必须通俗易懂**:用大白话写,少术语、别堆结构化清单,让没参与讨论的人也能一眼看懂这次发版干了啥。 + +输出后停下。 + +## 硬约束 + +- ❌ 不直接执行任何文件修改。只产出讨论结果。 +- ❌ 不碰版本 bump / CHANGELOG / git tag(用户自理)。 +- ❌ **不调 `gh issue create`,不开 issue**。 +- ✅ 全程讨论驱动;Issue 仅在用户确认后产出。 +- ✅ **输出通俗易懂**:用大白话写,别堆术语。 diff --git a/.claude/commands/new-article.md b/.claude/commands/new-article.md new file mode 100644 index 000000000..a2e2ec63a --- /dev/null +++ b/.claude/commands/new-article.md @@ -0,0 +1,26 @@ +--- +description: 起一篇新文章 —— 按骨架生成 frontmatter + 章节骨架,放进对应卷目录 +argument-hint: "[可选: 主题/线索;留空则交互问]" +--- + +# /new-article — 起新文章骨架 + +按项目骨架([`writing-style.md`](../style/writing-style.md) Part 1)生成一篇新文章的 frontmatter + 章节骨架。 + +## 输入 + +`$ARGUMENTS` —— 主题或线索(可选)。留空则依次问:文章类型、目标卷、主题、C++ 标准、难度。 + +## 流程 + +1. **确定类型**(四选一):通用文章 / 嵌入式平台教程 / 参考卡 / 标准库组件深入。 +2. **确定位置**:目标卷目录 `documents/vol*/`,文件名 `{序号}-{kebab-case}.md`,`order` = 文件名序号。 +3. **生成 frontmatter**:必填 `title` / `chapter` / `order`;`tags` **必须**来自 `scripts/validate_frontmatter.py` 的 VALID_TAGS(详见 [`documents-frontmatter.md`](../rules/documents-frontmatter.md))—— 1 个 platform + 1 个 difficulty + ≥1 个 topic。 +4. **填章节骨架**:按所选类型的骨架(Part 1.2)放占位结构。 +5. **提醒**:手动更新该卷 `index.md`,否则新文章在站点侧边栏不可达。 + +## 硬约束 + +- `tags` 必须来自 VALID_TAGS,否则 pre-commit / CI 校验会挂。 +- `order` 与文件名序号一致。 +- 生成后建议跑 `/preflight` 自检 frontmatter。 diff --git a/.claude/commands/patch.md b/.claude/commands/patch.md new file mode 100644 index 000000000..d1975aa9f --- /dev/null +++ b/.claude/commands/patch.md @@ -0,0 +1,64 @@ +--- +description: 讨论一轮 patch 小迭代 — 确认后产出【开发工作总结 + 稳定格式的迭代 Issue】到聊天框(只规划,不执行,不开 issue) +argument-hint: "[可选: 本轮 patch 主题/线索,留空则自动扫描候选]" +--- + +# /patch — 小迭代规划(规划者模式) + +你现在是**规划者**,不是执行者。本轮你**不修改任何文件、不碰版本号、不建 tag、不调 `gh`**。 + +## 输入 + +`$ARGUMENTS` —— 本轮 patch 的主题或线索。为空时,主动扫描发现候选 patch。 + +## 流程 + +### 1. 扫描现状 + +- **累积计数**(累积制核心): 运行 + `git log $(git describe --tags --abbrev=0)..HEAD --oneline` + 得到自上次 tag 以来的 patch 提交列表与数量 —— 用户判断"何时升 minor"的依据。 +- 按主题定位相关位置;若涉及内容,读对应 `todo/01x-vol*.md` 确认这个 patch 服务于哪条 next-step(仅作上下文)。 +- ⚠️ 项目 Python 脚本一律用 `.venv/bin/python`(有 PreToolUse hook 强制)。 +- 必要时做 web search 核查事实(准确性优先,token 成本其次)。 + +### 2. 判定类型并打标(仅用于聊天讨论,不进 Issue) + +| 标签 | 含义 | label 建议 | +|------|------|-----------| +| 内容 (feat) | 写新内容的一个片段。**完成一部分内容也算 patch** | feature | +| 修缮 (fix) | 错别字 / 注释错 / 代码缩进或缺行 / 事实性小错 | bug | +| 批量 (chore) | lint / frontmatter / 404 等机械修复(跨多文件也归此类) | documentation | +| 示例 (build) | 代码示例编译 / 行为修复 | bug | +| 工具链 (chore) | 脚本 / CI / 构建配置小修 | enhancement | + +### 3. 粒度与边界(累积制 —— patch 是开发的原子单位) + +- **patch 是所有开发工作的原子单位**,涵盖全部类型,**包括写新内容的一个片段**。不会因为"动了学习路径"或"写的是新内容"就升级 minor。 +- **不设文件数 / 行数硬上限,也不设类型升级线**。诉求太大就**拆成多个 patch**,不升级 minor。 +- **何时升 minor,纯粹由用户手工判定,与 patch 类型无关。** + +### 4. 提案 → 讨论 → 确认(核心是讨论) + +- 先用**一句话**讲清这轮打算做什么,作为讨论起点(类型/进度等上下文口头带过即可,**别堆列表**)。 +- 与用户**来回讨论打磨**,直到用户**明确确认**。 +- **保持灵活,不搞死板**:不强制列具体文件、不强制验收标准 —— 讲明白就行。 +- **未确认前,不产出 Issue。** + +### 5. 确认后,产出两样东西到聊天框(讨论的最终产物) + +**(a) 本轮确认的开发工作** —— 一句话总结聊定的内容。 + +**(b) 迭代 Issue(Markdown)** —— 套用 `.claude/templates/iteration-issue.md`(极简版:标题 + 一句话 + 一段"本轮打算做什么"),用 ` ```markdown ` 代码块包裹。用户微调后**自行**送 GitHub。 + +★ **(a)(b) 都必须通俗易懂**:用大白话写,少术语、别堆结构化清单,让没参与讨论的人也能一眼看懂这轮要干啥。 + +输出后停下。 + +## 硬约束 + +- ❌ 不直接执行任何文件修改。只产出讨论结果。 +- ❌ 不碰版本 bump / CHANGELOG / git tag(用户自理)。 +- ❌ **不调 `gh issue create`,不开 issue**。 +- ✅ 全程讨论驱动;Issue 仅在用户确认后产出。 +- ✅ **输出通俗易懂**:用大白话写,别堆术语 —— 用户要能看懂自己收到的 Issue。 diff --git a/.claude/commands/preflight.md b/.claude/commands/preflight.md new file mode 100644 index 000000000..bf3c663d7 --- /dev/null +++ b/.claude/commands/preflight.md @@ -0,0 +1,37 @@ +--- +description: 提交前自检 —— frontmatter 校验 + markdownlint + 链接/索引检查,汇总 pass/fail +argument-hint: "[可选: 目标文件或目录;留空则检查已暂存文件]" +--- + +# /preflight — 提交前自检 + +跑一遍 PR 前该过的检查,汇总通过项和待修项。 + +## 输入 + +`$ARGUMENTS` —— 目标文件 / 目录(可选)。留空则用 `git diff --cached --name-only` 取已暂存文件。 + +## 流程 + +1. **frontmatter 校验**:`.venv/bin/python scripts/validate_frontmatter.py`(**必须用 venv**,系统 python 缺 PyYAML 会全量误报)。 +2. **markdownlint**:`markdownlint <目标 .md 文件>`。 +3. **内部链接**:运行项目的 link check(若有,如 CI 用的脚本),或人工抽检新增 / 改动链接是否可达、`ChapterLink` 的 `href` 不以 `.md` 结尾等。 +4. **索引检查**:若新增或移动了文章,对应卷 `index.md` 是否已更新链接。 +5. **代码示例**(若涉及 `code/`):提醒确认可独立编译(无根 CMakeLists,逐目录构建)。 + +## 输出 + +``` +## Preflight 报告 +- [ ] frontmatter: pass / fail(细节) +- [ ] markdownlint: pass / fail +- [ ] 内部链接: pass / 待检 +- [ ] 索引更新: 已更新 / 待补 +- [ ] 代码示例: 不涉及 / 待编译确认 +待修清单:... +``` + +## 硬约束 + +- Python 一律 `.venv/bin/python`(有 PreToolUse hook 强制)。 +- 只读检查,不改文件(发现问题列出来,让用户决定怎么改)。 diff --git a/.claude/commands/verify-claim.md b/.claude/commands/verify-claim.md new file mode 100644 index 000000000..a43f82484 --- /dev/null +++ b/.claude/commands/verify-claim.md @@ -0,0 +1,39 @@ +--- +description: 验证一条 C++ 断言 —— 编译最小例子 + 查 cppreference,给出成立/不成立的实证(金科玉律固化版) +argument-hint: "[必填: 要验证的 C++ 断言]" +--- + +# /verify-claim — 验证一条 C++ 断言 + +C++ 语义随标准版本和实现变化,**禁止凭记忆断言**。本命令把这条金科玉律变成可执行流程:对一条断言,**用真实编译输出说话**。 + +> 命名说明:叫 `/verify-claim` 而非 `/verify`,是为了避开 Claude Code 内置的 `/verify`(验证代码改动是否生效)。本命令专做"C++ 断言的编译实证"。 + +## 输入 + +`$ARGUMENTS` —— 要验证的断言(如"`std::move` 作用于 `const` 对象会退化为拷贝")。为空则停下问。 + +## 流程 + +1. **写最小复现**:用 Write 把最小 `.cpp` 写到 `/tmp/`(`verify_<主题>.cpp`),只覆盖断言涉及的行为,别塞无关代码。 +2. **编译**(单独一次 Bash):`g++ -std=c++20 -O2 -o /tmp/verify_xxx /tmp/verify_xxx.cpp`;要看生成的指令再加一次 `g++ -std=c++20 -O2 -S -o /tmp/verify_xxx.s /tmp/verify_xxx.cpp`。 +3. **运行**(单独一次 Bash):`/tmp/verify_xxx`。**不要和编译用 `&&` 串。** +4. **查标准**(必要时):web search cppreference,标标准版本。 +5. **记录编译器**:`g++ --version`。 + +## 输出 + +``` +断言:<原文> +结论:成立 / 不成立 / 部分成立(说清条件) +标准:C++XX | 编译器:GCC XX +最小例子: +真实输出:<粘贴> +依据:cppreference 链接 / 汇编片段(如适用) +``` + +## 硬约束 + +- 编译产物**只到 `/tmp/`**;**禁止**在项目目录跑 cmake / make / build。 +- 编译和运行**分两次 Bash**,不串 `&&`。 +- 不确定就如实说"无法确定",**别编输出**。 diff --git a/.claude/hooks/block-bare-python.sh b/.claude/hooks/block-bare-python.sh new file mode 100755 index 000000000..816462212 --- /dev/null +++ b/.claude/hooks/block-bare-python.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# PreToolUse hook (matcher: Bash) +# 目的:拦截裸 python / python3 调用,强制使用 .venv/bin/python。 +# 原因:系统 python (/usr/sbin/python) 缺少 PyYAML 等依赖,会让 +# scripts/validate_frontmatter.py 等脚本把全仓文件误报成 error。 +# 策略:任何错误一律 fail-open (exit 0),避免有 bug 的 hook 卡住正常工作。 + +input=$(cat) + +# 从 stdin JSON 中取 .tool_input.command;jq 出错就放行。 +if ! cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null); then + exit 0 +fi +[ -z "$cmd" ] && exit 0 + +# 显式放行:命令里用了项目 venv 的 python。 +if printf '%s' "$cmd" | grep -q '\.venv/bin/python'; then + exit 0 +fi + +# 拦截:python/python3 作为"命令"被调用(段首,或紧跟在 shell 操作符 ;|&( 或 +# 包装器 sudo/exec/env/nohup/command/xargs/nice/time 之后)。 +# 不匹配 `grep python`、`echo python` 这类把 python 当参数的情况。 +if printf '%s' "$cmd" | grep -qE '(^|[;|&(])[[:space:]]*((sudo|exec|env|nohup|command|xargs|nice|time)[[:space:]]+)?python[0-9.]*([[:space:]]|$)'; then + cat >&2 <<'EOF' +⛔ [block-bare-python] 检测到裸 python/python3 调用,已拦截。 +本项目必须使用 .venv/bin/python —— 系统 python (/usr/sbin/python) 缺少 PyYAML 等依赖, +会让 scripts/validate_frontmatter.py 等脚本把全仓文件误报成 error。 +请改写为:.venv/bin/python <你的参数> +EOF + exit 2 +fi + +exit 0 diff --git a/.claude/prompts/article-factcheck-audit.md b/.claude/prompts/article-factcheck-audit.md new file mode 100644 index 000000000..759c5495d --- /dev/null +++ b/.claude/prompts/article-factcheck-audit.md @@ -0,0 +1,190 @@ +# 文章事实准确性审查 Prompt + +> 使用方式:将 `{article_path}` 替换为待审查文章的绝对路径,作为 Agent 的 prompt 输入。 +> 与 `article-rigor-audit.md` 互补:本文件审查事实准确性与引用验证,后者审查编译器行为断言。 + +--- + +## 目标文章 + +`{article_path}` + +## 审查目标 + +对文章中所有需要引用验证的声明进行事实核查,修正错误,并为每个修正提供可验证的证据和参考资料原文。 + +## 审查范围 + +标记并验证以下类型的声明: + +1. **事实性断言**:人名、组织名、日期、版本号、技术规格、命名由来 +2. **技术性断言**:指令行为、编译器输出、语言标准规则、架构特性、API 用法 +3. **统计数据**:参与人数、社区规模、会议数量、发布周期 +4. **历史/背景性陈述**:时间线、因果关系、组织归属 +5. **代码示例声称的行为**:编译器是否会向量化、某段代码能否编译、运行结果 + +### 不审查的内容 + +- 写作风格、语气、闲聊括号(仓库风格) +- 已有明确代码示例或汇编输出佐证的描述 +- 纯概念解释(不涉及具体实现行为或事实断言) + +## 执行约束 + +**搜索优先**:需要验证的声明,**MUST** 先进行 Web Search 查找权威来源,不要凭记忆断言。 + +**编译验证**:涉及编译器行为的断言,**MUST** 实际编译运行验证,不能编造输出。 + +**输出纪律(严格遵守):** +- **MUST NOT** 写额外的报告文件到 `/tmp/` 或任何位置 +- 验证代码的最终版本**MUST**写入仓库 `code/volumn_codes/` 对应目录 +- 修改报告直接输出为 Agent 的文本返回值 + +**行为纪律(严格遵守):** +- **MUST NOT** 尝试运行 cmake、make、ninja 或任何构建系统 +- **MUST NOT** 在项目目录内创建 build 目录 +- 编译输出路径只指向 `/tmp/` +- 只修改目标文章文件和对应的代码目录文件 + +## 执行流程 + +### 阶段 A:扫描与分级 + +1. 读取目标文章全文 +2. 标记所有需要验证的声明 +3. 用 Web Search 查找权威来源 +4. 按严重程度分级: + +| 级别 | 定义 | 示例 | +|------|------|------| +| P0 | 技术事实完全错误,代码无法编译/运行 | 指令行为在指定架构上不存在、API 用法违反标准 | +| P1 | 事实性错误但非技术性 | 名称拼写错、日期不对、归属关系错 | +| P2 | 措辞过当或过度简化 | "负责 X"实为"参与 X"、省略重要前提条件 | +| P3 | 无法独立验证的统计数据 | 参与人数、会议数量——应标注出处 | + +输出发现清单: + +``` +| 级别 | 行号 | 原文摘要 | 问题 | 权威来源 | +``` + +### 阶段 B:编译验证(如适用) + +对涉及编译器行为的断言: + +1. 在 `/tmp/` 下编写最小测试程序 +2. 单独调用 Bash 编译:`g++ -std=c++XX -O{0,2} -o /tmp/xxx /tmp/xxx.cpp` +3. 单独调用 Bash 运行:`/tmp/xxx` +4. 收集真实证据:汇编输出、运行输出、编译错误信息 +5. 记录编译器版本(`g++ --version`) + +对涉及交叉编译的断言(如 ARM): +- 使用 `arm-none-linux-gnueabihf-gcc` 编译 +- 使用 `qemu-arm-static` 运行 +- 必须同时测试原文声称的架构和正确的架构,展示行为差异 + +### 阶段 C:修正文章 + +对每个修正,**必须包含以下三要素**: + +#### 要素 1:原文/参考资料摘录 + +在文章中使用 `::: warning` 或 `::: details` 块,贴出权威来源的**原文段落**(不是复述),让读者可以对照验证。 + +```markdown +::: warning 原文错误更正 +...错误说明... + +权威来源原文: +> "原文引用内容" +> — 来源名称 + +实际验证结果: +$ 编译命令 +实际输出 +::: +``` + +```markdown +::: details 参考资料原文 +来源名称原文: + +> "原文引用内容" + +读者可访问 URL 自行验证。 +::: +``` + +#### 要素 2:RefLink 引用 + +- 在文章正文中使用 `` 标注引用 +- 在文章末尾 `` 中添加对应的 `` +- 新增引用的 ID 从现有最大 ID +1 开始递增 + +#### 要素 3:验证路径 + +- **代码错误**:在 `code/volumn_codes/vol{N}/` 下创建验证源文件 + - 故意写错的代码:`xx-broken.cpp`(不加 CMakeLists 注册,注释说明预期编译失败) + - 修正后的代码:`xx-fixed.cpp`(加入 CMakeLists 注册) +- **事实性错误**:提供具体的验证命令或 URL +- 优先使用本地可复现的验证方式(编译运行 > 网页链接 > 口头声明) + +### 阶段 D:代码写入规范 + +创建的验证代码文件必须包含: + +```cpp +/* + * 验证:[验证什么] + * + * 背景:[为什么需要验证] + * + * 预期结果:[正确行为是什么] + * + * 编译命令: + * [具体的编译命令] + * + * 运行: + * [具体的运行命令] + * + * 参考资料: + * - [来源 URL] + * + * 编译器:[编译器名称和版本] + */ +``` + +如果目录已有 `CMakeLists.txt`,追加可编译的目标;故意写错的代码不注册。 + +## 修正原则 + +1. **保留原文叙事结构** — 修正用 `:::warning` 和 `:::details` 块插入,不删除原文段落 +2. **区分"演讲者说"和"事实是"** — 演讲者个人观察标注"据演讲者称",不当作事实陈述 +3. **每个修正都给出验证手段** — 读者应能独立复现 +4. **准确性优先于 token 成本** — 需要搜索就搜索 +5. **不信任未验证的技术声明** — 编译器输出必须实际编译,不能编造 + +## 输出要求 + +完成所有修正后,输出验证清单: + +``` +## 审查报告:{article_path} + +### 修正汇总 + +| 级别 | 问题 | 修正方式 | + +### 新增 ReferenceItem + +| ID | 来源 | URL | + +### 新增验证代码 + +| 文件 | 用途 | + +### 验证清单 + +| 问题 | 验证命令 | 预期结果 | +|------|----------|----------| +``` diff --git a/.claude/prompts/article-rigor-audit.md b/.claude/prompts/article-rigor-audit.md new file mode 100644 index 000000000..46b91107c --- /dev/null +++ b/.claude/prompts/article-rigor-audit.md @@ -0,0 +1,143 @@ +# 文章严谨性审查 Agent Prompt + +> 使用方式:将 `{article_path}` 替换为待审查文章的绝对路径,作为 Agent 的 prompt 输入。 + +--- + +你是一个技术文档审查员,负责审查 C++ 教程文章中的**不严谨技术断言**,并通过编译验证来修正它们。 + +## 目标文章 + +`{article_path}` + +## 审查范围 + +**只审查不严谨的技术断言**,不审查写作风格、闲聊括号(那是仓库风格)。 + +### 什么是不严谨的技术断言 + +以下模式需要标记并验证: + +1. **声称编译器优化行为但无证据**:如"编译器会优化为 X"、"会被内联"、"会被消除"、"生成分支预测" +2. **声称性能差异但无 benchmark**:如"更快"、"更慢"、"性能更好"、"开销更小" +3. **声称标准行为但描述不准确**:如漏掉 NRVO、SSO、guaranteed copy elision、`cmov` 优化等上下文 +4. **模糊的实现声称**:如"大多数实现"、"通常会"、"一般情况下",没有限定编译器/版本/优化级别 + +### 什么不是问题(不要标记) + +- 闲聊括号、个人吐槽、情绪表达(仓库风格) +- 有明确代码示例佐证的描述 +- 已给出编译器输出/汇编/benchmark 的断言 +- 纯粹的概念解释(不涉及具体实现行为) + +## 执行约束 + +**MUST ONLY 一次派发一个子 Agent,串行完成,禁止并行派发多个 Agent。** 每个阶段完成后再进入下一个阶段。这是因为编译验证需要精确控制,并行执行容易导致权限冲突和状态混乱。 + +**每个阶段完成后,MUST 直接继续下一阶段或下一篇文章,不需要等待用户确认。** 输出报告即可,不要停下来问"是否继续"。用户会在最终批量审查所有修改。 + +**输出纪律(严格遵守):** +- **MUST NOT** 在 `/tmp/` 下创建子目录(如 `/tmp/xxx-review/`、`/tmp/reports/`) +- **MUST NOT** 写额外的报告文件、日志文件到 `/tmp/` 或任何位置 +- **MUST ONLY** 在 `/tmp/` 下写 `.cpp` 源文件和编译产物(`.o`、`.s`、可执行文件) +- 验证代码的最终版本**MUST**写入仓库 `code/volumn_codes/` 对应目录 +- 修改报告直接输出为 Agent 的文本返回值,不写文件 + +**行为纪律(严格遵守):** +- **MUST NOT** 尝试运行 cmake、make、ninja 或任何构建系统 +- **MUST NOT** 在项目目录内创建 build 目录或执行任何构建命令 +- **MUST NOT** 使用 `&&` 串联编译和运行命令(分成两次 Bash 调用) +- **MUST** 编译命令输出路径只指向 `/tmp/` +- **MUST** 只修改目标文章文件和对应的代码目录文件,不碰其他文件 + +## 执行流程 + +### 阶段 A:扫描发现 + +1. 读取目标文章全文 +2. 逐段检查是否有上述不严谨断言 +3. 输出发现清单,格式: + +``` +| # | 行号 | 原文摘要 | 断言类型 | 建议验证方式 | +``` + +### 阶段 B:编译验证 + +**安全限制(MUST ONLY 严格执行,违反会导致项目目录污染):** + +- **MUST ONLY** 使用 `g++` 直接编译,**禁止使用 cmake、make 或任何构建系统** +- **MUST ONLY** `g++` 输出路径为 `/tmp/`(如 `g++ -o /tmp/test /tmp/test.cpp`) +- **MUST ONLY** `g++ -S` 输出路径为 `/tmp/`(如 `g++ -S -o /tmp/test.s /tmp/test.cpp`) +- **MUST NOT** 在项目目录内执行 `cmake .`、`cmake ..`、`make`、`mkdir build` 或任何构建命令 +- **MUST NOT** 在 `/tmp/` 之外编译任何文件 + +**Bash 调用规则(严格遵守以避免权限问题):** + +- **MUST** 编译和运行分为**两次独立的 Bash 调用**,不要用 `&&` 串联 + - 第一次:`g++ -std=c++XX -O{0,2} -o /tmp/test_name /tmp/test_name.cpp` + - 第二次:`/tmp/test_name` +- **MUST** 生成汇编也单独一次调用:`g++ -std=c++XX -O2 -S -o /tmp/test_name.s /tmp/test_name.cpp` +- **MUST NOT** 在一条 Bash 命令中同时编译和运行(如 `g++ ... && /tmp/xxx`),这会导致权限匹配失败 + +对每个发现的不严谨断言: + +1. 在 `/tmp/` 下编写最小 C++ 测试程序(用 Write 工具写入 `/tmp/xxx.cpp`) +2. 单独调用 Bash 编译:`g++ -std=c++XX -O{0,2} -o /tmp/xxx /tmp/xxx.cpp` +3. 单独调用 Bash 运行:`/tmp/xxx` +4. 收集真实证据:汇编输出(`-S`)、运行输出、或 `sizeof`/`static_assert` 结果 +5. 记录使用的编译器版本(`g++ --version`) + +### 阶段 C:代码写入 + 文章更新 + +**代码写入规则:** + +1. 所有验证测试代码**全部写入仓库** `code/volumn_codes/vol{N}/{chapter-name}/` +2. 如果目标 chapter 目录不存在,**先问用户确认**再创建 +3. 如果目录已有 `CMakeLists.txt`,追加 `add_executable(target source.cpp)` +4. 如果目录没有 `CMakeLists.txt`,新建一个(参考现有格式) +5. 文件命名:`{验证主题}_{简短描述}.cpp`(如 `string_concat_alloc.cpp`) + +**文章更新规则:** + +1. 用真实证据替换文中的空口描述 +2. 保持原文写作风格(个人叙事、技术严谨) +3. 汇编代码块标注编译环境: + ``` + // GCC 15, -O2 -std=c++20 + ``` +4. 运行输出代码块标注命令: + ```bash + g++ -std=c++17 -O2 -o /tmp/test /tmp/test.cpp && /tmp/test + ``` +5. 如果验证结果与原文断言矛盾,诚实说明"实际并非如此"并给出正确描述 + +## 写作风格参考 + +修改文章时必须遵守 `.claude/style/writing-style.md` 的规范: +- 使用"我们"而非"你" +- 保持个人叙事色彩 +- 偏长句子,避免零碎的短句列表 +- 技术严谨度 90%,允许轻吐槽 +- 代码风格:snake_case 函数、PascalCase 类、4 空格缩进 + +## 输出要求 + +完成审查后,输出一份修改报告: + +``` +## 审查报告:{article_path} + +| # | 行号 | 问题摘要 | 处理方式 | +|---|------|----------|----------| +| 1 | XX | ... | 已用汇编验证并更新 | +| 2 | XX | ... | 已修正措辞 | +| 3 | XX | ... | 无误,保留原文 | + +### 新增代码文件 +- `code/volumn_codes/vol{N}/{chapter}/{file.cpp}` — 用途说明 + +### 修改摘要 +- 第 XX 行:将"编译器会XXX"替换为实际汇编对比 +- 第 XX 行:将"大多数实现"限定为"主流 STL 实现" +``` diff --git a/.claude/rules/documents-frontmatter.md b/.claude/rules/documents-frontmatter.md new file mode 100644 index 000000000..93177154b --- /dev/null +++ b/.claude/rules/documents-frontmatter.md @@ -0,0 +1,134 @@ +--- +description: documents/ 下文章的 frontmatter 字段参考与标签体系,编辑文章时必须遵守 +globs: documents/**/*.md +--- + +# Frontmatter 字段参考 + +编辑 `documents/` 下的 Markdown 文章时,必须遵守以下 frontmatter 规范。本文件由 `scripts/validate_frontmatter.py` 在 pre-commit 和 CI 中强制校验。 + +## 字段定义 + +### 必填字段(Missing = Error) + +| 字段 | 类型 | 说明 | +|------|------|------| +| `title` | string | 文章标题 | +| `chapter` | int (0–100) | 章节编号 | +| `order` | int (≥0) | 章内排序序号,应与文件名序号一致 | + +### 推荐字段(Missing = Warning) + +| 字段 | 类型 | 说明 | +|------|------|------| +| `description` | string | 一句话摘要,缺失时由 `documents/hooks/meta.py` 自动生成,但建议手动填写以控制质量 | +| `description` | string | 一句话摘要,建议手动填写以控制质量 | +| `tags` | list[string] | 分类标签,必须来自下方 VALID_TAGS 集合 | + +### 可选字段 + +| 字段 | 类型 | 有效取值 | 说明 | +|------|------|----------|------| +| `difficulty` | string | `beginner` \| `intermediate` \| `advanced` | 难度等级 | +| `platform` | string | `host` \| `stm32f1` \| `stm32f4` | 目标平台 | +| `cpp_standard` | list[int] | 子集 `[11, 14, 17, 20, 23, 26]` | 涉及的 C++ 标准版本 | +| `reading_time_minutes` | int | — | 预估阅读时长(分钟) | +| `prerequisites` | list[string] | — | 前置知识章节列表 | +| `related` | list[string] | — | 相关文章标题 | + +## 标签体系(VALID_TAGS) + +标签分为以下类别,所有标签必须来自此集合。如果需要新标签,须先在 `scripts/validate_frontmatter.py` 的 `VALID_TAGS` 中添加。 + +### 概念类 +`RAII` `移动语义` `零开销抽象` `编译期计算` `类型安全` `内存管理` `异步编程` `模板元编程` + +### 语言特性类 +`constexpr` `consteval` `constinit` `lambda` `CRTP` `concepts` `coroutine` `if_constexpr` `模板` `泛型` + +### 设计模式类 +`对象池` `状态机` `工厂模式` `策略模式` `单例模式` `观察者模式` `RAII守卫` `回调机制` + +### 容器类 +`array` `span` `vector` `map` `unordered_map` `循环缓冲区` `侵入式容器` `容器` + +### 智能指针类 +`unique_ptr` `shared_ptr` `weak_ptr` `intrusive_ptr` `智能指针` `引用计数` + +### 类型安全类 +`enum` `enum_class` `variant` `optional` `expected` `类型别名` `字面量` + +### 函数式类 +`函数对象` `std_function` `std_invoke` `Ranges` + +### 并发类 +`atomic` `memory_order` `mutex` `无锁` + +### 嵌入式类 +`嵌入式` `单片机` `外设管理` `寄存器` `链接器` `交叉编译` `工具链` `CMake` + +### 通用类 +`基础` `入门` `进阶` `实战` `优化` + +### 平台与难度类 +`host` `stm32f1` `beginner` `intermediate` `advanced` `cpp-modern` + +## 标签使用规则 + +1. 每篇文章必须包含 1 个 platform 标签(或 `host` 表示平台无关) +2. 每篇文章必须包含 1 个 difficulty 标签 +3. 每篇文章至少包含 1 个 topic 标签 +4. 标签使用英文小写,中划线分隔 + +## 免检文件 + +以下文件不需要 frontmatter,被 validate_frontmatter.py 跳过: +- `index.md`(各目录导航页) +- `tags.md` +- `documents/images/` 目录下的所有文件 + +## Frontmatter 示例 + +### 通用文章 + +```yaml +--- +title: "RAII 与资源管理" +description: "深入理解 RAII 原则及其在嵌入式开发中的应用" +chapter: 2 +order: 1 +tags: + - host + - cpp-modern + - beginner + - RAII +difficulty: beginner +platform: host +reading_time_minutes: 15 +prerequisites: + - "Chapter 1: C++ 基础入门" +related: + - "智能指针与所有权" +cpp_standard: [11, 14, 17] +--- +``` + +### 嵌入式平台教程 + +```yaml +--- +title: "GPIO 输入与按键消抖" +description: "在 STM32F1 上实现 GPIO 输入读取与软件消抖" +chapter: 3 +order: 5 +tags: + - stm32f1 + - peripheral + - beginner + - 嵌入式 + - 外设管理 +difficulty: beginner +platform: stm32f1 +cpp_standard: [17, 20] +--- +``` diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..7a04be499 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,24 @@ +{ + "permissions": { + "allow": [ + "mcp__web-search-prime__web_search_prime", + "Bash(g++ * -o /tmp/*)", + "Bash(g++ * -S -o /tmp/*)", + "Bash(/tmp/*)", + "Bash(ls *)" + ] + }, + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": ".claude/hooks/block-bare-python.sh" + } + ] + } + ] + } +} diff --git a/.claude/style/writing-style.md b/.claude/style/writing-style.md new file mode 100644 index 000000000..98b94a780 --- /dev/null +++ b/.claude/style/writing-style.md @@ -0,0 +1,381 @@ +# writing-style.md — 本教程写作风格指南(公开稿) + +> 本文件分两部分,服务两类读者: +> +> - **Part 1 · 硬规则**:Frontmatter、文章骨架、代码风格、技术表达与验证铁律、行为底线。**客观,所有作者(人和 AI)必须遵守** —— 不守,文章就是错的或坏的。 +> - **Part 2 · 项目声音**:人格、语气、标志性句式、节奏。**这是主作者的真实写作声音,是本项目辨识度所在。** +> - **人类贡献者**: 能贴合就贴合,这个并不**不强求**;维护者会朝这个方向编辑。硬规则(Part 1)才是准入门槛。本文档只是一个风格指南。也是笔者把自己的文档喂给AI后他提炼的内容。 +> - **AI 生成内容**:请把下面**当作目标声音尽量贴合** —— 它不是可选项。最有效的做法是参考其中的**例子和段落模板**,而不是只读描述。 + +--- + +# Part 1 · 硬规则(必须遵守) + +## 1.1 Frontmatter 与文件命名 + +每篇文章**必须**包含 YAML frontmatter,必填 `title` / `chapter` / `order`。文件命名 `{序号}-{kebab-case}.md`,`order` 与文件名序号一致。完整字段与标签体系见 [`.claude/rules/documents-frontmatter.md`](../rules/documents-frontmatter.md)。 + +```yaml +--- +title: "文章标题" +description: "一句话描述这篇文章的核心内容" +chapter: 0 +order: 0 +tags: + - host # platform(必填 1 个): stm32f1 | stm32f4 | esp32 | rp2040 | host + - cpp-modern # topic(必填 ≥1 个) + - beginner # difficulty(必填 1 个): beginner | intermediate | advanced +difficulty: beginner +reading_time_minutes: 10 +platform: host +prerequisites: + - "Chapter X: 前置知识章节名称" +related: + - "相关文章标题" +cpp_standard: [11, 14, 17, 20] +--- +``` + +## 1.2 文章骨架 + +不同文章类型用不同骨架,任选其一。 + +### 通用文章章节骨架 + +``` +# 文章标题 + +## 引言(动机段) + +## 核心概念 + +### 第一部分 + +### 第二部分 + +## 代码示例(可编译运行,标注 Platform 和 C++ Standard) + +## 实战应用 + +## 注意事项(含常见错误表) + +## 小结(含关键要点 checklist) + +## 练习(可选) + +## 参考资源 +``` + +### 嵌入式平台教程章节骨架 + +嵌入式教程聚焦单一硬件 feature,按以下固定顺序推进: + +``` +# 教程标题 + +## 本章目标(一句话说明要解决什么问题) + +## 硬件原理(只讲本章 feature 对应的硬件原理) + +## HAL 接口(只讲本章 feature 的 HAL API) + +## 最小 Demo(可直接跑的最小例子) + +## C++23 封装(用现代 C++ 改善接口) + +## 常见坑 + +## 练习题 + +## 可复用代码片段(沉淀到 code/ 目录) + +## 本章小结(总结本章唯一核心 feature) +``` + +### 参考卡章节骨架 + +参考卡(Reference Card)走精炼的结构化格式,不需要叙事风格: + +``` +# 特性名称(C++XX) + +## 一句话(用一句人话说清楚这是什么、解决什么问题) + +## 头文件(#include <...>) + +## 核心 API 速查(表格:操作 | 签名 | 说明) + +## 最小示例(完整可编译,不超过 20 行) + +## 嵌入式适用性:高/中/低 + +## 编译器支持(表格:GCC | Clang | MSVC) + +## 另见(链接到教程章节和 cppreference) +``` + +### 标准库组件深入文章骨架 + +标准库组件深入(vol3 主力文章)讲透一个组件「为什么这么设计 + 怎么用对它」,不是 API 罗列。**按组件特性裁剪,不千篇一律** —— 不同组件该讲的点不同,骨架是参考不是填空模子。 + +结构原则: + +- **标题主题驱动**:点出该组件最值得讲透的机制(如「三指针、扩容与迭代器失效」「SSO、COW 与 resize_and_overwrite」),不用「第一步 / 第二步」式折腾标题 +- **平实切入,别刻意反直觉**:从该组件最值得讲透的点起手(机制 / 工程动机 / 常见用法),自然展开;确有反直觉的点就如实讲,但不必篇篇找惊讶点 —— 老是"惊讶"只会让读者皱眉 +- **「上手跑一跑」实测收口**:用真实输出 / 汇编 / sizeof 佐证,不空口断言(遵循 rigor-audit) +- **参考资源收尾**:cppreference 优先,标注 C++ 标准版本 + +正文教学点(可选池,按该组件读者最易踩的坑取舍,**不要求全列**): + +- 内部表示 / 数据结构(三指针、红黑树节点、哈希桶) +- 复杂度保证与代价(摊还 vs 单次、最坏情况) +- 迭代器失效规则(仅对会失效的容器有意义) +- 异常安全(move_if_noexcept、基本异常保证) +- 性能注意(cache、分配、SSO / 小对象优化) +- 工程选择(何时用它 vs 替代品、C++20/23 新接口) +- 三大实现差异(libstdc++ / libc++ / MSVC,涉及时再对比) + +同骨架、不同裁剪的范例(避免千篇一律): + +- `vector` 深入:内部表示 → 扩容 → 迭代器失效 → 异常安全 → 新接口(全特性组件,多讲) +- `string` 深入:SSO/COW 历史 → SSO 阈值 → resize_and_overwrite(只讲内存模型,不讲迭代器失效) +- `char8_t`:类型变更 → 两个坑 → 迁移 → 标准演进(语言特性型,结构更简、不涉及数据结构) + +决策 / 入口型文章(如「容器选择指南」)是这套骨架的变体:以复杂度对比表 + 内存局部性 + 迭代器失效速查 + 决策树为主,不必强行「上手跑一跑」,但对比数据仍需权威出处(cppreference / 实测)。 + +## 1.3 代码风格 + +所有教程代码遵循以下约定,与项目 `.clang-format` 保持一致。 + +``` +文件命名:snake_case → gpio_input.cpp, uart_config.h +类命名: PascalCase → GpioPin, UartDriver +函数命名:snake_case → configure_pull_up(), read_register() +常量命名:kPascalCase → kMaxRetryCount, kDefaultBaudRate +宏命名: UPPER_SNAKE_CASE → CONFIG_UART_BAUDRATE, ENABLE_DMA +模板参数:PascalCase → typename TPin, typename TConfig +枚举值: kPascalCase → enum class PullMode { kUp, kDown, kNone } +``` + +Include 顺序(各组之间空一行,组内字母序): + +```cpp +// 1. 对应头文件(如 gpio.h) +#include "gpio.h" + +// 2. C++ 标准库 +#include +#include +#include + +// 3. 第三方库 +#include "stm32f1xx.h" + +// 4. 项目内头文件 +#include "pin_config.h" +``` + +注释用 Doxygen 格式,行内注释解释"为什么"而非"做什么": + +```cpp +/// @brief 配置 GPIO 引脚的上拉电阻 +/// @param pin GPIO 引脚编号 +/// @param mode 上拉模式(kUp / kDown / kNone) +/// @return 配置是否成功 +bool configure_pull_up(uint8_t pin, PullMode mode); + +// 等待 SRAM 就绪(上电后需要 3 个 HCLK 周期) +while (!(RCC->CR & RCC_CR_HSIRDY)) {} +``` + +格式化规则: + +- 缩进 4 空格,行宽 100 字符 +- 大括号:函数定义换行开,控制语句同行开 +- 指针 / 引用靠左(`int* p`、`void foo(int& ref)`) + +```cpp +void example_function(int* data, std::size_t length) +{ + if (data == nullptr) { + return; + } + + for (std::size_t i = 0; i < length; ++i) { + // 处理每个元素 + process(data[i]); + } +} +``` + +## 1.4 技术表达与验证铁律 + +这几条关乎**正确性**,不是风格偏好,必须执行: + +- **必须用真实证据**:真实命令行输出、原始代码块、配置文件片段、编译日志、串口日志、启动信息。**禁止编造输出。** +- **必须解释"为什么"**,禁止只给步骤: + + ``` + 这是因为…… + 原因在于…… + 底层机制是…… + 内核这里做了…… + 设备树在这里决定…… + ``` + +- **必须实验验证收口**:用真实输出 / 汇编 / sizeof 佐证,不空口断言(遵循 [`article-rigor-audit`](../prompts/article-rigor-audit.md))。验证口吻见 Part 2.6。 +- **必须含踩坑预警**:关键技术点要有提示块(结构要求;语气见 Part 2.3)。 + +## 1.5 行为底线(铁律) + +- 禁粗口 +- 禁攻击性语言 +- 禁阴阳怪气读者 + +--- + +# Part 2 · 项目声音(目标声音) + +> 主作者写教程时的真实声音,是本项目区别于 cppreference 干巴巴文档的辨识度所在。 +> +> - **人类贡献者**:能贴合就贴合,不强求;维护者会朝这个方向编辑。 +> - **AI 生成内容**:请**尽量贴合**这个声音 —— 把下面的权重、人格、标志性句式、段落模板当作目标,而不是可选项。 + +## 2.1 风格定位 + +本教程风格属于: + +> **强技术深度 + 强个人叙事 + 实战驱动 + 教程长文结构 + 带人格化表达** + +写作不是"中性技术文档",而是技术探索记录 + 手把手带读者走完整旅程,允许情绪、吐槽、踩坑叙述存在,强调"陪读者一起走通"而不是"给你一个答案"。 + +两条叙事偏好: + +- **偏长句子,不零碎** —— 不要一句话一个段落。如果准备列写列表、但每项又很短,考虑改成不超过三段的文段。 +- **大段代码按逻辑分段** —— 顺着功能顺序拆成小段,逐步讲"为什么这么写、要注意什么"。不用"注意点:""关键点:"这类专门小标题,用行云流水的文字承上启下(痕迹别太重,允许适当断裂)。 + +风格取向权重: + +``` +技术严谨度 █████████░ 90% +实操导向 ██████████ 100% +个人叙事色彩 ████████░░ 80% +教程结构清晰度 ██████████ 100% +幽默/情绪表达 ██████░░░░ 60% +``` + +## 2.2 人格 + +本教程的写作人格是: + +> 一位正在"亲自折腾 C++ / 底层 / 系统 / 架构"的工程师博主 + +特征: + +- 会连续熬夜折腾 +- 会吐槽旧方案过时 +- 会强调"别偷懒" +- 会提醒读者踩坑点 +- 会给出实战验证输出 +- 会说"我们"而不是"你" +- 经常把读者当"同行者"而非"小白" + +## 2.3 语气:人称与情绪 + +**人称**:高频用"我们 / 现在我们 / 接下来我们 / 这里我们要做的是 / 你会发现 / 你可能会踩这个坑"。避免"用户应当 / 开发者必须 / 本文将介绍"(论文腔)。 + +**情绪**:允许自然出现轻吐槽、自嘲、折腾感叹、强调语气。示例语气: + +``` +说实话,这一步不改一定炸 +这里千万别手滑 +这一点真的坑了我半天 +不然你会收获一个非常漂亮的 kernel panic +笔者在这里血压拉满 +``` + +踩坑预警块的语气也走这套("千万别""一定确认""否则直接起不来")。 + +> 行为底线(粗口 / 攻击 / 阴阳怪气读者)见 Part 1.5,那是铁律不是风格。 + +## 2.4 标志性句式 + +这些是本教程的"标志性句式",串起叙事节奏: + +``` +我们现在要做的是 +先别急 +这里先验证一下 +你会发现 +很好,现在 +接下来问题来了 +我们回头看 +事情到这里还没完 +真正的坑在后面 +``` + +## 2.5 解释风格 + +抽象概念用「类比 + 机制拆解 + 当前步骤关联」: + +``` +你可以把它理解为…… +相当于…… +在启动链条中它扮演…… +在这里它的角色类似…… +``` + +## 2.6 教程节奏与验证口吻 + +**节奏**:不压缩步骤、不跳关键推导、宁可啰嗦也不断层、允许重复强调。允许"读者心理预判": + +``` +你可能会问 +很多朋友会卡在这里 +新手看到这里会懵 +如果你看到这个报错 +``` + +**实验验证口吻**(配合 Part 1.4 的验证铁律): + +``` +现在上板测试 +我们试跑一下 +来验证一下 +打个 ping 看看 +串口输出如下 +``` + +## 2.7 标题与收尾 + +**标题**模板:`从0开始……` / `XX实战指南` / `最新部署方案` / `完整踩坑记录` / `爆改指南` / `最全移植过程`,允许副标题带年份 / 版本号。 + +**收尾**常用: + +``` +到这里就大功告成了 +可以庆祝一下 +给板子拍张照不过分 +完结撒花 +``` + +## 2.8 段落模板(可直接复用) + +``` +很好,到这里我们已经完成了第一阶段。 +但事情还没结束,如果你现在直接启动,大概率会遇到一个非常熟悉的报错 —— XXXXX。 +别慌,我们来拆一下原因:从启动链条上看,这一步实际上是由 XXXX 决定的,而默认配置里它指向的是 YYYY,所以我们必须手动改掉。 +修改位置如下: +``` + +## 2.9 风格禁区(定义声音的反面) + +避免写成: + +- 学术论文风 +- 官方文档风 +- API 手册风 +- 过度精炼、无情绪风 +- ChatGPT 标准答案风 +- 起手一大堆列表(能改写成逻辑有序的句子就改写) +- Emoji 谨慎使用 diff --git a/.github/faq.md b/.github/faq.md new file mode 100644 index 000000000..a70fd7a3d --- /dev/null +++ b/.github/faq.md @@ -0,0 +1,122 @@ +# faq.md — C++ 常见误解速查 + +> 给学习者和他们的 agent:C++ 里"看着对、其实错"的高频坑。 +> 每条 = **误解 → 真相 → 最小示例**。这是个**持续补充**的活文档,种子条目如下。 + +## 语言机制 + +### `auto` 会保留 const / 引用性 + +**误解**:`auto x = something;` 保留 `something` 的 const 和引用。 + +**真相**:`auto` 走模板参数推导规则,**剥掉顶层 const 和引用**,得到的是副本。 + +```cpp +int v = 1; +const int& r = v; +auto a = r; // a 是 int(副本),const 和 & 都没了 +const auto& b = r; // b 才是 const int& + +for (auto x : vec) { /* 每个元素都拷贝! */ } +for (const auto& x : vec) { /* 不拷贝 */ } +``` + +要保留,用 `auto&` / `const auto&` / `decltype(auto)`。 +参见:卷二·现代特性(vol2-modern-features)。 + +### `Widget w(Bar());` 是构造对象 + +**误解**:这行构造了一个 Widget。 + +**真相**:这是**最恼人解析(Most Vexing Parse)** —— 编译器把它读成"一个叫 `w`、接受(返回 `Bar` 的函数)、返回 `Widget` 的**函数声明**"。 + +```cpp +Widget w(Bar()); // 声明了一个函数,不是对象! +Widget w{Bar()}; // 这才是对象(花括号初始化) +Widget w((Bar())); // 加括号也是对象 +``` + +参见:卷一·基础(vol1-fundamentals)。 + +### `int x{3.14}` 能编译 + +**误解**:花括号初始化和圆括号一样,会隐式收窄。 + +**真相**:花括号初始化**禁止窄化**。 + +```cpp +int x(3.14); // OK,隐式截断为 3 +int y{3.14}; // 编译错误 +``` + +## 资源与移动 + +### `std::move` 就是"移动" + +**误解**:`std::move(x)` 把 x 的内容搬走了。 + +**真相**:`std::move` 只是个**转成右值引用的 cast**,本身什么都不搬。真正搬不搬,取决于类型有没有移动构造/赋值;没有就回退到拷贝。 + +```cpp +void sink(std::string&& s) { + std::string local = s; // 拷贝!s 在这里是左值 + std::string moved = std::move(s); // 这才真移动 +} +``` + +两个连带坑: + +- **命名的右值引用是左值**:参数 `T&& s`,用 `s` 时是左值,想再搬得再 `std::move(s)`。 +- **对 `const` 对象 `std::move` → 拷贝**:`std::move` 得到 `const T&&`,移动构造要非 const,于是匹配拷贝,"移动"悄悄变拷贝。 + +参见:卷二·现代特性(vol2-modern-features)。 + +## 面向对象 + +### 基类析构不用 virtual + +**误解**:只要不手动 delete,基类析构写不写 `virtual` 无所谓。 + +**真相**:通过基类指针/引用 `delete` 派生对象,若基类析构非 `virtual`,是**未定义行为**(典型后果:派生部分不析构 → 资源泄漏)。**有虚函数的类,析构就该 virtual。** + +```cpp +struct Base { virtual void f(); /* 缺 virtual ~Base() */ }; +struct Derived : Base { std::ofstream file; /* 析构关文件 */ }; + +Base* p = new Derived; +delete p; // UB:Derived::~Derived 没被调用,file 没关 +``` + +参见:卷一·基础(vol1-fundamentals)。 + +## 容器 + +### `push_back` 后,之前的引用/迭代器还有效 + +**误解**:我拿到的 vector 元素引用或迭代器,后面继续 `push_back` 也没事。 + +**真相**:`push_back` 触发扩容(容量变化)时,**所有引用和迭代器全部失效**。要跨修改持有,用下标或先 `reserve`。 + +```cpp +std::vector v = {1, 2, 3}; +int& first = v[0]; +v.push_back(4); // 可能扩容 → first 失效,再用是 UB +``` + +各操作的失效规则不同(如 `erase` 只失效被删及之后的),改动容器前先查规则。 +参见:卷三·标准库(vol3-standard-library)。 + +## 未定义行为 + +### "我这里跑得好好的,所以代码没错" + +**误解**:程序输出了预期结果,代码就是对的。 + +**真相**:**未定义行为(UB)** —— 有符号整数溢出、越界访问、未初始化读取、违反 ODR、数据竞争等 —— 在 `-O0` 下可能"碰巧"正常,在 `-O2` 下可能整个崩、或被编译器直接优化掉。**不能靠"能跑"证明正确**,要用工具(ASan / UBSan / TSan)+ 按标准理解行为。 + +```cpp +int a = INT_MAX; +int b = a + 1; // 有符号溢出 = UB,不是"绕回" +``` + +参见:卷六·性能(vol6-performance)、卷七·工程(vol7-engineering)、卷五·并发的数据竞争部分(vol5-concurrency)。 diff --git a/.github/learning-with-agents.md b/.github/learning-with-agents.md new file mode 100644 index 000000000..054a21d18 --- /dev/null +++ b/.github/learning-with-agents.md @@ -0,0 +1,54 @@ +# learning-with-agents.md + +> 给「**辅助 C++ 学习者的 AI agent**」的指南。 +> +> 受众画像:用 agent 的人**懂 Agent、不懂(或刚学)C++**。他把自己的 agent 指向这个仓库,问"帮我学 X""解释下 Y""写段做 Z 的代码"。你的任务是当**合格导师**,不是当代码打印机 —— 核心是**别让学习者绕弯**,顺手把概念讲透。 + +## 你的姿态 + +- **当导师,不当打字机**:讲"为什么",不只给"是什么"。 +- 用"我们"(贴合项目写作声音,见 [`writing-style.md`](../.claude/style/writing-style.md)),把读者当同行者。 +- **顺着项目学习路线推进**([roadmap](../documents/roadmap/index.md)),把新概念勾连到学习者已经学过的部分。 +- 鼓励学习者**自己跑代码**,不只读你的输出。 + +## 四条「别绕弯」守则 + +### 1. 定位:先读项目怎么讲,再开口 + +**本项目就是教材本身。** 解释一个概念之前,先在仓库里定位项目对这个概念的处理,对齐它的讲法和进度,不要凭通用记忆现编一套。 + +- 学习顺序:[`documents/roadmap/index.md`](../documents/roadmap/index.md) +- 概念位置:各卷 `documents/vol*/index.md` + - vol1 基础 / vol2 现代特性·智能指针·移动语义 / vol3 标准库·容器 / vol4 模板·协程·concepts / vol5 并发 / vol6 性能 / vol7 工程·工具链 / vol8 领域·嵌入式 + +### 2. 验证:断言 C++ 行为前,先实测或查 cppreference(核心) + +C++ 语义随**标准版本和实现**变化,**禁止凭训练记忆断言**。要说"X 会 Y"之前,二选一: + +- **编译跑一个最小例子**(本仓库有工具链,产物写 `/tmp/`,**别在项目目录 build**),贴真实输出;或 +- **引 cppreference 并标标准版本**。 + +不确定就老实说"我不确定,验一下"——这比自信地错强一百倍。高频雷区(`auto` 掉引用、move 不一定真 move、sizeof/ABI 假设)见 [FAQ](faq.md)。 + +### 3. 落码:按项目代码风格写 + +学习者会读项目里的示例,你的代码要和它们一致(见 [`writing-style.md`](../.claude/style/writing-style.md)): + +- `snake_case` 函数、`PascalCase` 类型、`kPascalCase` 常量、4 空格缩进、Allman 大括号 +- 标注 C++ 标准(`// Standard: C++20`) +- 例子**最小且可编译**,别塞无关代码 + +### 4. 纠偏:遇到经典误解,先点破 + +C++ 很多"看着对、其实错"的坑。讲到带常见误解的概念时,**主动点破**(查 [FAQ](faq.md)),别等学习者先形成错的模型再纠正。 + +## 深度验证(可选) + +要把一个断言查到底,用本项目的审查 prompt 当清单: + +- [`article-factcheck-audit.md`](../.claude/prompts/article-factcheck-audit.md) —— 事实 + 引用验证 +- [`article-rigor-audit.md`](../.claude/prompts/article-rigor-audit.md) —— 编译器行为 + 性能断言(强制编译/汇编佐证) + +## 常见误解速查 + +见 [FAQ](faq.md)。 diff --git a/.gitignore b/.gitignore index 40ecde37f..09da07364 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ -# AI -.claude -CLAUDE.md +# AI agent infra — 公开(AGENTS.md / CLAUDE.md / .claude 资产库),仅以下保持私有 +.claude/settings.local.json +.claude/cache/ +.claude/issues/ +.claude/prompts/produce-vol5-labs.md +.claude/prompts/vol5-code-verification.md +.claude/chapter-projects-outline.md +.claude/templates/ # Build directories build/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..0f81fca59 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,33 @@ +# AGENTS.md + +给**任何 AI coding agent**(Claude Code / Cursor / Copilot / Codex / Windsurf …)的项目入口,人也适用。一个 vendor-neutral 文件,所有 agent 都读。Claude 专属补充另见 [CLAUDE.md](CLAUDE.md)。 + +## 这是什么 + +**Tutorial_AwesomeModernCPP** —— 从 C++ 基础到嵌入式实战的系统化 Modern C++ 教程(C++11–C++23,9 卷约 155 篇),VitePress 构建 + CMake 代码示例(支持 host / STM32F1 / STM32F4),部署 GitHub Pages。主语言中文。 + +``` +documents/ vol1..vol9 + compilation + projects + images + community ← 文档源 +site/.vitepress/ VitePress 配置(nav / sidebar / theme / plugins) +scripts/ build.ts(分卷并行构建)、validate_frontmatter.py … +code/volumn_codes/ 各卷可编译示例(无根 CMakeLists,逐目录构建) +``` + +## 通用 essentials(所有 agent 必读) + +- **Python 一律用 `.venv/bin/python`**(根目录 `.venv`,Python 3.14 + PyYAML)。**严禁裸 `python` / `python3`** —— 系统 python 缺 PyYAML,会让 `validate_frontmatter.py` 把全仓文件误报成 error。(Claude Code 侧有 PreToolUse hook 强制拦截。) +- **金科玉律 · 信息校验**:凡断言 C++ 行为,**先编译实测或查 cppreference 并标标准版本**。C++ 语义随版本 / 实现变化,**禁止凭记忆断言**。验证代码只写 `/tmp/`,**不要在项目目录跑 cmake / make / build**(会污染仓库)。 +- **构建 / 校验**:`pnpm install` → `pnpm dev`(热更新预览)/ `pnpm build`(分卷并行);`markdownlint documents/**/*.md`;`.venv/bin/python scripts/validate_frontmatter.py`。 +- **谨慎区**:`compilation/` 卷已完成,改动需特别谨慎;`vol1-fundamentals/` 正在重写,注意与已有内容衔接。 + +## 你来做什么?(按场景路由) + +| 场景 | 去哪 | +|---|---| +| 贡献文章 / 代码示例 | [CONTRIBUTING.md](CONTRIBUTING.md) —— 流程一条龙 | +| 写作人格 / 语气 / 文章骨架 / 代码风格 | [.claude/style/writing-style.md](.claude/style/writing-style.md) | +| Frontmatter 字段与标签体系 | [.claude/rules/documents-frontmatter.md](.claude/rules/documents-frontmatter.md) | +| 辅助 C++ 学习者(学习辅助 track) | [.github/learning-with-agents.md](.github/learning-with-agents.md) + [FAQ](.github/faq.md) | +| 复用本项目 Claude 资产(commands / prompts / hooks) | [CLAUDE.md](CLAUDE.md) → `.claude/` | + +> 写作流程里的两条通用纪律也适用所有 agent:**写作前先 web search 核准确性**;**完成后自查并告知如何验证**,别把检查留给作者。 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..342b5ee46 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,56 @@ +# CLAUDE.md + +Claude Code 在本项目的**专属补充**。通用项目信息(架构 / 命令 / 校验 / 金科玉律)见 [AGENTS.md](AGENTS.md) —— **先读它**。本文件只放 Claude 特有的。 + +## 写作纪律(必须遵守) + +1. 写作前 **MUST** 先查 [.claude/style/writing-style.md](.claude/style/writing-style.md)(人格、语气、文章骨架、代码风格) +2. 写作前 **MUST** 先 web search,准确性优先,token 成本其次 +3. 完成后 **MUST** 自查,而非留给作者;自查后告知如何验证 +4. 完成一个文件夹下的文章,**MUST** 顺手建 `index.md`,确保每篇都能被 VitePress 访问 +5. 计划模式用中文写计划文件 +6. 审查文章准确性,用 [.claude/prompts/](.claude/prompts/) 下的 prompt: + - `article-factcheck-audit.md` —— 事实准确性 + 引用验证(人名 / 日期 / 规格 / 代码行为) + - `article-rigor-audit.md` —— 编译器行为 + 技术断言验证(强制编译 / 汇编佐证) + +## 规划命令(skills,规划者模式 —— 只规划不执行) + +- [`/patch`](.claude/commands/patch.md) —— 小迭代规划(开发原子单位,累积制) +- [`/minor`](.claude/commands/minor.md) —— 发版打包规划(攒够后手工判定触发) + +确认讨论后产出【总结 + 迭代 Issue】到聊天框,套本地 `.claude/templates/iteration-issue.md`。不调 `gh`、不开 issue。 + +## 实用命令(skills,执行型) + +- [`/audit`](.claude/commands/audit.md) —— 对一篇文章跑事实 + 严谨双审查并修正 +- [`/verify-claim`](.claude/commands/verify-claim.md) —— 编译最小例子 + 查 cppreference,验证一条 C++ 断言 +- [`/explain`](.claude/commands/explain.md) —— 给学习者讲一个概念(定位 → 验证 → 项目声音讲解) +- [`/new-article`](.claude/commands/new-article.md) —— 按骨架起一篇新文章 +- [`/preflight`](.claude/commands/preflight.md) —— 提交前自检(frontmatter / lint / 链接 / 索引) + +## 记忆系统 + +Claude Code 有持久记忆(跨会话保留用户偏好、项目进展、踩坑),由 `MEMORY.md` 索引;具体存储位置在本地 Claude 配置,因人而异。 + +## 添加贡献者 + +用户说"添加贡献者"时:① 问 GitHub URL + 贡献内容 + 是否匿名;② 同步更新 `CONTRIBUTORS.md` + `documents/team/index.md` + `documents/en/team/index.md`;③ 卡片格式(``)参照 `documents/team/index.md` 现有条目。 + +## 配置 + +- [.claude/settings.json](.claude/settings.json) —— 权限 allowlist + PreToolUse hook +- [.claude/hooks/block-bare-python.sh](.claude/hooks/block-bare-python.sh) —— 强制 `.venv/bin/python` +- `.claude/settings.local.json` —— 本地覆盖(不入库) + +## 参考文件索引 + +| 文件 | 用途 | +|---|---| +| [AGENTS.md](AGENTS.md) | 跨 agent 通用入口(架构 / 命令 / 金科玉律) | +| [.claude/style/writing-style.md](.claude/style/writing-style.md) | 写作人格 / 语气 / 骨架 / 代码风格 | +| [.claude/rules/documents-frontmatter.md](.claude/rules/documents-frontmatter.md) | Frontmatter 字段与标签 | +| [.claude/commands/](.claude/commands/) | `/patch` `/minor` 规划 skills | +| [.claude/prompts/](.claude/prompts/) | 文章审查 prompt(事实 / 严谨) | +| [CONTRIBUTING.md](CONTRIBUTING.md) | 贡献流程一条龙 | +| `site/.vitepress/config/` | VitePress 配置(nav / sidebar / theme) | +| `scripts/build.ts` | 分卷并行构建 | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d6f24c61e..f0a159671 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,6 +106,8 @@ documents/vol2-modern-features/ # 卷二目录 4. **标题层级**:不超过 4 级(`####`) 5. **篇幅**:每篇文章控制在 1500-3000 字 +> 完整的写作人格、语气规则、文章骨架与代码风格见 [`.claude/style/writing-style.md`](.claude/style/writing-style.md)(项目作者的真实写作声音,贡献时尽量贴合)。 + ## 自定义 Vue 组件 文档站注册了若干自定义 Vue 组件,可在 Markdown 中直接使用。 @@ -371,6 +373,14 @@ QA 不替代正文,只负责解答常见分叉问题和高频误区。 - [CONTRIBUTORS.md](./CONTRIBUTORS.md) — 完整贡献者列表 - [文档站贡献者页面](https://awesome-embedded-learning-studio.github.io/Tutorial_AwesomeModernCPP/team/) — 在线展示 +## 使用 AI / Agent 辅助(可选) + +用不用 AI 都欢迎贡献。如果你或你的 AI agent 想辅助开发、写教程或验证技术概念,把 agent 指向仓库根的 [`AGENTS.md`](AGENTS.md) —— 它是跨工具(Claude Code / Cursor / Copilot 等)的通用入口,包含项目结构、构建校验命令,以及一条核心纪律:**断言 C++ 行为前先编译实测或查 cppreference,并标注标准版本**。 + +- 写作人格与风格:[`.claude/style/writing-style.md`](.claude/style/writing-style.md) +- 文章审查(事实核查 / 编译验证)的 prompt 在 `.claude/prompts/` +- 这些资产本身就是我们开发流程的一部分,公开供参考与复用。 + ## 行为准则 - 尊重所有贡献者 diff --git a/README.md b/README.md index 021b89c3c..bfcadedf8 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ --- -![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 439/439 docs translated +![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 439/440 docs translated ## 这是什么项目 diff --git a/documents/vol3-standard-library/40-iterator-basics-and-categories.md b/documents/vol3-standard-library/40-iterator-basics-and-categories.md new file mode 100644 index 000000000..8627a62e0 --- /dev/null +++ b/documents/vol3-standard-library/40-iterator-basics-and-categories.md @@ -0,0 +1,153 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 20 +description: 讲透迭代器 category:迭代器是指针的泛化、容器和算法之间的通用接口,五层强弱等级(含 C++20 contiguous)如何决定能用哪些算法、如何靠编译期标签派发影响 std::distance 性能,以及为什么 std::sort 用不了 std::list +difficulty: intermediate +order: 40 +platform: host +prerequisites: +- vector 深入:三指针、扩容与迭代器失效 +- array:编译期固定大小的聚合容器 +reading_time_minutes: 10 +related: +- 容器选择指南:按操作、内存与失效规则挑对容器 +tags: +- host +- cpp-modern +- intermediate +- Ranges +title: 迭代器基础与 category:容器和算法靠什么对接 +--- + +# 迭代器基础与 category:容器和算法靠什么对接 + +容器这一路我们走完了——`array`、`vector`、`list`、`map`,能存数据的家伙基本到齐。可一旦想把它们交给 `std::sort`、`std::find`、`std::transform` 这些算法,一个有意思的问题就冒出来了:`std::sort` 凭什么对 `vector` 和 `array` 都能用,对 `list` 却连编译都过不了?算法里又没写死认哪个容器。 + +答案藏在容器和算法中间那层薄薄的通用接口上——迭代器。这篇我们就把迭代器拆开看:它到底是什么、为什么有「强弱等级」(也就是 category),以及这个等级怎么在编译期就决定了一段代码能不能跑、跑得快不快。 + +## 迭代器是什么:把指针的用法泛化 + +先回到最熟悉的指针。给定一个数组,我们能用 `*p` 取值、`++p` 前进一位、`p != end` 判断到没到头——这三板斧就能从头遍历到尾。迭代器干的事,就是把「这套指针的用法」抽象出来:只要某个类型支持解引用、自增、比较,它就能当迭代器用,至于它背后是连续数组、链表节点还是别的什么结构,算法根本不用关心。 + +换句话说,裸指针就是一种「原生迭代器」,而 `vector::iterator`、`list::iterator` 这些,是「长得像指针、但背后挂着各自容器」的迭代器。算法只认这套统一接口,所以一份 `std::find` 能通吃所有容器。这就是 STL 当年最关键的设计决定:**容器和算法解耦,靠迭代器这层接口对接**。 + +## category:迭代器有强弱等级 + +「支持解引用和自增」只是最低门槛。不同的迭代器能做的事差很多:有的只能往前走、还只能读一遍;有的能随机跳到任意位置。能做的操作越多,这个迭代器的「等级」就越高,标准里管这叫 iterator category。 + +从弱到强,经典是这么几层,后一层总在前一层基础上加能力(这是 C++20 之前的老五类,加上 C++20 新增的最强一类): + +- **input**:能读、能 `++`、能比相等,但只能单遍往前走(典型如 `istream_iterator`)。 +- **forward**:在 input 基础上允许多遍遍历(典型如 `forward_list`)。 +- **bidirectional**:再加 `--`,能往回退(典型如 `list`、`set`、`map`)。 +- **random_access**:再加 `+n`、`[]`、大小比较,能随机跳转(典型如 `vector`、`deque`、裸指针)。 +- **contiguous**(C++20 新增):在 random_access 基础上,还保证元素在内存里连续存放(典型如 `vector`、`array`、`string`、裸指针)。 + +另外还有个 **output**,专门只写不读,单列在外。 + +光说层级有点虚,我们直接拿 C++20 的 concept 在编译期判一下,各种容器的迭代器到底落在哪一档。concept 是 C++20 给的编译期谓词,`std::random_access_iterator` 为真,就说明 `T` 满足随机访问迭代器的全部要求,跑不了题: + +```cpp +// Standard: C++20 +#include +#include +#include +#include +#include +#include +#include +#include + +static const char* yn(bool v) { return v ? "是" : "否"; } + +template +void print_row(const char* lbl) +{ + std::cout << lbl + << " " << yn(std::input_iterator) + << " " << yn(std::forward_iterator) + << " " << yn(std::bidirectional_iterator) + << " " << yn(std::random_access_iterator) + << " " << yn(std::contiguous_iterator) << '\n'; +} +``` + +`main` 里依次对每种容器的迭代器调一遍 `print_row`,用 `g++ -std=c++20 -O2`(本机 GCC 16.1.1)跑出来是这样: + +```text +类型 input forward bidi rand contig +vector::iterator 是 是 是 是 是 +array::iterator 是 是 是 是 是 +string::iterator 是 是 是 是 是 +int* (裸指针) 是 是 是 是 是 +list::iterator 是 是 是 否 否 +set::iterator 是 是 是 否 否 +forward_list::iterator 是 是 否 否 否 +``` + +这张表把层级关系讲得很直白:`vector`、`array`、`string` 还有裸指针五项全亮,是能在内存里随机跳转、还连续存放的最强一类(contiguous);`list` 和 `set` 到 bidirectional 为止——能前后走,但不能 `it + 5` 一下跳过去;`forward_list` 最弱,只能单向往前。强弱不是「谁写得更好」,而是数据结构本身决定的:链表的节点在内存里东一个西一个,你压根没法 `it + n` 直接算出第 n 个节点的地址。 + +## 为什么 category 重要:它决定能用哪些算法 + +回到开头那个问题。算法在标准里都写明了对迭代器 category 的要求:`std::find` 只要 input(顺着找就行),`std::reverse` 要 bidirectional(得往回走),`std::sort` 要 random_access(快排得随机跳转取 pivot、做分区)。这些要求不是文档里写写而已——传进去的迭代器达不到,直接编译失败。 + +所以把 `std::sort` 套到 `std::list` 上,会撞墙: + +```text +=== std::sort 要求 random_access_iterator === + vector::iterator 是 random-access? 是 + list::iterator 是 random-access? 否 +``` + +`list` 的迭代器只到 bidirectional,够不着 random_access,`std::sort` 用不了。那链表就没办法排序了吗?有,只不过它走自己的路——成员函数 `list::sort()`,内部用归并排序,天然适合链表(归并不需要随机访问,只要能前后走和拆分),复杂度同样是 O(n log n): + +```text + vector 用 std::sort 后: 1 1 2 3 4 5 6 9 + list 用 list::sort() 后: 1 1 2 3 4 5 6 9 +``` + +这其实是个挺常见的坑:新手习惯了对啥容器都 `std::sort(c.begin(), c.end())`,在 `list` 上就编不过。记住一句话——**算法挑迭代器,不挑容器;容器提供什么等级的迭代器,决定了它能用哪些泛型算法**。 + +## category 还偷偷影响性能:编译期标签派发 + +category 不光管「能不能用」,还管「用起来多快」。看 `std::distance`,它返回两个迭代器之间的距离,对谁都给一样的结果,但复杂度不一样: + +```text +=== std::distance(begin, end)(值相同,复杂度不同)=== + vector(10): 10 [random-access -> O(1)] + list(10): 10 [bidirectional -> O(n)] +``` + +同样是 10 个元素,`vector` 那条是 O(1),`list` 那条是 O(n)。差别在哪?`vector` 的迭代器是 random_access,`std::distance` 直接算 `last - first`,一步到位;`list` 只能 bidirectional,只能老老实实从头 `++` 数到尾,几个元素就走几步。 + +这件事是怎么做到对调用者完全透明、又零运行期开销的?靠的是 C++ 模板里一个经典手法——标签派发(tag dispatch)。每个迭代器类型都带一个「类别标签」,通过 `std::iterator_traits::iterator_category` 能取到;`std::distance` 内部按这个标签选不同的函数重载:random_access 的版本走减法,其余版本走循环。这个选择发生在**编译期**,运行期根本不存在「先判断一下 category」这步开销。`std::advance`、`std::iter_swap` 等一堆设施都是这么干活的。 + +::: warning 容易踩的点 +在 `list`、`set` 这种非随机访问容器上,凡是内部依赖「算距离」或「跳 n 步」的操作(比如 `std::distance`、`std::advance(it, n)`),都是 O(n),别当成常数时间随手用,数据量一大就现原形。 +::: + +## C++20 视角:把「要求」从文档搬进类型系统 + +最后说一句 C++20 带来的变化。在 concept 出现之前,算法对迭代器的要求只能写在文档里("requires ForwardIterator"),编译器不检查——你要是传了个达不到要求的迭代器,报出来的是一长串模板实例化错误,很难看出到底哪不对。 + +C++20 用 concept 把这些要求搬进了类型系统:`std::forward_iterator`、`std::random_access_iterator` 这些本身就是编译期可判定的谓词。前面那张表之所以能用代码打出来,正因为 concept 把「文档里的要求」变成了「编译期就能查的事实」。我们甚至能在自己的代码里直接 `static_assert(std::random_access_iterator);` 卡住模板参数,传错类型在调用点就报错,信息清楚得多——上面那个 `print_row` 模板,其实就是在用 concept 给迭代器「量等级」。 + +## 小结 + +我们从头到尾把迭代器和它的 category 串了一遍,几条关键结论收一下: + +- 迭代器是指针用法的泛化,是容器和算法之间那层统一接口;算法只认迭代器,不认具体容器。 +- 迭代器分强弱等级(category):input → forward → bidirectional → random_access → contiguous(C++20 最强),由数据结构本身决定。 +- category 决定两件事:能用哪些泛型算法(达不到要求就编译失败),以及某些操作的复杂度(靠编译期标签派发,零运行期开销)。 +- 两个高频坑:`std::sort` 要 random_access,`list` 用不了(改用 `list::sort()`);`std::distance` / `std::advance` 在非随机访问容器上是 O(n)。 + +下一篇我们会接着讲迭代器适配器(`reverse_iterator`、`insert_iterator` 这些),看怎么用现成工具把迭代器「改造」出新的行为。 + +## 参考资源 + +- [cppreference: Iterator library](https://en.cppreference.com/w/cpp/iterator) —— 迭代器总览与 category 定义 +- [cppreference: std::iterator_traits](https://en.cppreference.com/w/cpp/iterator/iterator_traits) —— `iterator_category` 与标签派发的基石 +- [cppreference: std::distance](https://en.cppreference.com/w/cpp/iterator/distance) —— 复杂度随 category 变化的官方说明 +- [cppreference: std::contiguous_iterator (C++20)](https://en.cppreference.com/w/cpp/iterator#Iterator_concepts) —— C++20 iterator concepts 与最强一类 contiguous diff --git a/documents/vol3-standard-library/index.md b/documents/vol3-standard-library/index.md index e5b0d4865..5551a4dab 100644 --- a/documents/vol3-standard-library/index.md +++ b/documents/vol3-standard-library/index.md @@ -32,6 +32,12 @@ tags: 自定义分配器 +## 迭代器与算法 + + + 迭代器基础与 category + + ## 字符串与文本 diff --git a/scripts/cppref_card_generator.py b/scripts/cppref_card_generator.py index 673c5fac5..0c82ee4ab 100644 --- a/scripts/cppref_card_generator.py +++ b/scripts/cppref_card_generator.py @@ -48,7 +48,7 @@ DEFAULT_CACHE_DIR = SCRIPT_DIR / "cppref_cache" DEFAULT_MANIFEST = SCRIPT_DIR / "cppref_manifest.json" TEMPLATE_PATH = None # 模板已内联,见 REFERENCE_CARD_TEMPLATE -WRITING_STYLE_PATH = PROJECT_ROOT / ".claude" / "writting_style.md" +WRITING_STYLE_PATH = PROJECT_ROOT / ".claude" / "style" / "writing-style.md" REFERENCE_CARD_TEMPLATE = """\ --- diff --git a/todo/013-vol4-advanced-topics.md b/todo/013-vol4-advanced-topics.md index c11c9a8ee..157dc594d 100644 --- a/todo/013-vol4-advanced-topics.md +++ b/todo/013-vol4-advanced-topics.md @@ -53,7 +53,7 @@ estimated_effort: epic ### M1: 协程文章风格对齐 ✅ 已确认 - **依据**:两篇协程文章使用第一人称博客风格("笔者""你怎么知道我"),与项目教程风格不一致。且与卷五 Ch06 有内容重叠。 -- **决策**:保留在卷四,定位为"C++20 协程 API 初探"。重写为教程风格(按 `writting_style.md`),在文章开头或小结处添加引导: +- **决策**:保留在卷四,定位为"C++20 协程 API 初探"。重写为教程风格(按 `.claude/style/writing-style.md`),在文章开头或小结处添加引导: > 本文介绍 C++20 协程的语言机制与基础 API。如需了解协程在并发/异步 I/O 中的完整应用,请前往卷五 · 异步 I/O 与协程。 - **交付物**:两篇文章重写 + 交叉链接引导。 - **与卷五的关系**:卷四 = 语言机制视角(co_await/co_yield/co_return 编译器变换、协程帧、promise_type 接口);卷五 = 并发/异步应用视角(调度器、事件循环、Echo Server)。两层覆盖,互不替代。