函数
同样的几行代码要在好几个地方用,就可以把它们写成一个函数,起个名字,用的时候叫它的名字就行。其实你早就在用函数了:printf 和 main 都是。下面是自己写的第一个函数:
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 是参数,调用时括号里给的值是实参:调用时,实参的值放进参数里。
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 分钟
参数可以有好几个,用逗号隔开,每个都要写类型。调用时实参按顺序一个对一个:
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 把一个值交回给调用它的地方,函数也就到此结束。返回的值可以存进变量,也可以直接拿来用:
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,跑到哪个就在哪里结束:
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}
a + b 是 15,除以 2.0(一个小数),得到的是 7.5,不是整数除法。函数的返回类型是 double,7.5 原样交回来,%.1f 显示 7.5。
先声明,后使用
C 编译器从上往下读代码。读到一个调用时,它要已经知道这个函数长什么样:叫什么、要几个什么类型的参数、返回什么。函数写在 main 下面,编译器读到调用时还不认识它:
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 上面;或者在上面先写一行函数原型(也叫函数声明),说明它长什么样,函数本身放在后面:
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 这些函数的原型。
传值
调用函数时,交给它的是实参的值,参数是一个新的变量,装着这个值的一份副本。所以函数里改参数,改的是副本,外面的变量不会变:
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。
想让外面的变量变,最简单的办法是让函数把新值返回来,由调用的地方自己存:
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 课再讲。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
立方
✓写一个函数 cube,参数是一个 int,返回它的立方(自己乘自己三次)。程序要显示 27 和 125,各占一行。
27 125
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}
打折
✓写一个函数 discount,参数是价格 price(double)和折扣百分比 percent(int),返回打折后的价格。比如 discount(80.0, 25) 是 80 减去 25%,得 60。程序要显示 60.00 和 45.00。
60.00 45.00
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}
存款没变
✓deposit 想给余额加上一笔钱,可 main 里的余额一直是 100。改好它:让 deposit 返回新的余额,main 把它存回 balance。程序要显示 余额:150。
余额:150
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}
这一课学会了
- 函数把一段代码起个名字,写一次,到处调用;调用完回到调用的地方接着走。
- 定义函数要写返回类型、名字、参数(每个都带类型)和函数体;
void表示不返回值或者不要参数。 return交回一个值,函数到此结束。- 编译器从上往下读:函数要先声明再调用,可以把函数放在前面,或者先写原型。
- C 是传值的:参数是实参的副本,函数里改它影响不到外面。要改外面的变量,就把新值返回来。