NOI C++ / 语法入门 / 第03讲
m3-03 · 函数专题

函数定义与调用
形参与实参

告别"代码复制粘贴",学会用函数封装逻辑,让程序像搭积木一样清晰高效

函数定义 · 函数调用 形参 · 实参 值传递 · 返回值
本讲 · 第 17/56 讲函数定义与调用
01

情景导航

从一个"重复代码"的困境出发,理解函数为什么是 C++ 中不可或缺的"代码封装"工具。

📖故事引入:小强的"复制粘贴"烦恼

小强要写一个程序:输入 5 个学生的成绩,分别计算每个学生的平均分和等级。他不会用函数,于是写了 5 段几乎一模一样的代码,每段都重复了"求和、除以 3、判断等级"的逻辑。代码写得又长又乱,改一处要改 5 遍,差点崩溃。

老师告诉他:"把这些重复的逻辑打包成一个函数,只需要写一次,调用 5 次就行了!"

💡 生活类比:

🍳

函数 = 菜谱

写好一次菜谱(函数定义),每次做饭只需按步骤执行(函数调用),不用每次都重新发明做法。

定义一次,调用多次
📦

形参 = 模具

函数定义时的形参就像模具的"空腔",还没填入具体材料。它规定了函数需要什么样的"原料"。

定义时的占位符
🧱

实参 = 材料

调用函数时传入的实参就是实际放进去的"材料"。不同的材料经过同一个模具,产出不同的结果。

调用时的具体值
🏷️

返回值 = 成品

函数执行完后交出的结果。就像工厂加工完材料后交出的成品,调用者拿到成品继续下一步操作。

return 吐出的值
核心口诀 函数定义写一次,形参占位定规矩;
调用传参给实参,返回值里出结果;
重复代码不用写,封装调用最省事!
02

知识探究

系统学习函数的定义语法、调用方式、形参与实参的关系,以及值传递的本质。

1函数的定义语法

C++ 中,函数的基本定义格式如下:

返回类型 函数名(形参列表) {
  函数体(执行语句)
  return 返回值;
}
返回类型:告诉调用者函数会交出什么类型的结果 函数名:给这个功能起的名字,调用时用它来"点名" 形参列表:函数需要的"原料"清单(可空) 函数体:具体的"加工步骤"
C++定义一个求两数之和的函数
#include <iostream>
using namespace std;

// 函数定义:返回 int,函数名 add,两个形参 a 和 b
int add(int a, int b) {
    int sum = a + b;    // 函数体:执行求和
    return sum;         // 把结果交出去
}

int main() {
    int x, y;
    cin >> x >> y;
    // 函数调用:实参 x, y 传给形参 a, b
    int result = add(x, y);
    cout << result << endl;
    return 0;
}
💡 要点:函数定义可以写在 main 前面(推荐),也可以写在 main 后面但需要在 main 前面加函数声明(如 int add(int a, int b);)。
2形参与实参——一一对应的"传话"关系

这是本讲最重要的概念。很多初学者混淆形参和实参,导致"传错了值""改了没生效"等问题。

调用 add(x) 时发生的事 实参 x = 5 形参 a = 5 复制值 单向传递:只有实参 → 形参,没有回头路 形参只是"接收箱",不会改变实参本身
图 1 · 实参与形参是一一对应的"值复制"关系
🥊 形参 vs 实参:一次"复印件"的传递
  1. 形参写在函数定义的括号里,是占位符,调用时才分配内存(临时变量),函数结束即销毁。
  2. 实参写在调用的括号里,是真实的值或变量,有自己的存储空间。
  3. 对应顺序必须一一对齐:个数、类型、顺序都要匹配。

一句话记住:实参是"原件",形参是"复印件"——改复印件,原件不会变。

对比项形参(形式参数)实参(实际参数)
含义函数定义时写的参数,是"占位符"函数调用时传入的具体值或变量
出现位置函数定义的括号里函数调用的括号里
内存分配调用时才分配内存(临时变量)本身已有内存空间
生命周期函数结束时销毁在所属作用域内一直存在
类比菜谱上的"适量盐"你实际抓的那撮盐
C++形参与实参的对应关系
int max2(int a, int b) {  // a, b 是形参
    return a > b ? a : b;
}

int main() {
    int x = 5, y = 8;
    max2(x, y);    // x, y 是实参 → 传给 a, b
    max2(10, 3);  // 10, 3 是实参 → 传给 a, b
    max2(x + 1, y - 2);  // 表达式也可以是实参
}
⚠️ 注意:实参和形参的个数、类型、顺序必须一一对应!传多了、传少了、传错了类型,编译器都会报错。
3值传递——形参是实参的"副本"

C++ 中函数参数默认采用值传递(pass by value)。也就是说,实参的值被"复制"了一份给形参,函数内部对形参的任何修改都不会影响实参本身。

内存示意:x 与 a 是两块独立的空间 main 的内存 x 5 函数 f 的临时空间 a(副本) 5 → 10 ✅ 函数内 a 改成 10,main 里的 x 依然是 5 形参的生命周期:调用时出生,函数返回时销毁
图 2 · 值传递的本质:复制一份数据,两块内存互不影响
📦 为什么"改了没生效"?
x: 5
a: 5
a: 10
x: 5
  • 调用时:a = x,把 5 复制给 a。
  • 函数内:改 a 只改副本,main 的 x 纹丝不动。
  • 想让函数改到实参本身 → 用引用传递(形参加 &),见本页拓展案例。
C++值传递的"副本"特性
#include <iostream>
using namespace std;

void swap_val(int a, int b) {  // a, b 是 x, y 的副本
    int t = a;
    a = b;
    b = t;
    cout << "函数内: a=" << a << " b=" << b << endl;
}

int main() {
    int x = 3, y = 5;
    swap_val(x, y);  // 传入 x, y 的副本
    cout << "函数外: x=" << x << " y=" << y << endl;
    return 0;
}
运行结果
函数内: a=5 b=3
函数外: x=3 y=5

💭 思考:为什么函数内交换了,函数外没变?

因为 swap_val(x, y) 调用时,系统把 x 的值 3 复制给形参 a,把 y 的值 5 复制给形参 b。函数内交换的是 a 和 b(副本),x 和 y(原件)完全没有被碰过。

类比:你给秘书一张复印件让她修改,她在复印件上改得再花,你手里的原件也不会变。

4有返回值 vs 无返回值(void)

函数可以返回一个值(用 return),也可以不返回任何值(返回类型为 void)。

有返回值(如 int, double, string)
  • 用 return 值; 交出结果
  • 调用时可以赋值给变量或参与表达式
  • 例:int r = add(3, 5);
无返回值(void)
  • 只负责"做一件事",不交出结果
  • 可以没有 return,或写 return; 提前结束
  • 例:printHello();(只打印,不返回)
C++两种函数的对比
// 有返回值:计算面积
double area(double r) {
    return 3.14159 * r * r;
}

// 无返回值:打印问候
void greet(string name) {
    cout << "你好, " << name << "!" << endl;
    return;  // 可以省略,函数体结束自动返回
}

int main() {
    double s = area(5);       // 有返回值,可以赋值
    greet("小明");            // 无返回值,直接调用
    cout << "面积=" << area(3) << endl;  // 也可以直接用在表达式中
}
5数学函数速查表(需 #include <cmath>)
函数作用示例结果
abs(x)绝对值abs(-5)5
pow(x, n)x 的 n 次方pow(2, 3)8.0
sqrt(x)平方根sqrt(16)4.0
round(x)四舍五入round(3.6)4.0
floor(x)向下取整floor(3.9)3.0
ceil(x)向上取整ceil(3.1)4.0
fmax(a,b)两数最大值fmax(3.5, 4.2)4.2
fmin(a,b)两数最小值fmin(3.5, 4.2)3.5
C++数学函数使用示例
#include <iostream>
#include <cmath>
using namespace std;

int main() {
    cout << abs(-7) << endl;       // 输出: 7
    cout << pow(2, 10) << endl;    // 输出: 1024
    cout << sqrt(2) << endl;       // 输出: 1.41421
    cout << round(3.14159) << endl;  // 输出: 3
}
6函数调用的执行流程(重点!)

理解函数调用的"进出"流程,是掌握函数的关键。整个过程分为 5 步:

🎬 函数调用执行流程(可交互演示)
1main 执行到 add(x, y) 时暂停 main,准备进入函数
2把实参 x、y 的值复制给形参 a、b(建立临时空间)
3跳到函数体,从上往下执行里面的语句
4遇到 return sum,交出结果,函数结束、临时空间销毁
5回到 main 暂停的位置,用返回值继续执行下一行
💡 点击「自动演示」看全过程:暂停 → 传值 → 执行 → 返回 → 继续。函数像一次"外出办事",办完必须回到原处。
记忆口诀 调用暂停主程序,实参值传给形参;
函数体里执行完,return 交出结果;
回到原位继续走,这就是函数调用!
03

实践应用

通过 3 道综合案例,掌握函数的定义与调用在解题中的实际应用。

例1判断素数(函数封装)

题目:定义一个函数 bool isPrime(int n),判断 n 是否为素数。主函数读入一个整数,输出是否为素数。

输入样例:17

输出样例:Yes

C++参考代码
#include <iostream>
using namespace std;

// 判断素数的函数
bool isPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) return false;
    }
    return true;
}

int main() {
    int n;
    cin >> n;
    if (isPrime(n))
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
    return 0;
}
解析:函数 isPrime 负责"判断"这一独立逻辑,main 只负责"输入输出"。如果后面还有其他题目也需要判断素数,直接调用 isPrime 即可,不需要再写一遍循环。
例2计算阶乘(递归思想入门)

题目:定义函数 long long factorial(int n),计算 n!(n 的阶乘)。主函数读入 n,输出 n!。

输入样例:5

输出样例:120

C++参考代码
#include <iostream>
using namespace std;

long long factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int main() {
    int n;
    cin >> n;
    cout << factorial(n) << endl;
    return 0;
}
解析:这个例子展示了函数的"自我调用"(递归)。虽然递归是后面的内容,但这里先理解:函数 factorial(5) → 5 × factorial(4) → 5 × 4 × factorial(3) → ... → 5 × 4 × 3 × 2 × 1 = 120。
🎬 factorial(5) 的执行轨迹
1调用 factorial(5):n=5 ≠ 1,需要 5 × factorial(4)
2调用 factorial(4):需要 4 × factorial(3)
3调用 factorial(3):需要 3 × factorial(2)
4调用 factorial(2):需要 2 × factorial(1)
5调用 factorial(1):n ≤ 1 触发终止条件,直接 return 1(递归的"底")
6逐层回代:2×1 = 2 → 3×2 = 6 → 4×6 = 24 → 5×24 = 120
💡 递归 = 先【逐层下降】再【逐层回代】。没有终止条件(n ≤ 1)就会无限下降,导致栈溢出崩溃。
例3最大公约数(辗转相除法)

题目:定义函数 int gcd(int a, int b),用辗转相除法求 a 和 b 的最大公约数。主函数读入两个正整数,输出它们的 GCD。

输入样例:48 18

输出样例:6

C++参考代码
#include <iostream>
using namespace std;

// 辗转相除法求 GCD
int gcd(int a, int b) {
    while (b != 0) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}

int main() {
    int a, b;
    cin >> a >> b;
    cout << gcd(a, b) << endl;
    return 0;
}
解析:辗转相除法的原理:gcd(a, b) = gcd(b, a mod b),直到 b 为 0 时,a 就是最大公约数。把这个算法封装成函数,以后任何题目需要求 GCD 都可以直接调用。
🎬 gcd(48, 18) 的辗转相除过程
1初始:a = 48,b = 18,b ≠ 0 进入循环
2t = 18 → b = 48 % 18 = 12 → a = 18,得到 (18, 12)
3t = 12 → b = 18 % 12 = 6 → a = 12,得到 (12, 6)
4t = 6 → b = 12 % 6 = 0 → a = 6,得到 (6, 0)
5b == 0,循环结束,return a = 6 ✅ 与样例输出一致
💡 原理:gcd(a, b) = gcd(b, a mod b)。余数越滚越小,最后一个非零余数就是最大公约数。
04

总结提升

回顾本讲核心知识点,掌握函数定义的万能模板。

📋核心要点总结表
知识点要点
函数定义返回类型 + 函数名(形参列表) { 函数体 },定义一次可调用多次
函数调用函数名(实参列表),实参值复制给形参,函数暂停 main 去执行
形参 vs 实参形参是定义时的占位符,实参是调用时的具体值,个数/类型/顺序必须一一对应
值传递形参是实参的副本,函数内修改形参不影响实参
返回值有返回值用 return 交出结果,void 表示不返回
函数声明如果函数定义在 main 后面,需在 main 前加声明(如 int add(int, int);)
🔧函数万能模板
C++函数定义模板
// 模板:需要返回什么类型,就写什么返回类型
返回类型 函数名(形参类型 形参1, 形参类型 形参2, ...) {
    // 1. 函数体:写具体的处理逻辑
    // 2. 最后用 return 交出结果(void 函数不需要)
    return 结果;
}

// 调用示例
int 结果 = 函数名(实参1, 实参2);  // 有返回值
函数名(实参1, 实参2);              // 无返回值(void)
🚀拓展案例:交换函数(值传递 vs 引用传递对比)

问题:如何真正实现两个变量的交换?值传递做不到,需要用引用传递(&)。

C++值传递(失败)vs 引用传递(成功)
// 值传递:形参是副本,交换无效
void swap_bad(int a, int b) {
    int t = a; a = b; b = t;
}

// 引用传递:形参是实参的"别名",交换有效
void swap_good(int& a, int& b) {  // 注意 & 符号
    int t = a; a = b; b = t;
}

int main() {
    int x = 3, y = 5;
    swap_bad(x, y);  // x=3, y=5 不变
    swap_good(x, y); // x=5, y=3 交换成功!
}
说明:引用传递(形参前面加 &)让形参成为实参的"别名",对形参的修改直接作用于实参本身。这是后面要深入学习的概念,这里先知道"值传递改不了原件,引用传递可以"即可。
两种传参方式,交换结果完全不同 ❌ 值传递 swap_bad(int a, int b) x = 3 y = 5 函数内交换的是副本 a、b main 里 x、y 原封不动 ✅ 引用传递 swap_good(int& a, int& b) x = 5 y = 3 a、b 是 x、y 的别名 交换生效!
图 3 · 值传递 vs 引用传递:只差一个 & 符号
🔁 怎么才能真正交换两个变量?
  • 值传递:形参是副本 → 函数内怎么折腾,外面都不变。
  • 引用传递:形参加 &,成为实参的"别名"(同一块内存)→ 改形参就是改实参。
  • 交换三句口诀:t = a; a = b; b = t;(借助中间变量,不能直接互换)。

考点很多题故意用值传递写 swap 让你判断输出——记住:值传递的 swap 一定是"假交换"。

05

学习检测

检测本讲核心知识点,避免常见陷阱。

⚠️5 大易错点
易错 1

忘记写 return 或 return 类型不匹配

错误写法
int add(int a, int b) {
    a + b;        // 没有 return!
}
正确写法
int add(int a, int b) {
    return a + b; // 必须 return
}
易错 2

实参个数/类型/顺序与形参不匹配

错误写法
int add(int a, int b);
add(3.5);        // 类型错 + 个数少
add(1, 2, 3);  // 个数多
正确写法
int add(int a, int b);
add(3, 5);     // 类型对 + 个数对
add(10, 20);   // 表达式也可以
易错 3

以为函数内改形参就能改实参

说明
值传递下,形参是实参的副本。函数内修改形参不会影响实参。如果需要修改实参,要用引用传递(形参前加 &),这是后面的内容。
易错 4

函数定义和调用混为一谈

说明
定义是"写函数体",调用是"用函数名+实参执行它"。定义写一次,调用可以多次。在 main 里写函数定义是非法的(C++ 不允许嵌套定义函数)。
易错 5

void 函数写了 return 值

错误写法
void print() {
    return 42;  // 编译报错!
}
正确写法
void print() {
    cout << "Hello";
    return;  // 不返回值,只提前结束
}
📝6 张速记卡
① 函数定义格式

返回类型 函数名(形参) { 函数体; return 值; }

② 函数调用格式

变量 = 函数名(实参); 或 函数名(实参);

③ 形参 = 占位符

定义时的参数名,调用时才分配内存,函数结束即销毁

④ 实参 = 具体值

调用时传入的实际值,个数/类型/顺序必须与形参一一对应

⑤ 值传递

形参是实参的副本,改形参不影响实参

⑥ void 函数

不返回值,可以没有 return,或写 return; 提前结束

练 习 作 业

巩固提升

基础 → 提高 → 挑战,层层递进

📌总结练习(3 题)
L1读输出

题目:下面程序输出什么?

C++值传递陷阱
#include <iostream>
using namespace std;
void f(int x, int y) {
    x = x + y;
    y = x - y;
    x = x - y;
    cout << x << " " << y << endl;
}
int main() {
    int a = 3, b = 5;
    f(a, b);
    cout << a << " " << b << endl;
    return 0;
}
点击查看参考答案
C++完整参考代码
#include <iostream>
using namespace std;

void f(int x, int y) {            // x, y 只是 a, b 的副本
    x = x + y;                    // x = 3 + 5 = 8
    y = x - y;                    // y = 8 - 5 = 3
    x = x - y;                    // x = 8 - 3 = 5
    cout << x << " " << y << endl; // 函数内:输出 5 3
}

int main() {
    int a = 3, b = 5;
    f(a, b);                      // 值传递,a、b 本身不会被改动
    cout << a << " " << b << endl; // 函数外:输出 3 5
    return 0;
}

// 运行结果:
// 5 3
// 3 5

答案:5 3 / 3 5(第一行是函数内的输出,第二行是 main 里的输出)。

思路:先算函数内——x = 3 + 5 = 8,y = 8 − 5 = 3,x = 8 − 3 = 5,所以函数内打印「5 3」。回到 main,a、b 是原变量,值传递只把副本送进函数,它们从头到尾没被碰过,所以打印「3 5」。

考点:值传递的副本特性 + 用三步加减法交换两个数。

易错:看到函数里交换成功了,就以为外面的 a、b 也变了——这是最典型的失分点。想让函数真的改到实参,形参要写成 int& x(引用)。

思路:先算函数内——x = x+y = 8,y = x−y = 8−5 = 3,x = x−y = 8−3 = 5,所以函数内输出「5 3」;再回到 main,a、b 是原变量,从未被修改,输出「3 5」。
考点:值传递的副本特性 + 交换两数的技巧(三步加减法)。
易错:误以为函数内交换了,外面的 a、b 也会变——这是最典型的失分点。
L2改错

题目:下面代码有错误,请找出并改正。

C++找错
int square(int n) {
    n * n;
}
int main() {
    cout << square(5) << endl;
}
点击查看参考答案
C++完整参考代码
#include <iostream>
using namespace std;

int square(int n) {
    return n * n;              // ← 这里必须写 return,否则返回值是随机垃圾值
}

int main() {
    cout << square(5) << endl; // 输出 25
    return 0;
}

// 运行结果:25

答案:函数声明返回 int,但函数体没有 return 语句。改正办法:把 n * n; 改成 return n * n;。

思路:函数签名 int square(int n) 承诺返回一个 int,可函数体里只有表达式 n * n;,这个值被算出来后又立刻丢掉了,没有送回调用处。

考点:「返回类型不是 void,函数里就必须有 return」这条硬规则。

易错:只写 n * n; 并不会报语法错误(它是合法的表达式语句),但函数的返回值是随机垃圾值,要运行时才发现,最难查。

思路:函数签名 int square(int n) 承诺返回一个 int,但函数体只有表达式 n * n;,这个值被算出来后又立刻丢弃,没有送回调用处。
考点:「返回类型非 void 必须有 return」这条硬规则。
易错:只写 n * n; 不报语法错误(是合法表达式语句),但函数返回值是随机垃圾值,运行时才出问题,最难查。
L3编程

题目:定义函数 int countDigit(int n, int d),统计 n 中数字 d 出现的次数。例如 countDigit(122333, 3) 返回 3。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
int countDigit(int n, int d) {
    int cnt = 0;
    while (n > 0) {
        if (n % 10 == d) cnt++;
        n /= 10;
    }
    return cnt;
}
int main() {
    cout << countDigit(122333, 3) << endl;  // 输出: 3
}

易错:循环里忘了 n /= 10,n 永远不变会死循环;条件写成 n >= 0 时 0 无法退出也一样死循环。

思路:取末位(n % 10)与目标数字 d 比较,相同则计数 +1;再去掉末位(n /= 10),循环直到 n 为 0。
验证:122333 逐位取出 3、3、3、2、2、1,其中 3 出现 3 次,输出 3 ✅
考点:数位拆分的标准循环结构。
A 组基础巩固(4 题)
A1编程

题目:定义函数 double avg(double a, double b, double c),返回三个数的平均值。主函数读入三个数并输出平均值(保留 2 位小数)。

点击查看参考答案
C++参考代码
#include <iostream>
#include <iomanip>
using namespace std;
double avg(double a, double b, double c) {
    return (a + b + c) / 3.0;
}
int main() {
    double a, b, c;
    cin >> a >> b >> c;
    cout << fixed << setprecision(2) << avg(a, b, c) << endl;
}

易错:(a+b+c)/3 里的 3 要写成 3.0,整数除法会截断小数;输出忘了 fixed << setprecision(2) 也保不住两位小数。

思路:形参用 double 接收三个数,返回 (a + b + c) / 3;主函数用 cout << fixed << setprecision(2) 保留两位小数(需 #include <iomanip>)。
考点:返回类型与数据类型的匹配——若写成 int 返回类型,结果会被截断。
A2编程

题目:定义函数 int power(int base, int exp),计算 base 的 exp 次方(exp 为非负整数)。不用 pow 函数,用循环实现。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
int power(int base, int exp) {
    int result = 1;
    for (int i = 0; i < exp; i++)
        result *= base;
    return result;
}
int main() {
    cout << power(2, 10) << endl;  // 1024
}
思路:循环 exp 次累乘:int r = 1; for (int i = 0; i < exp; i++) r *= base;,注意 exp = 0 时应返回 1(任何数的 0 次方为 1,循环自然不执行,r 保持 1)。
验证:power(2, 10) = 1024 ✅
A3读输出

题目:下面程序输出什么?

C++函数调用顺序
#include <iostream>
using namespace std;
int f() {
    cout << "A";
    return 1;
}
int main() {
    cout << f() << "B" << f() << endl;
}
点击查看参考答案
C++完整参考代码
#include <iostream>
using namespace std;

int f() {
    cout << "A";                            // 副作用:每调用一次就打印一个 A
    return 1;
}

int main() {
    cout << f() << "B" << f() << endl;      // 链式输出从左往右求值
    return 0;
}

// 运行结果:A1BA1

答案:A1BA1。

思路:链式输出的求值顺序是从左到右:先执行第一个 f()(函数内打印 A,返回 1)→ 打印这个 1 → 打印字符 B → 再执行第二个 f()(打印 A,返回 1)→ 打印 1,合起来就是 A1BA1。

考点:函数调用本身可以带副作用(函数里也能 cout),要按执行顺序拆开看。

易错:把两次函数调用的输出提前或合并,误答成 AAB11 或 11BA1。记住:函数一被调用,函数里的打印就先发生。

思路:链式输出的求值顺序是从左到右:先执行 f()(函数内输出 A,返回 1)→ 输出这个 1 → 输出字符 B → 再执行第二个 f()(输出 A,返回 1)→ 输出 1,合起来是 A1BA1。
考点:函数调用本身可带副作用(cout),要按执行顺序拆开看。
易错:把两次函数调用的输出提前或合并,误答成 AAB11 或 11BA1。
A4编程

题目:定义函数 int max3(int a, int b, int c),返回三个数中的最大值。要求用嵌套的 max2 函数实现(max2 返回两个数的最大值)。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
int max2(int a, int b) {
    return a > b ? a : b;
}
int max3(int a, int b, int c) {
    return max2(max2(a, b), c);
}
int main() {
    cout << max3(5, 9, 3) << endl;  // 9
}

易错:max2 定义在 max3 之后又没有提前声明,编译时报“未声明的标识符”——被调函数要先定义或先声明。

思路:先写 int max2(int p, int q) { return p > q ? p : q; },再让 max3 返回 max2(max2(a, b), c)——把三个数拆成两次两两比较。
考点:函数复用(函数之间互相调用),这是"分而治之"思想的雏形。
B 组能力提高(4 题)
B1编程

题目:定义函数 int fib(int n),用递归返回第 n 个斐波那契数(fib(1)=1, fib(2)=1, fib(n)=fib(n-1)+fib(n-2))。输出前 10 个斐波那契数。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
int fib(int n) {
    if (n <= 2) return 1;
    return fib(n - 1) + fib(n - 2);
}
int main() {
    for (int i = 1; i <= 10; i++)
        cout << fib(i) << " ";
    cout << endl;
}

易错:忘了基准情况 if (n <= 2) return 1,递归没有出口直接栈溢出崩溃。

思路:用循环保存前两项轮流递推更简单:a = 1, b = 1; for (i = 3; i <= 10; i++) { c = a + b; a = b; b = c; };递归写法能体现定义式,但 fib(40) 就会非常慢(重复计算爆炸)。
答案:前 10 项 = 1 1 2 3 5 8 13 21 34 55 ✅
考点:递归定义式与递推实现的差异。
B2编程

题目:定义函数 int reverseNum(int n),返回 n 的逆序数。例如 reverseNum(123) 返回 321。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
int reverseNum(int n) {
    int rev = 0;
    while (n > 0) {
        rev = rev * 10 + n % 10;
        n /= 10;
    }
    return rev;
}
int main() {
    cout << reverseNum(123) << endl;  // 321
}
思路:边拆数位边拼新数:rev = rev * 10 + n % 10; n /= 10;,循环直到 n 为 0。
验证:123 → 3 → 32 → 321 ✅(注意 n = 0 时 rev 应返回 0;负数需先取绝对值)。
B3编程

题目:定义函数 bool isPalindrome(int n),判断 n 是否为回文数(正读反读一样)。利用 reverseNum 函数实现。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
int reverseNum(int n) {
    int rev = 0;
    while (n > 0) {
        rev = rev * 10 + n % 10;
        n /= 10;
    }
    return rev;
}
bool isPalindrome(int n) {
    return n == reverseNum(n);
}
int main() {
    cout << boolalpha << isPalindrome(12321) << endl;  // true
}

易错:reverseNum 里 rev = rev*10 + n%10 的顺序写反(先加后乘),翻转结果就错了。

思路:先调用已写好的 reverseNum(n) 得到逆序数,再与原数比较是否相等:return n == reverseNum(n);
验证:12321 逆序仍是 12321 → true;123 逆序为 321 → false ✅
考点:函数复用——不要重复实现"逆序"逻辑。
B4读输出

题目:下面程序输出什么?

C++函数嵌套调用
#include <iostream>
using namespace std;
int f(int x) { return x * x; }
int g(int x) { return f(x) + 1; }
int main() {
    cout << g(3) << endl;
}
点击查看参考答案
C++完整参考代码
#include <iostream>
using namespace std;

int f(int x) { return x * x; }      // f(3) = 9
int g(int x) { return f(x) + 1; }   // g(3) = f(3) + 1 = 10

int main() {
    cout << g(3) << endl;           // 输出 10
    return 0;
}

// 运行结果:10

答案:10。

思路:由内向外层层代入:g(3) → f(3) + 1,而 f(x) = x * x,所以 f(3) = 9,g(3) = 9 + 1 = 10。

考点:嵌套调用(一个函数里调用另一个函数),按「先算内层、再算外层」的顺序拆解。

易错:把 g 里那一句看成 3 + 1 = 4,漏掉了 f 的加工。看到函数名就先把它的返回值算出来,再继续往外算。

思路:由内向外层层代入:g(3) → f(3) + 1,而 f(x) = x * x,所以 f(3) = 9,g(3) = 9 + 1 = 10。
考点:嵌套调用(函数里调用另一个函数),按"先算内层、再算外层"的顺序拆解。
C 组思维挑战(4 题)
C1编程

题目:定义函数 int digits(int n),返回 n 的位数。例如 digits(123) 返回 3,digits(7) 返回 1。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
int digits(int n) {
    int cnt = 0;
    while (n > 0) {
        n /= 10;
        cnt++;
    }
    return cnt;
}
int main() {
    cout << digits(12345) << endl;  // 5
}
思路:循环除以 10 并计数:int c = 0; while (n > 0) { n /= 10; c++; } return c;
验证:123 → 12 → 1 → 0 共 3 次 → 返回 3 ✅;
易错:n = 0 时循环一次都不执行,位数应为 1,需特判 if (n == 0) return 1;。
C2编程

题目:定义函数 int sumDigits(int n),返回 n 的各位数字之和。例如 sumDigits(123) 返回 6(1+2+3)。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
int sumDigits(int n) {
    int s = 0;
    while (n > 0) {
        s += n % 10;
        n /= 10;
    }
    return s;
}
int main() {
    cout << sumDigits(123) << endl;  // 6
}

易错:循环里忘了 n /= 10 导致死循环;n 为 0 时函数返回 0,若题目要求 0 的数位和是 0 需注意边界。

思路:循环取末位累加:sum += n % 10; n /= 10;
验证:123 → 3 + 2 + 1 = 6 ✅(数位和无需特判 0:0 的数位和就是 0)。
C3编程

题目:定义函数 int gcd(int a, int b)(辗转相除法)和 int lcm(int a, int b)(最小公倍数),利用公式 lcm(a,b) = a*b/gcd(a,b) 实现。主函数读入两个正整数,输出它们的 GCD 和 LCM。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
int gcd(int a, int b) {
    while (b != 0) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}
int lcm(int a, int b) {
    return a / gcd(a, b) * b;  // 先除后乘防溢出
}
int main() {
    int a, b;
    cin >> a >> b;
    cout << gcd(a, b) << " " << lcm(a, b) << endl;
}
思路:gcd 用辗转相除(见例3);lcm 用公式 lcm = a / gcd * b——先除后乘可有效避免 a*b 溢出。
验证:gcd(48, 18) = 6,lcm = 48 / 6 × 18 = 144 ✅(48 与 18 的最小公倍数确实是 144)。
易错:写成 a * b / gcd 时,a、b 较大(如 10⁹ 量级)会先溢出再除,结果错误。
C4挑战题

题目:定义函数 int countPrime(int n),统计 1 到 n 之间有多少个素数。要求利用 isPrime 函数。

点击查看参考答案
C++参考代码
#include <iostream>
using namespace std;
bool isPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0) return false;
    return true;
}
int countPrime(int n) {
    int cnt = 0;
    for (int i = 2; i <= n; i++)
        if (isPrime(i)) cnt++;
    return cnt;
}
int main() {
    cout << countPrime(10) << endl;  // 4 (2,3,5,7)
}

易错:isPrime 从 1 开始判断会把 1 当成素数;i*i <= n 写成 i <= n 在大数时会超时。

思路:复用 isPrime(判断素数:n < 2 返回 false,从 2 试除到 n 的平方根);再循环 1~n 统计:for (i = 1; i <= n; i++) if (isPrime(i)) cnt++;
验证:1~100 中有 25 个素数(2 3 5 7 … 97),countPrime(100) = 25 ✅
考点:函数分层——小函数负责单一职责,再组合成大功能。