云计算百科
云计算领域专业知识百科平台

2-2 函数

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)。它的好处:

  • 查错容易——出了bug,你知道去哪个函数里找
  • 复用方便——checkWin 可以在多个地方调用,不用重复写判断逻辑
  • 测试简单——你可以单独测试 checkWin(“X赢了没有?”),不用启动整个游戏
  • 你会发现:写得好的程序,函数都很短,名字都很好读。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;
    }

    运行结果:

    2-1 函数例子

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

    2-2 void返回类型

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

    2-3 函数声明

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

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

    2-4 值传递

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

    2-5 数组传递

    5.3 为什么会有这个区别?

    简单说:数组比较大,复制一整份太浪费内存和时间,所以C语言干脆传地址。普通变量小,复制一份无所谓。

    这个区别非常重要——它意味着:

    • initBoard(board) 能清空棋盘,因为它直接操作原数组 ✓
    • checkWin(board, 'X') 只看不改,传地址也无所谓 ✓
    • 如果你写个函数想让 int 变量被修改,普通的参数传递做不到——这需要后面学的指针。

    6 总结

    知识点一句话
    函数结构 返回类型 + 函数名 + (参数) + { 函数体 }
    void函数 只做动作不返回,比如打印、初始化、清空
    值传递 普通变量传复印件,函数里改外面不变
    数组传递 传原件地址,函数里改外面跟着变
    函数声明 函数定义在main后面时,必须先声明

    📌 四条牢记:

  • 函数 = 返回类型 + 名字 + (参数) + { 函数体 }
  • 普通变量传值,数组传地址(函数内改数组,外面会变)
  • 函数定义在 main 后面时必须先在前面声明
  • 一个函数做一件事——printBoard() 只打印,checkWin() 只判断
  • 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-6 函数题目1

    题目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;
    }

    2-7 函数题目2

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 2-2 函数
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!