9 自定义函数与应用

正在加载视频播放器…

GESP C++ 三级:自定义函数与应用精讲

核心目标:理解函数在三级中的定位,掌握函数的定义、声明与调用,理解形参与实参、值传递、返回值、作用域,了解静态局部变量,能读懂函数相关的程序阅读题,并在编程题中合理使用函数组织代码。
函数是“打包一段逻辑,起个名字,需要时调用”;三级的要求是“会用、会读”,能结合数组和字符串解决实际问题。

一、函数的基本概念

1. 为什么要用函数

函数是把一段完成特定任务的代码“打包”起来,起一个名字,需要的时候调用它。好处有三:

  1. 复用:同一段逻辑不用重复写多次,调用函数名即可。
  2. 清晰:把复杂的程序拆成一个个小任务,每个函数只做一件事,代码更好读。
  3. 好改:逻辑要修改时,只改函数内部一处即可,不用到处改。

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三级中是“组织代码的工具”,而不是独立的知识模块。

  1. 三级学函数,重点是“会用、会读”,不是系统学习四级的值传递、引用传递、递归。
  2. 函数定义在 main 之后时,必须在 main 之前写声明。
  3. 返回类型和 return 的值必须匹配,void 函数不能返回值。
  4. 参数列表中每个参数都必须单独写类型。
  5. 值传递:函数内修改形参,不影响外面的实参。
  6. 数组参数:传的是首地址,函数内修改数组元素会影响原数组。
  7. 局部变量只在函数内有效,全局变量所有函数可访问,同名时局部遮蔽全局。
  8. 静态局部变量只初始化一次,值在函数调用之间保留。
  9. C++ 不允许在函数内部定义函数。
  10. 三级编程题中,函数用于把重复逻辑抽出来,让 main 更清晰,常与数组、字符串、模拟法结合。

复习建议:重点练习“函数定义是否正确”“值传递的结果”“作用域判断”“静态局部变量”四类程序阅读题,再练习“用函数封装数组操作”的编程题。考试中先看清函数返回类型和参数类型,再判断值传递/数组传递的影响,最后逐行跟踪变量变化。