這兩個概念僅一字之差,卻是 C 指針進階的分水嶺。本文從語法解析、記憶體模型、二維數組綁定、常見面試陷阱四個維度,一次講透。
| 名稱 | 聲明語法 | 語法結合優先級 | 本質 | 佔用空間 |
|---|---|---|---|---|
| 數組指針 (Array Pointer / Row Pointer) | int (*p)[N]; | () 優先 → p 是指針 → 指向 int[N] | 單一指針變量 | 1 個指針大小 |
| 指針數組 (Pointer Array) | int *p[N]; | [] 優先 → p 是數組 → 元素類型 int* | N 個指針組成的數組 | N 個指針大小 |
// 數組指針:單一指針 → 指向整個二維數組首行
int a[3][4];
int (*p)[4] = a; // p 指向 a[0] (即 &a[0][0])
p + 1; // 跨越 4 個 int,指向 a[1]
// 指針數組:3 個獨立指針 → 分別指向各行
int *q[3];
for (int i = 0; i < 3; ++i) q[i] = a[i]; // q[0]=&a[0][0], q[1]=&a[1][0]...
q + 1; // ❌ 錯誤:q 是數組類型,不可修改
// ✅ 數組指針:需固定列數
void print_row_ptr(int (*p)[4], int rows) {
for (int i = 0; i < rows; ++i)
for (int j = 0; j < 4; ++j)
printf("%d ", p[i][j]);
}
// ✅ 指針數組:列數可變
void print_ptr_array(int *p[], int rows, int cols) {
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
printf("%d ", p[i][j]);
}
// ❌ 雙重指針 int** 無法直接接收 int a[3][4]
// 因為 a 退化為 int (*)[4],而非 int**
// 方法 A:連續塊 + 數組指針(緩存友好)
int (*p)[4] = malloc(3 * sizeof *p); // 一次分配 3x4 連續空間
// 方法 B:指針數組 + 分行分配(行長可異)
int **q = malloc(3 * sizeof *q);
for (int i = 0; i < 3; ++i) q[i] = malloc(4 * sizeof *q[i]);
無論 p 是數組指針還是指針數組,下列四種寫法對 a[i][j] 均有效:
*(p[i] + j)
*(*(p + i) + j)
(*(p + i))[j]
p[i][j] // 編譯器自動轉換
「先看右邊」:
int (*p)[4]→ 右邊是[4]→ 指向「含 4 個 int 的數組」 → 數組指針
int *p[4]→ 右邊是[4]→ 「含 4 個指針的數組」 → 指針數組
char *p, a; 中 a 是 char,易誤解;建議 char *p; 貼近變量名。int (*)[N] 或先轉為指針數組。數組指針 = 一個指針,步長 = 整行大小,適合連續二維數組;
指針數組 = 多個指針,各自獨立,適合鋸齒數組、動態行長。
搞清 () 與 [] 結合優先級,配合「先看右邊」口訣,再不會混淆。