在 C 語言中,數組與指針的關係既緊密又微妙,是無數開發者從「會用」到「精通」的分水嶺。本文將從內存模型、類型系統、指針算術、函數傳參、多維數組五個維度,構建完整的知識體系,並附帶經典誤區避坑指南。
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // ptr 指向首元素
// 內存圖示:
// arr: [10][20][30][40][50] (連續 20 字節,不可移動)
// ptr: [addr_of_arr[0]] (單獨 4/8 字節變量,可修改指向)
int *),但不是變量,不能賦值、不能 arr++、sizeof(arr) 返回整個數組大小。sizeof(ptr) 返回指針本身大小(4/8 字節)。// 三種遍歷方式本質相同(編譯器優化後機器碼幾乎一致)
for (int i = 0; i < 5; i++) printf("%d ", arr[i]); // 下標法
for (int *p = arr; p < arr+5; p++) printf("%d ", *p); // 指針算術
for (int i = 0; i < 5; i++) printf("%d ", *(arr+i)); // 指針偏移
核心規則:指針 + n 表示向後移動 n * sizeof(元素類型) 字節,而非字節偏移。
int matrix[3][4]; // 3行4列,連續 48 字節 // 關鍵類型推導: int (*p_row)[4] = matrix; // 指向「含4個int的數組」的指針(行指針) int *p_elem = matrix[0]; // 指向首元素(退化為一維) // 訪問 matrix[i][j] 等價形式: matrix[i][j] // 標準下標 *(*(matrix + i) + j) // 指針解引用全展開 p_row[i][j] // 行指針下標 *(*(p_row + i) + j) // 行指針算術
面試高頻點:int **p 不能直接指向二維數組 int a[3][4],因為二維數組在內存中是連續塊,而 int ** 期望的是指針數組(非連續)。
// 以下三種聲明在函數參數列表中完全等價(均退化為 int *) void func1(int arr[]); void func2(int arr[10]); // 尺寸被忽略,僅作文檔 void func3(int *arr); // 本質真相 // 實際傳遞的是首元素地址,函數內 sizeof(arr) 返回指針大小! // 必須額外傳遞長度參數: void process(int *arr, size_t len);
| 誤區代碼 | 問題 | 正確寫法 |
|---|---|---|
int *p = malloc(10); p = arr; | 內存洩漏:malloc 內存地址丟失 | 直接 int *p = arr; 或 memcpy |
void f(int a[][]) | 編譯錯誤:二維數組需指定列數 | void f(int a[][COLS]) 或 void f(int (*a)[COLS]) |
return local_array; | 返回棧上局部數組地址 → 懸空指針 | 動態分配 malloc / 靜態 static / 輸出參數 |
sizeof(ptr) 獲取數組長度 | 返回指針大小而非元素個數 | 單獨傳遞 len 參數 |
int *ptr_arr[5]; // 指針數組:5個 int* 元素,每個可指向不同內存塊 int (*arr_ptr)[5]; // 數組指針:指向「含5個int的數組」的單一指針 // 應用場景: // 指針數組:字符串數組、不規則矩陣、命令行參數 argv // 數組指針:處理二維數組整體、矩陣運算庫
總結:數組是「連續內存塊的標籤」,指針是「攜帶類型信息的地址變量」。掌握退化規則、類型推導、指針算術單位,配合圖解內存佈局,即可破解所有數組指針難題。記住:
int a[5]在參數列表裡只是int *a的語法糖。