函数签名
double average(const vector<int>& values)
返回类型、函数名和参数列表说明怎么调用。名字应使用动词并表达真正工作。
Easy explanation
一家餐厅不会让同一个人同时接单、洗菜、炒菜、收款。每个工作台只负责一件事,并通过清楚的交接把结果传给下一步。程序也是如此:读取输入的函数不要顺便计算总价,计算函数不要顺便打印菜单。职责分开后,修改其中一步不会牵动整间厨房。
函数参数是交给工作台的原料,返回值是完成后的产品。值传递会复制一份原料,适合小型数字;引用让函数直接操作原对象;const 引用既避免大对象复制,又保证函数不会修改它。选择参数方式时先问:需要修改调用者的数据吗?数据复制成本高吗?
Core concepts
double average(const vector<int>& values)
返回类型、函数名和参数列表说明怎么调用。名字应使用动词并表达真正工作。
int square(int value)
函数收到副本,内部修改不会影响调用者。小型基础类型通常适合值传递。
void swapValues(int& a, int& b)
函数直接操作原变量,适合明确需要产生多个修改结果的情况。调用时要意识到数据会改变。
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);
}
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';
Algorithm thinking
阅读 main 时,应该能快速看出程序经历哪些阶段,而不是陷入几十行细节。可以先写高层伪代码:读取数据、验证数据、计算结果、输出报告,再为每一步建立函数。函数内部如果仍然太长,就继续按职责拆分。
函数接口要尽量小。一个函数如果需要十个参数,往往说明它承担太多职责,或者这些数据本来属于一个对象。返回值优先表达主要结果;需要修改调用者时才使用非 const 引用。全局变量虽然调用方便,却会隐藏依赖,让测试和排错变困难。
如果不能用一句话说明函数做什么,说明它可能需要继续拆分。
列出函数需要的数据、是否修改它们以及返回什么结果。
为函数准备正常、边界和错误数据,不依赖完整菜单才能运行。
main 只按业务顺序调用函数,并处理最高层的成功或失败。
Common mistakes
非 void 函数的所有合理路径都应返回结果,否则行为不可预测。
只读数据却使用非 const 引用,会让调用者担心它被修改。
把 cin 写进算法函数会使测试困难。把数据作为参数传入更灵活。
vector 按值传递会复制全部元素,只读时应使用 const 引用。
先准备一个最小输入,只保留能够重现问题的几行数据;再在关键步骤输出变量值,确认程序究竟在哪一步偏离预期。编译错误从第一条开始处理,运行错误则比较“实际结果”和“期望结果”。修复后别只重跑原来的例子,还要增加空输入、边界值和错误输入,防止问题换一个形式再次出现。
Homework
建议按顺序完成。前四题帮助巩固语法和基本操作,第五到第七题要求把多个知识点组合起来,第八题是小项目。每道题都要先写输入、处理、输出三行计划,再开始敲代码;程序运行后至少测试正常情况、边界情况和错误情况。
分别编写平方、绝对值、三数最大值和摄氏转华氏函数,每个函数至少测试三组数据。
函数接收两个数和一个引用结果,除数为零返回 false,成功时写入结果并返回 true。
用引用参数删除字符串首尾空格,再写只读函数判断清理后的字符串是否为空。
把上一课质数判断封装为 bool 函数,再写函数返回区间内所有质数。
编写 total、average、highest、passedCount 四个函数,main 只负责显示结果。
复用 gcd 计算 lcm,处理零和负数,并说明如何减少乘法溢出风险。
用重载分别计算圆、矩形和三角形面积,设计不同参数列表并验证编译器选择。
制作菜单调用素数、最大公约数、最小公倍数、阶乘和区间求和,每项功能必须是独立函数。
本课验收标准:main 能在一屏内看清主要流程;计算函数不直接读取用户输入;vector 和 string 的只读参数使用 const 引用;至少五个函数有独立测试,且能解释每个参数为什么按值或按引用传递。
提交内容应包括源代码、三组测试输入与输出、一个曾经出现的错误及修复方法。最后请用自己的话解释本课最重要的概念;如果只能照着代码念,还需要再独立重写一次核心例子。