「指針函數」與「函數指針」一字之差、天壤之別;二級指針、指針數組指針更是 C 語言指針體系的皇冠。本文通過**語法拆解、內存佈局圖解、實戰代碼模式**,徹底澄清這四大核心概念,建立不可動搖的指針心智模型。
| 維度 | 指針函數 | 函數指針 |
|---|---|---|
| 本質 | **函數** (返回指針) | **指針變量** (指向函數) |
| 聲明語法 | 返回類型 *函數名(參數表);int *func(int x, int y); |
返回類型 (*指針名)(參數表);int (*func)(int x, int y); |
| 核心區別 | * **不在括號內**,結合到返回類型 |
* **在括號內**,結合到標識符 (改變優先級) |
| 用途 | 返回動態分配內存、靜態數組元素地址、鏈表節點等 | 回調函數、策略模式、狀態機、動態分發、qsort 比較器 |
| 調用方式 | int *p = func(a, b); |
func = &real_func; func(a, b); 或 (*func)(a, b); |
int *GetDate(int wk, int dy) {
static int calendar[5][7] = { ... }; // 靜態存儲期,生命周期全程
return &calendar[wk-1][dy-1]; // 返回元素地址
}
int main() {
int *p = GetDate(2, 3);
printf("%d\n", *p); // 解引用獲取值
}
關鍵:返回指針指向的內存**必須在函數返回後仍有效** (靜態、全局、堆分配)。**嚴禁返回局部變量地址** (棧內存釋放)。
void FileFunc() { puts("File"); }
void EditFunc() { puts("Edit"); }
int main() {
void (*funcp)(); // 聲明函數指針,指向無參無返回值函數
funcp = FileFunc; // 賦值 (函數名隱式轉為地址)
funcp(); // 調用
funcp = EditFunc;
(*funcp)(); // 顯式解引用調用,語義更清晰
}
**ptr):修改指針本身的鑰匙
char c = 'A';
char *p = &c; // 一級指針:指向 char
char **cp = &p; // 二級指針:指向 char*
// 訪問鏈路:
// cp → p 的地址
// *cp → p (char*)
// **cp → c ('A')
void FindCredit(int **fpp) {
while (**fpp != 0) {
if (**fpp < 0) break;
else (*fpp)++; // 修改實參指針 p 本身 (使其指向下一個元素)
}
}
int main() {
int vals[] = {7, 6, 5, -4, 3, 2, 1, 0};
int *fp = vals; // fp 指向數組首元素
FindCredit(&fp); // 傳遞 fp 的地址
printf("%d\n", *fp); // 輸出 -4 (fp 已被函數移動到 -4 處)
}
關鍵:(*fpp)++ 先解引用獲取 fp,再遞增 fp。**括號不可省** (優先級:* 高於 ++)。
int **matrix = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++)
matrix[i] = malloc(cols * sizeof(int));
// 釋放時逆序
for (int i = 0; i < rows; i++) free(matrix[i]);
free(matrix);
char *Names[] = {
"Bill", "Sam", "Jim", "Paul", "Charles", NULL // 以 NULL 結束
};
// Names 類型:char*[6] → 退化為 char** (指向指針數組首元素的指針)
char **nm = Names; // nm 指向第一個 char* (即 "Bill")
while (*nm != NULL) {
printf("%s\n", *nm); // *nm 是 char*,%s 打印字符串
nm++; // 移動到下一個 char* (即 "Sam")
}
*nm++*nm++ = *(nm++) (後綴 ++ 優先級高於 *)。*nm (當前字符串指針) 用於打印,**再遞增** nm 指向下一個指針。printf("%s\n", *nm); nm++;。NULL (或 0) 結尾,循環條件 *nm != NULL。NULL。| 聲明 | 讀法 | 類型 | 典型用途 |
|---|---|---|---|
int *func(); |
函數 func 返回 int* | 指針函數 | 工廠函數、查找返回地址 |
int (*func)(); |
指針 func 指向返回 int 的函數 | 函數指針 | 回調、策略、虛函數表 |
int **pp; |
指向 int* 的指針 | 二級指針 | 修改指針實參、二維動態數組 |
char **argv; |
指向字符串數組的指針 | 指針數組指針 | main 參數、字符串列表遍歷 |
int (*p)[N]; |
指向含 N 個 int 數組的指針 | 數組指針 | 二維數組行指針、矩陣操作 |
int *p = func(); *p = 10; 若 func 返回局部變量地址 → UB。✅ 靜態/堆/全局內存。void (*fp)() = func(); 調用了 func。✅ void (*fp)() = func; (無括號)。void **pp = &p; 若 p 類型不匹配 (如 int*) → 編譯警告/運行時錯誤。✅ 類型嚴格匹配。char **p = 2D_array; 二維數組不可直接轉為雙重指針。✅ char (*p)[COLS] = 2D_array;。指針函數**返回地址**,函數指針**存儲地址**;二級指針**修改一級指針**,指針數組指針**遍歷字符串列表**。掌握聲明語法的優先級規則 (括號改變結合性)、內存佈局的指向關係、以及典型應用場景 (回調/輸出參數/動態數組/命令行參數),即可駕馭 C 語言指針體系的最高難度。**畫圖、寫最小測試代碼驗證、用右左法則讀聲明**,是攻克指針迷宮的三把鑰匙。