9 自定义函数与应用
正在加载视频播放器…
GESP C++ 三级:自定义函数与应用精讲
核心目标:理解函数在三级中的定位,掌握函数的定义、声明与调用,理解形参与实参、值传递、返回值、作用域,了解静态局部变量,能读懂函数相关的程序阅读题,并在编程题中合理使用函数组织代码。
函数是“打包一段逻辑,起个名字,需要时调用”;三级的要求是“会用、会读”,能结合数组和字符串解决实际问题。
一、函数的基本概念
1. 为什么要用函数
函数是把一段完成特定任务的代码“打包”起来,起一个名字,需要的时候调用它。好处有三:
- 复用:同一段逻辑不用重复写多次,调用函数名即可。
- 清晰:把复杂的程序拆成一个个小任务,每个函数只做一件事,代码更好读。
- 好改:逻辑要修改时,只改函数内部一处即可,不用到处改。
2. 函数的基本结构
返回类型 函数名(参数列表) {
函数体;
return 返回值; // 如果返回类型不是 void
}
示例:
int add(int a, int b) { // 返回类型 int,函数名 add,参数 a 和 b
return a + b; // 返回 a+b 的结果
}
3. 三级必须掌握的函数类型
| 类型 | 特点 | 示例 |
|---|---|---|
| 有返回值、有参数 | 最常用 | int add(int a, int b) |
| 有返回值、无参数 | 返回一个计算结果 | int getMax() |
无返回值(void)、有参数 |
只做操作,不返回结果 | void printArray(int a[], int n) |
无返回值(void)、无参数 |
最简单 | void sayHello() |
二、函数的定义、声明与调用
1. 定义与调用的顺序
函数必须先定义或声明,才能调用。 如果函数定义写在 main 函数之后,必须在 main 之前写函数声明。
#include <bits/stdc++.h>
using namespace std;
// 函数声明(写在 main 之前)
int add(int a, int b);
int main() {
int result = add(3, 5); // 调用函数
cout << result << endl; // 输出 8
return 0;
}
// 函数定义(写在 main 之后)
int add(int a, int b) {
return a + b;
}
如果函数定义直接写在 main 之前,则不需要单独声明:
int add(int a, int b) { // 定义即声明
return a + b;
}
int main() {
cout << add(3, 5) << endl;
return 0;
}
2. 真题示例(2025年9月 第5题)
要正确定义一个返回两个整数中较大值的函数
max,应该使用( )。
A)void max(int a, int b) { return a > b ? a : b; }
B)int max(int a, int b) { if (a > b) return a; else return b; }
C)int max(a, b) { if (a > b) return a; else return b; }
D)void max(a, b) { cout << (a > b ? a : b); }
答案:B
逐项分析:
- A 错在
void:函数要返回结果,返回类型必须是int,不能是void。虽然写了return,但void函数不能返回值。 - B 正确:返回类型
int,参数类型明确,有return返回结果。 - C 错在参数没写类型:C++ 中每个参数都必须单独声明类型,
int a, b才正确。 - D 错在两点:
void不返回结果,且参数没写类型。
这道题是三级函数题的最典型考法:一眼判断返回类型和参数类型是否正确。
三、形参与实参
1. 基本概念
int add(int a, int b) { // a 和 b 是形参(形式参数)
return a + b;
}
int main() {
int x = 3, y = 5;
cout << add(x, y) << endl; // x 和 y 是实参(实际参数)
cout << add(10, 20) << endl; // 实参也可以是常量
cout << add(x + 1, y * 2) << endl; // 实参也可以是表达式
return 0;
}
| 概念 | 位置 | 说明 |
|---|---|---|
| 形参 | 函数定义/声明的参数列表中 | 函数内部使用的“占位变量” |
| 实参 | 函数调用时传入的值 | 可以是变量、常量、表达式 |
2. 形参与实参的关键规则
- 形参和实参的个数必须一致。
- 形参和实参的类型必须匹配(或可以隐式转换)。
- 形参和实参的名字可以不同,互不影响。
- 函数调用时,实参的值“拷贝”给形参。
3. 真题示例
关于形参与实参,下面说法正确的是( )。
A) 形参写在函数定义或声明的参数列表中
B) 实参只能是变量,不能是常量或表达式
C) 形参和实参必须使用相同的名字
D) 函数调用结束后,实参一定会被修改
答案:A
B 错:实参可以是常量、表达式。C 错:名字可以不同。D 错:值传递下实参不会被修改。
四、值传递:三级最核心的易错点
1. 值传递的含义
C++ 默认的参数传递方式是值传递:实参的值被拷贝一份给形参,函数内部对形参的修改不会影响实参。
void change(int x) {
x = 100; // 修改的是形参 x,不是外面的实参
}
int main() {
int n = 7;
change(n);
cout << n; // 输出 7,不是 100
return 0;
}
记忆口诀:值传递,拷贝一份,里面改了外面不变。
2. 数组作为参数的特殊性(三级重点)
数组作为参数时,传递的是数组首地址,函数内部对数组元素的修改会影响原数组。这一点和普通变量不同,三级常考。
void modifyArray(int arr[], int n) {
arr[0] = 100; // 修改数组元素,会影响原数组
}
int main() {
int a[3] = {1, 2, 3};
modifyArray(a, 3);
cout << a[0]; // 输出 100
return 0;
}
| 参数类型 | 传递方式 | 函数内修改是否影响外面 |
|---|---|---|
int、double、char 等基本类型 |
值传递 | ❌ 不影响 |
数组 int arr[] |
传首地址 | ✅ 影响 |
string 对象 |
值传递(拷贝整个字符串) | ❌ 不影响 |
3. 真题示例
下面代码执行后的输出是( )。
void change(int x) { x = 100; } int main() { int n = 7; change(n); cout << n; }A) 7 B) 100 C) 0 D) 不能通过编译
答案:A
五、返回值
1. return 的两种作用
int add(int a, int b) {
return a + b; // 作用1:结束函数,作用2:把值返回给调用者
}
- 结束函数:
return执行后,函数立即结束,后面的代码不再执行。 - 返回值:把
return后面的值传给调用者。
2. 返回类型与 return 的匹配
int f1() { return 3.14; } // 返回 int,3.14 被截断为 3
double f2() { return 5; } // 返回 double,5 变成 5.0
void f3() { return; } // void 函数可以 return,但不能返回值
3. 没有 return 会怎样
如果函数声明了返回类型但不是 void,却没有 return,函数会返回一个不确定的值,程序行为不可预测。
int f() {
// 没有 return
}
int main() {
cout << f() << endl; // 输出不确定的值
}
4. 函数只能返回一个值
// 错误:不能同时返回两个值
int f() {
return a, b; // 逗号表达式,实际返回 b
}
如果需要返回多个结果,可以通过数组参数在函数内修改,或使用其他方式(四级会学引用传递,三级不需要)。
5. 真题示例
以下关于C++函数的描述,正确的是( )。
A) 函数必须要有参数。
B) 函数通过return语句只能返回一个值。
C)main函数可以被其他函数调用。
D) 函数的定义可以直接嵌套。
答案:B
A 错:函数可以没有参数。C 错:main 函数不能被其他函数调用。D 错:C++ 不允许在函数内部定义另一个函数。
六、作用域
1. 局部变量
在函数内部或代码块 {} 内声明的变量,只在声明它的范围内有效。
void f() {
int a = 10; // 局部变量,只在 f 内部有效
}
int main() {
// cout << a; // 错误:a 不在这里
return 0;
}
2. 全局变量
在所有函数外部声明的变量,整个程序都可以访问。
int g = 100; // 全局变量
void f() {
cout << g << endl; // 可以访问
}
int main() {
cout << g << endl; // 可以访问
return 0;
}
3. 同名变量的遮蔽
局部变量和全局变量同名时,函数内部优先使用局部变量(“遮蔽”全局变量)。
int x = 10; // 全局
void show() {
int x = 3; // 局部,遮蔽了全局的 x
cout << x; // 输出 3
}
int main() {
show(); // 输出 3
cout << " " << x; // 输出 10(全局的 x)
return 0;
}
输出:3 10。
4. 循环变量的作用域
for (int i = 0; i < 5; i++) { }
// cout << i; // 错误:i 只在循环内有效
5. 真题示例(2025年9月 第10题)
以下关于C++变量作用域的说法,错误的是( )。
A) 在for循环语句中声明的变量,其作用域仅限于该循环体内。
B) 在函数内部声明的变量(局部变量),仅在函数内部有效。
C) 在所有函数外部声明的变量,在整个程序中都有效。
D) 不同函数中的局部变量可以同名,它们代表不同的内存单元。
答案:C
C 错在“整个程序中都有效”——全局变量可以被多个函数访问,但如果某个函数内有同名的局部变量,局部变量会遮蔽全局变量,该函数内访问的是局部变量,不是全局变量。
七、静态局部变量(三级了解)
1. 基本概念
用 static 修饰的局部变量,只在第一次进入函数时初始化,之后函数调用之间保持值不变。
int nextNumber() {
static int x = 0; // 只初始化一次
x++;
return x;
}
int main() {
cout << nextNumber() << endl; // 1
cout << nextNumber() << endl; // 2
cout << nextNumber() << endl; // 3
return 0;
}
2. 普通局部变量 vs 静态局部变量
| 对比项 | 普通局部变量 | 静态局部变量 |
|---|---|---|
| 初始化次数 | 每次函数调用都初始化 | 只在第一次调用时初始化 |
| 函数调用之间 | 值不保留,下次调用重新开始 | 值保留 |
| 作用域 | 函数内部 | |
| 生命周期 | 函数调用期间 | 整个程序运行期间 |
3. 真题示例
下面代码执行后的输出是( )。
int nextNumber() { static int x = 0; x++; return x; } int main() { int a = nextNumber(); int b = nextNumber(); cout << a << " " << b; }A) 0 0 B) 1 1 C) 1 2 D) 2 2
答案:C
八、函数在三级编程题中的应用
1. 用函数组织数组操作
三级编程题中,函数主要用于把重复的逻辑抽出来,让 main 函数更清晰。
示例:进制转换函数
#include <iostream>
#include <string>
using namespace std;
string convert(int num, int base) {
string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (num == 0) return "0";
string result = "";
while (num > 0) {
int r = num % base;
result = digits[r] + result;
num /= base;
}
return result;
}
int main() {
int num, base;
cin >> num >> base;
cout << convert(num, base) << endl;
return 0;
}
这里 convert 函数把进制转换的逻辑封装起来,main 只需要负责输入输出。
2. 用函数实现数组的最值查找
int findMax(int arr[], int n) {
int maxv = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > maxv) maxv = arr[i];
}
return maxv;
}
int main() {
int n;
cin >> n;
int a[1005];
for (int i = 0; i < n; i++) cin >> a[i];
cout << findMax(a, n) << endl;
return 0;
}
注意:数组作为参数时,需要同时传递数组名和长度 n。
3. 用函数实现数位处理
int digitSum(int n) {
int sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
return sum;
}
4. 函数在模拟法中的应用
“数组清零”这类模拟题,可以把“找最大非零元素”“找最小非零元素”“检查是否全零”分别写成函数,让主循环更清晰。
bool isAllZero(int a[], int n) {
for (int i = 0; i < n; i++) {
if (a[i] != 0) return false;
}
return true;
}
int findMaxIdx(int a[], int n) {
int idx = -1;
for (int i = 0; i < n; i++) {
if (a[i] > 0) {
if (idx == -1 || a[i] >= a[idx]) idx = i;
}
}
return idx;
}
int findMinNonZero(int a[], int n) {
int minv = 1000000000;
for (int i = 0; i < n; i++) {
if (a[i] > 0 && a[i] < minv) minv = a[i];
}
return minv;
}
主循环就变得很简洁:
while (!isAllZero(a, n)) {
int maxIdx = findMaxIdx(a, n);
int minVal = findMinNonZero(a, n);
a[maxIdx] -= minVal;
ops++;
}
这种“把大问题拆成小函数”的写法,是三级编程题中函数应用的典型方式。
九、易错点汇总
1. 返回类型与 return 不匹配
void f() { return 5; } // ❌ void 函数不能返回值
int g() { return; } // ❌ int 函数必须返回一个 int 值
2. 忘记写参数类型
int max(a, b) { } // ❌ 参数必须写类型
int max(int a, int b) { } // ✅
3. 值传递的误解
void change(int x) { x = 100; }
int main() { int n = 7; change(n); cout << n; } // 输出 7,不是 100
4. 数组参数忘记传长度
void print(int arr[]) { // ❌ 不知道数组有多长
void print(int arr[], int n) { // ✅ 同时传长度
5. 函数定义嵌套
int main() {
int f() { return 1; } // ❌ C++ 不允许在函数内定义函数
}
6. 作用域混淆
for (int i = 0; i < 5; i++) { }
cout << i; // ❌ i 只在循环内有效
7. 局部变量遮蔽全局变量
int x = 10;
void f() {
int x = 3;
cout << x; // 输出 3,不是 10
}
8. 静态局部变量误解
int f() {
static int x = 0; // 只初始化一次
x++;
return x;
}
// 第一次调用返回 1,第二次返回 2,不是每次都返回 1
9. 函数声明和定义不一致
int add(int a, int b); // 声明
double add(int a, int b) { } // ❌ 定义返回类型与声明不同
10. main 函数被调用
int main() {
main(); // ❌ 不要这样写,会导致无限递归
}
十、阅读程序写结果
题目 1
int add(int a, int b) { return a + b; }
int main() { cout << add(3, 5); }
输出:
8
题目 2
void change(int x) { x = 100; }
int main() { int n = 7; change(n); cout << n; }
输出:
7
题目 3
int x = 10;
void show() { int x = 3; cout << x; }
int main() { show(); cout << " " << x; }
输出:
3 10
题目 4
int nextNumber() { static int x = 0; x++; return x; }
int main() { int a = nextNumber(); int b = nextNumber(); cout << a << " " << b; }
输出:
1 2
题目 5
int f(int x) { if (x > 0) return 1; return -1; }
int main() { cout << f(0) << " " << f(5); }
输出:
-1 1
题目 6
void modify(int arr[], int n) { arr[0] = 100; }
int main() { int a[3] = {1, 2, 3}; modify(a, 3); cout << a[0]; }
输出:
100
解释:数组参数传的是首地址,函数内修改会影响原数组。
十一、速查表
| 需求 | 写法 | 关键注意 |
|---|---|---|
| 函数定义 | 返回类型 函数名(参数列表) { 函数体 } |
返回类型与 return 要匹配 |
| 函数声明 | 返回类型 函数名(参数列表); |
定义在 main 之后时必须声明 |
| 函数调用 | 函数名(实参列表); |
实参个数、类型要匹配 |
| 无返回值 | void 函数名(...) |
不能 return 值 |
| 有返回值 | int/double/string 函数名(...) |
必须有 return |
| 值传递 | 默认方式 | 函数内修改不影响外面 |
| 数组参数 | int arr[] |
传首地址,函数内修改影响外面 |
| 局部变量 | 函数内声明 | 只在函数内有效 |
| 全局变量 | 函数外声明 | 所有函数可访问 |
| 静态局部变量 | static int x = 0; |
只初始化一次,值保留 |
| 函数内不能定义函数 | — | C++ 不允许嵌套定义 |
十二、总结
自定义函数在GESP三级中是“组织代码的工具”,而不是独立的知识模块。
- 三级学函数,重点是“会用、会读”,不是系统学习四级的值传递、引用传递、递归。
- 函数定义在
main之后时,必须在main之前写声明。 - 返回类型和
return的值必须匹配,void函数不能返回值。 - 参数列表中每个参数都必须单独写类型。
- 值传递:函数内修改形参,不影响外面的实参。
- 数组参数:传的是首地址,函数内修改数组元素会影响原数组。
- 局部变量只在函数内有效,全局变量所有函数可访问,同名时局部遮蔽全局。
- 静态局部变量只初始化一次,值在函数调用之间保留。
- C++ 不允许在函数内部定义函数。
- 三级编程题中,函数用于把重复逻辑抽出来,让
main更清晰,常与数组、字符串、模拟法结合。
复习建议:重点练习“函数定义是否正确”“值传递的结果”“作用域判断”“静态局部变量”四类程序阅读题,再练习“用函数封装数组操作”的编程题。考试中先看清函数返回类型和参数类型,再判断值传递/数组传递的影响,最后逐行跟踪变量变化。