14、函数(三):静态变量(static)、递归函数(阶乘/斐波那契)、递归与迭代的对比

好,咱们继续聊函数。前面两章我们把函数的参数、返回值、作用域都捋了一遍。这一章,我打算聊聊两个在实战中特别容易让人「栽跟头」的东西——静态变量递归

嗯,先别急着翻白眼。我知道递归这个概念,教科书上讲得玄乎其玄,什么「自己调用自己」之类的。但说白了,它就是个循环的另一种写法。只不过,这个循环是用函数调用栈来实现的。

咱们一个一个来。

静态变量(static)

先说说 static 这个关键字。很多人以为它只是用来修饰全局变量的,其实不然。它修饰局部变量时,效果完全不一样。

我举个例子。你写一个计数器函数,每次调用都加1。如果用普通局部变量,每次函数结束,变量就销毁了,下次调用又得重新初始化。这显然不行。

这时候,static 就派上用场了。

#include <stdio.h>

void counter() {
    static int count = 0;  // 静态局部变量
    count++;
    printf("第 %d 次调用\n", count);
}

int main() {
    counter();  // 第 1 次调用
    counter();  // 第 2 次调用
    counter();  // 第 3 次调用
    return 0;
}

你看,count 只初始化一次。之后每次调用,它都保留着上一次的值。这就是静态局部变量的核心特点:生命周期贯穿整个程序,但作用域仅限于函数内部

核心要点:

  • 静态局部变量在程序启动时分配内存,程序结束时才释放。
  • 它只初始化一次,后续调用不再重新初始化。
  • 它的作用域仍然是函数内部,外部无法直接访问。

我在项目中遇到过一种情况:一个嵌入式设备需要记录某个操作的总次数,但又不想用全局变量(怕被其他地方意外修改)。用 static 局部变量就完美解决了。既安全,又方便。

小技巧:如果你需要一个「函数级别的全局变量」,但又不想污染全局命名空间,就用 static 局部变量。这是 C 语言里很优雅的一种设计。

递归函数:阶乘与斐波那契

好,接下来是重头戏——递归。

递归,说白了就是一个函数调用它自己。但这里有个关键:必须有一个终止条件,否则就会无限递归,最终栈溢出。

我刚开始学递归的时候,总觉得这玩意儿很神奇。后来写多了才发现,它其实就是把一个大问题拆成一个个小问题,直到小到可以直接解决。

阶乘(Factorial)

阶乘是最经典的递归例子。n! = n * (n-1)!,边界条件是 0! = 1。

#include <stdio.h>

int factorial(int n) {
    if (n == 0) {
        return 1;  // 终止条件
    }
    return n * factorial(n - 1);  // 递归调用
}

int main() {
    int n = 5;
    printf("%d! = %d\n", n, factorial(n));  // 输出 120
    return 0;
}

你看,代码很简洁。但简洁的背后,是函数调用栈在默默工作。每次递归调用,都会在栈上压入一个新的函数帧,保存当前的状态。等递归返回时,再一层层弹出。

注意:递归虽然代码简洁,但并不是万能的。如果递归深度太大,比如几万层,栈空间可能不够用,程序就会崩溃。我曾经在调试一个递归算法时,就因为没注意深度,导致栈溢出,查了半天才找到原因。

斐波那契数列(Fibonacci)

斐波那契数列是另一个经典例子。F(n) = F(n-1) + F(n-2),边界条件是 F(0)=0,F(1)=1。

#include <stdio.h>

int fibonacci(int n) {
    if (n == 0) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    int n = 10;
    printf("F(%d) = %d\n", n, fibonacci(n));  // 输出 55
    return 0;
}

嗯,这里要注意。这个递归实现虽然直观,但效率极低。为什么?因为它会重复计算很多次。比如计算 F(5),它会计算 F(4) 和 F(3),而 F(4) 又会计算 F(3) 和 F(2)……你想想看,F(3) 被重复计算了多少次?

这就是递归的一个典型问题:重复计算。对于斐波那契数列,递归的时间复杂度是 O(2^n),指数级的增长,n 稍微大一点就扛不住了。

递归与迭代的对比

好,既然递归有这些问题,那有没有更好的办法?当然有——迭代。

迭代,说白了就是用循环来解决问题。同样是斐波那契数列,用迭代写出来是这样的:

#include <stdio.h>

int fibonacci_iter(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;

    int a = 0, b = 1, c;
    for (int i = 2; i <= n; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return b;
}

int main() {
    int n = 10;
    printf("F(%d) = %d\n", n, fibonacci_iter(n));  // 输出 55
    return 0;
}

你看,迭代版本的时间复杂度是 O(n),空间复杂度是 O(1)。相比之下,递归版本的时间复杂度是 O(2^n),空间复杂度是 O(n)(因为调用栈深度)。

我个人的习惯是:能用迭代解决的问题,尽量不用递归。除非递归的代码特别简洁,而且递归深度可控,我才会考虑用递归。

下面这张图,可以帮你更直观地理解递归和迭代的区别:

递归 vs 迭代 核心对比 递归 • 函数调用自身 • 需要终止条件 • 代码简洁,逻辑清晰 • 栈空间消耗大 • 可能有重复计算 • 适合:树、图遍历 缺点: 深度过大 → 栈溢出 效率低(如斐波那契) 迭代 • 使用循环(for/while) • 需要循环条件 • 代码稍长,但直观 • 栈空间消耗小(O(1)) • 无重复计算 • 适合:线性问题 优点: 效率高,空间省 适合大规模计算 选择建议:递归深度可控且逻辑清晰 → 用递归;否则 → 用迭代

你看,递归和迭代各有各的适用场景。递归适合那些天然具有递归结构的问题,比如树的遍历、图的深度优先搜索。而迭代则更适合线性问题,比如数组求和、链表遍历。

总结一下:

  • 静态变量:生命周期贯穿程序,作用域限于函数内部。适合做函数级别的计数器或状态保存。
  • 递归:代码简洁,但要注意深度和重复计算问题。适合树、图等递归结构。
  • 迭代:效率高,空间省。适合线性问题和大规模计算。

我的建议:面试时如果遇到递归问题,先别急着写递归。先想想能不能用迭代实现。如果能,优先写迭代。如果面试官要求递归,再写递归。这样既能展示你的思维广度,又能体现你的工程经验。

好,这一章就到这里。静态变量和递归,说白了都是 C 语言里很基础但又很实用的东西。你想想看,掌握了这些,面试官再问你「递归和迭代有什么区别」,你就能从原理、效率、适用场景等多个角度去回答了。

嗯,下一章我们聊聊指针。那才是 C 语言的精髓所在。

专注资料整理