第二阶段第6周

函数定义、参数与返回值

这周你会从“把代码写出来”升级为“把代码组织好”。函数可以让代码复用、可测试、可维护,是后续项目开发的关键基础。

学完这周,你能自己设计函数签名,知道什么时候传参数、什么时候返回结果。

C++ 第二阶段第6周课程封面

6.1 函数的价值

函数不是为了“看起来高级”,而是为了减少重复和降低复杂度。一个函数尽量只做一件事,主流程会更清楚。

本周达成:你能写出带参数和返回值的函数,并把原本堆在 `main` 的逻辑拆分出来。

6.2 函数签名先行

先写函数名、参数、返回类型,再写函数体。这样能先想清楚“输入是什么、输出是什么”。

double calcAverage(int a, int b, int c) {
    return (a + b + c) / 3.0;
}

6.3 参数与返回值设计

参数负责输入,返回值负责输出结果。尽量避免在函数内直接依赖外部全局状态,这样函数更容易复用和测试。

int maxOfThree(int a, int b, int c) {
    int m = a;
    if (b > m) m = b;
    if (c > m) m = c;
    return m;
}

6.4 函数调试思路

6.5 深度讲解:函数设计的四个关键问题

函数设计看似简单,真正拉开差距的是“设计前思考”。你在写函数前至少要回答四个问题:这个函数解决什么问题?输入是什么?输出是什么?失败时怎么处理?如果这四个问题不清楚,函数通常会出现职责混乱、参数混乱、返回值不稳定等问题。很多同学会写出一个“万能函数”,既读输入、又计算、又打印、还修改全局变量,短期能跑,长期极难维护。正确做法是把职责拆开:读输入是一个函数,计算是一个函数,展示是一个函数。

函数命名也是设计的一部分。建议使用“动词 + 对象”的模式,例如 `loadScores`、`calcAverage`、`printReport`。看到名字就知道行为,调试时几乎不需要猜。如果函数名太抽象(如 `processData`、`doTask`),通常说明函数职责也不清晰。命名不是表面工作,而是帮助你思考边界和责任。你可以给自己一个小规则:如果函数名无法在 10 秒内解释清楚这个函数做什么,那就重新命名或重新拆分。

参数数量要克制。参数过多会显著增加调用错误概率,尤其在参数类型相似时很容易传错位置。比如 `(int a, int b, int c, int d)` 这类签名一旦顺序错,编译器未必报错但结果会错。解决方法是:把相关参数封装成结构体,或者拆成更小函数。初学阶段建议一个函数控制在 1 到 3 个核心参数,超过 4 个就考虑是否需要重构。

6.6 参数传递与返回值策略

参数传递方式会影响性能和安全性。值传递最直观,但会发生拷贝;引用传递可以避免拷贝,但要谨慎修改外部数据;`const &` 是只读大对象时最常见、最稳妥的选择。你不需要一次掌握所有细节,但要形成选择意识:小而简单的数据可值传,大容器优先 `const &`。一旦你开始处理 `vector`、`map`、`string` 等对象,这个习惯会非常重要。

返回值方面,优先让函数返回“完成任务所需的最小结果”。不要依赖“顺便修改外部变量”来传递结果,这会让函数行为变得隐蔽。比如计算平均分就返回一个 `double`;验证是否合法就返回 `bool`。如果函数可能失败,可以返回状态值并在调用方处理。写程序时请尽量把“业务逻辑”和“展示输出”分开:计算函数返回结果,打印函数只负责展示。这样你后续做单元测试会轻松很多。

bool parseScore(int input, int& output) {
    if (input < 0 || input > 100) return false;
    output = input;
    return true;
}

double calcAverage(const 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();
}

6.7 函数调试与重构流程

推荐你使用“先单测函数,再整合主流程”的策略。先给每个函数准备最小测试:正常值、边界值、异常值。函数单独通过后,再放进主流程。这样一旦结果异常,你可以快速判断是函数本身问题还是调用问题。很多同学调试效率低,是因为一开始就在完整系统里排查,问题范围太大。把问题切小,效率会立刻提升。

重构时可以遵循三步:第一步,复制旧函数并命名为新版本;第二步,小步改动并每步运行;第三步,验证新旧行为一致再删除旧版本。这个流程能避免“改着改着全崩”。此外,给关键函数写简短注释(输入、输出、边界假设)也很有价值。不是写长篇文档,而是写能让未来的你三秒读懂函数目的的说明。

函数能力是第二阶段最重要的分水岭。你能把功能写出来,是“会做题”;你能把功能拆成可复用函数并稳定迭代,才是“会开发”。第6周请把精力放在设计质量上,不要只追求“跑通一次”。

6.8 课堂问答与进阶练习

问:函数越多越好吗? 不是。函数数量不是目标,职责清晰才是目标。一个 60 行但职责单一的函数,比 6 个互相耦合的小函数更好维护。判断标准是:这个函数能不能用一句话说清楚它做什么;如果不能,优先拆分职责,而不是机械追求“函数数量”。

问:我能不能在函数里直接 `cout` 结果,省得返回值? 可以,但要看场景。若函数核心是“计算”,建议返回值;若函数核心是“展示”,可以 `cout`。把计算与展示分开,后续做测试和复用会更轻松。比如你要把结果写入文件或发送到网络时,返回值方案几乎总是更灵活。

问:为什么我调用函数时总传错参数顺序? 说明函数签名不够友好,或者参数含义不够清楚。你可以用两种方式改进:第一,给参数起更明确的名字;第二,把强相关参数封装成结构体,减少位置错误。调用错误并不丢人,它是提醒你改进接口设计的信号。

进阶练习建议:制作“战斗回合计算模块”。拆分为 `calcDamage`、`isCriticalHit`、`applyDefense`、`printBattleLog` 四个函数,要求: 第一,计算函数不直接输出,由展示函数统一输出; 第二,所有函数都写明输入和返回值含义; 第三,至少准备 10 组测试,覆盖高攻低防、低攻高防、暴击触发、边界值等场景。 完成后再做一步重构:把常量(如暴击倍率)抽成命名常量,验证结果保持一致。

你还可以做“函数替换实验”:先用一段长逻辑实现同一功能,再用函数模块化重写,比较两种版本在可读性、调试速度、出错率上的差异。这个实验会让你直观体会为什么工程项目几乎都离不开函数设计。

6.9 本周练习与自测清单

本周练习

  1. 实现 `calcAverage`、`calcMax`、`calcMin`、`isPass` 四个函数。
  2. 用这些函数拼出一个完整成绩分析程序。
  3. 提交至少 8 组函数测试记录。

自测清单

补充建议:每次提交前随机抽 2 个函数做“盲读测试”,看不看函数体只看签名和命名,能否说出作用。若说不清,优先改接口再改实现。

返回 C++ 第二阶段