12、函数(一):函数的定义与声明、形参与实参、return语句、函数的调用过程
函数这东西,说白了就是C语言的积木块。你把一段逻辑封装起来,给它起个名字,以后想用的时候直接喊它一声就行。我刚开始学C的时候,总觉得函数是个麻烦事——明明可以直接写代码,干嘛非要绕一圈?后来做项目才明白,没有函数,代码就是一坨浆糊。
今天咱们就把函数的老底翻一翻。定义、声明、参数、返回值、调用过程,一个都不放过。
函数的定义:你得告诉编译器长什么样
函数定义,就是告诉编译器:我这个函数叫什么、要什么参数、返回什么类型、里面干什么事。语法很简单:
返回类型 函数名(参数列表) {
// 函数体
// 干活的代码
return 返回值;
}
举个例子,写一个加法函数:
int add(int a, int b) {
int sum = a + b;
return sum;
}
这里 int 是返回类型,add 是函数名,int a, int b 是参数列表。函数体里做了加法,然后用 return 把结果送回去。
func1、doSomething 这种名字。你想想看,三个月后你自己回来看代码,看到 func1 是什么感觉?反正我经历过,那叫一个痛苦。
函数的声明:先打个招呼
有时候你把函数定义写在后面,但前面已经用到它了。这时候编译器会报错——它不认识这个函数啊。解决办法就是先声明,后使用。
声明就是告诉编译器:嘿,后面有个函数长这样,你先记着。语法就是把定义的第一行抄一遍,末尾加分号:
int add(int a, int b); // 声明
声明可以放在文件开头,也可以放在头文件里。实际项目中,我习惯把所有函数声明集中到一个 .h 文件里,这样管理起来清爽。
形参与实参:名字不同,本质也不同
这两个概念,很多初学者容易搞混。我当年也迷糊过一阵子。
- 形参(形式参数):写在函数定义里的参数,比如上面
add函数里的int a, int b。它们只是占个位置,没有实际的值。 - 实参(实际参数):调用函数时传进去的具体值,比如
add(3, 5)里的3和5。
调用的时候,实参的值会拷贝给形参。这就是所谓的值传递。函数内部修改形参,不会影响实参本身。
void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
// 这里交换的是形参,外面的实参纹丝不动
}
int main() {
int a = 10, b = 20;
swap(a, b);
printf("a=%d, b=%d\n", a, b); // 输出:a=10, b=20
return 0;
}
为什么会这样?因为 swap 函数拿到的是 a 和 b 的副本,不是它们本人。你想真正交换两个变量的值?得用指针。这个后面再细说。
return语句:把结果送回来
return 有两个作用:
- 返回值:把函数计算的结果传回调用处。
- 结束函数:遇到
return,函数立刻停止,后面的代码不执行了。
int check(int score) {
if (score < 0) {
return -1; // 非法分数,直接返回错误码
}
if (score > 100) {
return -1; // 同样返回错误码
}
return 0; // 分数合法
}
这里用了多个 return,每个都代表一种情况。一旦命中某个条件,函数就结束了。
return。编译器没报错,但运行时返回值是垃圾值。那次排查了整整两个小时,最后发现是 return 漏了。嗯,从那以后我每次写函数都会检查:所有路径都有 return 吗?
如果函数返回类型是 void,表示不返回任何值。这时候可以写 return; 来提前结束函数,也可以不写。
函数的调用过程:栈帧的舞蹈
函数调用的时候,底层发生了什么?我画个图帮你理解。
调用过程大致分几步:
- 压栈:把实参的值拷贝给形参,把返回地址(函数执行完后该回到哪里)压入栈中。
- 跳转:程序跳转到函数的第一条指令开始执行。
- 执行:函数体里的代码一条条跑。
- 返回:遇到
return,把返回值放到指定位置(通常是寄存器),然后弹出栈帧,回到调用处继续执行。
这个过程,说白了就是栈帧的压入和弹出。每次调用函数,栈上就多一块空间(栈帧),里面放着局部变量、参数、返回地址。函数返回了,这块空间就释放了。
实战建议
说了这么多,给你几个实在的建议:
- 函数不要太长:我个人的习惯是,一个函数不超过 50 行。超过就考虑拆分成小函数。你想想看,一个 200 行的函数,读起来是什么感觉?
- 一个函数只做一件事:比如上面那个
check函数,只做分数合法性检查。别把打印、计算、保存全塞进去。 - 参数不要太多:超过 3 个参数,就考虑用结构体打包。否则调用的时候你自己都记不清顺序。
- 返回值要明确:成功返回什么,失败返回什么,提前约定好。我见过一个项目,有的函数返回 0 表示成功,有的返回 1 表示成功……那叫一个乱。
函数这东西,看着简单,但用好了能让代码质量上一个台阶。多写、多拆、多思考,慢慢就有感觉了。
公众号:蓝海资料掘金营,微信deep3321