首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言指標深度解析(七):函數指標完整指南

     編輯:關於C語言

前言

函數指標是 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);

讀法技巧:右左法則

  1. 從指標名開始
  2. 向右讀參數列表
  3. 向左讀 *(指標)
  4. 向左讀返回類型

例:void (*FunP)(int) → FunP 是 指向 (接收 int 回傳 void) 的指標

typedef 簡化複雜宣告

// 定義函數指標類型
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);         // 合法但罕見

標準保證: 以上四種呼叫方式語義完全相同。

函數指標作為參數:回呼模式

經典應用:qsort 比較函數

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;
}

進階應用模式

1. 函數指標陣列:跳躍表

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;
}

2. 狀態機

typedef void (*StateHandler)(void);

void StateIdle() { /* ... */ }
void StateRun() { /* ... */ }
void StateError() { /* ... */ }

StateHandler currentState = StateIdle;

void EventLoop() {
    while (1) {
        currentState();  // 執行當前狀態邏輯
        // 根據事件切換狀態
        // currentState = StateRun;
    }
}

3. 物件導向模擬:虛函數表

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*)
  • ✅ 提供「空操作」預設回呼,避免 NULL 檢查分散
  • ✅ 文件化回呼的呼叫時機、執行緒上下文、生命週期
  • ✅ 考慮使用 C++11 std::function、lambda 替代(C++ 專案)

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 語言實現多型、延遲綁定、模組解耦的核心基礎設施。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved