指標是 C 語言區別於其他高階語言的核心特性,直接對應硬體記憶體位址。本文從計算機儲存體架構出發,建立「位址→指標→指標變數」的完整心智模型,涵蓋宣告、賦值、運算子(&/*)、指標算術、陣列退化規則,並以圖解方式消除初學者最常見的困惑。
0x7FFC1234)。比喻:銀行帳號 = 指標,存摺內容 = 資料,記錄帳號的紙條 = 指標變數。
類型名 *變數名; // * 屬於宣告子,非型別一部分
int *p1; // p1 指向 int
double *p2; // p2 指向 double
char *p3; // p3 指向 char
// 同行多宣告陷阱:
int *p, q; // p 是 int*,q 是 int!建議分行寫。
int a = 10;
int *p = &a; // 取址運算子 & 取得 a 的位址
// p = 1000; // 錯誤:整數不能直接賦值給指標(需強制轉型)
// *p = &a; // 錯誤:*p 是 int,不能存位址
鐵律:指標變數必須初始化後才能使用,未初始化指標(野指標)解參考會導致未定義行為。
| 運算子 | 名稱 | 操作數 | 結果 | 優先級/結合性 |
|---|---|---|---|---|
& | 取址 | 左值(變數、陣列元素) | 該變數位址(常量指標) | 單目,右結合 |
* | 解參考/間接存取 | 指標變數 | 指標指向的變數(左值) | 單目,右結合 |
int a = 5, *p = &a;
printf("%d\n", *p); // 5
*p = 20; // 修改 a
printf("%d\n", a); // 20
指標只能進行:
p + n 移動 n * sizeof(*p) bytes。ptrdiff_t)。禁止:指標 + 指標、指標 × 數、指標 % 數、指標與整數比較(除 0)。
int a[5] = {10,20,30,40,50};
int *p = a;
while (p < a+5) {
printf("%d ", *p);
++p; // 移動 sizeof(int) bytes
}
在表達式中,陣列名 a 自動轉換為 &a[0](指向首元素的指標),例外:
sizeof(a) → 整個陣列大小(bytes)。&a → 指向整個陣列的指標(型別 int(*)[5])。char a[] = "abc";。int a[5];
printf("%p %p %p\n", a, &a[0], &a); // 數值相同,型別不同!
// a : int*
// &a[0] : int*
// &a : int(*)[5]
int a[3][4];
a → int(*)[4] (指向 4-int 陣列的指標)
a[0] → int* (指向 a[0][0])
a[i] → int* (指向 a[i][0])
*a → int* (同 a[0])
const int *p1; // 指向常數的指標:不可 *p1=...,可 p1=...
int *const p2; // 常數指標:可 *p2=...,不可 p2=...
const int *const p3; // 完全鎖死
int const *p4; // 同 p1(const 在 * 左邊修飾指向的型別)
口訣:* 左邊的 const 鎖指標本身,右邊的鎖指向的資料。
| 錯誤程式碼 | 問題 | 修正 |
|---|---|---|
int *p; *p = 10; | 野指標寫入隨機位址 | int a; int *p = &a; *p = 10; |
void f(int *p) { p = malloc(4); } | 修改區域副本,呼叫端不受影響 | void f(int **p) { *p = malloc(4); } |
char *s = "hello"; s[0] = 'H'; | 字串常數在唯讀區,寫入崩潰 | char s[] = "hello"; s[0] = 'H'; |
int a[2][3]; int *p = a; | 型別不匹配(int(*)[3] vs int*) | int (*p)[3] = a; |
free(p); p = NULL; 漏寫 p = NULL | 懸空指標,後續誤用 | 養成 free(p); p = NULL; 肌肉記憶 |
int (*(*p)[3])(int, int) 的完整宣告含義(函式指標陣列的指標)。void swap(int *a, int *b) 並說明為何不能寫成 void swap(int a, int b)。sizeof("abc") 與 strlen("abc") 差異。指標不是魔法,只是「儲存位址的變數」加上「型別感知的算術」。建立正確心智模型,寫出安全、高效的 C 程式將不再困難。