「指標函數」與「函數指標」僅一字之差,卻是截然不同的實體。搞混它們是 C/C++ 學習者的通病。本文從語法解析、記憶體模型、呼叫慣例到實戰模式,徹底釐清兩者差異,並延伸至多級指標與指標陣列技巧。
本質:一個函數,其回傳型別是指標。
宣告格式:型別 *函數名(參數列表);
int* GetData(); // 回傳 int*
int* Transform(int, int); // 回傳 int*
int* GetDate(int week, int day) {
static int calendar[5][7] = { /* ... */ };
return &calendar[week-1][day-1]; // 回傳靜態陣列元素位址,生命週期覆蓋全程式
}
關鍵點:回傳的指標必須指向**存活期超過函數呼叫**的記憶體(靜態、全域、堆、呼叫端傳入的緩衝區)。**嚴禁回傳區域變數位址**。
本質:一個變數,儲存函數的**進入位址**,可透過它間接呼叫函數。
宣告格式:型別 (*指標變數名)(參數列表); —— 括號不可省略,否則變成指標函數宣告。
void (*funcPtr)(); // 指向無參無回傳函數
int (*cmp)(const void*, const void*); // 指向比較函數 (如 qsort 回調)
void FileFunc() { puts("FileFunc"); }
void EditFunc() { puts("EditFunc"); }
void (*fp)() = FileFunc; // 取址 & 可省略
fp(); // 呼叫:相當於 FileFunc()
(*fp)(); // 明確解引用風格,兩者等價
fp = EditFunc;
fp(); // 呼叫 EditFunc()
qsort、bsearch、事件處理、訊號槽。dlopen/GetProcAddress 取得函數位址賦值給函數指標。讀懂複雜宣告的通用口訣:從變數名起始,向右讀到盡頭,再向左,遇到括號優先處理括號內。
| 宣告 | 解讀步驟 | 含義 |
|---|---|---|
int *fp(); | fp → () → * → int | 函數,回傳 int* |
int (*fp)(); | fp → * → () → int | 指標,指向回傳 int 的函數 |
int *(*fp[])(); | fp → [] → * → () → * → int | 陣列,元素是指向回傳 int* 函數的指標 |
int (*(*fp)[])(); | fp → * → [] → * → () → int | 指標,指向陣列,陣列元素是指向回傳 int 函數的指標 |
char c = 'A';
char *p = &c; // 一級指標
char **pp = &p; // 二級指標,指向 p
*p = 'B'; // 修改 c
**pp = 'C'; // 透過 pp 修改 c
*pp = &otherChar; // 修改 p 指向
實戰用途:
void FindCredit(int**) 移動指標掃描陣列)。int** matrix = new int*[rows]; for(i) matrix[i] = new int[cols];main(int argc, char** argv):命令列參數陣列。
char* names[] = { "Bill", "Sam", "Jim", "Paul", nullptr };
char** nm = names; // nm 指向首元素
while (*nm) {
puts(*nm); // 印出字串
++nm; // 移至下一個 char*
}
以 nullptr 作哨兵,無需額外傳遞長度,彈性增刪元素。
std::function<R(Args...)> + Lambda/仿函數,支援捕獲狀態、類型擦除。std::vector<T>、std::array<T,N>、std::span<T> (C++20),避免手動生命週期管理。vector<vector<T>>)或扁平化 vector<T> + 索引運算。指標函數是「函數」,函數指標是「變數」。掌握宣告語法的右左法則、生命週期契約與現代替代工具,即可從容駕馭這對經典難點,寫出型別安全、資源無洩漏的程式碼。