C 語言中,二維數組作為函數參數時會退化為指向一維數組的指針(即 int (*)[N]),而非 int **。編譯器需要知道列數(第二維大小)才能正確計算內存偏移(a[i][j] == *(*(a + i) + j))。因此,函數簽名必須包含列數,行數可省略。
// 行數可省略,列數必須是常量表達式
void func1(int arr[][3], int rows);
void func1(int arr[5][3], int rows); // 等價,行數 5 被忽略
// 調用
int a[5][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12},{13,14,15}};
func1(a, 5);
優點:語法直觀,調用方直接傳數組名,內部可直接用 arr[i][j] 訪問。
限制:列數必須是編譯期常量,不支持變長數組(VLA)作為形參(C99 允許 VLA 形參,但需傳遞維度參數)。
void func2(int (*arr)[3], int rows); // arr 是指向「含 3 個 int 的數組」的指針 // 內部訪問同樣用 arr[i][j] // 調用方式相同:func2(a, 5);
這是方式一的等價寫法,更能體現「退化為指針」的本質。
// C++ 專屬,需指定行列數 void func3(int (&arr)[5][3]); // 行列數都固定,靈活性差
注意:純 C 編譯器(GCC -std=c11, MSVC C 模式)不支持此語法。
void func4(int (*arr)[5][3]); // 指向「5行3列整型數組」的指針 // 調用需取地址 func4(&a); // 內部訪問:(*arr)[i][j]
用於需要在函數內修改指針本身指向的場景,較少見。
int **(完全不同的內存佈局)// 適用於運行時動態分配行指針數組 + 每行獨立 malloc
void func5(int **arr, int rows, int cols) {
// 訪問:arr[i][j] 正常工作
// 但無法直接傳入靜態二維數組 int a[5][3]!
}
// 動態分配示例
int **p = malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) p[i] = malloc(cols * sizeof(int));
// 釋放時需逐行 free 再 free(p)
關鍵區別:
| 特性 | 靜態二維數組 int a[5][3] | 動態雙重指針 int **p |
|---|---|---|
| 內存佈局 | 連續塊(5×3 個 int) | 行指針數組 + 離散行數據 |
| 類型 | int (*)[3] | int ** |
| 函數參數 | int arr[][3] 或 int (*arr)[3] | int **arr |
| 訪問語法 | arr[i][j] | arr[i][j](編譯器生成雙重間接) |
| 跨函數傳遞 | 直接傳名 | 傳 int ** |
void f(int **a) 接收靜態二維數組 → 類型不匹配,運行時崩潰(訪問邏輯錯誤)。void f(int a[][n])(C89 非法,C99 VLA 形參合法但需傳 n)。#define COLS 3,形參寫 int a[][COLS]。int **)或單塊連續內存 int *p = malloc(rows*cols*sizeof(int)) 並手動計算索引 p[i*cols+j]。#include <stdio.h>
#include <stdlib.h>
#define COLS 3
void print_arr1(int arr[][COLS], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < COLS; j++) printf("%d ", arr[i][j]);
printf("\n");
}
}
void print_arr2(int (*arr)[COLS], int rows) { // 等價寫法
print_arr1(arr, rows);
}
int main() {
int a[2][COLS] = {{1,2,3},{4,5,6}};
printf("方式一/二:\n");
print_arr1(a, 2);
// 動態分配演示方式五
int rows = 2, cols = COLS;
int **p = malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
p[i] = malloc(cols * sizeof(int));
for (int j = 0; j < cols; j++) p[i][j] = a[i][j];
}
printf("方式五 (int **):\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) printf("%d ", p[i][j]);
printf("\n");
}
for (int i = 0; i < rows; i++) free(p[i]);
free(p);
return 0;
}
二維數組傳參的核心是「列數固定、退化為指向一維數組的指針」。掌握 int arr[][COLS] / int (*arr)[COLS] 兩種主流寫法,明確區分靜態二維數組與動態雙重指針的內存佈局與類型差異,即可避開 90% 的坑。C99 VLA 形參(void f(int rows, int cols, int arr[rows][cols]))是現代 C 的推薦寫法,但需注意編譯器支持度(MSVC 不支持)。