Skip to content

第 29 课:认识函数 ​

蛙蛙发现最近的编程学习越来越复杂了,有些编程题目甚至要用到上百行代码,如果出现错误,去调试的话非常麻烦。他担心,如果以后代码越来越多,维护起来会越来越困难。

氪町博士准备教会他一项实用编程技能,帮他将一个程序分成多个模块,每个模块负责特定的任务。比如构建一个学生管理系统,就可以将这个系统划分为求平均分、找最高分、找最低分的模块,这样不仅能更好地维护每个功能,还可以重复使用这些功能,以减少代码的编写,并且可以让代码结构更清晰,提高可读性,易于理解和调试。

像这种编程技能,我们称为函数。

认识函数 ​

我们可以将函数理解成:为了实现某种功能而组合起来的多个指令。

例如我们可以把"做饭"当作一个函数,它里面包含了淘米、加水、煮饭等多条指令。

我们下达"做饭"指令(调用函数),就相当于执行了淘米、加水、煮饭等多条指令。

场景实现 ​

假设我们就在主函数内调用自定义的"做饭"函数:

cpp
#include <iostream>
using namespace std;
int main()
{
    zuofan(); // 这里用 zuofan 代表做饭函数
    return 0;
}

但执行程序后,会发现程序报错,显示:'zuofan' was not declared in this scope.

这是因为我们使用了一个未定义的函数,我们还需对其进行定义:

cpp
#include <iostream>
using namespace std;
void zuofan() // 定义 zuofan 函数
{
    cout << "淘米" << endl; // 执行"淘米"指令
    cout << "加水" << endl; // 执行"加水"指令
    cout << "煮饭" << endl; // 执行"煮饭"指令
}
int main()
{
    zuofan();
    return 0;
}

运行结果:

淘米
加水
煮饭

解释:在主函数内调用 zuofan 函数,相当于执行了 zuofan 内的指令。该函数里面有三条指令,分别是输出"淘米""加水""煮饭",并且换行显示,其中定义 zuofan 函数的 void 是数据类型,表示这个函数不会返回任何值。

听了这句解释,蛙蛙有些不解:没有返回值,为什么还会输出"淘米""加水""煮饭"呢?

氪町博士告诉他,这是因为执行 zuofan 函数,就相当于执行做饭里面的程序,里面的程序就是直接输出这三个词,但是并没有返回任何值。我们再来看一个有返回值的例子,并详细了解函数的结构。

29.1 函数的定义 ​

函数的定义格式如下:

数据类型 函数名 ()   // 函数头
{
    函数体;          // 执行语句(包括返回值)
}

需要注意的是,在 C++ 中,函数不允许嵌套定义,即不能在一个函数内部定义另一个函数,不过函数允许嵌套调用。

样例程序 ​

cpp
#include <iostream>
using namespace std;
int jc(){
    int s = 1;
    for (int i = 1; i < 6; i++) {
        s *= i;
    }
    return s;
}
int main() {
    cout << jc() << endl;
    return 0;
}

关于本段程序的详细解释如下。

  1. 函数的功能:函数 jc 是我们自己定义的函数,它的功能是计算 5 的阶乘(即 1 * 2 * 3 * 4 * 5),最后再返回这个阶乘的结果。

  2. 函数的定义:int jc(){}。

    a. int:表示函数的返回类型,它表明函数 jc 执行完毕后会返回一个 int 类型的值。

    b. jc:是函数的名称,这里用于表示阶乘。

    c. ():括号内为空,表示函数 jc 不需要任何参数,也就是说,在调用 jc 函数时,不需要给它传递任何数据(后面会提到有参数的情况,这里先做了解)。

    d. {}:大括号内的是函数要执行的具体代码,这里的代码是用来计算 5 的阶乘,并将结果保存到变量 s 中。

  3. 返回结果:return s;。

    a. return 是一个关键字,用于将函数的执行结果返回给调用者。

    b. 这里返回变量 s 的值,也就是 5 的阶乘的结果 120,所以在主函数内输出 jc 函数,相当于输出 120。

29.2 形参与实参 ​

修改样例程序如下:

cpp
#include <iostream>
using namespace std;
int jc(int n) // 添加参数(即小括号内声明变量)
{
    int s = 1;
    for (int i = 1; i <= n; i++) {
        s *= i;
    }
    return s;
}
int main()
{
    cout << jc(5) << endl;
    return 0;
}
  1. 对于 int jc(int n):这里的变量 n 叫作形式参数(简称形参),主函数内 jc(5) 中的数 5 叫作实际参数(简称实参)。

  2. 形式参数相当于函数的入口,我们可以通过形参将一些数值(实参)传给函数,来丰富函数的功能。

  3. 多个形式参数之间要用","隔开,并且都要指明数据类型。

    例如:(int a, char b, double c, int d)。

总结:形式参数(简称形参)是在函数定义时声明的参数,用于指定函数在被调用时需要接收的数据类型和数量;实际参数(简称实参)是在函数调用时传递给函数的具体数据,用于为函数的形参提供实际的值。

例 1:阶乘的和 ​

输入一个数 n,求 1! + 2! + … + n! 的结果。

【输入格式】输入一行,一个正整数 n,n < 20。

【输出格式】输出一行,一个整数,表示 1! + 2! + … + n! 的结果。

【输入样例】3

【输出样例】9

解析

  1. 定义阶乘函数。

    a. 通过 long long jc(int x) 定义一个名为 jc 的函数,这个函数可以接收一个整型参数 x,并返回一个 long long 类型的值,long long 类型可以用于存储比较大的阶乘结果,避免数据溢出。

    b. 计算阶乘:通过 for 循环,从 1 开始,每次循环 i 递增 1,直到 i 等于 x 为止,在每次循环中,j = j * i; 将当前的 j 值乘以 i,更新 j 的值,从而实现阶乘的计算。

    c. 返回结果:return j; 将计算得到的阶乘结果返回给调用者。

  2. 主函数求阶乘和:利用 for 循环进行阶乘累加,从 1 开始,每次循环 i 递增 1,直到 i 等于 n,在每次循环中,sum += jc(i); 调用 jc 函数计算 i 的阶乘,并将结果累加到 sum 中,这样便可以求出 1 到 n 的阶乘的累加和。

参考代码

cpp
#include <iostream>
using namespace std;
long long jc(int x) // 求阶乘
{
    long long j = 1;
    for (int i = 1; i <= x; i++)
        j = j * i;
    return j; // 返回某数的阶乘结果
}
int main()
{
    int n;
    long long sum = 0;
    cin >> n;
    for (int i = 1; i <= n; i++)
        sum += jc(i); // 计算阶乘和
    cout << sum;
    return 0;
}

29.3 函数的声明 ​

当程序中有多个函数时,如果都将函数定义在 main 之前,则整个程序会相当混乱。

为了避免这种情况,我们可以在 main 之前先声明函数,在 main 函数之后统一定义,函数声明的格式如下:

数据类型 函数名 (形式参数);   // 也就是函数头之后加了一个分号(;)

比如我们可以使用 long long jc(int x); 对 jc 函数进行声明。这样,main 函数在调用 jc 函数时就知道它的存在和参数、返回值类型。

接下来我们通过一个例题加深理解。

例 2:计算结果 ​

输入 N 行(N ≤ 20)数据 a、b、c,分别计算表达式 ab - 4ac 的值。

【输入格式】输入 N+1 行,第 1 行输入一个数 N,后面 N 行每行分别输入三个整数 a、b、c,用空格隔开,a、b、c 均小于 10000。

【输出格式】输出 N 行,每行输出一个数,为式子的结果。

【输入样例】

2
1 2 3
1 3 5

【输出样例】

-10
-17

解析

  1. 函数声明:声明一个函数 js,含有三个 int 类型的形式参数。代码如下:
cpp
void js(int a, int b, int c);
  1. 调用函数:在主函数里面调用此函数。
cpp
int main()
{
    ……
    js(a, b, c);
    ……
}
  1. 定义 js 函数:js 函数到目前为止并没有什么实际意义,我们还需要定义此函数的功能,功能就是输出 a * b - 4 * a * c。
cpp
void js(int a, int b, int c)
{
    cout << a * b - 4 * a * c << endl;
}

参考代码

cpp
#include <iostream>
using namespace std;
void js(int a, int b, int c);
int main()
{
    int n, a, b, c;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a >> b >> c;
        js(a, b, c);
    }
    return 0;
}
void js(int a, int b, int c)
{
    cout << a * b - 4 * a * c << endl;
}

29.4 函数的值传递和引用传递 ​

蛙蛙编写了一个程序,他的目的是交换 a 和 b 的值,代码如下:

cpp
#include <iostream>
using namespace std;
void sp(int x, int y)
{
    int t;
    t = x; x = y; y = t;
}
int main()
{
    int a, b;
    cin >> a >> b;
    sp(a, b);
    cout << a << " " << b;
    return 0;
}

这是因为刚才蛙蛙编写的 sp 函数,只是交换了形参 x 和 y 的数值,并未影响到变量 a 和 b 的初始值。若在 sp 函数内输出 x 和 y 的值,会发现已经发生了改变,因为这里的函数参数是值传递。

函数的值传递 ​

蛙蛙刚刚编写的函数,在函数调用时,只是将实参的值复制到形参里,因此对形参的任何操作都不会改变实参。

比如 sp 函数只是交换了 x、y 的值,实参 a、b 未受影响。

函数的引用传递 ​

若想通过 sp 函数改变实参,可以使用引用传递。

在函数调用时,形参是实参的引用,也就是形参和实参指向同一块内存地址,函数内部对形参的操作会直接影响到实参。

引用传递格式:sp(int& x, int& y) // 在形参前加上取地址符 &

也可以写成这样:sp(int &x, int &y) // 两者没有本质区别

蛙蛙将 sp 函数部分进行修改,加上 & 后再编译运行,果然可以交换 a 和 b 的数值了。

例 3:质数判断 ​

输入 n 个正整数,请你判断每个数是不是质数。

【输入格式】输入 n + 1 行。

第 1 行,一个正整数 n,表示有 n 组数据,n 和输入的数据均小于 100000。

接下来 n 行,每行一个正整数 m,表示数据 m 是否为质数,是则输出 yes,否则输出 no。

【输出格式】输出 n 行,每行一个字符串 yes 或者 no。

【输入样例】

3
1
17
51

【输出样例】

no
yes
no

解析

为了检验自己有没有掌握刚学的内容,蛙蛙准备采用最新学习的知识来完成这个任务。

  1. 函数声明:他声明了一个名为 prime 的函数,用于判断接收的参数是不是质数。代码如下:
cpp
void prime(int &x);
  1. 质数判断

    a. 如果 x 小于 2,由于质数是大于 1 且只能被 1 和自身整除的正整数,所以 x 不是质数,输出 no 并使用 return 语句提前结束函数。

    b. 通过 for 循环判断大于或等于 2 的数,从 2 开始,每次循环 i 递增 1,直到 i * i 大于 x 为止。这样做的原因是,如果一个数 x 不是质数,那么它一定可以分解为两个因数,其中一个数小于 x 的平方根,所以只需要检查到 x 的平方根即可,降低时间复杂度。

    c. 在循环中,判断 x 是否能被 i 整除。如果 x 能被 i 整除,说明 x 不是质数,输出 no 并使用 return 语句提前结束函数;如果循环结束后都没有找到能整除 x 的数,说明 x 是质数,输出 yes 并使用 return 语句结束函数。

参考代码

cpp
#include <iostream>
using namespace std;
void prime(int &x);
int main()
{
    int n, a, i;
    cin >> n;
    while (n--)
    {
        cin >> a;
        prime(a);
    }
    return 0;
}
void prime(int &x)
{
    if (x < 2)
    {
        cout << "no" << endl;
        return;
    }
    for (int i = 2; i * i <= x; i++)
    {
        if (x % i == 0)
        {
            cout << "no" << endl;
            return;
        }
    }
    cout << "yes" << endl;
    return;
}

29.5 数组作为函数参数 ​

数组可以作为函数参数,其中数组名是该数组在内存中的首地址,将数组名作为参数传给函数,就相当于引用传递了。

在被调用函数中对数组元素值进行改变,主函数中实参数组的相应元素值也会改变。接下来我们以实现选择排序的函数为例子来说明数组作为函数参数的应用。

例 4:学生成绩排序 ​

现在需要对一些班级的学生成绩进行处理。

输入多组数据,每组数据的第一个数 x 表示这个班级中参与统计的学生人数(2 ≤ x ≤ 100),接下来输入 x 个整数,表示这些学生的考试成绩(成绩范围为 0 到 100 分,包含 0 和 100)。

对于每组数据输出一行,将该班级学生的成绩按照从大到小的顺序排列好后输出,成绩用空格隔开。

【输入格式】输入 t + 1 行。

第 1 行输入一个整数 t,表示有 t 组数据。

之后的每一行,第一个数为 x,接下来 x 个数为该班级学生的考试成绩。

【输出格式】输出 t 行,每行为一个班级学生成绩从大到小排序的序列。

【输入样例】

2
8 88 76 92 65 80 78 95 82
5 70 70 60 80 60

【输出样例】

95 92 88 82 80 78 76 65
80 70 70 60 60

解析

  1. 在主函数中先读取 t 组输入数据:首先读取一个整数 t,它代表数据的组数,然后读取学生人数 x 及 x 个学生的成绩并存储到数组中。

  2. 在选择排序的函数中循环处理每组数据。对于每组数据,执行以下操作:

    a. 对数组中的成绩进行从大到小的排序;

    b. 输出排序后的成绩序列。

参考代码

cpp
#include <iostream>
using namespace std;
void selectSort(int a[], int x); // 声明选择排序函数
int a[101], t, x;
int main()
{
    cin >> t;
    while (t--)
    {
        cin >> x;
        for (int i = 0; i < x; i++)
            cin >> a[i];
        selectSort(a, x);
    }
    return 0;
}
void selectSort(int a[], int x) // 定义选择排序函数
{
    for (int i = 0; i < x - 1; i++)
    {
        int maxIndex = i; // 假设当前位置为最大值的索引
        for (int j = i + 1; j < x; j++)
        {
            // 如果找到比当前最大值更大的元素,更新最大值的索引
            if (a[j] > a[maxIndex])
            {
                maxIndex = j;
            }
        }
        // 如果最大值的索引不是当前位置,交换它们
        if (maxIndex != i)
        {
            int temp = a[i];
            a[i] = a[maxIndex];
            a[maxIndex] = temp;
        }
    }
    // 输出排序好的数组
    for (int i = 0; i < x; i++)
        cout << a[i] << " ";
    cout << endl;
    return;
}

基于 MIT 协议发布