2-2 函数

2-2 函数

⏱ 本节预计用时:40分钟

本节意义:函数是把大程序拆成小模块的工具。井字棋的 initBoard()、printBoard()、checkWin() 都是独立的函数------各做一个零件,拼起来就是一台机器。

本节目标及要点:掌握函数的定义、声明、调用,理解值传递和数组传递的本质区别,会用函数组织井字棋逻辑。

1 100行全堆在main里 vs 拆成4个函数

先给你看两张图------它们做的是同一件事,但写法完全不同。

写法A:全部堆在main里

c 复制代码
main() {
    初始化棋盘9个格子
    打印棋盘
    显示提示文字
    读取玩家输入
    检查输入是否合法
    把棋子放到对应位置
    检查横线是否连成一条
    检查竖线是否连成一条
    检查对角线是否连成一条
    如果赢了就结束
    如果没赢就换人
    再打印棋盘
    再读取输入
    ... 中间省略80行 ...
}

这读起来就像一份没有分段的合同------你每次改一个地方,都得从上到下扫一遍才能确定改哪。

写法B:拆成4个函数

c 复制代码
initBoard(board);      // 清空棋盘
printBoard(board);     // 打印当前状态
checkWin(board, 'X');  // 判断X是否赢了
getPlayerMove();       // 获取玩家输入

每个函数只负责一件事,main 变成了"总指挥"(main也是一个函数,叫主函数)------它不亲自干活,只安排别人干活。

如果你写的函数名里出现了"并且"两个字,就该拆成两个函数。

  • printBoard()------只打印,不判断,不修改 ✓
  • checkWin()------只判断,不打印,不结束游戏 ✓
  • checkWinAndPrint() ❌------又判断又打印,拆!

这个原则叫单一职责(Single Responsibility)。它的好处:

  1. 查错容易------出了bug,你知道去哪个函数里找
  2. 复用方便------checkWin 可以在多个地方调用,不用重复写判断逻辑
  3. 测试简单------你可以单独测试 checkWin("X赢了没有?"),不用启动整个游戏

你会发现:写得好的程序,函数都很短,名字都很好读。main 函数读起来像流程图------三步走、五步走,一目了然。

2 函数的结构

一个函数由四部分组成,缺一不可(void函数缺 return 除外):

c 复制代码
返回类型  函数名  (  参数列表  )  {
    函数体
    return 返回值;
}

举个例子:

c 复制代码
int add(int a, int b) {
    int result = a + b;
    return result;
}
  • 返回类型 int------告诉编译器"我算完会还你一个整数"
  • 函数名 add------你给它的名字,以后用这个名字调用它
  • 参数 (int a, int b)------你要给它的原材料,这里需要两个整数
  • 函数体 { ... }------它干活的步骤
  • return------把结果交还给调用方

调用的时候:

c 复制代码
int sum = add(3, 5);   // sum 变成 8
printf("%d\n", add(10, 20));  // 直接打印 30

一句话记结构:返回类型告诉别人"我给你什么",参数告诉别人"你给我什么",函数体是"我怎么干",return是"干完了,拿去吧"。

完整示例:

c 复制代码
#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 返回类型

不是所有函数都要返回东西。打印棋盘只需要做动作,不需要"还"什么东西回来:

c 复制代码
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语言编译器有个"倔脾气":编译时从上往下读,读到函数调用的时候,必须已经见过这个函数------要么见过它的完整定义,要么见过它的"预告"。

这个"预告"就叫函数声明(也叫函数原型):

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]);
}

声明只写一行,末尾加分号:

c 复制代码
int checkWin(char board[], char symbol);   // 声明------分号结尾

定义写完整,末尾没有分号(花括号代替了):

c 复制代码
int checkWin(char board[], char symbol) {  // 定义------花括号开始
    // 具体逻辑
}

经验法则:如果所有函数都写在 main 前面,就不需要声明。一旦有函数定义在 main 后面,就必须在前面声明。实际项目里通常把所有声明放在文件开头,定义放在后面------这样 main 永远在第一眼能看到的位置。

5 值传递 vs 数组传递------函数的"底线"

这是新手最容易踩的坑,但原理极其简单。

5.1 普通变量:传的是"复印件"

当你把一个普通变量(int、char、float)传给函数时,函数拿到的是一份副本。你在函数里怎么改,原件纹丝不动。

c 复制代码
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 数组:传的是"原件地址"

数组不一样。当你把数组传给函数时,函数拿到的不是副本,而是原件的地址。函数按地址找到原件,直接在上面改。

c 复制代码
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后面时,必须先声明

📌 四条牢记:

  1. 函数 = 返回类型 + 名字 + (参数) + { 函数体 }
  2. 普通变量传值,数组传地址(函数内改数组,外面会变)
  3. 函数定义在 main 后面时必须先在前面声明
  4. 一个函数做一件事------printBoard() 只打印,checkWin() 只判断

7 练习题

兄弟,我知道你可能累了,但现在还不是休息的时候,把这两道题做了,它能帮助你变得更强。

题目1 :自定义max3(int a,int b,int c),返回三个数中最大值

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),计算数组所有元素和

c 复制代码
#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;
}
相关推荐
从小白到软工2 小时前
2-3 结构体
c
从小白到软工5 小时前
2-1 二维数组
c
书生执笔画浮沉7 小时前
Linux桌面端应用向鸿蒙PC迁移适配纪实
c++·qt·c·信创·海思·libc·ohos
带土19 小时前
15. sscanf 函数详解
c
C++ 老炮儿的技术栈1 天前
sizeof操作符
c语言·c++·人工智能·mfc·c
多弗朗皮卡丘3 天前
数据结构番外:环形队列
数据结构·c
91刘仁德3 天前
传输层协议深度拆解:端口寻址、UDP 报文与 TCP 可靠性机制全解析
linux·网络·c++·网络协议·tcp/ip·udp·c
C++ 老炮儿的技术栈4 天前
不一样的数值交换
数据结构·c++·人工智能·算法·c·csdn开发云
C++ 老炮儿的技术栈4 天前
main函数之后的调用
c语言·c++·人工智能·qt·mfc·c