Kodewick
第 8 课,共 30 课目录

函数

第 8 课,共 30 课 · 第 2 部分 · 函数和数组 · 约 15 分钟

同样的几行代码要在好几个地方用,就可以把它们写成一个函数,起个名字,用的时候叫它的名字就行。其实你早就在用函数了:printf 和 main 都是。下面是自己写的第一个函数:

main.c
1#include <stdio.h>2 3void say_hello(void) {4    printf("你好!\n");5    printf("欢迎光临。\n");6}7 8int main(void) {9    say_hello();10    say_hello();11    return 0;12}
输出
你好!
欢迎光临。
你好!
欢迎光临。

say_hello 写了一次,用了两次。main 里的 say_hello(); 叫调用:程序跳到函数里,把里面的语句跑完,再回到调用的地方接着往下走。

定义一个函数

一个函数由这几部分组成:

部分在 int square(int n) 里是什么
返回类型int函数算完交回来的值是什么类型
名字square调用时用的名字,起名规矩和变量一样
参数(int n)调用时要交给它的值,每个都写上类型
函数体{ ... }花括号里是函数要做的事

前面的 say_hello 返回类型是 void,意思是「什么也不交回来」;括号里也写 void,意思是「不要参数」。这和 int main(void) 里的 void 是一个意思。

参数

参数让同一个函数每次做的事稍有不同。定义里的 minutes 是参数,调用时括号里给的值是实参:调用时,实参的值放进参数里。

main.c
1#include <stdio.h>2 3void print_time(int minutes) {4    printf("%d 小时 %d 分钟\n", minutes / 60, minutes % 60);5}6 7int main(void) {8    print_time(135);9    print_time(90);10    print_time(45);11    return 0;12}
输出
2 小时 15 分钟
1 小时 30 分钟
0 小时 45 分钟

参数可以有好几个,用逗号隔开,每个都要写类型。调用时实参按顺序一个对一个:

main.c
1#include <stdio.h>2 3void print_line(int count, char symbol) {4    for (int i = 0; i < count; i++) {5        printf("%c", symbol);6    }7    printf("\n");8}9 10int main(void) {11    print_line(5, '*');12    print_line(3, '#');13    print_line(8, '-');14    return 0;15}
输出
*****
###
--------

返回值

return 把一个值交回给调用它的地方,函数也就到此结束。返回的值可以存进变量,也可以直接拿来用:

main.c
1#include <stdio.h>2 3int square(int n) {4    return n * n;5}6 7int main(void) {8    int a = square(4);9    printf("%d\n", a);10    printf("%d\n", square(3) + square(5));11    return 0;12}
输出
16
34

一个函数里可以有好几个 return,跑到哪个就在哪里结束:

main.c
1#include <stdio.h>2 3int larger(int a, int b) {4    if (a > b) {5        return a;6    }7    return b;8}9 10int main(void) {11    printf("%d\n", larger(7, 12));12    printf("%d\n", larger(30, 5));13    printf("%.1f\n", larger(3, 9) / 2.0);14    return 0;15}
输出
12
30
4.5

返回类型是 double 的函数,返回的就是小数:

先猜一猜

这段程序显示什么?

1#include <stdio.h>2 3double average(int a, int b) {4    return (a + b) / 2.0;5}6 7int main(void) {8    printf("%.1f\n", average(7, 8));9    return 0;10}

先声明,后使用

C 编译器从上往下读代码。读到一个调用时,它要已经知道这个函数长什么样:叫什么、要几个什么类型的参数、返回什么。函数写在 main 下面,编译器读到调用时还不认识它:

main.c
1#include <stdio.h>2 3int main(void) {4    printf("%.1f\n", half(5));5    return 0;6}7 8double half(int n) {9    return n / 2.0;10}
输出
main.c: In function 'main':
main.c:4:22: warning: implicit declaration of function 'half' [-Wimplicit-function-declaration]
    4 |     printf("%.1f\n", half(5));
      |                      ^~~~
main.c:4:16: warning: format '%.1f' expects argument of type 'double', but argument 2 has type 'int' [-Wformat=]
    4 |     printf("%.1f\n", half(5));
      |             ~~~^     ~~~~~~~
      |                |     |
      |                |     int
      |                double
      |             %.1d
main.c: At top level:
main.c:8:8: error: conflicting types for 'half'; have 'double(int)'
    8 | double half(int n) {
      |        ^~~~
main.c:4:22: note: previous implicit declaration of 'half' with type 'int()'
    4 |     printf("%.1f\n", half(5));
      |                      ^~~~

从上往下看:第 4 行,编译器第一次见到 half,先给出 warning(警告),说这个函数没声明过,只好猜它返回 int。到了第 8 行,它发现 half 其实返回 double,和刚才猜的对不上,这才是 error。中间那条关于 %.1f 的警告也是这么来的:编译器以为 half(5) 是 int。(新一些的 gcc 从 14 版起,第一条警告就直接算错误。)

办法有两个:把函数挪到 main 上面;或者在上面先写一行函数原型(也叫函数声明),说明它长什么样,函数本身放在后面:

main.c
1#include <stdio.h>2 3double half(int n);4 5int main(void) {6    printf("%.1f\n", half(5));7    return 0;8}9 10double half(int n) {11    return n / 2.0;12}
输出
2.5

原型就是函数的第一行,后面加一个分号,没有函数体。函数多了以后,常见的写法是把原型都放在文件开头,main 紧跟着,别的函数放在后面。其实 #include <stdio.h> 拿进来的,就是 printf 这些函数的原型。

传值

调用函数时,交给它的是实参的值,参数是一个新的变量,装着这个值的一份副本。所以函数里改参数,改的是副本,外面的变量不会变:

main.c
1#include <stdio.h>2 3void add_bonus(int score) {4    score = score + 10;5    printf("函数里:%d\n", score);6}7 8int main(void) {9    int score = 80;10    add_bonus(score);11    printf("main 里:%d\n", score);12    return 0;13}
输出
函数里:90
main 里:80

两个变量都叫 score,但它们是两个不同的变量。点单步看看:调用 add_bonus 时,内存里多出一个框,这是这次调用的栈帧,里面有它自己的 score。函数里加了 10,变的只是这个框里的 score;函数结束,这个框就没了,main 框里的 score 一直是 80。

想让外面的变量变,最简单的办法是让函数把新值返回来,由调用的地方自己存:

main.c
1#include <stdio.h>2 3int add_bonus(int score) {4    return score + 10;5}6 7int main(void) {8    int score = 80;9    score = add_bonus(score);10    printf("main 里:%d\n", score);11    return 0;12}
输出
main 里:90

小技巧: 让函数直接改外面的变量也有办法,要用到指针,第 16 课再讲。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

立方

写一个函数 cube,参数是一个 int,返回它的立方(自己乘自己三次)。程序要显示 27 和 125,各占一行。

要显示
27
125
exercise1.c
1#include <stdio.h>2 3// 在这里写 cube4 5int main(void) {6    printf("%d\n", cube(3));7    printf("%d\n", cube(5));8    return 0;9}
输出
点「运行」或按 Ctrl+Enter
练习 2

打折

写一个函数 discount,参数是价格 price(double)和折扣百分比 percent(int),返回打折后的价格。比如 discount(80.0, 25) 是 80 减去 25%,得 60。程序要显示 60.00 和 45.00。

要显示
60.00
45.00
exercise2.c
1#include <stdio.h>2 3double discount(double price, int percent) {4 5}6 7int main(void) {8    printf("%.2f\n", discount(80.0, 25));9    printf("%.2f\n", discount(50.0, 10));10    return 0;11}
输出
点「运行」或按 Ctrl+Enter
练习 3

存款没变

deposit 想给余额加上一笔钱,可 main 里的余额一直是 100。改好它:让 deposit 返回新的余额,main 把它存回 balance。程序要显示 余额:150。

要显示
余额:150
exercise3.c
1#include <stdio.h>2 3void deposit(int balance, int amount) {4    balance = balance + amount;5}6 7int main(void) {8    int balance = 100;9    deposit(balance, 50);10    printf("余额:%d\n", balance);11    return 0;12}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • 函数把一段代码起个名字,写一次,到处调用;调用完回到调用的地方接着走。
  • 定义函数要写返回类型、名字、参数(每个都带类型)和函数体;void 表示不返回值或者不要参数。
  • return 交回一个值,函数到此结束。
  • 编译器从上往下读:函数要先声明再调用,可以把函数放在前面,或者先写原型。
  • C 是传值的:参数是实参的副本,函数里改它影响不到外面。要改外面的变量,就把新值返回来。