第一阶段第3周

控制流、函数设计与首次发布流程

第三周的关键任务,是把前两周零散能力整合成“可交付项目能力”。你将从单段练习代码,升级为一个可运行、可测试、可打包、可说明的小项目。

学完这周,你不只会写功能,还能解释设计、验证结果、记录版本,这些都是开发者真正会用到的能力。

C++ 第3周课程封面

3.1 控制流:把逻辑路径写得清楚且可维护

控制流是程序行为的骨架。你可以把它理解为“程序在不同条件下走哪条路”。第三周我们重点复盘三类结构:顺序、分支、循环。顺序负责稳定执行,分支负责条件决策,循环负责重复处理。单个结构并不难,难的是组合之后仍然清晰。

初学者常见问题是“分支太深”。例如 if 里面套 if 再套 if,最后自己都看不懂。解决思路是:先提前处理异常情况,尽早 return;再处理主流程。这样可以把嵌套层数降下来。另一个问题是循环边界不明确,例如 `for (int i = 0; i <= n; i++)` 经常多跑一次。建议先写出“应执行几次”,再确定边界表达式。

第三周开始,建议你在写循环前先回答两个问题:循环变量是什么、退出条件是什么。只要这两个点不清楚,就先别急着编码。很多 bug 都来自“退出条件没想清楚”。

for (int i = 0; i < scores.size(); i++) {
    if (scores[i] < 0 || scores[i] > 100) {
        cout << "发现非法成绩,索引: " << i << "\n";
        continue;
    }
    // 正常处理逻辑
}

3.2 函数设计:从“能跑”走向“可复用”

函数是第三周的主角。函数的价值不只是减少重复代码,更重要的是把“一个明确职责”封装起来。我们建议采用“单一职责”原则:一个函数只解决一个核心问题。比如 `calcAverage` 只算平均值,不负责打印;`printReport` 只负责输出,不负责计算。职责清晰后,测试和维护都会简单很多。

参数设计也很关键。若函数只读取数据,尽量使用 `const` 引用参数,避免不必要拷贝;若需要修改外部数据,再考虑引用参数。返回值方面,优先返回“完成任务所需的结果”,不要通过全局变量传递状态,这会让依赖关系变得混乱。你可以用“函数签名先行”来训练自己:先写函数名、参数、返回值,再写函数体。

命名建议使用“动词 + 对象”,例如 `loadStudents`、`sortScores`、`saveReport`。好的命名本身就是文档。你未来阅读代码时,看到函数名就应知道它做什么。反之,如果函数名很抽象,通常意味着职责也不清晰。

double calcAverage(const std::vector<int>& scores) {
    if (scores.empty()) return 0.0;
    long long sum = 0;
    for (int s : scores) sum += s;
    return static_cast<double>(sum) / scores.size();
}

void printSummary(const std::vector<int>& scores) {
    std::cout << "人数: " << scores.size() << "\n";
    std::cout << "平均分: " << calcAverage(scores) << "\n";
}

3.3 模块整合:从多个函数到一个完整程序

把函数写好只是第一步,真正挑战是把它们组织成可运行流程。你可以用“主流程清单法”:先列出程序入口、菜单展示、输入读取、业务处理、结果输出、退出收尾六个步骤,再把每步映射到对应函数。这样 `main` 会非常清晰,不会变成“上百行混合逻辑”。

模块整合时,最常见问题是“接口不一致”。例如函数声明参数是 `const vector<int>&`,定义时写成 `vector<int>`,或者返回类型不一致。这类问题在链接阶段才暴露,定位成本较高。建议每次修改函数签名后,先全局搜索同名函数,确认声明与定义完全一致。

另一个常见问题是输入处理与业务逻辑耦合过深,导致一改输入格式就要改大量代码。我们建议把输入解析独立成函数,业务逻辑只接收“已经解析好的数据”。这个分层会显著提高稳定性,也更容易测试。

3.4 本周项目:学习任务管理器(MVP)

本周统一项目是“学习任务管理器”。你需要实现一个控制台菜单程序,支持新增任务、查看任务、标记完成、删除任务、保存到文件。该项目覆盖了控制流、函数拆分、文件读写、错误处理四个核心能力,是第一阶段的综合练习模板。

项目建议拆分:`task_model` 负责数据结构,`task_service` 负责业务操作,`storage` 负责读写文件,`ui` 负责菜单交互。`main` 只负责循环调用。这样后续扩展“按优先级排序”或“按关键词搜索”时,只改对应模块即可。

struct Task {
    int id;
    std::string title;
    bool done;
};

void showMenu() {
    std::cout << "\n=== 学习任务管理器 ===\n";
    std::cout << "1. 新增任务\n2. 查看任务\n3. 标记完成\n4. 删除任务\n5. 保存并退出\n";
}

int main() {
    int choice = 0;
    while (true) {
        showMenu();
        std::cin >> choice;
        // 根据 choice 分派到不同函数
        if (choice == 5) break;
    }
    return 0;
}
最小可交付标准:功能可完整演示,异常输入不崩溃,README 可指导他人复现。

3.5 测试策略:别只测“理想输入”

第三周开始,我们将测试分为三层:功能测试、边界测试、异常测试。功能测试验证主流程可用;边界测试验证极值输入是否合理;异常测试验证错误输入是否可控。比如任务管理器中,边界场景包括“空列表下删除任务”“超长标题输入”“不存在的任务 id”。如果这些场景不测,发布后就容易出现崩溃。

建议使用“测试清单表”,每个用例记录输入、预期输出、实际输出、是否通过。这样做的好处是:你可以快速定位回归问题,也能向自己和同伴清晰展示你做了哪些验证。测试不是形式,它是质量证据。

当测试失败时,不要立即改代码。先确认预期是否正确,再确认输入是否复现了同一问题,最后再定位函数。这个顺序能避免“改来改去越改越乱”。

3.6 首次发布流程:从本地可跑到可交付

发布并不是把可执行文件丢给别人那么简单。一个合格的发布包应包含:可执行文件、README、版本说明、测试摘要。README 至少要写清楚“如何运行、输入格式、已知限制、联系方式”。如果别人拿到包无法在 3 分钟内启动,你的交付就不算完成。

版本号建议采用 `v0.1.0` 形式。新增功能可以升次版本,修复 bug 升补丁版本。第三周我们不追求复杂流程,但要建立“每次发布都有版本记录”的习惯。这样一旦出现问题,可以快速回滚到上一个稳定版本。

发布前最后一步是回归测试:至少跑一遍核心用例,确认没有因为最后改动引入新问题。很多事故都发生在“临发布前改一行”而未回归。请把回归测试当作发布硬门槛。

3.7 高频问题清单与修复建议

3.8 本周练习与自测清单

本周练习

  1. 完成“学习任务管理器”MVP,并保证五项功能可演示。
  2. 至少拆分 4 个函数,且每个函数职责清晰。
  3. 提供 8 组测试样例(含边界与异常)。
  4. 提交发布包:可执行文件、README、版本说明、测试摘要。

自测清单

第三周是第一阶段的关键收束点。做完这周,你不只是“会写 C++ 语句”,而是已经具备把需求做成可交付程序的完整路径。进入第二阶段后,你会发现语法和算法学习更有抓手,节奏也更稳。

返回 C++ 第一阶段