指標是 C 語言的靈魂,也是區分初學者與資深開發者的關鍵分水嶺。本文將系統梳理指標的進階用法,涵蓋指標算術、const 修飾規則、空指標防護、雙重指標操作字串陣列、void* 型別擦除、以及函數指標實現回調與策略模式等核心技術。透過完整的程式碼範例,助你掌握指標在實際專案中的靈活運用。
指標最經典的應用是實現函數間的「傳址呼叫」。以下範例展示了 swap 函數如何透過指標交換兩個變數的值,同時演示指標的加減運算本質——步長由資料型別決定。
#include <stdio.h>
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main() {
int a = 10;
int *p = &a;
printf("a = %d\n", *p); // 透過指標讀取值
printf("p = %p\n", (void*)p); // 輸出位址
p = p + 1; // 指標 +1,位址增加 sizeof(int) 位元組
printf("p + 1 = %p\n", (void*)p);
p = p - 1; // 指標 -1,位址減少 sizeof(int) 位元組
printf("p - 1 = %p\n", (void*)p);
int b = 20;
printf("a = %d, b = %d\n", a, b);
swap(&a, &b); // 傳遞位址,實現真正交換
printf("a = %d, b = %d\n", a, b);
return 0;
}
const 修飾指標的三種模式const 與指標結合時,位置決定語意:
const int *p (或 int const *p): 指向常數的指標。無法透過 *p 修改值,但指標本身可指向別處。int *const p: 常數指標。指標本身被鎖死不可改變指向,但可透過 *p 修改值。const int *const p: 雙重鎖定,指標與指向值皆不可變。const int *p1 = &a; // *p1 = 30; // 錯誤:不能修改值 p1 = &b; // 正確:可改變指向 int *const p2 = &a; // p2 = &b; // 錯誤:不能改變指向 *p2 = 40; // 正確:可修改值
指標用完後立即置 NULL,配合 if (p) 判斷,是防禦性程式設計的良好習慣,能有效避免野指標導致的未定義行為。
p = NULL;
if (p) printf("p = %p\n", (void*)p); // 條件為假,不會執行,保證安全
雙重指標 (char **) 常用於操作「指標陣列」,即字串陣列。陣列名 name 退化為指向首元素的指標 (char **),透過 p[i] 即可存取每個字串。
#include <stdio.h>
int main() {
char *name[] = {"zhangsan", "lisi", "wangwu", "zhaoliu"};
char **p = name; // 雙重指標指向指標陣列
for (int i = 0; i < 4; i++)
printf("%s\n", p[i]); // p[i] 等價於 name[i]
return 0;
}
利用雙重指標作為「輸出型參數」,函數可修改主調函數中的指標指向,實現「消費字串」的效果:解析前導數字,並將指標推進至第一個非數字字符。
#include <stdio.h>
int convert(char **str) {
int num = 0;
while (**str) {
if (**str >= '0' && **str <= '9')
num = num * 10 + **str - '0';
else
break;
(*str)++; // 移動主調函數中的指標
}
return num;
}
int main() {
char str[] = "1234abc";
char *p = str;
printf("%d\n", convert(&p)); // 輸出 1234
printf("%s\n", p); // 輸出 abc (指標已移動)
return 0;
}
void* 的型別擦除與還原void* 可接受任意型別指標,但解引用前必須強制轉型。這是實現泛型介面(如 qsort, memcpy)的基石。
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
void *p1 = p; // 隱式轉換為 void*
// *p1 = 20; // 錯誤:void* 無法直接解引用
*(int*)p1 = 20; // 正確:先轉型為 int* 再解引用
printf("a = %d\n", *(int*)p1);
return 0;
}
函數指標變數宣告語法:返回型別 (*指標名)(參數列表)。透過指標呼叫函數,可在執行期動態決定行為。
#include <stdio.h>
int min(int *buf, int size) {
int m = buf[0];
for (int i = 0; i < size; i++) if (buf[i] < m) m = buf[i];
return m;
}
int max(int *buf, int size) {
int m = buf[0];
for (int i = 0; i < size; 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 (*p)(int*, int); // 宣告函數指標
p = min;
printf("min = %d\n", p(data, 10)); // 透過指標呼叫 min
p = max;
printf("max = %d\n", p(data, 10)); // 透過指標呼叫 max
return 0;
}
將比較規則抽象為函數指標傳入排序函數,分離演算法邏輯與業務規則,實現「策略模式」。
#include <stdio.h>
void print(int *a, int n) {
for (int i = 0; i < n; i++) printf("%d ", a[i]);
printf("\n");
}
// 比較規則:升序
int rule1(int x, int y) { return x - y; }
// 比較規則:降序
int rule2(int x, int y) { return y - x; }
// 比較規則:按模 3 餘數排序
int rule3(int x, int y) { return x % 3 - y % 3; }
// 通用氣泡排序
void sort(int *a, int n, int (*f)(int, int)) {
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - 1 - i; j++)
if (f(a[j], a[j+1]) > 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};
print(a, 10);
sort(a, 10, rule1); print(a, 10); // 升序
sort(a, 10, rule2); print(a, 10); // 降序
sort(a, 10, rule3); print(a, 10); // 自訂規則
return 0;
}
指標進階技術是 C 語言高效、靈活程式設計的基石。掌握 const 語意、雙重指標操作動態資料結構、void* 實現泛型、函數指標實現回調與策略模式,將讓你能編寫出更健壯、可複用的系統級程式碼。