函數指標是 C 語言最強大也最令人困惑的特性之一。它將函數視為一等公民,可作為參數傳遞、儲存於陣列、動態決定執行邏輯,是回呼函數、狀態機、外掛架構的基石。本文從記憶體模型出發,徹底解析函數指標的宣告、賦值、呼叫與進階應用。
函數在編譯後佔據代碼段的一塊連續記憶體,其**進入點位址**即為函數位址。函數名 MyFun 在表達式中(除 sizeof、& 外)會**隱式轉換**為函數指標:
void MyFun(int x); // 函數宣告
// 以下等價:
MyFun(10); // 直接呼叫
(*MyFun)(10); // 顯式解參考呼叫
(&MyFun)(10); // 取址後呼叫(標準保證 &MyFun == MyFun)
| 概念 | 資料變數 | 函數 |
|---|---|---|
| 實體 | int i = 10; | void MyFun(int) { ... } |
| 位址 | &i (int*) | &MyFun / MyFun (void(*)(int)) |
| 指標變數 | int *pi = &i; | void (*pf)(int) = MyFun; |
| 解參考 | *pi = 20; | (*pf)(20); |
返回類型 (*指標名) (參數列表);
// 指向「接收 int,回傳 void」的函數
void (*FunP)(int);
// 指向「接收 int, double,回傳 char*」的函數
char* (*FuncPtr)(int, double);
// 陣列:10 個函數指標
void (*handlers[10])(int);
// 函數回傳函數指標
void (*signal(int, void(*)(int)))(int);
*(指標)例:void (*FunP)(int) → FunP 是 指向 (接收 int 回傳 void) 的指標
// 定義函數指標類型
typedef void (*FunType)(int);
// 使用類型宣告變數
FunType FunP1, FunP2, FunP3;
// 作為參數類型
void RegisterCallback(FunType cb);
// 作為回傳類型
FunType GetHandler(int id);
void MyFun(int x) { printf("%d\n", x); }
void (*FunP)(int);
FunP = MyFun; // 隱式轉換
FunP = &MyFun; // 顯式取址(等價)
MyFun(10); // 直接呼叫
FunP(10); // 隱式解參考(現代風格)
(*FunP)(10); // 顯式解參考(傳統風格)
(&FunP)(10); // 合法但罕見
標準保證: 以上四種呼叫方式語義完全相同。
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void*, const void*));
#include <stdio.h>
typedef void (*Handler)(int);
void ProcessData(int data[], int n, Handler callback) {
for (int i = 0; i < n; i++)
callback(data[i]); // 呼叫回呼
}
void PrintSquare(int x) { printf("%d ", x*x); }
void PrintCube(int x) { printf("%d ", x*x*x); }
int main() {
int data[] = {1, 2, 3, 4, 5};
ProcessData(data, 5, PrintSquare); // 輸出: 1 4 9 16 25
puts("");
ProcessData(data, 5, PrintCube); // 輸出: 1 8 27 64 125
return 0;
}
typedef int (*OpFunc)(int, int);
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; }
OpFunc ops[] = {Add, Sub, Mul, Div}; // 索引對應操作碼
int Calculate(int opcode, int a, int b) {
if (opcode >= 0 && opcode < 4)
return ops[opcode](a, b);
return 0;
}
typedef void (*StateHandler)(void);
void StateIdle() { /* ... */ }
void StateRun() { /* ... */ }
void StateError() { /* ... */ }
StateHandler currentState = StateIdle;
void EventLoop() {
while (1) {
currentState(); // 執行當前狀態邏輯
// 根據事件切換狀態
// currentState = StateRun;
}
}
typedef struct {
void (*draw)(void*);
void (*move)(void*, int, int);
void (*destroy)(void*);
} ShapeVTable;
typedef struct {
ShapeVTable *vptr;
int x, y;
} Shape;
void Shape_Draw(void *self) { /* ... */ }
void Shape_Move(void *self, int dx, int dy) { /* ... */ }
ShapeVTable shapeVTable = {Shape_Draw, Shape_Move, free};
Shape* CreateShape(int x, int y) {
Shape *s = malloc(sizeof(Shape));
s->vptr = &shapeVTable;
s->x = x; s->y = y;
return s;
}
// 多型呼叫
s->vptr->draw(s);
s->vptr->move(s, 10, 20);
| 陷阱 | 錯誤代碼 | 正確代碼 | 說明 |
|---|---|---|---|
| 宣告語法錯誤 | void *FunP(int); | void (*FunP)(int); | 前者是回傳 void* 的函數 |
| 參數不匹配 | FunP = MyFun; // MyFun(float) | FunP = (void(*)(int))MyFun; | 嚴格類型檢查,需強制轉換 |
| 呼叫空指標 | FunP(10); // 未初始化 | if (FunP) FunP(10); | 必須檢查非 NULL |
| 調用約定不符 | __stdcall vs __cdecl | 統一使用同一約定 | Windows API 需注意 CALLBACK |
typedef 定義函數指標類型,提升可讀性const 正確性:void (*cb)(const int*)std::function、lambda 替代(C++ 專案)#include <functional>
#include <vector>
using Handler = std::function<void(int)>;
void Process(const std::vector<int>& data, Handler callback) {
for (int x : data) callback(x);
}
// Lambda 表達式
Process(data, [](int x) { printf("%d ", x*x); });
// 綁定成員函數
class Processor {
void Handle(int x) { /* ... */ }
};
Processor p;
Process(data, std::bind(&Processor::Handle, &p, std::placeholders::_1));
函數指標雖然語法晦澀,但掌握其「函數即位址」的本質,配合 typedef 與右左法則,即可駕馭這項強大機制。從 qsort 回呼到 GUI 事件循環、從狀態機到虛函數表,函數指標是 C 語言實現多型、延遲綁定、模組解耦的核心基礎設施。