函數是 C 語言實現模塊化、復用性與可維護性的核心機制。本文系統梳理函數分類、參數傳遞、遞歸、作用域與多文件協作,並給出工程級編碼範例。
| 類型 | 返回值 | 參數 | 典型場景 | 示例 |
|---|---|---|---|---|
| 無返回無參 | void | void | 初始化、打印固定信息 | void printLogo() |
| 無返回有參 | void | 有 | 數據處理、輸出 | void printArray(int a[], int n) |
| 有返回無參 | 非 void | void | 獲取常量、隨機數 | double getPI() |
| 有返回有參 | 非 void | 有 | 計算、轉換、查詢 | int max(int a, int b) |
C 語言只有值傳遞。函數接收的是實參的副本,無法直接修改外部變量。需修改外部值時,傳遞指針(地址值)。
// 錯誤:無法修改外部 a
void swap_err(int a, int b) { int t=a; a=b; b=t; }
// 正確:傳遞地址,通過解引用修改
void swap_ok(int *a, int *b) { int t=*a; *a=*b; *b=t; }
// 調用
int x=1, y=2;
swap_ok(&x, &y); // 必須取地址
函數參數聲明為 int arr[] 或 int arr[10] 實際等同於 int *arr。sizeof(arr) 返回指針大小而非數組長度,必須額外傳遞長度參數。
void process(int arr[], int len) { // 等價於 int *arr
// sizeof(arr) == 4/8 (指針大小),不可用於求長度
for (int i=0; i
3. 遞歸:終止條件與棧幀開銷
// 階乘遞歸
long long fact(int n) {
if (n <= 1) return 1; // 基線條件(終止條件)
return n * fact(n - 1); // 遞歸步驟
}
// 斐波那契:雙遞歸導致指數級複雜度,實際用迭代/備忘錄
long long fib(int n) {
if (n <= 2) return 1;
return fib(n-1) + fib(n-2); // 大量重複計算,教學用途
}
遞歸陷阱:棧溢出(深度過大)、重複計算(斐波那契)、尾遞歸優化機會(編譯器依賴)。
4. static 關鍵字:三重語義
位置 作用 生命周期 作用域 示例
函數內局部變量 保持值不銷毀 程序全周期 僅函數內可見 static int cnt = 0;
函數外全局變量/函數 內部鏈接 程序全周期 僅本文件可見 static int g_var;
類成員 屬於類而非對象 程序全周期 類作用域 class A { static int s; };
5. 多文件協作:聲明與定義分離
// MyMath.h —— 接口聲明(供調用者包含)
#ifndef MY_MATH_H
#define MY_MATH_H
int add(int a, int b); // 函數聲明
double getPI(void);
#endif
// MyMath.c —— 實現定義
#include "MyMath.h"
int add(int a, int b) { return a + b; }
double getPI(void) { return 3.1415926535; }
// main.c —— 使用者
#include "MyMath.h"
int main() { printf("%d\n", add(3, 5)); }
工程實戰:模塊化計算器
文件結構
calc/
├── include/
│ ├── calc.h // 公共接口
│ └── operator.h // 運算符接口
├── src/
│ ├── calc.c // 主邏輯
│ ├── operator.c // 四則運算實現
│ └── utils.c // 輔助函數
└── test/
└── test_calc.c
關鍵代碼片段
// operator.h
typedef enum { ADD, SUB, MUL, DIV } OpType;
double calculate(double a, double b, OpType op);
// operator.c
double calculate(double a, double b, OpType op) {
switch (op) {
case ADD: return a + b;
case SUB: return a - b;
case MUL: return a * b;
case DIV: return (b != 0) ? a / b : NAN;
}
return NAN;
}
// calc.c —— 表達式求值(簡化版:雙棧法)
double eval(const char *expr) {
// 實現中綴轉後綴 + 棧機求值...
}
常見錯誤與最佳實踐
- 未聲明直接調用:C99 要求隱式聲明已廢棄,務必包含頭文件
- 參數類型不匹配:
scanf("%d", &f) 傳 float* 導致未定義行為
- 遞歸無基線條件:必然棧溢出
- 大對象值傳遞:結構體傳指針避免拷貝開銷
- 函數過長:單一職責,超過 50 行考慮拆分
總結
函數是 C 語言抽象與復用的基本單元。精通值傳遞本質、指針模擬引用、遞歸終止條件、static 作用域控制、多文件接口分離,是寫出清晰、高效、可維護 C 代碼的關鍵。實戰中堅持「聲明在 .h,定義在 .c,接口最小化,實現細節隱藏」的模塊化原則。