C 語言中「數組名可看作指向首元素的指針」是初學者最易混淆的概念之一。關鍵在於:數組名不是指針,它沒有儲存地址的記憶體單元,無法賦值、無法修改。它只是編譯器在特定語境下(如傳參、取值)自動轉換為「指向首元素的常量指針」。
char str[] vs char *strchar str1[] = "abc"; // 栈上分配 4 字節:'a','b','c','\0',str1 為首元素地址(常量)
char *str2 = "abc"; // str2 指向唯讀區的 "abc",修改 str2[0] 會觸發段錯誤
《K&R》明確規定:僅在函數形參宣告時,int a[] 與 int *a 等價;其他場合截然不同。
C 標準無「二維數組」一說,int a[3][4] 實為「含 3 個元素的一維數組,每個元素是含 4 個 int 的數組」。記憶體按行優先連續佈局。
char *color[] = {"red", "green", "blue"}; // 指針數組:3 個 char*,分離存儲
char colors[][6] = {"red", "green", "blue"}; // 二維數組:連續 3×6 字節
color:類型 char **,指向指針陣列首元素。colors:類型 char (*)[6],指向含 6 字元數組的指針(行指針)。| 表達式 | 類型 | 含義 | 輸出 |
|---|---|---|---|
color | char ** | 指針陣列首地址 | 地址值 |
*color | char * | 第一行字串 "red" | "red" |
**color | char | 'r' | 'r' |
(*color)[1] | char | 第一行第 2 字元 'e' | 'e' |
*color[1] | char | 第二行首字元 'g' | 'g' |
colors | char (*)[6] | 第一行地址 | 地址值 |
*colors | char * | 退化為第一行首元素地址 | "red" |
**(colors+1) | char | 第二行首字元 'g' | 'g' |
[] 優先級高於 **color[1] ≡ *(color[1]):先取 color[1](第二行指針),再解引用 → 'g'(*color)[1]:先解引用 color 得第一行數組,再取下標 1 → 'e'void foo(char *p[]); // 接受指針數組
void bar(char (*p)[6]); // 接受行指針(列數固定)
void baz(char **p); // 接受指針數組(退化後同 foo)
char *c[] = {...};
char d[][6] = {...};
foo(c); // ✓
foo(d); // ✗ 類型不匹配:char (*)[6] 無法隱式轉 char **
bar(c); // ✗
bar(d); // ✓
結論:二維數組退化為「行指針」,指針數組退化為「指針的指針」,兩者絕不相容。
// 方法 1:連續記憶體(模擬靜態二維數組)
int (*p)[4] = malloc(3 * sizeof *p); // p 指向 3 行 4 列
p[1][2] = 42; // 正常下標訪問
// 方法 2:指針數組(行長度可變)
int **q = malloc(3 * sizeof *q);
for (int i=0; i<3; ++i) q[i] = malloc(4 * sizeof **q);
// 釋放時需逐行 free
sizeof(arr) 給出整個數組大小;sizeof(ptr) 只給指針大小(4/8 字節)。char *s = "abc"; s[0]='x'; → 未定義行為(通常崩潰)。void f(int a[3][4]) 實為 void f(int (*a)[4]),第一維資訊丟失,需額外傳遞行數。掌握「數組即數組、指針即指針、退化只在特定語境」這一核心,配合從右向左讀取宣告(如 int (*p)[4] 讀作「p 是指向含 4 個 int 數組的指針」),即可輕鬆駕馭 C 語言最靈活也最危險的記憶體模型。