指針進階的核心在於「指針的指針」與「函數指針」——它們是構建靈活框架、實現回調與泛型編程的基石。本文通過五個精煉案例,帶你攻克二級指針操作字符串數組、字符串轉整數解析器、通用指針 void* 的類型擦除與還原、函數指針實現策略模式與自定義排序規則。
#include
void swap(int *a, int *b) {
int tmp = *a; *a = *b; *b = tmp;
}
int main() {
int a = 10, *p = &a;
printf("a=%d, p=%p\n", *p, p);
p++; printf("p+1=%p (跨過 %zu 字節)\n", p, sizeof(int));
p--; printf("p-1=%p\n", p);
int b = 20;
swap(&a, &b); // 地址傳遞實現交換
printf("a=%d, b=%d\n", a, b);
/* const 修飾位置決定鎖定對象 */
const int *p1 = &a; // *p1 不可改
// *p1 = 30; // 錯誤
p1 = &b; // 合法
int *const p2 = &a; // p2 不可改
*p2 = 40; // 合法
// p2 = &b; // 錯誤
/* 野指針防範:用完置 NULL,用前判空 */
p = NULL;
if (p) printf("%p\n", p); // 不會執行
return 0;
}
sizeof(所指類型)。const 在 * 左 → 鎖定值;在 * 右 → 鎖定指針。NULL 是明確的「無效地址」;野指針是未初始化或已釋放的隨機地址,極其危險。char *names[] = {"ZhangSan", "LiSi", "WangWu", "ZhaoLiu"};
/* 內存佈局:
names (棧) → [ptr0] → "ZhangSan" (常量區)
[ptr1] → "LiSi" (常量區)
[ptr2] → "WangWu" (常量區)
[ptr3] → "ZhaoLiu" (常量區)
*/
char **p = names; // p 指向首個 char*
for (int i = 0; i < 4; i++)
printf("%s\n", p[i]); // p[i] 等價 names[i] 等價 *(p+i)
/* 輸出四行名字 */
本質:char **p 指向 char* 類型的數組,解引用一次得到 char*,再解引用得到字符。
需求:逐字符掃描,遇非數字停止,返回已解析整數,並將輸入指針推進到首個非數字字符——這正是 strtol 的核心邏輯。
#include
int convert(char **str) { // 輸入輸出參數:雙重指針
int num = 0;
while (**str) { // **str 當前字符
if (**str >= '0' && **str <= '9')
num = num * 10 + (**str - '0');
else
break;
(*str)++; // 移動原始指針(需括號:先解引用再++)
}
return num;
}
int main() {
char buf[] = "1234abc";
char *p = buf;
printf("數值: %d\n", convert(&p)); // 輸出 1234
printf("剩餘: %s\n", p); // 輸出 abc
return 0;
}
char **str:調用者傳 &p,函數內通過 *str 修改主調的 p。(*str)++:括號至關重要——*str++ 會被解析為 *(str++)(移動二級指針本身),導致邏輯錯誤。void* 可接收任意類型指針賦值(隱式轉換)。*pv 編譯錯誤,編譯器不知道目標大小。*(int*)pv = 20;。#include
int main() {
int a = 10;
int *pi = &a;
void *pv = pi; // 隱式轉換
// *pv = 20; // 錯誤:void* 無法解引用
*(int*)pv = 20; // 正確:先轉回 int*
printf("a = %d\n", a); // 輸出 20
return 0;
}
void* data。void* (*)(void*)。void* ctx。返回類型 (*指針名)(參數列表);
// 例:int (*cmp)(int, int); // 指向「接收兩 int、返回 int」的函數
#include
int minVal(int *buf, int n) {
int m = buf[0];
for (int i = 1; i < n; i++) if (buf[i] < m) m = buf[i];
return m;
}
int maxVal(int *buf, int n) {
int m = buf[0];
for (int i = 1; i < n; i++) if (buf[i] > m) m = buf[i];
return m;
}
int main() {
int data[10] = {1,2,3,4,5,6,7,8,9,0};
int (*op)(int*, int); // 函數指針變量
op = minVal;
printf("min = %d\n", op(data, 10)); // 0
op = maxVal;
printf("max = %d\n", op(data, 10)); // 9
return 0;
}
#include
void printArr(int *a, int n) {
for (int i = 0; i < n; i++) printf("%d ", a[i]);
puts("");
}
/* 三種比較規則 */
int asc(int x, int y) { return x - y; } // 升序
int desc(int x, int y) { return y - x; } // 降序
int mod3(int x, int y) { return x%3 - y%3; } // 按模 3 排序
/* 通用排序:接收比較器函數指針 */
void bubbleSort(int *a, int n, int (*cmp)(int, int)) {
for (int i = 0; i < n-1; i++)
for (int j = 0; j < n-1-i; j++)
if (cmp(a[j], a[j+1]) > 0) { // cmp 返回 >0 表示需交換
int t = a[j]; a[j] = a[j+1]; a[j+1] = t;
}
}
int main() {
int a[10] = {1,2,3,4,5,6,7,8,9,0};
printArr(a, 10);
bubbleSort(a, 10, asc); printArr(a, 10); // 0 1 2 3 4 5 6 7 8 9
bubbleSort(a, 10, desc); printArr(a, 10); // 9 8 7 6 5 4 3 2 1 0
bubbleSort(a, 10, mod3); printArr(a, 10); // 0 3 6 9 1 4 7 2 5 8
return 0;
}
typedef 簡化聲明typedef int (*CmpFunc)(int, int);
void sort(int *a, int n, CmpFunc cmp); // 更清晰
/* 標準庫 qsort 簽名 */
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
結合 void*、函數指針、二級指針,可構建極簡的泛型框架:
/* 通用回調類型 */
typedef void (*VisitFunc)(void *elem, void *ctx);
/* 遍歷任意類型數組 */
void forEach(void *base, size_t n, size_t sz, VisitFunc visit, void *ctx) {
char *p = (char*)base;
for (size_t i = 0; i < n; i++, p += sz)
visit(p, ctx);
}
/* 使用示例 */
void printInt(void *e, void *) { printf("%d ", *(int*)e); }
void printStr(void *e, void *) { printf("%s ", *(char**)e); }
int main() {
int nums[] = {1,2,3};
char *words[] = {"A","B","C"};
forEach(nums, 3, sizeof(int), printInt, NULL); // 1 2 3
puts("");
forEach(words, 3, sizeof(char*), printStr, NULL); // A B C
return 0;
}
| 陷阱 | 症狀 | 修正 |
|---|---|---|
(*str)++ 寫成 *str++ | 指針未正確推進,解析錯誤 | 加括號:先解引用再自增 |
void* 直接解引用 | 編譯錯誤「void value not ignored」 | 強轉後再解引用 |
| 函數指針賦值類型不匹配 | 警告或運行時崩潰 | 嚴格匹配簽名,用 typedef 統一 |
| 二級指針未分配內存就解引用 | 段錯誤 | 確保 **p 指向有效 *p |
回調函數修改 const 數據 | 未定義行為 | 回調參數加 const 約束 |
下一步進階:深入 qsort/bsearch 標準庫實現、函數指針表驅動狀態機、void* 類型安全封裝(_Generic 關鍵字)、C++ 模板與 C 泛型的對比思考。