第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;把计算结果返回给调用者
calculateAverage、readSensorData。别用 func1、doSomething 这种名字,三个月后你自己都看不懂。
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)里的a和b。它们只是占位符,没有实际的值。 - 实参(实际参数):调用函数时传入的具体值,比如
add(3, 5)里的3和5。它们是真正参与运算的数据。
调用时,实参的值会复制一份给形参。这就是所谓的"值传递"。形参在函数内部怎么改,都不会影响实参本身。
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有两个作用:
- 返回值:把函数计算的结果传给调用者
- 结束函数:遇到return,函数立即停止执行,后面的代码不再运行
看个例子:
int checkPositive(int num)
{
if (num <= 0)
{
return 0; // 非正数,直接返回0
}
// 这里只有num大于0才会执行
return 1;
}
如果函数返回值类型是 void,表示不需要返回值。这时候可以写 return; 或者不写return都行。
return;,这样代码逻辑更清晰。我在项目中见过太多因为漏写return导致函数继续执行错误逻辑的bug。
15.6 知识体系总览
下面这张图把函数基础的核心知识点串起来了,你可以对照着复习:
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