第15章:函数基础——函数的定义、声明、调用,形参与实参,return语句

函数,说白了就是一段可以重复使用的代码块。我刚开始学C语言时,觉得函数这东西可有可无,反正代码能跑就行。直到有一次接手一个两千行的项目,所有逻辑全塞在main函数里,改一个bug要翻半天——嗯,从那以后我再也不敢不写函数了。

15.1 函数的定义:给代码块起个名字

函数的定义,就是告诉编译器:我这里有一段代码,它叫什么名字,需要什么原料,干完活后返回什么结果。

语法格式很简单:

返回值类型 函数名(参数列表)
{
    函数体
    return 返回值;
}

举个例子,我写一个计算两个整数之和的函数:

int add(int a, int b)
{
    int sum = a + b;
    return sum;
}

这里:

  • int 是返回值类型,表示这个函数会返回一个整数
  • add 是函数名,你自己起,但别用关键字
  • int a, int b 是参数列表,告诉调用者需要传入两个整数
  • return sum; 把计算结果返回给调用者
我的习惯:函数名尽量用动词+名词的组合,比如 calculateAveragereadSensorData。别用 func1doSomething 这种名字,三个月后你自己都看不懂。

15.2 函数的声明:先打个招呼

你有没有遇到过这种情况?函数写在main函数后面,结果编译报错说"函数未定义"。这是因为C语言编译器是从上往下读代码的,它看到你调用函数时,还不知道这个函数长什么样。

解决办法就是——先声明,后使用。

函数声明也叫函数原型,格式就是去掉函数体,加上分号:

int add(int a, int b);  // 声明

声明告诉编译器:后面会有一个叫add的函数,它接受两个int参数,返回int。编译器知道这些信息后,就能正确编译调用代码了。

我曾经踩过的坑:有一次我声明函数时参数类型写对了,但定义时参数类型写错了。编译器没报错,但运行时传参全乱套了。记住:声明和定义必须完全一致,包括参数类型和返回值类型。

15.3 函数的调用:让代码跑起来

定义好了函数,怎么让它干活?很简单,直接写函数名加括号:

#include <stdio.h>

int add(int a, int b);  // 声明

int main()
{
    int result = add(3, 5);  // 调用
    printf("3 + 5 = %d\n", result);
    return 0;
}

int add(int a, int b)  // 定义
{
    return a + b;
}

调用时,程序会跳转到函数定义的地方执行,执行完后再回到调用点继续往下走。这个过程叫"函数调用栈",你想想看,就像你写了一张便签,去办完事回来继续看便签上的下一件事。

15.4 形参与实参:名字不同,本质不同

这两个概念初学者特别容易搞混。我当年也迷糊过一阵子。

  • 形参(形式参数):定义函数时写的参数,比如上面 int add(int a, int b) 里的 ab。它们只是占位符,没有实际的值。
  • 实参(实际参数):调用函数时传入的具体值,比如 add(3, 5) 里的 35。它们是真正参与运算的数据。

调用时,实参的值会复制一份给形参。这就是所谓的"值传递"。形参在函数内部怎么改,都不会影响实参本身。

void change(int x)
{
    x = 100;  // 只改了形参,不影响实参
}

int main()
{
    int num = 10;
    change(num);
    printf("%d\n", num);  // 输出还是10
    return 0;
}

为什么会这样?因为C语言默认就是值传递。你想改变实参的值?那就得用指针。这个后面章节会细讲。

核心区别一句话:形参是"接收盒",实参是"放进去的东西"。盒子里东西变了,不代表你原来的东西也变了。

15.5 return语句:把结果带回来

return有两个作用:

  1. 返回值:把函数计算的结果传给调用者
  2. 结束函数:遇到return,函数立即停止执行,后面的代码不再运行

看个例子:

int checkPositive(int num)
{
    if (num <= 0)
    {
        return 0;  // 非正数,直接返回0
    }
    // 这里只有num大于0才会执行
    return 1;
}

如果函数返回值类型是 void,表示不需要返回值。这时候可以写 return; 或者不写return都行。

我的建议:即使函数是void类型,在需要提前退出的地方也写上 return;,这样代码逻辑更清晰。我在项目中见过太多因为漏写return导致函数继续执行错误逻辑的bug。

15.6 知识体系总览

下面这张图把函数基础的核心知识点串起来了,你可以对照着复习:

函数基础 函数的定义 返回值类型 函数名(参数) { 函数体; return; } 函数的声明 int add(int a, int b); 告诉编译器函数原型 函数的调用 result = add(3, 5); 跳转执行,返回继续 形参与实参 形参:占位符,无实际值 实参:具体值,复制给形参 值传递,形参改不影响实参 return语句 返回值给调用者 立即结束函数执行 void类型可写 return; 先声明,后定义,再调用 —— 三步走,别搞反

15.7 避坑指南

最后分享几个我实际项目中踩过的坑:

  • 忘记声明:函数定义放在调用之后,又不写声明,编译直接报错。养成习惯:要么把定义写在调用之前,要么在文件开头统一声明。
  • return类型不匹配:函数声明返回int,结果return了一个double。编译器会做隐式转换,但精度可能丢失。我遇到过因为这种问题导致计算金额差了0.01元,排查了半天。
  • 参数顺序搞反:比如 void setPosition(int x, int y),调用时写成 setPosition(y, x)。这种bug编译器查不出来,只能靠人眼盯。所以参数顺序要固定,最好用有意义的参数名。
  • 忘记写return:非void函数如果执行路径没走到return,函数会返回一个不确定的值。这种bug时隐时现,特别恶心。我建议编译时打开 -Wall -Werror 选项,让编译器帮你检查。

函数这东西,说白了就是C语言的积木块。你把一个个小功能封装成函数,然后像搭积木一样组合起来,再大的项目也能拆解清楚。刚开始写函数可能会觉得麻烦,但等你写过几千行的项目后,就会明白——不写函数才是真正的麻烦。


公众号:蓝海资料掘金营,微信deep3321