{
  "schemaVersion": 2,
  "description": "CStudy C17/C++17 正式课程",
  "courses": [
    {
      "id": "c",
      "title": "C 语言系统课",
      "phase": "c",
      "description": "从程序运行模型到文件与模块化的完整 C17 路径。",
      "chapters": [
        {
          "id": "c1",
          "title": "开发环境和第一个程序",
          "orderIndex": 1,
          "status": "complete",
          "summary": "系统掌握开发环境和第一个程序，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c1l1",
              "title": "编译与链接",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释编译与链接",
                "能在小程序中正确应用编译与链接"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握编译与链接。",
              "knowledgePoints": [
                {
                  "title": "编译与链接",
                  "content": "C 源文件依次经过预处理、编译、汇编和链接，最终得到由操作系统加载的可执行文件。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把编译与链接理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住编译与链接的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">开发环境和第一个程序</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">1.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    printf(\"Hello, CStudy!\\n\");\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c1l1e1",
                  "type": "choice",
                  "question": "关于“编译与链接”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "C 源文件依次经过预处理、编译、汇编和链接，最终得到由操作系统加载的可执行文件。",
                  "difficulty": "easy",
                  "knowledge_point": "编译与链接",
                  "required": true,
                  "options": [
                    "C 源文件依次经过预处理、编译、汇编和链接，最终得到由操作系统加载的可执行文件。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c1l1e2",
                  "type": "true_false",
                  "question": "判断：学习“编译与链接”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "编译与链接",
                  "required": true
                },
                {
                  "id": "c1l1e3",
                  "type": "program",
                  "question": "输出 CStudy 和 Hello C!，每项单独一行。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "编译与链接",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "",
                      "expected_stdout": "CStudy\nHello C!\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "",
                      "expected_stdout": "CStudy\nHello C!\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c1l2",
              "title": "程序入口",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释程序入口",
                "能在小程序中正确应用程序入口"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握程序入口。",
              "knowledgePoints": [
                {
                  "title": "程序入口",
                  "content": "main 是程序入口；int 返回类型和 return 0 共同表达程序正常结束。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把程序入口理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住程序入口的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">开发环境和第一个程序</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">1.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    printf(\"Hello, CStudy!\\n\");\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c1l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "程序入口",
                  "explanation": "核心术语是 程序入口。",
                  "difficulty": "easy",
                  "knowledge_point": "程序入口",
                  "required": true
                },
                {
                  "id": "c1l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "Hello, CStudy!",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "程序入口",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) {\n    printf(\"Hello, CStudy!\\n\");\n    return 0;\n}\n"
                },
                {
                  "id": "c1l2e3",
                  "type": "program",
                  "question": "输出 Learning C17。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "程序入口",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "",
                      "expected_stdout": "Learning C17\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "",
                      "expected_stdout": "Learning C17\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c1l3",
              "title": "标准输出",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释标准输出",
                "能在小程序中正确应用标准输出"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握标准输出。",
              "knowledgePoints": [
                {
                  "title": "标准输出",
                  "content": "stdio.h 声明 printf，字符串中的转义序列用于表示换行、制表等不可直接书写的字符。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把标准输出理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住标准输出的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">开发环境和第一个程序</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">1.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    printf(\"Hello, CStudy!\\n\");\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c1l3e1",
                  "type": "choice",
                  "question": "应用“标准输出”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "标准输出",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c1l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "标准输出",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "开发环境和第一个程序任务舱",
            "brief": "综合运用本章知识完成“输出两行问候语”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nint main(void) {\n    printf(\"Hello, CStudy!\\n\");\n    return 0;\n}\n",
            "expected_output": "Hello, CStudy!\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c2",
          "title": "变量与数据类型",
          "orderIndex": 2,
          "status": "complete",
          "summary": "系统掌握变量与数据类型，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [
            "c1"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c2l1",
              "title": "变量与初始化",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释变量与初始化",
                "能在小程序中正确应用变量与初始化"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握变量与初始化。",
              "knowledgePoints": [
                {
                  "title": "变量与初始化",
                  "content": "变量是带类型的命名存储位置；初始化让对象从创建时就处于确定状态。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把变量与初始化理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住变量与初始化的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">变量与数据类型</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">2.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int age = 20;\n    double score = 95.5;\n    printf(\"age=%d score=%.1f\\n\", age, score);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c2l1e1",
                  "type": "choice",
                  "question": "关于“变量与初始化”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "变量是带类型的命名存储位置；初始化让对象从创建时就处于确定状态。",
                  "difficulty": "easy",
                  "knowledge_point": "变量与初始化",
                  "required": true,
                  "options": [
                    "变量是带类型的命名存储位置；初始化让对象从创建时就处于确定状态。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c2l1e2",
                  "type": "true_false",
                  "question": "判断：学习“变量与初始化”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "变量与初始化",
                  "required": true
                },
                {
                  "id": "c2l1e3",
                  "type": "program",
                  "question": "读入摄氏温度，输出华氏温度并保留一位小数。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "变量与初始化",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "0\n",
                      "expected_stdout": "32.0\n"
                    },
                    {
                      "stdin": "100\n",
                      "expected_stdout": "212.0\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "0\n",
                      "expected_stdout": "32.0\n"
                    },
                    {
                      "stdin": "100\n",
                      "expected_stdout": "212.0\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c2l2",
              "title": "整数与浮点数",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释整数与浮点数",
                "能在小程序中正确应用整数与浮点数"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握整数与浮点数。",
              "knowledgePoints": [
                {
                  "title": "整数与浮点数",
                  "content": "整数类型适合离散数量，浮点类型近似表示实数；范围、精度和运算规则不同。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把整数与浮点数理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住整数与浮点数的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">变量与数据类型</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">2.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int age = 20;\n    double score = 95.5;\n    printf(\"age=%d score=%.1f\\n\", age, score);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c2l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "整数与浮点数",
                  "explanation": "核心术语是 整数与浮点数。",
                  "difficulty": "easy",
                  "knowledge_point": "整数与浮点数",
                  "required": true
                },
                {
                  "id": "c2l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "age=20 score=95.5",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "整数与浮点数",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) {\n    int age = 20;\n    double score = 95.5;\n    printf(\"age=%d score=%.1f\\n\", age, score);\n    return 0;\n}\n"
                },
                {
                  "id": "c2l2e3",
                  "type": "program",
                  "question": "读入三个整数，输出浮点平均值并保留两位小数。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "整数与浮点数",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "1 2 3\n",
                      "expected_stdout": "2.00\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "1 2 3\n",
                      "expected_stdout": "2.00\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c2l3",
              "title": "类型转换",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释类型转换",
                "能在小程序中正确应用类型转换"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握类型转换。",
              "knowledgePoints": [
                {
                  "title": "类型转换",
                  "content": "隐式转换可能改变符号或精度；显式转换应表达程序员已经理解这种变化。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把类型转换理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住类型转换的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">变量与数据类型</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">2.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int age = 20;\n    double score = 95.5;\n    printf(\"age=%d score=%.1f\\n\", age, score);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c2l3e1",
                  "type": "choice",
                  "question": "应用“类型转换”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "类型转换",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c2l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "类型转换",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "变量与数据类型任务舱",
            "brief": "综合运用本章知识完成“温度换算”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nint main(void) {\n    int age = 20;\n    double score = 95.5;\n    printf(\"age=%d score=%.1f\\n\", age, score);\n    return 0;\n}\n",
            "expected_output": "age=20 score=95.5\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c3",
          "title": "输入与输出",
          "orderIndex": 3,
          "status": "complete",
          "summary": "系统掌握输入与输出，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [
            "c2"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c3l1",
              "title": "格式化输出",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释格式化输出",
                "能在小程序中正确应用格式化输出"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握格式化输出。",
              "knowledgePoints": [
                {
                  "title": "格式化输出",
                  "content": "printf 的格式串描述值如何呈现，格式说明符必须与实参类型匹配。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把格式化输出理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住格式化输出的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">输入与输出</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">3.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int x;\n    if (scanf(\"%d\", &x) == 1) printf(\"value=%d\\n\", x);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c3l1e1",
                  "type": "choice",
                  "question": "关于“格式化输出”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "printf 的格式串描述值如何呈现，格式说明符必须与实参类型匹配。",
                  "difficulty": "easy",
                  "knowledge_point": "格式化输出",
                  "required": true,
                  "options": [
                    "printf 的格式串描述值如何呈现，格式说明符必须与实参类型匹配。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c3l1e2",
                  "type": "true_false",
                  "question": "判断：学习“格式化输出”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "格式化输出",
                  "required": true
                },
                {
                  "id": "c3l1e3",
                  "type": "program",
                  "question": "读入不含空格的姓名和年龄，输出 name=<姓名> age=<年龄>。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "格式化输出",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "Morn 19\n",
                      "expected_stdout": "name=Morn age=19\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "Morn 19\n",
                      "expected_stdout": "name=Morn age=19\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c3l2",
              "title": "安全输入",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释安全输入",
                "能在小程序中正确应用安全输入"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握安全输入。",
              "knowledgePoints": [
                {
                  "title": "安全输入",
                  "content": "scanf 需要变量地址并检查返回值；限制字符串宽度可以避免越界。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把安全输入理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住安全输入的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">输入与输出</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">3.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int x;\n    if (scanf(\"%d\", &x) == 1) printf(\"value=%d\\n\", x);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c3l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "安全输入",
                  "explanation": "核心术语是 安全输入。",
                  "difficulty": "easy",
                  "knowledge_point": "安全输入",
                  "required": true
                },
                {
                  "id": "c3l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "value=42",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "安全输入",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) {\n    int x;\n    if (scanf(\"%d\", &x) == 1) printf(\"value=%d\\n\", x);\n    return 0;\n}\n"
                },
                {
                  "id": "c3l2e3",
                  "type": "program",
                  "question": "读入一个 ASCII 字符，输出字符和对应十进制编码。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "安全输入",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "A\n",
                      "expected_stdout": "A 65\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "A\n",
                      "expected_stdout": "A 65\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c3l3",
              "title": "字符与整行",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释字符与整行",
                "能在小程序中正确应用字符与整行"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握字符与整行。",
              "knowledgePoints": [
                {
                  "title": "字符与整行",
                  "content": "getchar 处理单个字符，fgets 保留空格并能限制读取长度，适合整行文本。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把字符与整行理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住字符与整行的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">输入与输出</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">3.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int x;\n    if (scanf(\"%d\", &x) == 1) printf(\"value=%d\\n\", x);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c3l3e1",
                  "type": "choice",
                  "question": "应用“字符与整行”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "字符与整行",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c3l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "字符与整行",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "输入与输出任务舱",
            "brief": "综合运用本章知识完成“个人名片”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nint main(void) {\n    int x;\n    if (scanf(\"%d\", &x) == 1) printf(\"value=%d\\n\", x);\n    return 0;\n}\n",
            "expected_output": "value=42\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c4",
          "title": "运算符与表达式",
          "orderIndex": 4,
          "status": "complete",
          "summary": "系统掌握运算符与表达式，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [
            "c3"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c4l1",
              "title": "算术与余数",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释算术与余数",
                "能在小程序中正确应用算术与余数"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握算术与余数。",
              "knowledgePoints": [
                {
                  "title": "算术与余数",
                  "content": "整数除法舍弃小数部分，余数运算用于周期、奇偶和整除判断。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把算术与余数理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住算术与余数的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">运算符与表达式</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">4.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int a = 17, b = 5;\n    printf(\"%d %d\\n\", a / b, a % b);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c4l1e1",
                  "type": "choice",
                  "question": "关于“算术与余数”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "整数除法舍弃小数部分，余数运算用于周期、奇偶和整除判断。",
                  "difficulty": "easy",
                  "knowledge_point": "算术与余数",
                  "required": true,
                  "options": [
                    "整数除法舍弃小数部分，余数运算用于周期、奇偶和整除判断。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c4l1e2",
                  "type": "true_false",
                  "question": "判断：学习“算术与余数”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "算术与余数",
                  "required": true
                },
                {
                  "id": "c4l1e3",
                  "type": "program",
                  "question": "读入总秒数，输出小时、分钟、秒，格式 h m s。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "算术与余数",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "3661\n",
                      "expected_stdout": "1 1 1\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "3661\n",
                      "expected_stdout": "1 1 1\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c4l2",
              "title": "优先级与结合性",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释优先级与结合性",
                "能在小程序中正确应用优先级与结合性"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握优先级与结合性。",
              "knowledgePoints": [
                {
                  "title": "优先级与结合性",
                  "content": "优先级决定分组，括号能把意图写清楚；不要依赖难以阅读的隐式顺序。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把优先级与结合性理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住优先级与结合性的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">运算符与表达式</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">4.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int a = 17, b = 5;\n    printf(\"%d %d\\n\", a / b, a % b);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c4l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "优先级与结合性",
                  "explanation": "核心术语是 优先级与结合性。",
                  "difficulty": "easy",
                  "knowledge_point": "优先级与结合性",
                  "required": true
                },
                {
                  "id": "c4l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "3 2",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "优先级与结合性",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) {\n    int a = 17, b = 5;\n    printf(\"%d %d\\n\", a / b, a % b);\n    return 0;\n}\n"
                },
                {
                  "id": "c4l2e3",
                  "type": "program",
                  "question": "读入三位正整数，依次输出百位、十位和个位。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "优先级与结合性",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "507\n",
                      "expected_stdout": "5 0 7\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "507\n",
                      "expected_stdout": "5 0 7\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c4l3",
              "title": "自增与短路",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释自增与短路",
                "能在小程序中正确应用自增与短路"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握自增与短路。",
              "knowledgePoints": [
                {
                  "title": "自增与短路",
                  "content": "自增在复杂表达式中容易产生歧义；逻辑与、逻辑或会在结果确定后停止求值。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把自增与短路理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住自增与短路的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">运算符与表达式</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">4.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int a = 17, b = 5;\n    printf(\"%d %d\\n\", a / b, a % b);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c4l3e1",
                  "type": "choice",
                  "question": "应用“自增与短路”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "自增与短路",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c4l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "自增与短路",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "运算符与表达式任务舱",
            "brief": "综合运用本章知识完成“秒数拆分”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nint main(void) {\n    int a = 17, b = 5;\n    printf(\"%d %d\\n\", a / b, a % b);\n    return 0;\n}\n",
            "expected_output": "3 2\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c5",
          "title": "条件判断",
          "orderIndex": 5,
          "status": "complete",
          "summary": "系统掌握条件判断，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [
            "c4"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c5l1",
              "title": "if 分支",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释if 分支",
                "能在小程序中正确应用if 分支"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握if 分支。",
              "knowledgePoints": [
                {
                  "title": "if 分支",
                  "content": "if 根据布尔条件选择路径；互斥条件应使用清晰的 if/else if/else 链。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把if 分支理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住if 分支的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">条件判断</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">5.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int n = 7;\n    printf(\"%s\\n\", n % 2 ? \"odd\" : \"even\");\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c5l1e1",
                  "type": "choice",
                  "question": "关于“if 分支”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "if 根据布尔条件选择路径；互斥条件应使用清晰的 if/else if/else 链。",
                  "difficulty": "easy",
                  "knowledge_point": "if 分支",
                  "required": true,
                  "options": [
                    "if 根据布尔条件选择路径；互斥条件应使用清晰的 if/else if/else 链。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c5l1e2",
                  "type": "true_false",
                  "question": "判断：学习“if 分支”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "if 分支",
                  "required": true
                },
                {
                  "id": "c5l1e3",
                  "type": "program",
                  "question": "读入三个整数，输出最大值。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "if 分支",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "3 9 4\n",
                      "expected_stdout": "9\n"
                    },
                    {
                      "stdin": "-2 -8 -1\n",
                      "expected_stdout": "-1\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "3 9 4\n",
                      "expected_stdout": "9\n"
                    },
                    {
                      "stdin": "-2 -8 -1\n",
                      "expected_stdout": "-1\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c5l2",
              "title": "switch 选择",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释switch 选择",
                "能在小程序中正确应用switch 选择"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握switch 选择。",
              "knowledgePoints": [
                {
                  "title": "switch 选择",
                  "content": "switch 适合对单个离散值分派，break 防止无意贯穿到后续分支。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把switch 选择理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住switch 选择的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">条件判断</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">5.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int n = 7;\n    printf(\"%s\\n\", n % 2 ? \"odd\" : \"even\");\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c5l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "switch 选择",
                  "explanation": "核心术语是 switch 选择。",
                  "difficulty": "easy",
                  "knowledge_point": "switch 选择",
                  "required": true
                },
                {
                  "id": "c5l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "odd",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "switch 选择",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) {\n    int n = 7;\n    printf(\"%s\\n\", n % 2 ? \"odd\" : \"even\");\n    return 0;\n}\n"
                },
                {
                  "id": "c5l2e3",
                  "type": "program",
                  "question": "读入 0~100 分，输出 A/B/C/D/F。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "switch 选择",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "86\n",
                      "expected_stdout": "B\n"
                    },
                    {
                      "stdin": "59\n",
                      "expected_stdout": "F\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "86\n",
                      "expected_stdout": "B\n"
                    },
                    {
                      "stdin": "59\n",
                      "expected_stdout": "F\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c5l3",
              "title": "边界设计",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释边界设计",
                "能在小程序中正确应用边界设计"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握边界设计。",
              "knowledgePoints": [
                {
                  "title": "边界设计",
                  "content": "条件题最容易错在等号和区间交界，应先列出边界样例再写表达式。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把边界设计理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住边界设计的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">条件判断</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">5.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int n = 7;\n    printf(\"%s\\n\", n % 2 ? \"odd\" : \"even\");\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c5l3e1",
                  "type": "choice",
                  "question": "应用“边界设计”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "边界设计",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c5l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "边界设计",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "条件判断任务舱",
            "brief": "综合运用本章知识完成“三数最大值”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nint main(void) {\n    int n = 7;\n    printf(\"%s\\n\", n % 2 ? \"odd\" : \"even\");\n    return 0;\n}\n",
            "expected_output": "odd\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c6",
          "title": "循环",
          "orderIndex": 6,
          "status": "complete",
          "summary": "系统掌握循环，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "intermediate",
          "estimated_minutes": 120,
          "prerequisites": [
            "c5"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c6l1",
              "title": "循环三要素",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释循环三要素",
                "能在小程序中正确应用循环三要素"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握循环三要素。",
              "knowledgePoints": [
                {
                  "title": "循环三要素",
                  "content": "初始化、继续条件和状态更新共同保证循环推进；缺失任何一项都可能导致错误。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把循环三要素理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住循环三要素的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">循环</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">6.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int sum = 0;\n    for (int i = 1; i <= 5; ++i) sum += i;\n    printf(\"%d\\n\", sum);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c6l1e1",
                  "type": "choice",
                  "question": "关于“循环三要素”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "初始化、继续条件和状态更新共同保证循环推进；缺失任何一项都可能导致错误。",
                  "difficulty": "easy",
                  "knowledge_point": "循环三要素",
                  "required": true,
                  "options": [
                    "初始化、继续条件和状态更新共同保证循环推进；缺失任何一项都可能导致错误。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c6l1e2",
                  "type": "true_false",
                  "question": "判断：学习“循环三要素”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "循环三要素",
                  "required": true
                },
                {
                  "id": "c6l1e3",
                  "type": "program",
                  "question": "读入正整数 n，输出 1 到 n 的和。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "循环三要素",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "10\n",
                      "expected_stdout": "55\n"
                    },
                    {
                      "stdin": "1\n",
                      "expected_stdout": "1\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "10\n",
                      "expected_stdout": "55\n"
                    },
                    {
                      "stdin": "1\n",
                      "expected_stdout": "1\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c6l2",
              "title": "for 与 while",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释for 与 while",
                "能在小程序中正确应用for 与 while"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握for 与 while。",
              "knowledgePoints": [
                {
                  "title": "for 与 while",
                  "content": "次数明确时 for 更紧凑，依赖输入或状态终止时 while 更自然。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把for 与 while理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住for 与 while的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">循环</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">6.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int sum = 0;\n    for (int i = 1; i <= 5; ++i) sum += i;\n    printf(\"%d\\n\", sum);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c6l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "for 与 while",
                  "explanation": "核心术语是 for 与 while。",
                  "difficulty": "easy",
                  "knowledge_point": "for 与 while",
                  "required": true
                },
                {
                  "id": "c6l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "15",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "for 与 while",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) {\n    int sum = 0;\n    for (int i = 1; i <= 5; ++i) sum += i;\n    printf(\"%d\\n\", sum);\n    return 0;\n}\n"
                },
                {
                  "id": "c6l2e3",
                  "type": "program",
                  "question": "读入非负整数，输出十进制位数，0 的位数为 1。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "for 与 while",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "0\n",
                      "expected_stdout": "1\n"
                    },
                    {
                      "stdin": "12030\n",
                      "expected_stdout": "5\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "0\n",
                      "expected_stdout": "1\n"
                    },
                    {
                      "stdin": "12030\n",
                      "expected_stdout": "5\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c6l3",
              "title": "嵌套与控制",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释嵌套与控制",
                "能在小程序中正确应用嵌套与控制"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握嵌套与控制。",
              "knowledgePoints": [
                {
                  "title": "嵌套与控制",
                  "content": "break 只退出最内层循环，continue 跳过本轮剩余语句；嵌套循环要估算总次数。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把嵌套与控制理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住嵌套与控制的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">循环</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">6.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int sum = 0;\n    for (int i = 1; i <= 5; ++i) sum += i;\n    printf(\"%d\\n\", sum);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c6l3e1",
                  "type": "choice",
                  "question": "应用“嵌套与控制”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "嵌套与控制",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c6l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "嵌套与控制",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "循环任务舱",
            "brief": "综合运用本章知识完成“累加求和”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nint main(void) {\n    int sum = 0;\n    for (int i = 1; i <= 5; ++i) sum += i;\n    printf(\"%d\\n\", sum);\n    return 0;\n}\n",
            "expected_output": "15\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c7",
          "title": "数组",
          "orderIndex": 7,
          "status": "complete",
          "summary": "系统掌握数组，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "intermediate",
          "estimated_minutes": 120,
          "prerequisites": [
            "c6"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c7l1",
              "title": "连续存储",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释连续存储",
                "能在小程序中正确应用连续存储"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握连续存储。",
              "knowledgePoints": [
                {
                  "title": "连续存储",
                  "content": "数组在内存中连续保存同类型元素，下标从 0 开始，有效范围必须始终受控。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把连续存储理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住连续存储的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">数组</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">7.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int a[] = {3, 1, 4, 1, 5};\n    int max = a[0];\n    for (int i = 1; i < 5; ++i) if (a[i] > max) max = a[i];\n    printf(\"%d\\n\", max);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c7l1e1",
                  "type": "choice",
                  "question": "关于“连续存储”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "数组在内存中连续保存同类型元素，下标从 0 开始，有效范围必须始终受控。",
                  "difficulty": "easy",
                  "knowledge_point": "连续存储",
                  "required": true,
                  "options": [
                    "数组在内存中连续保存同类型元素，下标从 0 开始，有效范围必须始终受控。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c7l1e2",
                  "type": "true_false",
                  "question": "判断：学习“连续存储”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "连续存储",
                  "required": true
                },
                {
                  "id": "c7l1e3",
                  "type": "program",
                  "question": "先读 n，再读 n 个整数，输出最大值。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "连续存储",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "5\n3 1 9 2 4\n",
                      "expected_stdout": "9\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "5\n3 1 9 2 4\n",
                      "expected_stdout": "9\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c7l2",
              "title": "遍历与聚合",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释遍历与聚合",
                "能在小程序中正确应用遍历与聚合"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握遍历与聚合。",
              "knowledgePoints": [
                {
                  "title": "遍历与聚合",
                  "content": "用一个循环完成求和、最值和计数；先确定不变量再更新累计结果。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把遍历与聚合理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住遍历与聚合的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">数组</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">7.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int a[] = {3, 1, 4, 1, 5};\n    int max = a[0];\n    for (int i = 1; i < 5; ++i) if (a[i] > max) max = a[i];\n    printf(\"%d\\n\", max);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c7l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "遍历与聚合",
                  "explanation": "核心术语是 遍历与聚合。",
                  "difficulty": "easy",
                  "knowledge_point": "遍历与聚合",
                  "required": true
                },
                {
                  "id": "c7l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "5",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "遍历与聚合",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) {\n    int a[] = {3, 1, 4, 1, 5};\n    int max = a[0];\n    for (int i = 1; i < 5; ++i) if (a[i] > max) max = a[i];\n    printf(\"%d\\n\", max);\n    return 0;\n}\n"
                },
                {
                  "id": "c7l2e3",
                  "type": "program",
                  "question": "读入 n 个整数，按相反顺序输出，数字之间一个空格。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "遍历与聚合",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "4\n1 2 3 4\n",
                      "expected_stdout": "4 3 2 1\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "4\n1 2 3 4\n",
                      "expected_stdout": "4 3 2 1\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c7l3",
              "title": "二维数组",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释二维数组",
                "能在小程序中正确应用二维数组"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握二维数组。",
              "knowledgePoints": [
                {
                  "title": "二维数组",
                  "content": "二维数组按行连续存储，声明时列数决定地址计算方式。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把二维数组理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住二维数组的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">数组</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">7.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    int a[] = {3, 1, 4, 1, 5};\n    int max = a[0];\n    for (int i = 1; i < 5; ++i) if (a[i] > max) max = a[i];\n    printf(\"%d\\n\", max);\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c7l3e1",
                  "type": "choice",
                  "question": "应用“二维数组”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "二维数组",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c7l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "二维数组",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "数组任务舱",
            "brief": "综合运用本章知识完成“数组最大值”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nint main(void) {\n    int a[] = {3, 1, 4, 1, 5};\n    int max = a[0];\n    for (int i = 1; i < 5; ++i) if (a[i] > max) max = a[i];\n    printf(\"%d\\n\", max);\n    return 0;\n}\n",
            "expected_output": "5\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c8",
          "title": "字符串",
          "orderIndex": 8,
          "status": "complete",
          "summary": "系统掌握字符串，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "intermediate",
          "estimated_minutes": 120,
          "prerequisites": [
            "c7"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c8l1",
              "title": "字符数组",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释字符数组",
                "能在小程序中正确应用字符数组"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握字符数组。",
              "knowledgePoints": [
                {
                  "title": "字符数组",
                  "content": "C 字符串是以空字符 \\0 结束的字符数组；容量必须为终止符预留空间。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把字符数组理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住字符数组的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">字符串</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">8.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">8.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">8.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\n#include <string.h>\nint main(void) {\n    char s[] = \"CStudy\";\n    printf(\"%zu\\n\", strlen(s));\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c8l1e1",
                  "type": "choice",
                  "question": "关于“字符数组”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "C 字符串是以空字符 \\0 结束的字符数组；容量必须为终止符预留空间。",
                  "difficulty": "easy",
                  "knowledge_point": "字符数组",
                  "required": true,
                  "options": [
                    "C 字符串是以空字符 \\0 结束的字符数组；容量必须为终止符预留空间。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c8l1e2",
                  "type": "true_false",
                  "question": "判断：学习“字符数组”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "字符数组",
                  "required": true
                },
                {
                  "id": "c8l1e3",
                  "type": "program",
                  "question": "读入一行英文文本，忽略大小写统计元音字母数量。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "字符数组",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "Hello World\n",
                      "expected_stdout": "3\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "Hello World\n",
                      "expected_stdout": "3\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c8l2",
              "title": "标准字符串函数",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释标准字符串函数",
                "能在小程序中正确应用标准字符串函数"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握标准字符串函数。",
              "knowledgePoints": [
                {
                  "title": "标准字符串函数",
                  "content": "strlen、strcmp、strcpy 等函数依赖正确终止的字符串，目标缓冲区容量由调用者保证。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把标准字符串函数理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住标准字符串函数的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">字符串</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">8.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">8.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">8.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\n#include <string.h>\nint main(void) {\n    char s[] = \"CStudy\";\n    printf(\"%zu\\n\", strlen(s));\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c8l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "标准字符串函数",
                  "explanation": "核心术语是 标准字符串函数。",
                  "difficulty": "easy",
                  "knowledge_point": "标准字符串函数",
                  "required": true
                },
                {
                  "id": "c8l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "6",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "标准字符串函数",
                  "required": true,
                  "code": "#include <stdio.h>\n#include <string.h>\nint main(void) {\n    char s[] = \"CStudy\";\n    printf(\"%zu\\n\", strlen(s));\n    return 0;\n}\n"
                },
                {
                  "id": "c8l2e3",
                  "type": "program",
                  "question": "读入一个不含空格的单词，是回文输出 YES，否则输出 NO。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "标准字符串函数",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "level\n",
                      "expected_stdout": "YES\n"
                    },
                    {
                      "stdin": "study\n",
                      "expected_stdout": "NO\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "level\n",
                      "expected_stdout": "YES\n"
                    },
                    {
                      "stdin": "study\n",
                      "expected_stdout": "NO\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c8l3",
              "title": "安全文本处理",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释安全文本处理",
                "能在小程序中正确应用安全文本处理"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握安全文本处理。",
              "knowledgePoints": [
                {
                  "title": "安全文本处理",
                  "content": "优先使用 fgets 限制输入长度，移除尾部换行时先检查字符串是否为空。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把安全文本处理理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住安全文本处理的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">字符串</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">8.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">8.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">8.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\n#include <string.h>\nint main(void) {\n    char s[] = \"CStudy\";\n    printf(\"%zu\\n\", strlen(s));\n    return 0;\n}\n",
              "exercises": [
                {
                  "id": "c8l3e1",
                  "type": "choice",
                  "question": "应用“安全文本处理”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "安全文本处理",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c8l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "安全文本处理",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "字符串任务舱",
            "brief": "综合运用本章知识完成“统计元音”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\n#include <string.h>\nint main(void) {\n    char s[] = \"CStudy\";\n    printf(\"%zu\\n\", strlen(s));\n    return 0;\n}\n",
            "expected_output": "6\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c9",
          "title": "函数",
          "orderIndex": 9,
          "status": "complete",
          "summary": "系统掌握函数，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "intermediate",
          "estimated_minutes": 120,
          "prerequisites": [
            "c8"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c9l1",
              "title": "声明与调用",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释声明与调用",
                "能在小程序中正确应用声明与调用"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握声明与调用。",
              "knowledgePoints": [
                {
                  "title": "声明与调用",
                  "content": "函数声明给出接口，定义提供实现；参数和返回值构成调用者与函数之间的契约。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把声明与调用理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住声明与调用的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">函数</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">9.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">9.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">9.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint square(int x) { return x * x; }\nint main(void) { printf(\"%d\\n\", square(6)); return 0; }\n",
              "exercises": [
                {
                  "id": "c9l1e1",
                  "type": "choice",
                  "question": "关于“声明与调用”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "函数声明给出接口，定义提供实现；参数和返回值构成调用者与函数之间的契约。",
                  "difficulty": "easy",
                  "knowledge_point": "声明与调用",
                  "required": true,
                  "options": [
                    "函数声明给出接口，定义提供实现；参数和返回值构成调用者与函数之间的契约。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c9l1e2",
                  "type": "true_false",
                  "question": "判断：学习“声明与调用”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "声明与调用",
                  "required": true
                },
                {
                  "id": "c9l1e3",
                  "type": "program",
                  "question": "实现 gcd 并读入两个正整数，输出最大公约数。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "声明与调用",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "18 24\n",
                      "expected_stdout": "6\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "18 24\n",
                      "expected_stdout": "6\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c9l2",
              "title": "值传递",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释值传递",
                "能在小程序中正确应用值传递"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握值传递。",
              "knowledgePoints": [
                {
                  "title": "值传递",
                  "content": "C 默认复制实参值，修改形参不会改变调用者变量；需要修改时传入地址。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把值传递理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住值传递的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">函数</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">9.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">9.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">9.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint square(int x) { return x * x; }\nint main(void) { printf(\"%d\\n\", square(6)); return 0; }\n",
              "exercises": [
                {
                  "id": "c9l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "值传递",
                  "explanation": "核心术语是 值传递。",
                  "difficulty": "easy",
                  "knowledge_point": "值传递",
                  "required": true
                },
                {
                  "id": "c9l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "36",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "值传递",
                  "required": true,
                  "code": "#include <stdio.h>\nint square(int x) { return x * x; }\nint main(void) { printf(\"%d\\n\", square(6)); return 0; }\n"
                },
                {
                  "id": "c9l2e3",
                  "type": "program",
                  "question": "实现 is_prime，读入整数，是素数输出 YES，否则输出 NO。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "值传递",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "29\n",
                      "expected_stdout": "YES\n"
                    },
                    {
                      "stdin": "1\n",
                      "expected_stdout": "NO\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "29\n",
                      "expected_stdout": "YES\n"
                    },
                    {
                      "stdin": "1\n",
                      "expected_stdout": "NO\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c9l3",
              "title": "作用域与递归",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释作用域与递归",
                "能在小程序中正确应用作用域与递归"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握作用域与递归。",
              "knowledgePoints": [
                {
                  "title": "作用域与递归",
                  "content": "局部变量只在所在块有效；递归必须有基线条件，并保证每次调用接近基线。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把作用域与递归理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住作用域与递归的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">函数</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">9.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">9.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">9.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint square(int x) { return x * x; }\nint main(void) { printf(\"%d\\n\", square(6)); return 0; }\n",
              "exercises": [
                {
                  "id": "c9l3e1",
                  "type": "choice",
                  "question": "应用“作用域与递归”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "作用域与递归",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c9l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "作用域与递归",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "函数任务舱",
            "brief": "综合运用本章知识完成“最大公约数函数”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nint square(int x) { return x * x; }\nint main(void) { printf(\"%d\\n\", square(6)); return 0; }\n",
            "expected_output": "36\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c10",
          "title": "指针与动态内存",
          "orderIndex": 10,
          "status": "complete",
          "summary": "系统掌握指针与动态内存，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "intermediate",
          "estimated_minutes": 120,
          "prerequisites": [
            "c9"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c10l1",
              "title": "地址与解引用",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释地址与解引用",
                "能在小程序中正确应用地址与解引用"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握地址与解引用。",
              "knowledgePoints": [
                {
                  "title": "地址与解引用",
                  "content": "指针保存对象地址，& 获取地址，* 访问该地址指向的对象；类型决定访问宽度和解释方式。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把地址与解引用理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住地址与解引用的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">指针与动态内存</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">10.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">10.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">10.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nvoid swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }\nint main(void) { int a=2,b=7; swap(&a,&b); printf(\"%d %d\\n\",a,b); return 0; }\n",
              "exercises": [
                {
                  "id": "c10l1e1",
                  "type": "choice",
                  "question": "关于“地址与解引用”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "指针保存对象地址，& 获取地址，* 访问该地址指向的对象；类型决定访问宽度和解释方式。",
                  "difficulty": "easy",
                  "knowledge_point": "地址与解引用",
                  "required": true,
                  "options": [
                    "指针保存对象地址，& 获取地址，* 访问该地址指向的对象；类型决定访问宽度和解释方式。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c10l1e2",
                  "type": "true_false",
                  "question": "判断：学习“地址与解引用”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "地址与解引用",
                  "required": true
                },
                {
                  "id": "c10l1e3",
                  "type": "program",
                  "question": "实现 swap，通过指针交换两个输入整数并输出。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "地址与解引用",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "4 9\n",
                      "expected_stdout": "9 4\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "4 9\n",
                      "expected_stdout": "9 4\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c10l2",
              "title": "指针与数组",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释指针与数组",
                "能在小程序中正确应用指针与数组"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握指针与数组。",
              "knowledgePoints": [
                {
                  "title": "指针与数组",
                  "content": "数组表达式常转换为首元素指针，但数组大小信息不会随指针传递。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把指针与数组理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住指针与数组的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">指针与动态内存</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">10.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">10.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">10.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nvoid swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }\nint main(void) { int a=2,b=7; swap(&a,&b); printf(\"%d %d\\n\",a,b); return 0; }\n",
              "exercises": [
                {
                  "id": "c10l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "指针与数组",
                  "explanation": "核心术语是 指针与数组。",
                  "difficulty": "easy",
                  "knowledge_point": "指针与数组",
                  "required": true
                },
                {
                  "id": "c10l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "7 2",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "指针与数组",
                  "required": true,
                  "code": "#include <stdio.h>\nvoid swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }\nint main(void) { int a=2,b=7; swap(&a,&b); printf(\"%d %d\\n\",a,b); return 0; }\n"
                },
                {
                  "id": "c10l2e3",
                  "type": "program",
                  "question": "读入 n 和 n 个整数，动态分配数组并输出平均值，保留两位。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "指针与数组",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "4\n1 2 3 4\n",
                      "expected_stdout": "2.50\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "4\n1 2 3 4\n",
                      "expected_stdout": "2.50\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c10l3",
              "title": "动态内存",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释动态内存",
                "能在小程序中正确应用动态内存"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握动态内存。",
              "knowledgePoints": [
                {
                  "title": "动态内存",
                  "content": "malloc 分配原始内存，使用前检查空指针；所有权结束后 free，并避免继续访问悬空指针。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把动态内存理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住动态内存的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">指针与动态内存</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">10.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">10.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">10.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nvoid swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }\nint main(void) { int a=2,b=7; swap(&a,&b); printf(\"%d %d\\n\",a,b); return 0; }\n",
              "exercises": [
                {
                  "id": "c10l3e1",
                  "type": "choice",
                  "question": "应用“动态内存”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "动态内存",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c10l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "动态内存",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "指针与动态内存任务舱",
            "brief": "综合运用本章知识完成“指针交换”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nvoid swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }\nint main(void) { int a=2,b=7; swap(&a,&b); printf(\"%d %d\\n\",a,b); return 0; }\n",
            "expected_output": "7 2\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c11",
          "title": "结构体与枚举",
          "orderIndex": 11,
          "status": "complete",
          "summary": "系统掌握结构体与枚举，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "intermediate",
          "estimated_minutes": 120,
          "prerequisites": [
            "c10"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c11l1",
              "title": "结构体建模",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释结构体建模",
                "能在小程序中正确应用结构体建模"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握结构体建模。",
              "knowledgePoints": [
                {
                  "title": "结构体建模",
                  "content": "结构体把相关字段组合成一个值，成员名表达含义，适合描述现实对象。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把结构体建模理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住结构体建模的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">结构体与枚举</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">11.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">11.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">11.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\ntypedef struct { char name[16]; int score; } Student;\nint main(void) { Student s={\"Ming\",92}; printf(\"%s %d\\n\",s.name,s.score); return 0; }\n",
              "exercises": [
                {
                  "id": "c11l1e1",
                  "type": "choice",
                  "question": "关于“结构体建模”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "结构体把相关字段组合成一个值，成员名表达含义，适合描述现实对象。",
                  "difficulty": "easy",
                  "knowledge_point": "结构体建模",
                  "required": true,
                  "options": [
                    "结构体把相关字段组合成一个值，成员名表达含义，适合描述现实对象。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c11l1e2",
                  "type": "true_false",
                  "question": "判断：学习“结构体建模”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "结构体建模",
                  "required": true
                },
                {
                  "id": "c11l1e3",
                  "type": "program",
                  "question": "读入 n 个学生姓名和分数，输出平均分，保留一位。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "结构体建模",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "2\nMing 80\nHong 90\n",
                      "expected_stdout": "85.0\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "2\nMing 80\nHong 90\n",
                      "expected_stdout": "85.0\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c11l2",
              "title": "结构体数组与指针",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释结构体数组与指针",
                "能在小程序中正确应用结构体数组与指针"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握结构体数组与指针。",
              "knowledgePoints": [
                {
                  "title": "结构体数组与指针",
                  "content": "结构体可以构成数组；指针访问成员使用 ->，普通对象使用 .。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把结构体数组与指针理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住结构体数组与指针的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">结构体与枚举</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">11.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">11.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">11.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\ntypedef struct { char name[16]; int score; } Student;\nint main(void) { Student s={\"Ming\",92}; printf(\"%s %d\\n\",s.name,s.score); return 0; }\n",
              "exercises": [
                {
                  "id": "c11l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "结构体数组与指针",
                  "explanation": "核心术语是 结构体数组与指针。",
                  "difficulty": "easy",
                  "knowledge_point": "结构体数组与指针",
                  "required": true
                },
                {
                  "id": "c11l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "Ming 92",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "结构体数组与指针",
                  "required": true,
                  "code": "#include <stdio.h>\ntypedef struct { char name[16]; int score; } Student;\nint main(void) { Student s={\"Ming\",92}; printf(\"%s %d\\n\",s.name,s.score); return 0; }\n"
                },
                {
                  "id": "c11l2e3",
                  "type": "program",
                  "question": "读入 n 个姓名和分数，输出最高分学生姓名。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "结构体数组与指针",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "3\nA 70\nB 95\nC 88\n",
                      "expected_stdout": "B\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "3\nA 70\nB 95\nC 88\n",
                      "expected_stdout": "B\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c11l3",
              "title": "枚举与类型别名",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释枚举与类型别名",
                "能在小程序中正确应用枚举与类型别名"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握枚举与类型别名。",
              "knowledgePoints": [
                {
                  "title": "枚举与类型别名",
                  "content": "枚举为离散状态命名，typedef 简化类型书写，但不会创造新的运行时类型。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把枚举与类型别名理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住枚举与类型别名的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">结构体与枚举</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">11.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">11.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">11.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\ntypedef struct { char name[16]; int score; } Student;\nint main(void) { Student s={\"Ming\",92}; printf(\"%s %d\\n\",s.name,s.score); return 0; }\n",
              "exercises": [
                {
                  "id": "c11l3e1",
                  "type": "choice",
                  "question": "应用“枚举与类型别名”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "枚举与类型别名",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c11l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "枚举与类型别名",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "结构体与枚举任务舱",
            "brief": "综合运用本章知识完成“学生平均分”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\ntypedef struct { char name[16]; int score; } Student;\nint main(void) { Student s={\"Ming\",92}; printf(\"%s %d\\n\",s.name,s.score); return 0; }\n",
            "expected_output": "Ming 92\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "c12",
          "title": "文件与模块化",
          "orderIndex": 12,
          "status": "complete",
          "summary": "系统掌握文件与模块化，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "intermediate",
          "estimated_minutes": 120,
          "prerequisites": [
            "c11"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "c12l1",
              "title": "文件流",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释文件流",
                "能在小程序中正确应用文件流"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握文件流。",
              "knowledgePoints": [
                {
                  "title": "文件流",
                  "content": "fopen 建立文件流，模式决定读写语义；每一步操作都应检查失败并最终 fclose。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把文件流理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住文件流的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">文件与模块化</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">12.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">12.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">12.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    FILE *f = tmpfile();\n    if (!f) return 1;\n    fputs(\"CStudy\", f); rewind(f);\n    char s[16]; fgets(s, sizeof s, f); puts(s); fclose(f); return 0;\n}\n",
              "exercises": [
                {
                  "id": "c12l1e1",
                  "type": "choice",
                  "question": "关于“文件流”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "fopen 建立文件流，模式决定读写语义；每一步操作都应检查失败并最终 fclose。",
                  "difficulty": "easy",
                  "knowledge_point": "文件流",
                  "required": true,
                  "options": [
                    "fopen 建立文件流，模式决定读写语义；每一步操作都应检查失败并最终 fclose。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "c12l1e2",
                  "type": "true_false",
                  "question": "判断：学习“文件流”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "文件流",
                  "required": true
                },
                {
                  "id": "c12l1e3",
                  "type": "program",
                  "question": "读入一行文本，输出其中非空白字符数量。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "文件流",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "C Study 17\n",
                      "expected_stdout": "8\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "C Study 17\n",
                      "expected_stdout": "8\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c12l2",
              "title": "文本与二进制",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释文本与二进制",
                "能在小程序中正确应用文本与二进制"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握文本与二进制。",
              "knowledgePoints": [
                {
                  "title": "文本与二进制",
                  "content": "文本文件便于阅读，二进制文件忠实保存字节；跨平台格式需要显式定义编码和布局。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把文本与二进制理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住文本与二进制的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">文件与模块化</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">12.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">12.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">12.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    FILE *f = tmpfile();\n    if (!f) return 1;\n    fputs(\"CStudy\", f); rewind(f);\n    char s[16]; fgets(s, sizeof s, f); puts(s); fclose(f); return 0;\n}\n",
              "exercises": [
                {
                  "id": "c12l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "文本与二进制",
                  "explanation": "核心术语是 文本与二进制。",
                  "difficulty": "easy",
                  "knowledge_point": "文本与二进制",
                  "required": true
                },
                {
                  "id": "c12l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "CStudy",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "文本与二进制",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) {\n    FILE *f = tmpfile();\n    if (!f) return 1;\n    fputs(\"CStudy\", f); rewind(f);\n    char s[16]; fgets(s, sizeof s, f); puts(s); fclose(f); return 0;\n}\n"
                },
                {
                  "id": "c12l2e3",
                  "type": "program",
                  "question": "读入 key=value，分别输出 key 和 value，中间一个空格。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "文本与二进制",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "theme=dark\n",
                      "expected_stdout": "theme dark\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "theme=dark\n",
                      "expected_stdout": "theme dark\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "c12l3",
              "title": "头文件与多文件",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释头文件与多文件",
                "能在小程序中正确应用头文件与多文件"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握头文件与多文件。",
              "knowledgePoints": [
                {
                  "title": "头文件与多文件",
                  "content": "头文件声明公共接口，源文件提供实现；包含保护防止同一声明重复展开。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把头文件与多文件理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住头文件与多文件的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">文件与模块化</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">12.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">12.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">12.3</text></svg>"
                }
              ],
              "exampleCode": "#include <stdio.h>\nint main(void) {\n    FILE *f = tmpfile();\n    if (!f) return 1;\n    fputs(\"CStudy\", f); rewind(f);\n    char s[16]; fgets(s, sizeof s, f); puts(s); fclose(f); return 0;\n}\n",
              "exercises": [
                {
                  "id": "c12l3e1",
                  "type": "choice",
                  "question": "应用“头文件与多文件”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "头文件与多文件",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "c12l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "头文件与多文件",
                  "required": true,
                  "code": "#include <stdio.h>\nint main(void) { printf(\"check\\n\") return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "文件与模块化任务舱",
            "brief": "综合运用本章知识完成“文本统计”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <stdio.h>\nint main(void) {\n    FILE *f = tmpfile();\n    if (!f) return 1;\n    fputs(\"CStudy\", f); rewind(f);\n    char s[16]; fgets(s, sizeof s, f); puts(s); fclose(f); return 0;\n}\n",
            "expected_output": "CStudy\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        }
      ]
    },
    {
      "id": "cpp",
      "title": "现代 C++ 入门",
      "phase": "cpp",
      "description": "在 C 基础上学习资源管理、面向对象与 STL 的 C++17 路径。",
      "chapters": [
        {
          "id": "cpp1",
          "title": "引用与现代 C++ 基础",
          "orderIndex": 1,
          "status": "complete",
          "summary": "系统掌握引用与现代 C++ 基础，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "cpp1l1",
              "title": "引用",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释引用",
                "能在小程序中正确应用引用"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握引用。",
              "knowledgePoints": [
                {
                  "title": "引用",
                  "content": "引用是已有对象的别名，必须初始化；引用参数能表达函数需要操作原对象。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把引用理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住引用的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">引用与现代 C++ 基础</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">1.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nvoid swap_value(int& a,int& b){int t=a;a=b;b=t;}\nint main(){int a=3,b=8;swap_value(a,b);std::cout<<a<<\" \"<<b<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp1l1e1",
                  "type": "choice",
                  "question": "关于“引用”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "引用是已有对象的别名，必须初始化；引用参数能表达函数需要操作原对象。",
                  "difficulty": "easy",
                  "knowledge_point": "引用",
                  "required": true,
                  "options": [
                    "引用是已有对象的别名，必须初始化；引用参数能表达函数需要操作原对象。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "cpp1l1e2",
                  "type": "true_false",
                  "question": "判断：学习“引用”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "引用",
                  "required": true
                },
                {
                  "id": "cpp1l1e3",
                  "type": "program",
                  "question": "读入两个整数，使用引用参数交换后输出。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "引用",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "2 5\n",
                      "expected_stdout": "5 2\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "2 5\n",
                      "expected_stdout": "5 2\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp1l2",
              "title": "const 正确性",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释const 正确性",
                "能在小程序中正确应用const 正确性"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握const 正确性。",
              "knowledgePoints": [
                {
                  "title": "const 正确性",
                  "content": "const 引用避免复制并承诺不修改对象，是只读参数的常用形式。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把const 正确性理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住const 正确性的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">引用与现代 C++ 基础</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">1.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nvoid swap_value(int& a,int& b){int t=a;a=b;b=t;}\nint main(){int a=3,b=8;swap_value(a,b);std::cout<<a<<\" \"<<b<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp1l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "const 正确性",
                  "explanation": "核心术语是 const 正确性。",
                  "difficulty": "easy",
                  "knowledge_point": "const 正确性",
                  "required": true
                },
                {
                  "id": "cpp1l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "8 3",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "const 正确性",
                  "required": true,
                  "code": "#include <iostream>\nvoid swap_value(int& a,int& b){int t=a;a=b;b=t;}\nint main(){int a=3,b=8;swap_value(a,b);std::cout<<a<<\" \"<<b<<\"\\n\";}\n"
                },
                {
                  "id": "cpp1l2e3",
                  "type": "program",
                  "question": "读入一个单词，通过 const string& 参数返回长度。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "const 正确性",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "CStudy\n",
                      "expected_stdout": "6\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "CStudy\n",
                      "expected_stdout": "6\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp1l3",
              "title": "作用域与自动类型",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释作用域与自动类型",
                "能在小程序中正确应用作用域与自动类型"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握作用域与自动类型。",
              "knowledgePoints": [
                {
                  "title": "作用域与自动类型",
                  "content": "命名空间组织名称，auto 从初始化式推导类型，但不应隐藏重要语义。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把作用域与自动类型理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住作用域与自动类型的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">引用与现代 C++ 基础</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">1.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">1.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nvoid swap_value(int& a,int& b){int t=a;a=b;b=t;}\nint main(){int a=3,b=8;swap_value(a,b);std::cout<<a<<\" \"<<b<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp1l3e1",
                  "type": "choice",
                  "question": "应用“作用域与自动类型”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "作用域与自动类型",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "cpp1l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "作用域与自动类型",
                  "required": true,
                  "code": "#include <iostream>\nint main(){ std::cout << \"check\" << \"\\n\" return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "引用与现代 C++ 基础任务舱",
            "brief": "综合运用本章知识完成“引用交换”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <iostream>\nvoid swap_value(int& a,int& b){int t=a;a=b;b=t;}\nint main(){int a=3,b=8;swap_value(a,b);std::cout<<a<<\" \"<<b<<\"\\n\";}\n",
            "expected_output": "8 3\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "cpp2",
          "title": "string 与 vector",
          "orderIndex": 2,
          "status": "complete",
          "summary": "系统掌握string 与 vector，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [
            "cpp1"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "cpp2l1",
              "title": "std::string",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释std::string",
                "能在小程序中正确应用std::string"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握std::string。",
              "knowledgePoints": [
                {
                  "title": "std::string",
                  "content": "string 管理动态字符序列，提供长度、拼接、查找和安全迭代能力。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把std::string理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住std::string的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">string 与 vector</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">2.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{1,2,3,4};int s=0;for(int x:v)s+=x;std::cout<<s<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp2l1e1",
                  "type": "choice",
                  "question": "关于“std::string”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "string 管理动态字符序列，提供长度、拼接、查找和安全迭代能力。",
                  "difficulty": "easy",
                  "knowledge_point": "std::string",
                  "required": true,
                  "options": [
                    "string 管理动态字符序列，提供长度、拼接、查找和安全迭代能力。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "cpp2l1e2",
                  "type": "true_false",
                  "question": "判断：学习“std::string”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "std::string",
                  "required": true
                },
                {
                  "id": "cpp2l1e3",
                  "type": "program",
                  "question": "读入 n 个整数存入 vector，输出总和。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "std::string",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "4\n1 2 3 4\n",
                      "expected_stdout": "10\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "4\n1 2 3 4\n",
                      "expected_stdout": "10\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp2l2",
              "title": "std::vector",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释std::vector",
                "能在小程序中正确应用std::vector"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握std::vector。",
              "knowledgePoints": [
                {
                  "title": "std::vector",
                  "content": "vector 管理连续动态数组，size 表示元素数，push_back 在末尾追加。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把std::vector理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住std::vector的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">string 与 vector</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">2.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{1,2,3,4};int s=0;for(int x:v)s+=x;std::cout<<s<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp2l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "std::vector",
                  "explanation": "核心术语是 std::vector。",
                  "difficulty": "easy",
                  "knowledge_point": "std::vector",
                  "required": true
                },
                {
                  "id": "cpp2l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "10",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "std::vector",
                  "required": true,
                  "code": "#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{1,2,3,4};int s=0;for(int x:v)s+=x;std::cout<<s<<\"\\n\";}\n"
                },
                {
                  "id": "cpp2l2e3",
                  "type": "program",
                  "question": "读入一行字符串，输出反转结果。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "std::vector",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "CStudy\n",
                      "expected_stdout": "ydutSC\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "CStudy\n",
                      "expected_stdout": "ydutSC\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp2l3",
              "title": "范围循环与迭代器",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释范围循环与迭代器",
                "能在小程序中正确应用范围循环与迭代器"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握范围循环与迭代器。",
              "knowledgePoints": [
                {
                  "title": "范围循环与迭代器",
                  "content": "范围 for 适合遍历全部元素；需要位置或算法组合时使用迭代器。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把范围循环与迭代器理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住范围循环与迭代器的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">string 与 vector</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">2.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">2.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{1,2,3,4};int s=0;for(int x:v)s+=x;std::cout<<s<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp2l3e1",
                  "type": "choice",
                  "question": "应用“范围循环与迭代器”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "范围循环与迭代器",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "cpp2l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "范围循环与迭代器",
                  "required": true,
                  "code": "#include <iostream>\nint main(){ std::cout << \"check\" << \"\\n\" return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "string 与 vector任务舱",
            "brief": "综合运用本章知识完成“vector 求和”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{1,2,3,4};int s=0;for(int x:v)s+=x;std::cout<<s<<\"\\n\";}\n",
            "expected_output": "10\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "cpp3",
          "title": "类与对象",
          "orderIndex": 3,
          "status": "complete",
          "summary": "系统掌握类与对象，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [
            "cpp2"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "cpp3l1",
              "title": "封装",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释封装",
                "能在小程序中正确应用封装"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握封装。",
              "knowledgePoints": [
                {
                  "title": "封装",
                  "content": "类把状态和维护状态的操作组合在一起，private 成员阻止外部破坏不变量。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把封装理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住封装的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">类与对象</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">3.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Rectangle{int w,h;public:Rectangle(int a,int b):w(a),h(b){} int area()const{return w*h;}};\nint main(){Rectangle r(3,4);std::cout<<r.area()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp3l1e1",
                  "type": "choice",
                  "question": "关于“封装”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "类把状态和维护状态的操作组合在一起，private 成员阻止外部破坏不变量。",
                  "difficulty": "easy",
                  "knowledge_point": "封装",
                  "required": true,
                  "options": [
                    "类把状态和维护状态的操作组合在一起，private 成员阻止外部破坏不变量。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "cpp3l1e2",
                  "type": "true_false",
                  "question": "判断：学习“封装”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "封装",
                  "required": true
                },
                {
                  "id": "cpp3l1e3",
                  "type": "program",
                  "question": "实现 Rectangle，读入宽高并通过成员函数输出面积。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "封装",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "3 7\n",
                      "expected_stdout": "21\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "3 7\n",
                      "expected_stdout": "21\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp3l2",
              "title": "成员函数",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释成员函数",
                "能在小程序中正确应用成员函数"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握成员函数。",
              "knowledgePoints": [
                {
                  "title": "成员函数",
                  "content": "成员函数通过 this 访问当前对象；const 成员函数承诺不修改可观察状态。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把成员函数理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住成员函数的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">类与对象</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">3.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Rectangle{int w,h;public:Rectangle(int a,int b):w(a),h(b){} int area()const{return w*h;}};\nint main(){Rectangle r(3,4);std::cout<<r.area()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp3l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "成员函数",
                  "explanation": "核心术语是 成员函数。",
                  "difficulty": "easy",
                  "knowledge_point": "成员函数",
                  "required": true
                },
                {
                  "id": "cpp3l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "12",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "成员函数",
                  "required": true,
                  "code": "#include <iostream>\nclass Rectangle{int w,h;public:Rectangle(int a,int b):w(a),h(b){} int area()const{return w*h;}};\nint main(){Rectangle r(3,4);std::cout<<r.area()<<\"\\n\";}\n"
                },
                {
                  "id": "cpp3l2e3",
                  "type": "program",
                  "question": "实现 Counter，读入初值和增加次数，输出最终值。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "成员函数",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "5 3\n",
                      "expected_stdout": "8\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "5 3\n",
                      "expected_stdout": "8\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp3l3",
              "title": "接口设计",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释接口设计",
                "能在小程序中正确应用接口设计"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握接口设计。",
              "knowledgePoints": [
                {
                  "title": "接口设计",
                  "content": "公开接口应小而稳定，命名表达意图，并在构造后保持对象始终有效。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把接口设计理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住接口设计的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">类与对象</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">3.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">3.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Rectangle{int w,h;public:Rectangle(int a,int b):w(a),h(b){} int area()const{return w*h;}};\nint main(){Rectangle r(3,4);std::cout<<r.area()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp3l3e1",
                  "type": "choice",
                  "question": "应用“接口设计”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "接口设计",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "cpp3l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "接口设计",
                  "required": true,
                  "code": "#include <iostream>\nint main(){ std::cout << \"check\" << \"\\n\" return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "类与对象任务舱",
            "brief": "综合运用本章知识完成“矩形类”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <iostream>\nclass Rectangle{int w,h;public:Rectangle(int a,int b):w(a),h(b){} int area()const{return w*h;}};\nint main(){Rectangle r(3,4);std::cout<<r.area()<<\"\\n\";}\n",
            "expected_output": "12\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "cpp4",
          "title": "构造、析构与资源管理",
          "orderIndex": 4,
          "status": "complete",
          "summary": "系统掌握构造、析构与资源管理，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [
            "cpp3"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "cpp4l1",
              "title": "构造函数",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释构造函数",
                "能在小程序中正确应用构造函数"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握构造函数。",
              "knowledgePoints": [
                {
                  "title": "构造函数",
                  "content": "构造函数建立对象不变量，成员初始化列表直接初始化成员，优于先默认构造再赋值。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把构造函数理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住构造函数的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">构造、析构与资源管理</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">4.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Point{int x,y;public:Point(int a,int b):x(a),y(b){} int sum()const{return x+y;}};\nint main(){Point p(4,6);std::cout<<p.sum()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp4l1e1",
                  "type": "choice",
                  "question": "关于“构造函数”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "构造函数建立对象不变量，成员初始化列表直接初始化成员，优于先默认构造再赋值。",
                  "difficulty": "easy",
                  "knowledge_point": "构造函数",
                  "required": true,
                  "options": [
                    "构造函数建立对象不变量，成员初始化列表直接初始化成员，优于先默认构造再赋值。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "cpp4l1e2",
                  "type": "true_false",
                  "question": "判断：学习“构造函数”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "构造函数",
                  "required": true
                },
                {
                  "id": "cpp4l1e3",
                  "type": "program",
                  "question": "实现 Product 构造函数，读入单价和数量，输出总价并保留两位。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "构造函数",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "2.5 4\n",
                      "expected_stdout": "10.00\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "2.5 4\n",
                      "expected_stdout": "10.00\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp4l2",
              "title": "析构与 RAII",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释析构与 RAII",
                "能在小程序中正确应用析构与 RAII"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握析构与 RAII。",
              "knowledgePoints": [
                {
                  "title": "析构与 RAII",
                  "content": "析构函数释放对象拥有的资源；把资源寿命绑定到对象寿命可覆盖异常和提前返回。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把析构与 RAII理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住析构与 RAII的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">构造、析构与资源管理</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">4.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Point{int x,y;public:Point(int a,int b):x(a),y(b){} int sum()const{return x+y;}};\nint main(){Point p(4,6);std::cout<<p.sum()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp4l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "析构与 RAII",
                  "explanation": "核心术语是 析构与 RAII。",
                  "difficulty": "easy",
                  "knowledge_point": "析构与 RAII",
                  "required": true
                },
                {
                  "id": "cpp4l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "10",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "析构与 RAII",
                  "required": true,
                  "code": "#include <iostream>\nclass Point{int x,y;public:Point(int a,int b):x(a),y(b){} int sum()const{return x+y;}};\nint main(){Point p(4,6);std::cout<<p.sum()<<\"\\n\";}\n"
                },
                {
                  "id": "cpp4l2e3",
                  "type": "program",
                  "question": "构造 Interval，自动规范左右端点并输出长度。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "析构与 RAII",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "9 3\n",
                      "expected_stdout": "6\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "9 3\n",
                      "expected_stdout": "6\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp4l3",
              "title": "拷贝与移动",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释拷贝与移动",
                "能在小程序中正确应用拷贝与移动"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握拷贝与移动。",
              "knowledgePoints": [
                {
                  "title": "拷贝与移动",
                  "content": "拷贝产生独立副本，移动转移资源所有权；优先使用标准容器避免手写资源管理。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把拷贝与移动理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住拷贝与移动的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">构造、析构与资源管理</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">4.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">4.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Point{int x,y;public:Point(int a,int b):x(a),y(b){} int sum()const{return x+y;}};\nint main(){Point p(4,6);std::cout<<p.sum()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp4l3e1",
                  "type": "choice",
                  "question": "应用“拷贝与移动”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "拷贝与移动",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "cpp4l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "拷贝与移动",
                  "required": true,
                  "code": "#include <iostream>\nint main(){ std::cout << \"check\" << \"\\n\" return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "构造、析构与资源管理任务舱",
            "brief": "综合运用本章知识完成“商品类”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <iostream>\nclass Point{int x,y;public:Point(int a,int b):x(a),y(b){} int sum()const{return x+y;}};\nint main(){Point p(4,6);std::cout<<p.sum()<<\"\\n\";}\n",
            "expected_output": "10\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "cpp5",
          "title": "继承与组合",
          "orderIndex": 5,
          "status": "complete",
          "summary": "系统掌握继承与组合，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "beginner",
          "estimated_minutes": 120,
          "prerequisites": [
            "cpp4"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "cpp5l1",
              "title": "is-a 与 has-a",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释is-a 与 has-a",
                "能在小程序中正确应用is-a 与 has-a"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握is-a 与 has-a。",
              "knowledgePoints": [
                {
                  "title": "is-a 与 has-a",
                  "content": "继承表达可替换的 is-a，组合表达拥有的 has-a；优先组合减少不必要耦合。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把is-a 与 has-a理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住is-a 与 has-a的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">继承与组合</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">5.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Shape{public:int width;Shape(int w):width(w){}};\nclass Square:public Shape{public:Square(int w):Shape(w){} int area()const{return width*width;}};\nint main(){Square s(5);std::cout<<s.area()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp5l1e1",
                  "type": "choice",
                  "question": "关于“is-a 与 has-a”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "继承表达可替换的 is-a，组合表达拥有的 has-a；优先组合减少不必要耦合。",
                  "difficulty": "easy",
                  "knowledge_point": "is-a 与 has-a",
                  "required": true,
                  "options": [
                    "继承表达可替换的 is-a，组合表达拥有的 has-a；优先组合减少不必要耦合。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "cpp5l1e2",
                  "type": "true_false",
                  "question": "判断：学习“is-a 与 has-a”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "is-a 与 has-a",
                  "required": true
                },
                {
                  "id": "cpp5l1e3",
                  "type": "program",
                  "question": "用继承表示 Manager，读入基础工资和奖金，输出总收入。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "is-a 与 has-a",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "5000 1200\n",
                      "expected_stdout": "6200\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "5000 1200\n",
                      "expected_stdout": "6200\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp5l2",
              "title": "访问控制",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释访问控制",
                "能在小程序中正确应用访问控制"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握访问控制。",
              "knowledgePoints": [
                {
                  "title": "访问控制",
                  "content": "public 继承保留公共接口，protected 仅供派生实现使用，不应成为共享全局状态。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把访问控制理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住访问控制的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">继承与组合</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">5.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Shape{public:int width;Shape(int w):width(w){}};\nclass Square:public Shape{public:Square(int w):Shape(w){} int area()const{return width*width;}};\nint main(){Square s(5);std::cout<<s.area()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp5l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "访问控制",
                  "explanation": "核心术语是 访问控制。",
                  "difficulty": "easy",
                  "knowledge_point": "访问控制",
                  "required": true
                },
                {
                  "id": "cpp5l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "25",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "访问控制",
                  "required": true,
                  "code": "#include <iostream>\nclass Shape{public:int width;Shape(int w):width(w){}};\nclass Square:public Shape{public:Square(int w):Shape(w){} int area()const{return width*width;}};\nint main(){Square s(5);std::cout<<s.area()<<\"\\n\";}\n"
                },
                {
                  "id": "cpp5l2e3",
                  "type": "program",
                  "question": "Circle 组合 Point 圆心，读入半径并输出面积，保留两位。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "访问控制",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "2\n",
                      "expected_stdout": "12.57\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "2\n",
                      "expected_stdout": "12.57\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp5l3",
              "title": "构造顺序",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释构造顺序",
                "能在小程序中正确应用构造顺序"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握构造顺序。",
              "knowledgePoints": [
                {
                  "title": "构造顺序",
                  "content": "基类先于派生类构造，成员按声明顺序构造；析构顺序与之相反。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把构造顺序理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住构造顺序的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">继承与组合</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">5.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">5.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Shape{public:int width;Shape(int w):width(w){}};\nclass Square:public Shape{public:Square(int w):Shape(w){} int area()const{return width*width;}};\nint main(){Square s(5);std::cout<<s.area()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp5l3e1",
                  "type": "choice",
                  "question": "应用“构造顺序”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "构造顺序",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "cpp5l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "构造顺序",
                  "required": true,
                  "code": "#include <iostream>\nint main(){ std::cout << \"check\" << \"\\n\" return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "继承与组合任务舱",
            "brief": "综合运用本章知识完成“员工与经理”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <iostream>\nclass Shape{public:int width;Shape(int w):width(w){}};\nclass Square:public Shape{public:Square(int w):Shape(w){} int area()const{return width*width;}};\nint main(){Square s(5);std::cout<<s.area()<<\"\\n\";}\n",
            "expected_output": "25\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "cpp6",
          "title": "多态与抽象接口",
          "orderIndex": 6,
          "status": "complete",
          "summary": "系统掌握多态与抽象接口，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "intermediate",
          "estimated_minutes": 120,
          "prerequisites": [
            "cpp5"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "cpp6l1",
              "title": "虚函数",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释虚函数",
                "能在小程序中正确应用虚函数"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握虚函数。",
              "knowledgePoints": [
                {
                  "title": "虚函数",
                  "content": "通过基类指针调用 virtual 函数会按对象动态类型分派，实现统一接口下的不同行为。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把虚函数理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住虚函数的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">多态与抽象接口</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">6.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Animal{public:virtual ~Animal()=default;virtual const char* sound()const=0;};\nclass Cat:public Animal{public:const char* sound()const override{return \"meow\";}};\nint main(){Cat c;Animal& a=c;std::cout<<a.sound()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp6l1e1",
                  "type": "choice",
                  "question": "关于“虚函数”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "通过基类指针调用 virtual 函数会按对象动态类型分派，实现统一接口下的不同行为。",
                  "difficulty": "easy",
                  "knowledge_point": "虚函数",
                  "required": true,
                  "options": [
                    "通过基类指针调用 virtual 函数会按对象动态类型分派，实现统一接口下的不同行为。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "cpp6l1e2",
                  "type": "true_false",
                  "question": "判断：学习“虚函数”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "虚函数",
                  "required": true
                },
                {
                  "id": "cpp6l1e3",
                  "type": "program",
                  "question": "用抽象接口实现加法和乘法，读入操作符与两数后输出结果。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "虚函数",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "+ 3 4\n",
                      "expected_stdout": "7\n"
                    },
                    {
                      "stdin": "* 3 4\n",
                      "expected_stdout": "12\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "+ 3 4\n",
                      "expected_stdout": "7\n"
                    },
                    {
                      "stdin": "* 3 4\n",
                      "expected_stdout": "12\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp6l2",
              "title": "抽象类",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释抽象类",
                "能在小程序中正确应用抽象类"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握抽象类。",
              "knowledgePoints": [
                {
                  "title": "抽象类",
                  "content": "纯虚函数定义必须由派生类完成的契约；抽象基类通常需要虚析构函数。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把抽象类理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住抽象类的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">多态与抽象接口</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">6.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Animal{public:virtual ~Animal()=default;virtual const char* sound()const=0;};\nclass Cat:public Animal{public:const char* sound()const override{return \"meow\";}};\nint main(){Cat c;Animal& a=c;std::cout<<a.sound()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp6l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "抽象类",
                  "explanation": "核心术语是 抽象类。",
                  "difficulty": "easy",
                  "knowledge_point": "抽象类",
                  "required": true
                },
                {
                  "id": "cpp6l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "meow",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "抽象类",
                  "required": true,
                  "code": "#include <iostream>\nclass Animal{public:virtual ~Animal()=default;virtual const char* sound()const=0;};\nclass Cat:public Animal{public:const char* sound()const override{return \"meow\";}};\nint main(){Cat c;Animal& a=c;std::cout<<a.sound()<<\"\\n\";}\n"
                },
                {
                  "id": "cpp6l2e3",
                  "type": "program",
                  "question": "通过 Shape 指针调用派生类面积函数，读入 square 边长并输出面积。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "抽象类",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "square 6\n",
                      "expected_stdout": "36\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "square 6\n",
                      "expected_stdout": "36\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp6l3",
              "title": "替换原则",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释替换原则",
                "能在小程序中正确应用替换原则"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握替换原则。",
              "knowledgePoints": [
                {
                  "title": "替换原则",
                  "content": "派生对象应能在基类契约下正常工作，不能加强前置条件或破坏调用者预期。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把替换原则理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住替换原则的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">多态与抽象接口</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">6.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">6.3</text></svg>"
                }
              ],
              "exampleCode": "#include <iostream>\nclass Animal{public:virtual ~Animal()=default;virtual const char* sound()const=0;};\nclass Cat:public Animal{public:const char* sound()const override{return \"meow\";}};\nint main(){Cat c;Animal& a=c;std::cout<<a.sound()<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp6l3e1",
                  "type": "choice",
                  "question": "应用“替换原则”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "替换原则",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "cpp6l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "替换原则",
                  "required": true,
                  "code": "#include <iostream>\nint main(){ std::cout << \"check\" << \"\\n\" return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "多态与抽象接口任务舱",
            "brief": "综合运用本章知识完成“多态运算器”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <iostream>\nclass Animal{public:virtual ~Animal()=default;virtual const char* sound()const=0;};\nclass Cat:public Animal{public:const char* sound()const override{return \"meow\";}};\nint main(){Cat c;Animal& a=c;std::cout<<a.sound()<<\"\\n\";}\n",
            "expected_output": "meow\n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        },
        {
          "id": "cpp7",
          "title": "STL 算法与容器",
          "orderIndex": 7,
          "status": "complete",
          "summary": "系统掌握STL 算法与容器，从概念理解过渡到可编译、可验证的小程序。",
          "difficulty": "intermediate",
          "estimated_minutes": 120,
          "prerequisites": [
            "cpp6"
          ],
          "common_mistakes": [
            "忽略输入与边界条件",
            "只看代码不亲自编译运行",
            "没有根据错误信息定位问题"
          ],
          "lessons": [
            {
              "id": "cpp7l1",
              "title": "容器选择",
              "orderIndex": 1,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释容器选择",
                "能在小程序中正确应用容器选择"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握容器选择。",
              "knowledgePoints": [
                {
                  "title": "容器选择",
                  "content": "vector 强调连续与随机访问，map 维护键值映射，set 维护唯一有序元素。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把容器选择理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住容器选择的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">STL 算法与容器</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">7.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.3</text></svg>"
                }
              ],
              "exampleCode": "#include <algorithm>\n#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{4,1,3,2};std::sort(v.begin(),v.end());for(int x:v)std::cout<<x<<\" \";std::cout<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp7l1e1",
                  "type": "choice",
                  "question": "关于“容器选择”，哪项表述最准确？",
                  "answer": "0",
                  "explanation": "vector 强调连续与随机访问，map 维护键值映射，set 维护唯一有序元素。",
                  "difficulty": "easy",
                  "knowledge_point": "容器选择",
                  "required": true,
                  "options": [
                    "vector 强调连续与随机访问，map 维护键值映射，set 维护唯一有序元素。",
                    "它只影响界面颜色",
                    "它与程序行为无关",
                    "它可以被任意省略"
                  ]
                },
                {
                  "id": "cpp7l1e2",
                  "type": "true_false",
                  "question": "判断：学习“容器选择”时必须同时关注输入边界和失败路径。",
                  "answer": "1",
                  "explanation": "正确。可靠程序不仅处理典型输入，也明确边界与失败行为。",
                  "difficulty": "easy",
                  "knowledge_point": "容器选择",
                  "required": true
                },
                {
                  "id": "cpp7l1e3",
                  "type": "program",
                  "question": "读入 n 个整数，排序并去重后输出。",
                  "answer": "judge:program_output",
                  "explanation": "先把输入、处理和输出分成三个步骤，再对照样例检查边界。",
                  "difficulty": "hard",
                  "knowledge_point": "容器选择",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "6\n3 1 3 2 1 4\n",
                      "expected_stdout": "1 2 3 4\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "6\n3 1 3 2 1 4\n",
                      "expected_stdout": "1 2 3 4\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp7l2",
              "title": "泛型算法",
              "orderIndex": 2,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释泛型算法",
                "能在小程序中正确应用泛型算法"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握泛型算法。",
              "knowledgePoints": [
                {
                  "title": "泛型算法",
                  "content": "sort、find、count_if 等算法通过迭代器工作，把遍历机制与业务条件分离。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把泛型算法理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住泛型算法的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">STL 算法与容器</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">7.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.3</text></svg>"
                }
              ],
              "exampleCode": "#include <algorithm>\n#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{4,1,3,2};std::sort(v.begin(),v.end());for(int x:v)std::cout<<x<<\" \";std::cout<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp7l2e1",
                  "type": "fill",
                  "question": "填空：本课的核心术语是“____”。",
                  "answer": "泛型算法",
                  "explanation": "核心术语是 泛型算法。",
                  "difficulty": "easy",
                  "knowledge_point": "泛型算法",
                  "required": true
                },
                {
                  "id": "cpp7l2e2",
                  "type": "output_predict",
                  "question": "阅读示例代码并写出完整输出。",
                  "answer": "1 2 3 4 ",
                  "explanation": "逐句跟踪变量与输出，不要忽略空格和换行。",
                  "difficulty": "medium",
                  "knowledge_point": "泛型算法",
                  "required": true,
                  "code": "#include <algorithm>\n#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{4,1,3,2};std::sort(v.begin(),v.end());for(int x:v)std::cout<<x<<\" \";std::cout<<\"\\n\";}\n"
                },
                {
                  "id": "cpp7l2e3",
                  "type": "program",
                  "question": "读入 n 个单词，用 map 按字典序输出单词和次数。",
                  "answer": "judge:program_output",
                  "explanation": "先用纸笔验证最小、典型和边界输入，再实现程序。",
                  "difficulty": "hard",
                  "knowledge_point": "泛型算法",
                  "required": true,
                  "test_cases": [
                    {
                      "stdin": "4\nc b c a\n",
                      "expected_stdout": "a 1\nb 1\nc 2\n"
                    }
                  ],
                  "hidden_test_cases": [
                    {
                      "stdin": "4\nc b c a\n",
                      "expected_stdout": "a 1\nb 1\nc 2\n"
                    }
                  ]
                }
              ]
            },
            {
              "id": "cpp7l3",
              "title": "Lambda",
              "orderIndex": 3,
              "estimated_minutes": 25,
              "goals": [
                "能用自己的话解释Lambda",
                "能在小程序中正确应用Lambda"
              ],
              "summary": "本课从概念、边界和实践三个层面掌握Lambda。",
              "knowledgePoints": [
                {
                  "title": "Lambda",
                  "content": "Lambda 把短小策略放在调用点，可捕获上下文；捕获范围应尽量小且生命周期清晰。 学习时先明确它解决的问题，再观察内存、类型或控制流发生了什么，最后用反例验证边界。",
                  "analogy": "可以把Lambda理解为一份明确的操作契约：输入满足条件，程序就产生可预测结果。",
                  "common_mistake": "只记住Lambda的语法而忽略返回值、范围和生命周期，是本课最常见错误。",
                  "diagram_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"720\" height=\"150\" viewBox=\"0 0 720 150\"><rect width=\"720\" height=\"150\" rx=\"18\" fill=\"#09172a\"/><path d=\"M60 75H660\" stroke=\"#284d76\" stroke-width=\"4\"/><circle cx=\"90\" cy=\"75\" r=\"25\" fill=\"#755cff\"/><circle cx=\"360\" cy=\"75\" r=\"25\" fill=\"#20d9ff\"/><circle cx=\"630\" cy=\"75\" r=\"25\" fill=\"#38d6a2\"/><text x=\"360\" y=\"28\" fill=\"#f2f7ff\" text-anchor=\"middle\" font-size=\"18\">STL 算法与容器</text><text x=\"90\" y=\"82\" fill=\"white\" text-anchor=\"middle\">7.1</text><text x=\"360\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.2</text><text x=\"630\" y=\"82\" fill=\"#03101a\" text-anchor=\"middle\">7.3</text></svg>"
                }
              ],
              "exampleCode": "#include <algorithm>\n#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{4,1,3,2};std::sort(v.begin(),v.end());for(int x:v)std::cout<<x<<\" \";std::cout<<\"\\n\";}\n",
              "exercises": [
                {
                  "id": "cpp7l3e1",
                  "type": "choice",
                  "question": "应用“Lambda”时，最优先检查什么？",
                  "answer": "0",
                  "explanation": "先验证对象生命周期、输入范围和接口约定，再优化写法。",
                  "difficulty": "medium",
                  "knowledge_point": "Lambda",
                  "required": true,
                  "options": [
                    "生命周期、边界与接口约定",
                    "按钮颜色",
                    "文件名长度",
                    "注释数量"
                  ]
                },
                {
                  "id": "cpp7l3e2",
                  "type": "compile_error",
                  "question": "下面代码不能编译，主要原因是什么？",
                  "answer": "0",
                  "explanation": "输出语句末尾缺少分号；编译器通常会在该行或下一行报告语法错误。",
                  "difficulty": "medium",
                  "knowledge_point": "Lambda",
                  "required": true,
                  "code": "#include <iostream>\nint main(){ std::cout << \"check\" << \"\\n\" return 0; }\n",
                  "options": [
                    "输出语句后缺少分号",
                    "main 不能返回 int",
                    "头文件不可使用",
                    "没有错误"
                  ]
                }
              ]
            }
          ],
          "project": {
            "title": "STL 算法与容器任务舱",
            "brief": "综合运用本章知识完成“排序去重”，并为典型与边界输入保存运行结果。",
            "starter_code": "#include <algorithm>\n#include <iostream>\n#include <vector>\nint main(){std::vector<int> v{4,1,3,2};std::sort(v.begin(),v.end());for(int x:v)std::cout<<x<<\" \";std::cout<<\"\\n\";}\n",
            "expected_output": "1 2 3 4 \n",
            "acceptance": [
              "程序可以使用 CStudy 本地编译器构建",
              "至少通过两个输入场景",
              "变量和函数命名能表达用途"
            ]
          }
        }
      ]
    }
  ]
}
