第二阶段第7周

函数拆分、作用域与复用

这一周重点是把程序拆开后仍然可维护。你会练函数拆分、变量作用域和引用传参,让代码从“能跑”进阶到“能长期维护”。

学完这周,你能判断什么时候用局部变量,什么时候该用引用参数,以及怎么避免作用域冲突。

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

7.1 函数拆分策略

先列主流程,再按职责拆函数:输入、处理、输出。一个函数尽量只做一件事,这样改动需求时不容易牵连全局。

本周达成:你能把长流程拆成职责明确的函数模块,并保证模块间接口一致。

7.2 变量作用域

局部变量只在对应函数或代码块内有效。作用域混乱会导致“变量未定义”或“误用旧值”问题。

void demo() {
    int x = 10;
    if (x > 5) {
        int y = x * 2;
        cout << y << "\n";
    }
    // cout << y; // y 在这里不可用
}

7.3 引用传参与 const 引用

需要修改外部变量时用引用参数 `&`,只读场景优先 `const &`,这样既清晰又高效。

void addBonus(int& score, int bonus) {
    score += bonus;
}

int calcTotal(const vector<int>& scores) {
    int sum = 0;
    for (int s : scores) sum += s;
    return sum;
}

7.4 深度讲解:作用域错误为什么总在重构后爆发

很多同学会发现:代码刚写完时能跑,拆函数后反而报错。常见原因就是作用域理解不完整。变量在函数里定义,只在该函数有效;变量在 `if` 或 `for` 代码块里定义,只在那个块内有效。重构时你把代码移动位置后,原来的变量可能已经超出作用域,导致“未定义标识符”或“读到错误旧值”。这不是你写得差,而是说明你正在进入更真实的工程阶段:代码位置变化会影响可见性。

解决思路是先画“数据流图”:这个变量在哪里创建?谁负责更新?谁负责读取?什么时候失效?只要这四个问题清楚,作用域问题基本都能提前避免。你可以把“创建变量”的位置尽量靠近使用点,减少生命周期;也要避免把大量无关变量放在函数最上面,这会增加误用风险。局部变量并不是越少越好,而是“有效范围越小越安全”。

另一个高频坑是重名变量(shadowing)。例如函数外有 `int score`,函数内又定义了 `int score`,你以为在修改同一个值,实际上改的是另一个局部变量。为了避免这种隐蔽错误,建议在重构阶段使用更明确命名,例如 `currentScore`、`inputScore`、`totalScore`。命名清晰后,你的大脑负担会明显减轻。

7.5 深度讲解:引用传参与副作用控制

引用传参 `&` 很强大,因为它允许函数直接修改外部变量。但“强大”也意味着“更容易出副作用”。副作用指的是:调用者看起来只是调用了函数,却不清楚函数悄悄改了哪些数据。副作用过多会让程序行为变得难以预测。建议你把“会修改外部数据”的函数在命名上明确表达出来,比如 `updateScore`、`appendTask`,让调用者一眼有心理预期。

对于只读场景,优先使用 `const &`。这样既能避免拷贝,又能防止误改数据。例如 `void printTasks(const vector<Task>& tasks)` 明确告诉读者:这个函数只读不改。养成这个习惯后,你会自动形成“接口契约”思维:函数输入输出边界是清楚且可信的。后续多人协作时,这种契约思维比写代码速度更重要。

当你不确定是否该用引用时,可以先用值传递写通逻辑,再评估性能和修改需求。如果函数确实需要把结果回传给调用方,再改为引用参数。这样做能降低一次性设计过度带来的复杂度。学习阶段重点不是“写出最炫的语法”,而是写出行为可预测、可解释的代码。

void markDoneById(vector<Task>& tasks, int id) {
    for (Task& t : tasks) {
        if (t.id == id) {
            t.done = true;
            return;
        }
    }
}

void printTasks(const vector<Task>& tasks) {
    for (const Task& t : tasks) {
        cout << t.id << " " << t.title << " " << t.done << "\n";
    }
}

7.6 重构实战:从长函数到模块流程

本周建议你做一次完整重构演练。假设你有一个 150 行的任务管理程序,先不要急着“美化代码”,先做结构拆分:菜单展示、输入处理、业务逻辑、结果输出分别独立。然后逐个替换旧流程,每替换一步就运行一次测试。这个节奏叫“小步重构”,它能让你始终保持可运行状态,出错时也能迅速回退。

重构时最容易忽视的是“行为一致性”。你改完结构后,功能是否与旧版本完全一致?最稳妥的办法是准备固定测试集,重构前后都跑一遍,比较输出。只要结果一致,你就能确认本次重构是安全的。请记住:重构的目标是提升结构质量,不是偷偷改变业务规则。很多项目线上事故都来自“重构顺手改了逻辑却没测试”。

当你完成一次高质量重构,你会明显感到两个变化:第一,新增功能不再牵一发而动全身;第二,调试速度变快,因为每个函数边界清晰。第7周的意义就在这里:你开始真正具备“维护代码”的能力,而不只是“写一段能跑的代码”。

7.7 课堂问答与进阶练习

问:重构时我最怕“改坏原功能”,怎么办? 用“冻结行为”策略:先记录旧版本输入输出,再开始重构。每重构一小步,就跑同一组测试对比结果。只要结果一致,说明你在改结构而不是改业务。这个方法比凭感觉安全得多,也更符合真实团队开发流程。

问:什么时候应该把变量提升到函数外? 只有在确实需要跨函数共享、并且共享范围可控时才考虑。默认优先局部变量,让生命周期尽量短。范围越小,副作用越少;范围越大,排错越难。你可以把“能局部就不全局”当成默认策略,除非有明确理由打破。

问:引用传参是不是一定比值传递好? 不一定。对小类型(如 `int`、`double`)值传递已经足够,代码也更直观。对大对象(如 `vector`、`string`)才优先考虑 `const &`。如果函数要修改外部对象,再用非常量引用。选择依据是“语义 + 成本”,而不是统一套模板。

进阶练习建议:把“任务清单程序 v2”继续升级为“任务清单程序 v3”。要求: 第一,新增“按关键字搜索任务”; 第二,新增“导出已完成任务列表”; 第三,所有数据修改操作都通过独立函数完成; 第四,写出模块边界说明:哪些函数属于输入层、哪些属于业务层、哪些属于输出层。 这个练习能强迫你真正执行分层,而不是口头分层。

完成后做一次复盘答辩:用 3 分钟讲清你的模块结构、数据流向、测试策略和已知限制。能讲清楚,才说明你真的掌握了重构思维。第7周的目标不是“代码行数更多”,而是“系统结构更清楚”。

7.8 本周练习与自测清单

本周练习

  1. 把旧程序重构成至少 5 个函数。
  2. 至少 2 个函数使用 `const &` 传参。
  3. 补一份函数职责说明。

自测清单

补充建议:把本周重构过程整理成“改动日志”,至少记录 5 个关键决策点。未来你会发现,这份日志比代码本身更能帮助你快速回忆设计思路。

返回 C++ 第二阶段