跳转至

『C语言教程』4. 函数

T09:06:00+08:00

〇、什么是函数

一天老师给了你一个数 a ,要你求出 a 的阶乘,于是你在 main 函数中写了下面的代码:

C
1
2
3
4
5
6
7
int x, ans = 1;
scanf("%d", &x);

int i;
for (i = 2; i <= x; i++) ans *= i;

printf("%d\n", ans);

第二天,老师突发奇想,想要你求出 a 的阶乘的阶乘,于是你在 main 函数中写了下面的代码:

C
int x
scanf("%d", &x);

int i, ans1 = 1;
for (i = 2; i <= x; i++)    ans1 *= i;

int ans2 = 1;
for (i = 2; i <= ans1; i++) ans2 *= i;

printf("%d\n", ans2);

第三天,老师再次突发奇想,想要你求 a 的阶乘的阶乘的阶乘

......

我们发现,下面这两段代码,十分相似:

C
for (i = 2; i <= x; i++)    ans1 *= i;
C
for (i = 2; i <= ans1; i++) ans2 *= i;

它们的作用都是依照 某一个给定的数,计算其阶乘得到 结果

执行过程都是将从 2某一个给定的数 累乘起来得到一个 结果,我们可以把它们抽象成下图:

「C语言」函数_引入1.png

如果可以只写一次这一坨代码,在以后需要计算针对不同的 x 的结果时将 x 传递给这一坨代码(比如此例中,将 x 放到 for循环的条件中),那么一定很省事。

—— 于是,函数 产生了

函数 即为一段写好的可以反复使用代码。

一些概念

  • 函数的 参数:为传递给函数的值,在函数中可以使用。

在上例中即为 x

  • 函数的 返回值:即为函数的输出,调用函数所得到的值。(当然也有些函数没有返回值)

  • 调用 函数:将参数传给函数,运行这一坨代码。

函数原型

用来描述一个函数,提供了最基础的函数名、参数个数及对应类型、返回值类型的信息。

长这样:

Text Only
<返回值类型> <函数名>(<参数1类型>, <参数2类型>...);

比如:

C
int factorial(int);

这个函数原型说明了名为 factorial 的函数有一个 int 型的参数,其返回值是 int 型的。

一、使用 —— 调用函数

调用函数就是使用 函数名小括号 并在小括号中给定 参数

如果该函数有返回值,那么在函数执行完并返回了返回值后,这一段语句就会有一个值,等于返回值。

我们来看一些我们已经用了很久的函数:

1.1 sqrt 函数

函数原型:

C
double sqrt(double);

名为 sqrt,有一个 double 型参数,返回值为 double 类型

使用:

C
printf("%lf", sqrt(2));

这里使用了 sqrt(2) 调用了 sqrt 函数,并在小括号中写了 2 作为参数。

发起了调用后,当 sqrt 内所写的一坨代码运行完后得到结果(根号2)并作为返回值返回后,sqrt(2) 则具有一个值,等于该返回值(根号2),也就是说当函数得到 1.41421 的返回值后,这段话相当于这样:

Text Only
printf("%lf", 1.41421);

1.2 putchar 函数

函数原型:

C
void putchar(char);

名为 putchar,有一个 char 型参数,返回值为 void 类型

你可能会想,WTF??void 是什么类型。

记得上面提到有些函数是没有返回值的么,就像是这样:

「C语言」函数_引入2.png

也就是这个函数内的一坨代码只是执行特定的功能,而非要得到一个具体的结果:

C
putchar('\n');

这里使用了 putchar('\n') 调用了 putchar 函数,并在小括号中写了 \n 作为参数。

发起调用后,会运行 putchar 内的一坨代码,这一坨代码的功能则是将参数提供的字符打印出来。

二、创造 —— 定义函数

我们其实早就在定义函数了,来看看主函数 main

C
1
2
3
4
int main() {

    return 0;
}

我们写了这一坨代码就是定义了主函数,程序每次运行的时候会调用主函数。

定义函数的格式是这样的:

C
1
2
3
函数返回值 函数名(参数1类型 参数1名, 参数2类型 参数2名...) {
    // ...
}

这段东西分为两个部分:

  • 函数头 函数返回值 函数名(参数1类型 参数1名, 参数2类型 参数2名...)

长得和函数原型很像,不过给每个参数都起了个名。

  • 函数体 { \\... }

大括号扩者的,我们所说的那 "一坨代码"。

我们再回来看主函数:

C
1
2
3
4
int main() {

    return 0;
}

像函数原型一样,int 说明这个函数返回值是 int 型的,括号里是空,也就是没有参数。

return 0 是一个语句,含义是 以0为返回值 退出函数

一般程序正常运行退出返回值都是0,如果不是0则是发生了 运行时错误(Runtime Error)(比如运算过程中作为除数的变量值为0啊等等)。

我们来一步步动手创造函数吧。

Lv1 参数

定义:

C
1
2
3
void printSth() {
    printf("Hello GuGuGu.\n");
}

返回值为 void 类型(无返回值),无参数,名为 printSth

调用:

C
1
2
3
4
5
6
int main() {
    printSth();
    printSth();
    printSth();
    return 0;
}

调用了3次这个函数,结果应该是打印出来三行 Hello GuGuGu.


假如我想添加一个参数 n 让输出的 Gu 的数量取决于这个 n 呢?

定义:

C
1
2
3
4
5
void printSth(int n) {
    printf("Hello ");
    while (n--) printf("Gu");
    puts(".");
}

puts 函数的作用是输出字符串并换行。

调用:

C
1
2
3
4
5
int main() {
    int i;
    for (i = 1; i <= 3; i++) printSth(i);
    return 0;
}

调用了3次,传入的参数分别为 1 2 3,结果应该是打印出来:

C
1
2
3
Hello Gu.
Hello GuGu.
Hello GuGuGu.

Lv2 返回值

假如说我想编写一个函数来计算一个数的阶乘,并将这个结果返回。

定义:

C
1
2
3
4
5
int fac(int x) {
    int i, ans = 1;
    for (i = 2; i <= x; i++) ans *= i;
    return ans;
}

返回值为 int 类型,名为 fac 有一个 int 型的参数。

调用:

C
1
2
3
4
5
int main() {
    int i;
    for (i = 1; i <= 5; i++) printf("%d! = %d\n", i, fac(i));
    return 0;
}

在执行5次 printf 语句的时候分别都调用了1次,传入的参数分别为 1...5

每次调用时,将 i 的值取出赋给 x ,并执行函数体,计算得到 ans 后通过 return 返回,于是 fac(i) 有一个值(即为返回值),将其打印出来。

Lv3 递归

我们不难得到这样一个数学规律(或者说递推公式): $$ \begin{cases} \Large n! = n \times (n-1)! \ \Large 1! = 1 \end{cases} $$ 注意,函数是可以自己调用自己的,那么我们可以改造下我们求阶乘的函数:

C
1
2
3
4
int fac(int x) {
    if (x == 1) return 1;
    return x * fac(x-1);
}

假如传入 5,函数的调用与返回情况应如下图所示:

「C语言」函数_递归.png


【差不多?】

Text Only
1
2
3
scanf(): int scanf(char *format[, argument, ...]);
printf(): int printf(const char *format, ...);
//  返回值为int 表示正确输入输出的参数个数。

评论