18. 内联函数与递归.md 4.8 KB

18 内联函数与递归

18.1 完整概念讲解

内联函数

内联函数(Inline Function)是编译器优化建议,建议将函数代码直接展开到调用点,避免函数调用开销。

特点:

  • inline关键字只是建议,编译器可忽略
  • static inline组合在头文件中使用,避免多重定义
  • 内联函数有函数的所有优点(类型安全、调试方便)
  • 比宏更安全(宏有副作用问题)

递归函数

递归(Recursion)是函数调用自身的编程技巧。

递归三要素:

  1. 明确的终止条件(Base Case):防止无限递归
  2. 递归调用:将问题分解为更小的子问题
  3. 收敛性:每次递归都向终止条件靠近

尾递归优化:

  • 递归调用是函数的最后操作
  • 尾递归可被编译器优化为循环,避免栈溢出
  • 某些编译器支持(如GCC -O2)

嵌入式中的风险:

  • 嵌入式系统栈空间有限(通常几KB)
  • 深度递归可能导致栈溢出
  • 应评估递归深度,必要时改用迭代

18.2 核心API/语法

// 内联函数定义
inline 返回类型 函数名(参数列表) {
    // 函数体
}

// static inline(头文件中使用)
static inline 返回类型 函数名(参数列表) {
    // 函数体
}

// 递归函数示例
int factorial(int n) {
    if (n <= 1) return 1;      // 终止条件
    return n * factorial(n-1); // 递归调用
}

18.3 代码示例(完整可编译,附gcc命令)

#include <stdio.h>
#include <time.h>

// 内联函数示例
static inline int max_inline(int a, int b) {
    return (a > b) ? a : b;
}

// 普通宏(有副作用问题)
#define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b))

// 递归:阶乘
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

// 递归:斐波那契数列(普通递归,效率低)
int fibonacci_naive(int n) {
    if (n <= 0) return 0;
    if (n == 1) return 1;
    return fibonacci_naive(n - 1) + fibonacci_naive(n - 2);
}

// 尾递归:斐波那契
int fibonacci_tail(int n, int a, int b) {
    if (n == 0) return a;
    if (n == 1) return b;
    return fibonacci_tail(n - 1, b, a + b);
}

// 递归:二分查找
int binary_search(int arr[], int left, int right, int target) {
    if (left > right) return -1;

    int mid = left + (right - left) / 2;

    if (arr[mid] == target) return mid;
    else if (arr[mid] < target)
        return binary_search(arr, mid + 1, right, target);
    else
        return binary_search(arr, left, mid - 1, target);
}

int main() {
    // 内联函数测试
    printf("max_inline(3, 5) = %d\n", max_inline(3, 5));

    // 宏的副作用问题
    int x = 3, y = 5;
    printf("MAX_MACRO(x++, y) = %d\n", MAX_MACRO(x++, y));
    printf("After MAX_MACRO: x = %d\n", x);  // x被递增两次!

    // 递归:阶乘
    printf("\n5! = %d\n", factorial(5));
    printf("10! = %d\n", factorial(10));

    // 递归:斐波那契
    printf("\nFibonacci(10) = %d\n", fibonacci_naive(10));
    printf("Fibonacci(10) tail = %d\n", fibonacci_tail(10, 0, 1));

    // 递归:二分查找
    int arr[] = {1, 3, 5, 7, 9, 11, 13, 15};
    int size = sizeof(arr) / sizeof(arr[0]);
    int target = 7;
    int idx = binary_search(arr, 0, size - 1, target);
    printf("\nBinary search %d: index = %d\n", target, idx);

    return 0;
}

编译命令:

gcc -Wall -Wextra -O2 -o inline_recursive inline_recursive.c
./inline_recursive

18.4 注意事项与易错点

  1. 内联不是强制的:编译器可能忽略inline建议(如函数体过大、含循环等)
  2. 宏的副作用MAX_MACRO(x++, y)会导致x递增两次,内联函数不会
  3. 递归必须有终止条件:否则导致无限递归和栈溢出
  4. 递归效率问题:普通斐波那契递归时间复杂度O(2^n),应使用迭代或记忆化
  5. 嵌入式栈限制:评估最大递归深度,必要时改为迭代实现

18.5 面试要点(3-5个Q&A)

Q1: inline函数和宏有什么区别? A: inline是函数,有类型检查、作用域、调试信息;宏是文本替换,有副作用风险。inline更安全,但编译器可能不内联。

Q2: 什么是尾递归?为什么能优化? A: 尾递归是递归调用作为函数最后操作。编译器可将其优化为循环,复用当前栈帧,避免栈增长。

Q3: 递归和迭代如何选择? A: 递归代码更简洁(如树遍历),但有栈开销。迭代效率更高。嵌入式系统优先考虑迭代。

Q4: 如何避免递归栈溢出? A: 1) 确保递归深度有限 2) 使用尾递归 3) 改为迭代 4) 增大栈空间(嵌入式有限)

Q5: static inline在头文件中的作用? A: static确保每个编译单元有自己的副本,避免链接时多重定义错误。inline建议编译器内联展开。