2-2 函数
⏱ 本节预计用时:40分钟
本节意义:函数是把大程序拆成小模块的工具。井字棋的 initBoard()、printBoard()、checkWin() 都是独立的函数——各做一个零件,拼起来就是一台机器。
本节目标及要点:掌握函数的定义、声明、调用,理解值传递和数组传递的本质区别,会用函数组织井字棋逻辑。
1 100行全堆在main里 vs 拆成4个函数
先给你看两张图——它们做的是同一件事,但写法完全不同。
写法A:全部堆在main里
main() {
初始化棋盘9个格子
打印棋盘
显示提示文字
读取玩家输入
检查输入是否合法
把棋子放到对应位置
检查横线是否连成一条
检查竖线是否连成一条
检查对角线是否连成一条
如果赢了就结束
如果没赢就换人
再打印棋盘
再读取输入
... 中间省略80行 ...
}
这读起来就像一份没有分段的合同——你每次改一个地方,都得从上到下扫一遍才能确定改哪。
写法B:拆成4个函数
initBoard(board); // 清空棋盘
printBoard(board); // 打印当前状态
checkWin(board, 'X'); // 判断X是否赢了
getPlayerMove(); // 获取玩家输入
每个函数只负责一件事,main 变成了"总指挥"(main也是一个函数,叫主函数)——它不亲自干活,只安排别人干活。
如果你写的函数名里出现了"并且"两个字,就该拆成两个函数。
- printBoard()——只打印,不判断,不修改 ✓
- checkWin()——只判断,不打印,不结束游戏 ✓
- checkWinAndPrint() ❌——又判断又打印,拆!
这个原则叫单一职责(Single Responsibility)。它的好处:
你会发现:写得好的程序,函数都很短,名字都很好读。main 函数读起来像流程图——三步走、五步走,一目了然。
2 函数的结构
一个函数由四部分组成,缺一不可(void函数缺 return 除外):
返回类型 函数名 ( 参数列表 ) {
函数体
return 返回值;
}
举个例子:
int add(int a, int b) {
int result = a + b;
return result;
}
- 返回类型 int——告诉编译器"我算完会还你一个整数"
- 函数名 add——你给它的名字,以后用这个名字调用它
- 参数 (int a, int b)——你要给它的原材料,这里需要两个整数
- 函数体 { … }——它干活的步骤
- return——把结果交还给调用方
调用的时候:
int sum = add(3, 5); // sum 变成 8
printf("%d\\n", add(10, 20)); // 直接打印 30
一句话记结构:返回类型告诉别人"我给你什么",参数告诉别人"你给我什么",函数体是"我怎么干",return是"干完了,拿去吧"。
完整示例:
#include <stdio.h>
int add(int a, int b) {
int result = a + b;
return result;
}
int main() {
int sum = add(3, 5); // sum 变成 8
printf("%d\\n", add(10, 20)); // 直接打印 30
return 0;
}
运行结果:

3 void 返回类型
不是所有函数都要返回东西。打印棋盘只需要做动作,不需要"还"什么东西回来:
void printBoard(char board[]) { //[]里写数字没用,讲指针的时候会说明原因。
printf("\\n");
printf(" %c | %c | %c \\n", board[0], board[1], board[2]);
printf("—+—+—\\n");
printf(" %c | %c | %c \\n", board[3], board[4], board[5]);
printf("—+—+—\\n");
printf(" %c | %c | %c \\n", board[6], board[7], board[8]);
printf("\\n");
}

- void 的意思是"空的"——我不返还任何东西
- void函数里可以写 return;(直接返回,不带值),也可以什么都不写,到了 } 自动返回
- void函数不能放在赋值语句右边:int x = printBoard(board); ——它没有返回值给你
什么时候用void? 当函数只做"动作"不做"计算"时——打印、初始化、清空、显示菜单,这些都是void。
4 函数声明——目录先告诉你书名在哪页
C语言编译器有个"倔脾气":编译时从上往下读,读到函数调用的时候,必须已经见过这个函数——要么见过它的完整定义,要么见过它的"预告"。
这个"预告"就叫函数声明(也叫函数原型):
void initBoard(char board[]); // 声明:告诉编译器"这个东西存在"
void printBoard(char board[]); // 声明
int checkWin(char board[], char symbol); // 声明
int main() {
char board[9];
initBoard(board); // ✓ 编译器知道 initBoard 存在
printBoard(board); // ✓ 编译通过
return 0;
}
// 定义在main后面——没关系,前面已经声明过了
void initBoard(char board[]) {
for (int i = 0; i < 9; i++) {
board[i] = 'X';
}
}
void printBoard(char board[]) {
printf(" %c | %c | %c \\n", board[0], board[1], board[2]);
}

声明只写一行,末尾加分号:
int checkWin(char board[], char symbol); // 声明——分号结尾
定义写完整,末尾没有分号(花括号代替了):
int checkWin(char board[], char symbol) { // 定义——花括号开始
// 具体逻辑
}
经验法则:如果所有函数都写在 main 前面,就不需要声明。一旦有函数定义在 main 后面,就必须在前面声明。实际项目里通常把所有声明放在文件开头,定义放在后面——这样 main 永远在第一眼能看到的位置。
5 值传递 vs 数组传递——函数的"底线"
这是新手最容易踩的坑,但原理极其简单。
5.1 普通变量:传的是"复印件"
当你把一个普通变量(int、char、float)传给函数时,函数拿到的是一份副本。你在函数里怎么改,原件纹丝不动。
void changeIt(int x) {
x = 100;
printf("函数内:x = %d\\n", x); // 100
}
int main() {
int num = 10;
changeIt(num);
printf("函数外:num = %d\\n", num); // 还是 10!没变!
return 0;
}

5.2 数组:传的是"原件地址"
数组不一样。当你把数组传给函数时,函数拿到的不是副本,而是原件的地址。函数按地址找到原件,直接在上面改。
void changeFirst(char arr[]) {
arr[0] = 'Z'; // 这改的是原件!
}
int main() {
char name[] = {'A', 'B', 'C'};
changeFirst(name);
printf("name[0] = %c\\n", name[0]); // Z!变了!
return 0;
}

5.3 为什么会有这个区别?
简单说:数组比较大,复制一整份太浪费内存和时间,所以C语言干脆传地址。普通变量小,复制一份无所谓。
这个区别非常重要——它意味着:
- initBoard(board) 能清空棋盘,因为它直接操作原数组 ✓
- checkWin(board, 'X') 只看不改,传地址也无所谓 ✓
- 如果你写个函数想让 int 变量被修改,普通的参数传递做不到——这需要后面学的指针。
6 总结
| 函数结构 | 返回类型 + 函数名 + (参数) + { 函数体 } |
| void函数 | 只做动作不返回,比如打印、初始化、清空 |
| 值传递 | 普通变量传复印件,函数里改外面不变 |
| 数组传递 | 传原件地址,函数里改外面跟着变 |
| 函数声明 | 函数定义在main后面时,必须先声明 |
📌 四条牢记:
7 练习题
兄弟,我知道你可能累了,但现在还不是休息的时候,把这两道题做了,它能帮助你变得更强。
题目1:自定义max3(int a,int b,int c),返回三个数中最大值
#include <stdio.h>
int max(int x, int y) {
return x > y ? x : y;
}
int max3(int a,int b,int c) {
return max(max(a,b), c);
}
int main() {
int a,b,c;
scanf("%d%d%d", &a,&b,&c);
printf("最大值:%d", max3(a,b,c));
return 0;
}

题目2:函数sumArr(int arr[], int len),计算数组所有元素和
#include <stdio.h>
int sumArr(int arr[], int len) {
int sum = 0;
for(int i = 0; i < len; i++) {
sum += arr[i];
}
return sum;
}
int main() {
int a[5] = {1,2,3,4,5};
printf("总和:%d", sumArr(a, 5));
return 0;
}




