Skip to content
约 0 字 · 预计阅读 0 分钟

函数详解

什么是函数?

函数是 完成特定任务的独立代码块。它是 C 语言模块化编程的基础,通过函数可以将复杂问题分解为多个小问题,提高代码的可读性和复用性。

函数的基本结构

c
int add(int a, int b)
{
    return a + b;
}

上述代码定义了一个简单的加法函数:

函数组成:

部分说明
int返回类型,表示函数返回整数
add函数名,用于调用函数
(int a, int b)参数列表,定义输入数据
return a + b;函数体,执行具体操作并返回结果

函数声明与定义

c
int add(int a, int b);  

int add(int a, int b)   
{
    return a + b;
}

上述代码展示了函数的声明和定义:

声明与定义的区别:

特性声明定义
关键字无特殊关键字无特殊关键字
函数体无,以分号结尾有,包含具体代码
作用告诉编译器函数存在实现函数功能
次数可以多次声明只能定义一次

参数传递

值传递

c
void swap_wrong(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

void swap_right(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main(void)
{
    int x = 10, y = 20;
    
    swap_wrong(x, y);
    printf("x=%d, y=%d\n", x, y);  
    
    swap_right(&x, &y);
    printf("x=%d, y=%d\n", x, y);  
    return 0;
}

上述代码展示了值传递与指针传递的区别:

┌─────────────────────────────────────────────────────────────┐
│                    值传递过程                                │
│                                                             │
│  调用 swap_wrong(x, y):                                     │
│                                                             │
│  main 函数:           swap_wrong 函数:                      │
│  ┌───┬───┐           ┌───┬───┐                              │
│  │ x │ y │           │ a │ b │                              │
│  │10 │20 │           │10 │20 │  ← 复制了值                   │
│  └───┴───┘           └───┴───┘                              │
│                                                             │
│  函数内交换 a 和 b:                                          │
│  ┌───┬───┐                                                  │
│  │ a │ b │                                                  │
│  │20 │10 │  ← 交换了副本                                    │
│  └───┴───┘                                                  │
│                                                             │
│  main 中的 x, y 不受影响                                     │
├─────────────────────────────────────────────────────────────┤
│                    指针传递过程                              │
│                                                             │
│  调用 swap_right(&x, &y):                                   │
│                                                             │
│  main 函数:           swap_right 函数:                      │
│  ┌───┬───┐           ┌───┬───┐                              │
│  │ x │ y │    ◄────  │ a │ b │                              │
│  │10 │20 │           │&x │&y │  ← 传递了地址                 │
│  └───┴───┘           └───┴───┘                              │
│      ▲                   │                                   │
│      └───────────────────┘                                   │
│                                                             │
│  通过地址修改:                                               │
│  *a = 20, *b = 10                                           │
│                                                             │
│  main 中的 x, y 被修改                                       │
└─────────────────────────────────────────────────────────────┘

数组参数

c
void print_array(int arr[], int size)
{
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

void print_array_ptr(int *arr, int size)
{
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

上述代码展示了数组作为参数的传递方式:

关键点:

声明方式实际类型说明
int arr[]int *arr数组退化为指针
int *arrint *arr明确是指针
int arr[10]int *arr大小被忽略

注意: 数组作为参数时会退化为指针,无法通过 sizeof 获取数组大小,必须单独传递数组长度。

const 参数

c
int sum_array(const int *arr, int size)
{
    int total = 0;
    for (int i = 0; i < size; i++) {
        total += arr[i];
    }
    
    return total;
}

上述代码展示了 const 参数的使用:

const 参数的作用:

位置含义示例
const int *p指向的值不可修改*p = 10 错误
int * const p指针本身不可修改p = &a 错误
const int * const p都不可修改两者都错误

返回值

返回基本类型

c
int max(int a, int b)
{
    return (a > b) ? a : b;
}

double divide(int a, int b)
{
    if (b == 0) {
        return 0.0;  
    }
    return (double)a / b;
}

上述代码展示了基本类型的返回值:

返回值规则:

  • 返回值类型必须与函数声明一致
  • 可以通过类型转换返回不同类型
  • 无返回值使用 void

返回指针

c
int *find_max(int *arr, int size)
{
    if (size <= 0) {
        return NULL;
    }
    
    int *max_ptr = arr;
    for (int i = 1; i < size; i++) {
        if (arr[i] > *max_ptr) {
            max_ptr = &arr[i];
        }
    }
    
    return max_ptr;
}

int *wrong_return(void)
{
    int local = 10;
    return &local;  
}

上述代码展示了返回指针的正确与错误方式:

返回指针的规则:

返回对象安全性说明
静态变量安全生命周期贯穿程序
动态分配内存安全调用者负责释放
参数传入的指针安全生命周期由调用者管理
局部变量危险函数返回后失效

返回结构体

c
typedef struct {
    int x;
    int y;
} Point;

Point create_point(int x, int y)
{
    Point p = {x, y};
    return p;  
}

Point *create_point_heap(int x, int y)
{
    Point *p = malloc(sizeof(Point));
    if (p) {
        p->x = x;
        p->y = y;
    }
    return p;  
}

上述代码展示了结构体的返回方式:

两种方式对比:

方式优点缺点
返回结构体值简单安全,自动管理内存大结构体复制开销大
返回结构体指针效率高,适合大结构体需要手动释放内存

作用域与生命周期

变量作用域

c
int global_var = 100;  

void func(void)
{
    int local_var = 10;  
    
    {
        int block_var = 20;  
        local_var = 30;      
        global_var = 200;    
    }
    
}

static int file_var = 50;  

static void internal_func(void)
{
    
}

上述代码展示了不同作用域的变量:

作用域类型:

类型作用域生命周期初始化
全局变量整个程序程序运行期间自动初始化为 0
局部变量函数内部函数执行期间未初始化,值不确定
块变量代码块内代码块执行期间未初始化,值不确定
静态全局变量当前文件程序运行期间自动初始化为 0
静态局部变量函数内部程序运行期间只初始化一次

静态局部变量

c
void counter(void)
{
    static int count = 0;  
    count++;
    printf("调用次数: %d\n", count);
}

int main(void)
{
    counter();  
    counter();  
    counter();  
    return 0;
}

上述代码展示了静态局部变量的特性:

静态局部变量的特点:

┌─────────────────────────────────────────────────────────────┐
│                  静态局部变量内存布局                        │
│                                                             │
│  内存区域:                                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                  代码段 (.text)                      │   │
│  │                  存储程序指令                         │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │                  数据段 (.data)                      │   │
│  │         已初始化的全局变量和静态变量                  │   │
│  │         count (静态局部变量存储在这里)               │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │                  BSS 段 (.bss)                       │   │
│  │         未初始化的全局变量和静态变量                  │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │                  堆               │   │
│  │         动态分配的内存                               │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │                  栈               │   │
│  │         局部变量 (非静态)                            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  静态局部变量:                                              │
│  - 存储在数据段,不在栈上                                   │
│  - 只初始化一次                                            │
│  - 值在函数调用之间保持                                     │
└─────────────────────────────────────────────────────────────┘

递归函数

递归的基本概念

c
int factorial(int n)
{
    if (n <= 1) {
        return 1;  
    }
    return n * factorial(n - 1);  
}

上述代码实现了阶乘的递归计算:

递归执行过程:

┌─────────────────────────────────────────────────────────────┐
│                  factorial(4) 执行过程                      │
│                                                             │
│  factorial(4)                                               │
│      │                                                      │
│      ├─► 4 * factorial(3)                                   │
│      │       │                                              │
│      │       ├─► 3 * factorial(2)                           │
│      │       │       │                                      │
│      │       │       ├─► 2 * factorial(1)                   │
│      │       │       │       │                              │
│      │       │       │       └─► return 1  (基准情况)       │
│      │       │       │                                      │
│      │       │       └─► return 2 * 1 = 2                   │
│      │       │                                              │
│      │       └─► return 3 * 2 = 6                           │
│      │                                                      │
│      └─► return 4 * 6 = 24                                  │
│                                                             │
│  结果: 24                                                   │
└─────────────────────────────────────────────────────────────┘

递归与栈

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

上述代码实现了斐波那契数列的递归计算:

递归调用栈:

调用 fibonacci(5):

栈帧结构:
┌────────────────┐
│ fibonacci(5)   │ ← 栈顶
├────────────────┤
│ fibonacci(4)   │
├────────────────┤
│ fibonacci(3)   │
├────────────────┤
│ fibonacci(2)   │
├────────────────┤
│ fibonacci(1)   │ ← 基准情况
└────────────────┘

问题: 重复计算
fibonacci(3) 被计算了 2 次
fibonacci(2) 被计算了 3 次

尾递归优化

c
int factorial_tail(int n, int acc)
{
    if (n <= 1) {
        return acc;
    }
    return factorial_tail(n - 1, n * acc);
}

int factorial(int n)
{
    return factorial_tail(n, 1);
}

上述代码展示了尾递归的实现:

尾递归特点:

特性普通递归尾递归
递归调用位置可能在表达式中间函数最后一步
栈帧使用每次调用创建新栈帧可复用栈帧
空间复杂度O(n)O(1)(优化后)
编译器优化可能优化为循环

递归转迭代

c
int factorial_iterative(int n)
{
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

int fibonacci_iterative(int n)
{
    if (n <= 1) {
        return n;
    }
    
    int prev = 0, curr = 1;
    for (int i = 2; i <= n; i++) {
        int next = prev + curr;
        prev = curr;
        curr = next;
    }
    
    return curr;
}

上述代码将递归转换为迭代:

递归与迭代对比:

特性递归迭代
代码可读性通常更清晰可能更复杂
空间效率需要栈空间通常更高效
栈溢出风险
调试难度较难较易

函数指针

函数指针基础

c
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

int main(void)
{
    int (*op)(int, int);  
    
    op = add;
    printf("10 + 5 = %d\n", op(10, 5));
    
    op = sub;
    printf("10 - 5 = %d\n", op(10, 5));
    
    op = mul;
    printf("10 * 5 = %d\n", op(10, 5));
    
    return 0;
}

上述代码展示了函数指针的基本用法:

函数指针语法:

c
返回类型 (*指针名)(参数类型列表);

int (*fp)(int, int);          

typedef int (*Operation)(int, int);  
Operation op = add;

函数指针数组

c
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return b ? a / b : 0; }

int main(void)
{
    int (*operations[])(int, int) = {add, sub, mul, div};
    char *op_names[] = {"+", "-", "*", "/"};
    
    int a = 20, b = 5;
    for (int i = 0; i < 4; i++) {
        printf("%d %s %d = %d\n", a, op_names[i], b, operations[i](a, b));
    }
    
    return 0;
}

上述代码展示了函数指针数组的应用:

函数指针数组用途:

  • 实现命令模式
  • 状态机实现
  • 策略模式
  • 回调机制

回调函数

c
typedef int (*CompareFunc)(const void *, const void *);

void bubble_sort(void *arr, size_t count, size_t size, CompareFunc cmp)
{
    char *base = (char *)arr;
    char *temp = malloc(size);
    
    for (size_t i = 0; i < count - 1; i++) {
        for (size_t j = 0; j < count - 1 - i; j++) {
            void *a = base + j * size;
            void *b = base + (j + 1) * size;
            
            if (cmp(a, b) > 0) {
                memcpy(temp, a, size);
                memcpy(a, b, size);
                memcpy(b, temp, size);
            }
        }
    }
    
    free(temp);
}

int compare_int(const void *a, const void *b)
{
    return *(int *)a - *(int *)b;
}

int compare_int_desc(const void *a, const void *b)
{
    return *(int *)b - *(int *)a;
}

上述代码展示了回调函数在排序中的应用:

回调函数的优势:

  • 将算法与比较逻辑分离
  • 同一排序函数支持不同排序规则
  • 提高代码复用性

可变参数函数

可变参数基础

c
#include <stdarg.h>

int sum(int count, ...)
{
    va_list args;
    int total = 0;
    
    va_start(args, count);
    
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);
    }
    
    va_end(args);
    
    return total;
}

int main(void)
{
    printf("Sum: %d\n", sum(3, 10, 20, 30));       
    printf("Sum: %d\n", sum(5, 1, 2, 3, 4, 5));    
    return 0;
}

上述代码展示了可变参数函数的实现:

可变参数宏:

说明
va_list可变参数列表类型
va_start(list, last_fixed)初始化参数列表,last_fixed 是最后一个固定参数
va_arg(list, type)获取下一个参数,type 是参数类型
va_end(list)清理参数列表

可变参数实现原理

┌─────────────────────────────────────────────────────────────┐
│                  可变参数内存布局                            │
│                                                             │
│  调用 sum(3, 10, 20, 30):                                   │
│                                                             │
│  栈布局 (从高地址到低地址):                                   │
│  ┌──────────┐                                              │
│  │    30    │ ← va_arg(args, int) 第3次                    │
│  ├──────────┤                                              │
│  │    20    │ ← va_arg(args, int) 第2次                    │
│  ├──────────┤                                              │
│  │    10    │ ← va_arg(args, int) 第1次                    │
│  ├──────────┤                                              │
│  │    3     │ ← count (最后一个固定参数)                    │
│  ├──────────┤                                              │
│  │ 返回地址 │                                              │
│  └──────────┘                                              │
│                                                             │
│  va_start(args, count):                                     │
│  让 args 指向 count 之后的第一个可变参数                     │
│                                                             │
│  va_arg(args, int):                                         │
│  读取当前位置的值,然后移动到下一个参数                       │
└─────────────────────────────────────────────────────────────┘

实现类似 printf 的函数

c
#include <stdarg.h>

void my_printf(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    
    while (*format) {
        if (*format == '%') {
            format++;
            switch (*format) {
                case 'd': {
                    int val = va_arg(args, int);
                    printf("%d", val);
                    break;
                }
                case 's': {
                    char *str = va_arg(args, char *);
                    printf("%s", str);
                    break;
                }
                case 'c': {
                    char ch = (char)va_arg(args, int);
                    putchar(ch);
                    break;
                }
                case '%':
                    putchar('%');
                    break;
                default:
                    putchar('%');
                    putchar(*format);
            }
        } else {
            putchar(*format);
        }
        format++;
    }
    
    va_end(args);
}

上述代码实现了一个简化版的 printf

参数说明:

  • format:格式字符串
  • ...:可变参数

支持的格式:

  • %d:整数
  • %s:字符串
  • %c:字符
  • %%:百分号

内联函数

内联函数基础

c
inline int square(int x)
{
    return x * x;
}

int main(void)
{
    int a = 5;
    int b = square(a);  
    return 0;
}

上述代码展示了内联函数的定义:

内联函数特点:

特性内联函数普通函数
调用方式代码展开跳转执行
函数调用开销
代码大小可能增大不变
适用场景简短、频繁调用的函数复杂函数

内联函数的展开

┌─────────────────────────────────────────────────────────────┐
│                    内联函数展开                              │
│                                                             │
│  源代码:                                                    │
│  int b = square(a);                                         │
│                                                             │
│  展开后 (编译器决定):                                        │
│  int b = a * a;                                             │
│                                                             │
│  生成的汇编 (无函数调用):                                    │
│  mov  eax, [a]        ; 加载 a                              │
│  imul eax, eax        ; eax = a * a                         │
│  mov  [b], eax        ; 存储 b                              │
│                                                             │
│  对比普通函数调用:                                           │
│  push [a]             ; 压栈参数                            │
│  call square          ; 调用函数                            │
│  add  esp, 4          ; 清理栈                              │
│  mov  [b], eax        ; 存储返回值                          │
└─────────────────────────────────────────────────────────────┘

总结

概念要点
参数传递C 语言只有值传递,指针传递本质也是值传递
返回值不要返回局部变量的指针
作用域全局变量、局部变量、静态变量有不同的生命周期
递归需要基准情况和递归步骤,注意栈溢出
函数指针实现回调、策略模式等高级功能
可变参数使用 stdarg.h 中的宏处理
内联函数适合简单、频繁调用的函数

参考资料

[1] C Programming Language. Brian W. Kernighan, Dennis M. Ritchie

[2] C Primer Plus. Stephen Prata

[3] Expert C Programming. Peter van der Linden

相关主题

基于 VitePress 构建