第9章 函数(二):局部变量与全局变量、变量的作用域与生命周期、静态变量static、递归函数
各位同学,咱们继续聊函数。上一章我们把函数的定义、声明、参数传递这些基本功讲完了。这一章要深入一点,聊聊变量在函数里到底是怎么“活”的,以及一个很酷的技巧——递归。
说实话,我刚学C语言那会儿,最头疼的就是“作用域”和“生命周期”这两个概念。明明都是变量,为什么有的函数里能用,换个函数就不认识了?为什么有的变量函数一结束就没了,有的却一直赖着不走?
别急,今天咱们就把这些事彻底捋清楚。
9.1 局部变量与全局变量
先看个最简单的例子:
#include <stdio.h>
int global_var = 100; // 全局变量
void func() {
int local_var = 200; // 局部变量
printf("局部变量: %d\n", local_var);
printf("全局变量: %d\n", global_var);
}
int main() {
func();
// printf("%d\n", local_var); // 这行会报错!local_var 在这里不可见
printf("全局变量: %d\n", global_var);
return 0;
}
局部变量:定义在函数内部的变量。它的作用域仅限于这个函数。函数一结束,它就“消失”了。我在项目中见过不少新手,在main函数里想访问另一个函数里的局部变量,结果编译报错,一脸懵。
全局变量:定义在所有函数之外的变量。从定义位置开始,到文件结束,所有函数都能访问它。听起来很方便对吧?但我要提醒你:全局变量是把双刃剑。
我的建议是:能用局部变量解决的问题,就别用全局变量。全局变量只适合那些真正需要“全局共享”的数据,比如系统配置参数、硬件状态标志等。
9.2 变量的作用域与生命周期
这两个概念经常被混为一谈,其实它们是两码事。
- 作用域:变量在代码的哪些地方可以被访问。说白了就是“看得见摸得着”的范围。
- 生命周期:变量从创建到销毁的这段时间。说白了就是“活着”的时间段。
咱们用一张图来理解:
你看,全局变量活最久,但范围也最广。局部变量范围小,活得也短。静态局部变量呢?范围小,但活得久——这个我们马上讲。
9.3 静态变量 static
static 这个关键字,在C语言里是个“多面手”。用在局部变量上,效果很特别。
#include <stdio.h>
void counter() {
static int count = 0; // 静态局部变量
count++;
printf("第 %d 次调用\n", count);
}
int main() {
counter(); // 输出:第 1 次调用
counter(); // 输出:第 2 次调用
counter(); // 输出:第 3 次调用
return 0;
}
看到了吗?普通局部变量每次函数调用都会重新创建,但 static 变量只初始化一次。函数返回后,它的值依然保留。下次再调用这个函数,count 还是上次的值。
不过要注意:static 变量虽然生命周期长,但作用域没变。它还是只能在定义它的函数里访问。你不能在 main 函数里直接访问 counter 函数里的 count 变量——编译器会毫不留情地报错。
9.4 递归函数
递归,说白了就是“函数调用自己”。听起来有点绕,但用对了地方,代码会非常简洁优雅。
先看一个经典例子——计算阶乘:
#include <stdio.h>
int factorial(int n) {
if (n <= 1) {
return 1; // 递归终止条件
}
return n * factorial(n - 1); // 递归调用
}
int main() {
int result = factorial(5);
printf("5! = %d\n", result); // 输出:5! = 120
return 0;
}
递归函数有两个关键点:
- 递归终止条件:必须有一个条件让递归停下来,否则会无限递归,最终栈溢出。
- 递归调用:每次调用都向终止条件靠近一步。
咱们用图来理解 factorial(4) 的执行过程:
你看,递归的过程就像“先往下挖,挖到底,再一层层往上返”。
但递归也有代价:每次函数调用都会消耗栈空间。如果递归层数太深(比如几万层),栈会溢出。嵌入式开发中,栈空间本来就有限,用递归要格外小心。
9.5 避坑指南
最后,分享几个我踩过的坑:
- 全局变量滥用:我曾经在一个通信协议栈里用了全局变量来保存状态,结果多线程环境下数据全乱了。后来改成函数参数传递,问题解决。
- 递归忘记终止条件:有一次写递归,忘了写 if 判断,程序直接崩溃。调试了半天才发现是栈溢出了。
- static 变量误解:有人以为 static 变量作用域也是全局的,结果在另一个文件里 extern 引用,编译报错。记住:static 只是生命周期变长,作用域不变。
- 局部变量返回地址:千万不要返回局部变量的地址!函数结束后,局部变量的内存就被回收了,返回的指针成了野指针。
好了,这一章的内容就到这里。变量怎么活、怎么死,递归怎么用,心里有数了吧?代码写多了,这些概念自然就熟了。多练练,别怕犯错——我当年也是这么过来的。