C++ practical course · lesson 11

C++ 函数、参数与程序拆分

函数把大任务拆成能独立理解和测试的小步骤。本课重点不是记住括号,而是学会设计清楚的输入、输出和职责。

Easy explanation

函数像厨房里分工明确的工作台。

一家餐厅不会让同一个人同时接单、洗菜、炒菜、收款。每个工作台只负责一件事,并通过清楚的交接把结果传给下一步。程序也是如此:读取输入的函数不要顺便计算总价,计算函数不要顺便打印菜单。职责分开后,修改其中一步不会牵动整间厨房。

函数参数是交给工作台的原料,返回值是完成后的产品。值传递会复制一份原料,适合小型数字;引用让函数直接操作原对象;const 引用既避免大对象复制,又保证函数不会修改它。选择参数方式时先问:需要修改调用者的数据吗?数据复制成本高吗?

目标 1独立编写函数声明、定义和调用
目标 2区分值传递、引用与 const 引用
目标 3用返回值表达成功结果或状态
目标 4把长程序按职责拆成可测试函数
C++ 函数、参数与程序拆分通俗学习插图
每个函数都有清楚的入口、职责和出口,组合后形成完整程序。

Core concepts

先把四个核心知识点说清楚。

01

函数签名

double average(const vector<int>& values)

返回类型、函数名和参数列表说明怎么调用。名字应使用动词并表达真正工作。

02

值传递

int square(int value)

函数收到副本,内部修改不会影响调用者。小型基础类型通常适合值传递。

03

引用参数

void swapValues(int& a, int& b)

函数直接操作原变量,适合明确需要产生多个修改结果的情况。调用时要意识到数据会改变。

04

只读引用

void print(const string& text)

不复制较大的对象,同时承诺不会修改。字符串、vector 和自定义对象常用这种方式传入。

bool isValidScore(int score);

只负责判断并返回真假,名字和返回类型都很清楚。

double area(double radius);

输入半径、返回面积,不在计算函数里读取 cin。

void normalize(string& text);

引用表示函数会直接修改传入的文本。

int max(int a, int b); double max(double a, double b);

同名函数参数不同叫重载,编译器根据参数选择版本。

Complete examples

两个例子,把知识变成能运行的程序。

Example 1

例子一:拆分个人消费分析器

main 只负责组织流程:读取数据、计算总额、显示报告。具体细节交给三个函数。以后数据来源改成文件,只需替换读取部分,统计函数不必改变。

total 接收 const 引用,因为它只读取 vector。返回 double 表示计算结果,让调用者决定如何显示。

输入、统计和显示各负其责
double total(const vector<double>& expenses)
{
    double sum = 0;
    for (double value : expenses)
        sum += value;
    return sum;
}

void printReport(const vector<double>& expenses)
{
    double sum = total(expenses);
    cout << "记录数:" << expenses.size() << '\n';
    cout << "总支出:" << sum << '\n';
    if (!expenses.empty())
        cout << "平均:" << sum / expenses.size() << '\n';
}

int main()
{
    vector<double> data{12.5, 8.0, 19.5};
    printReport(data);
}
记录数:3总支出:40平均:13.3333

Example 2

例子二:欧几里得最大公约数算法

两个正整数的最大公约数满足 gcd(a,b)=gcd(b,a%b)。每一轮余数都会变小,直到 b 为零,此时 a 就是答案。

把算法放进函数后,可以轻松测试多组数据。函数先把负数转正,让输入范围更宽;两个零则返回零作为约定结果。

把算法封装成可测试函数
int gcd(int a, int b)
{
    a = abs(a);
    b = abs(b);
    while (b != 0)
    {
        int remainder = a % b;
        a = b;
        b = remainder;
    }
    return a;
}

cout << gcd(48, 18) << '\n';
cout << gcd(21, 14) << '\n';
cout << gcd(-24, 9) << '\n';
gcd(48,18) = 6gcd(21,14) = 7gcd(-24,9) = 3

Algorithm thinking

让 main 像目录,而不是仓库。

阅读 main 时,应该能快速看出程序经历哪些阶段,而不是陷入几十行细节。可以先写高层伪代码:读取数据、验证数据、计算结果、输出报告,再为每一步建立函数。函数内部如果仍然太长,就继续按职责拆分。

函数接口要尽量小。一个函数如果需要十个参数,往往说明它承担太多职责,或者这些数据本来属于一个对象。返回值优先表达主要结果;需要修改调用者时才使用非 const 引用。全局变量虽然调用方便,却会隐藏依赖,让测试和排错变困难。

  1. 1
    写一句职责

    如果不能用一句话说明函数做什么,说明它可能需要继续拆分。

  2. 2
    确定输入输出

    列出函数需要的数据、是否修改它们以及返回什么结果。

  3. 3
    先独立测试

    为函数准备正常、边界和错误数据,不依赖完整菜单才能运行。

  4. 4
    最后组织流程

    main 只按业务顺序调用函数,并处理最高层的成功或失败。

Common mistakes

这些错误很常见,学会自己排查。

1

忘记返回值

非 void 函数的所有合理路径都应返回结果,否则行为不可预测。

2

引用用得太多

只读数据却使用非 const 引用,会让调用者担心它被修改。

3

函数同时输入和计算

把 cin 写进算法函数会使测试困难。把数据作为参数传入更灵活。

4

复制大型容器

vector 按值传递会复制全部元素,只读时应使用 const 引用。

本课通用调试法

先准备一个最小输入,只保留能够重现问题的几行数据;再在关键步骤输出变量值,确认程序究竟在哪一步偏离预期。编译错误从第一条开始处理,运行错误则比较“实际结果”和“期望结果”。修复后别只重跑原来的例子,还要增加空输入、边界值和错误输入,防止问题换一个形式再次出现。

Homework

第 11 课课后作业:从模仿到独立完成。

建议按顺序完成。前四题帮助巩固语法和基本操作,第五到第七题要求把多个知识点组合起来,第八题是小项目。每道题都要先写输入、处理、输出三行计划,再开始敲代码;程序运行后至少测试正常情况、边界情况和错误情况。

1

基础计算函数

分别编写平方、绝对值、三数最大值和摄氏转华氏函数,每个函数至少测试三组数据。

2

安全除法

函数接收两个数和一个引用结果,除数为零返回 false,成功时写入结果并返回 true。

3

字符串清理

用引用参数删除字符串首尾空格,再写只读函数判断清理后的字符串是否为空。

4

质数工具

把上一课质数判断封装为 bool 函数,再写函数返回区间内所有质数。

5

分数统计库

编写 total、average、highest、passedCount 四个函数,main 只负责显示结果。

6

最小公倍数

复用 gcd 计算 lcm,处理零和负数,并说明如何减少乘法溢出风险。

7

重载面积函数

用重载分别计算圆、矩形和三角形面积,设计不同参数列表并验证编译器选择。

8

数字工具箱项目

制作菜单调用素数、最大公约数、最小公倍数、阶乘和区间求和,每项功能必须是独立函数。

本课验收标准:main 能在一屏内看清主要流程;计算函数不直接读取用户输入;vector 和 string 的只读参数使用 const 引用;至少五个函数有独立测试,且能解释每个参数为什么按值或按引用传递。

提交内容应包括源代码、三组测试输入与输出、一个曾经出现的错误及修复方法。最后请用自己的话解释本课最重要的概念;如果只能照着代码念,还需要再独立重写一次核心例子。

Lesson 11 summary

函数让程序从“一长串命令”变成“合作完成任务的模块”。

声明与定义返回值引用参数const 引用职责拆分

下一课会集中处理字符串。清楚的函数接口会帮助我们把分词、查找、替换和统计拆成可复用的文本工具。