Skip to content

函数(Functions)

函数是将一段逻辑封装为可复用单元的核心机制。合理拆分函数能让代码结构清晰、易于测试和维护。


1. 为什么需要函数

没有函数,所有代码堆在 main 里:

int main()
{
    // 100 行验证输入的代码
    // 200 行计算逻辑
    // 50 行输出结果
    // ——难以阅读、难以修改、难以复用
}

拆分为函数后:

int main()
{
    int input = get_valid_input();
    int result = calculate(input);
    print_result(result);
    // ——意图一目了然
}

函数的核心价值:封装细节、命名意图、支持复用


2. 函数的基本结构

返回类型  函数名(参数列表)
{
    // 函数体
    return 返回值;  // 如果返回类型不是 void
}
// 一个完整的函数
int add(int a, int b)
{
    int sum = a + b;
    return sum;
}
组成部分 示例中的值 说明
返回类型 int 函数返回值的类型;不返回用 void
函数名 add 描述函数做什么(动词或动词短语)
参数列表 int a, int b 调用者传入的数据
函数体 { ... } 具体执行逻辑
返回值 return sum; 将结果传回调用者

3. 函数声明与定义

C 语言要求函数在使用前被编译器知晓。有两种方式:

方式一:定义在前(先写函数体,再调用)

#include <stdio.h>

// 定义在前
int add(int a, int b)
{
    return a + b;
}

int main()
{
    int result = add(3, 5);   // 可以调用
    printf("%d\n", result);
    return 0;
}

方式二:声明在前,定义在后(推荐)

#include <stdio.h>

// 函数声明(原型)——只写签名,不写函数体
int add(int a, int b);

int main()
{
    int result = add(3, 5);   // OK,编译器已看到声明
    printf("%d\n", result);
    return 0;
}

// 函数定义——可以在后面,甚至可以在另一个 .c 文件中
int add(int a, int b)
{
    return a + b;
}

声明中的参数名可省略(int add(int, int);),但保留名称有助于理解。


4. 参数传递

4.1 传值(Pass by Value)

C 语言默认传值——函数收到参数的副本,修改副本不影响原变量:

void increment(int x)
{
    x = x + 1;               // 只修改了局部副本
    printf("函数内:%d\n", x);
}

int main()
{
    int n = 10;
    increment(n);
    printf("函数外:%d\n", n);   // 仍然是 10
    return 0;
}
// 输出:
// 函数内:11
// 函数外:10

4.2 传指针(模拟传引用)

要让函数修改外部变量,需传递该变量的地址

void increment(int* p)
{
    *p = *p + 1;             // 通过指针修改原值
}

int main()
{
    int n = 10;
    increment(&n);           // 传入 n 的地址
    printf("%d\n", n);       // 11
    return 0;
}

传值 vs 传指针对比:

传递方式 函数内修改 适用场景
传值 void f(int x) 不影响原变量 只需要读取数据
传指针 void f(int* p) 影响原变量 需要修改数据;传递大体积数据避免拷贝

4.3 数组作为参数

数组作为参数时退化为指针,传递的是首元素地址而非整个数组:

void print_array(int arr[], int size)   // arr 实际上是一个指针
{
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
}

因此必须额外传递数组的长度。在函数内部 sizeof(arr) 返回的是指针大小(通常 4 或 8 字节),而非数组实际大小。


5. 返回值

5.1 基本用法

int max(int a, int b)
{
    if (a > b) return a;
    else       return b;
}

return 做了两件事: 1. 将返回值传递给调用者 2. 立即退出当前函数

int divide(int a, int b, int* result)
{
    if (b == 0) {
        return 0;              // 失败立即返回,后续代码不执行
    }
    *result = a / b;
    return 1;                  // 成功
}

5.2 无返回值:void

void greet(const char* name)
{
    printf("你好,%s!\n", name);
    // 可以写 return; 但不必要;函数末尾自动返回
}

6. 递归

函数调用自身称为递归,适合处理具有自相似结构的问题。

6.1 阶乘示例

int factorial(int n)
{
    if (n <= 1) {
        return 1;                     // 基准条件(base case)
    }
    return n * factorial(n - 1);      // 递归条件
}

执行 factorial(4) 的展开过程:

factorial(4) = 4 * factorial(3)
             = 4 * 3 * factorial(2)
             = 4 * 3 * 2 * factorial(1)
             = 4 * 3 * 2 * 1
             = 24

6.2 递归的两个必备要素

要素 说明 缺失的后果
基准条件(base case) 停止递归的出口 无限递归,最终栈溢出
递归条件 向基准条件推进 同上

6.3 递归的代价

每次函数调用都会消耗栈空间(存储局部变量、返回地址等)。递归层数过深会导致栈溢出(Stack Overflow)。对于简单的循环问题,迭代(for/while)通常比递归更高效。


7. 函数设计原则

原则 说明 反面示例
单一职责 一个函数只做一件事 calculate_and_print_and_save()
命名表意图 函数名描述做什么,而非怎么做的 procData() → 应改为 filter_valid_records()
参数宜少 超过 4 个参数考虑封装为结构体 void draw(int, int, int, int, int, int, char*)
无副作用 能不修改外部状态就不修改 get_count() 内部却修改了全局变量
长度适中 一屏内(约 20-50 行)易于理解 200 行的单一函数

8. 变量作用域与函数

int global = 100;             // 全局变量——所有函数可见

void foo()
{
    static int call_count = 0;  // 静态局部变量——值在多次调用间保持
    call_count++;
    printf("foo 被调用了 %d 次\n", call_count);

    int local = 10;             // 局部变量——每次调用重新创建
}

int main()
{
    foo();   // foo 被调用了 1 次
    foo();   // foo 被调用了 2 次
    foo();   // foo 被调用了 3 次
    return 0;
}

关于存储类的完整讨论,详见《存储类与作用域》。


9. 常见错误

错误 问题 正确做法
忘记函数声明 调用在定义之前却没有前置声明 在文件顶部或头文件中添加函数原型
返回局部变量地址 return &x; 中 x 是局部变量,函数返回后其内存已失效 返回值本身(return x),或用 static/动态分配
数组参数丢失大小信息 void f(int arr[]) 内部用 sizeof(arr) 额外传递 size 参数
递归无基准条件 无限递归导致栈溢出 确保每次递归都向基准条件靠近
修改传值参数 期望 f(x) 后 x 被修改 改用指针参数 f(&x)

10. 总结

要点 说明
函数声明 调用前让编译器知晓函数签名(返回类型 + 名称 + 参数类型)
默认传值 函数内修改参数不影响原变量
需要修改用指针 void f(int* p) 传入地址以修改外部变量
数组参数退化 数组作为参数时退化为指针,需额外传长度
void 无返回值 用于纯副作用的函数
递归需基准条件 否则导致栈溢出
返回局部变量地址是危险行为 函数返回后该地址的内存已无效

作者: Qing | 修改日期: 2026-07-18