控制流、函数设计与首次发布流程
第三周的关键任务,是把前两周零散能力整合成“可交付项目能力”。你将从单段练习代码,升级为一个可运行、可测试、可打包、可说明的小项目。
学完这周,你不只会写功能,还能解释设计、验证结果、记录版本,这些都是开发者真正会用到的能力。
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;
}
3.5 测试策略:别只测“理想输入”
第三周开始,我们将测试分为三层:功能测试、边界测试、异常测试。功能测试验证主流程可用;边界测试验证极值输入是否合理;异常测试验证错误输入是否可控。比如任务管理器中,边界场景包括“空列表下删除任务”“超长标题输入”“不存在的任务 id”。如果这些场景不测,发布后就容易出现崩溃。
建议使用“测试清单表”,每个用例记录输入、预期输出、实际输出、是否通过。这样做的好处是:你可以快速定位回归问题,也能向自己和同伴清晰展示你做了哪些验证。测试不是形式,它是质量证据。
当测试失败时,不要立即改代码。先确认预期是否正确,再确认输入是否复现了同一问题,最后再定位函数。这个顺序能避免“改来改去越改越乱”。
3.6 首次发布流程:从本地可跑到可交付
发布并不是把可执行文件丢给别人那么简单。一个合格的发布包应包含:可执行文件、README、版本说明、测试摘要。README 至少要写清楚“如何运行、输入格式、已知限制、联系方式”。如果别人拿到包无法在 3 分钟内启动,你的交付就不算完成。
版本号建议采用 `v0.1.0` 形式。新增功能可以升次版本,修复 bug 升补丁版本。第三周我们不追求复杂流程,但要建立“每次发布都有版本记录”的习惯。这样一旦出现问题,可以快速回滚到上一个稳定版本。
发布前最后一步是回归测试:至少跑一遍核心用例,确认没有因为最后改动引入新问题。很多事故都发生在“临发布前改一行”而未回归。请把回归测试当作发布硬门槛。
3.7 高频问题清单与修复建议
- 问题一:菜单循环无法退出。原因多为退出条件写错或输入读取失败。修复:先打印 choice,再检查分支逻辑。
- 问题二:函数修改后编译通过但行为异常。原因多为调用方参数顺序不一致。修复:统一检查函数签名与调用位置。
- 问题三:文件保存成功但读取为空。原因可能是写入路径与读取路径不一致。修复:输出绝对路径并确认读写同一路径。
- 问题四:删除任务后索引错乱。原因通常是遍历时删除。修复:先记录目标索引,再统一删除,或使用安全遍历策略。
- 问题五:最终演示紧张导致漏讲逻辑。修复:提前准备“项目结构 -> 核心流程 -> 测试结果 -> 已知限制”的固定讲解模板。
3.8 本周练习与自测清单
本周练习
- 完成“学习任务管理器”MVP,并保证五项功能可演示。
- 至少拆分 4 个函数,且每个函数职责清晰。
- 提供 8 组测试样例(含边界与异常)。
- 提交发布包:可执行文件、README、版本说明、测试摘要。
自测清单
- 控制流清楚、函数职责单一,代码可维护。
- 功能完整且稳定,异常输入不会直接崩溃。
- 测试覆盖到正常、边界、异常,并记录修复过程。
- 发布包完整,README 清楚,别人可以快速复现。
第三周是第一阶段的关键收束点。做完这周,你不只是“会写 C++ 语句”,而是已经具备把需求做成可交付程序的完整路径。进入第二阶段后,你会发现语法和算法学习更有抓手,节奏也更稳。