二维数组
二维数组是数组的数组,用于存储表格、矩阵等二维数据。在内存中,二维数组按行优先(Row-major Order)连续存储。理解二维数组的存储布局和指针表示,是处理矩阵运算、图像数据、游戏地图等场景的基础。
声明与初始化
int matrix[3][4]; /* 3 行 4 列 */
/* 完全初始化 */
int mat1[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
/* 部分初始化 */
int mat2[3][4] = {
{1, 2}, /* 第一行:1, 2, 0, 0 */
{4, 5, 6}, /* 第二行:4, 5, 6, 0 */
{7} /* 第三行:7, 0, 0, 0 */
};
/* 省略第一维 */
int mat3[][3] = { /* 编译器计算行数:2 */
{1, 2, 3},
{4, 5, 6}
};
内存布局
二维数组在内存中按行优先连续存储:
int mat[2][3] = {{1, 2, 3}, {4, 5, 6}};
/* 内存布局:
地址 值
0x1000 1 ← mat[0][0]
0x1004 2 ← mat[0][1]
0x1008 3 ← mat[0][2]
0x100C 4 ← mat[1][0]
0x1010 5 ← mat[1][1]
0x1014 6 ← mat[1][2]
*/
mat[i][j] 的地址 = 基地址 + (i × 列数 + j) × 元素大小
访问元素
int mat[3][4];
/* 赋值 */
mat[0][0] = 1;
mat[1][2] = 5;
/* 遍历 */
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++)
printf("%d ", mat[i][j]);
printf("\n");
}
作为函数参数
二维数组作为参数时,除第一维外,其他维度必须显式指定:
void print_matrix(int mat[][4], int rows)
{
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++)
printf("%d ", mat[i][j]);
printf("\n");
}
}
/* 等价声明 */
void print_matrix(int (*mat)[4], int rows)
int main(void)
{
int mat[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
print_matrix(mat, 3);
return 0;
}
int mat[][4] 等价于 int (*mat)[4]:指向包含 4 个 int 的数组的指针。
为什么不能省略第二维?编译器需要知道每行的大小来计算 mat[i][j] 的地址:
mat[i][j] 的地址 = mat + i * (4 * sizeof(int)) + j * sizeof(int)
指针表示
int mat[3][4];
/* mat 的类型:int (*)[4] */
/* mat[i] 的类型:int *(指向第 i 行的首元素) */
int *row = mat[0]; /* 第一行的首地址 */
printf("%d\n", row[2]); /* mat[0][2] */
/* 遍历 */
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++)
printf("%d ", *(*(mat + i) + j)); /* mat[i][j] */
printf("\n");
}
常见操作
矩阵转置:
void transpose(int src[3][4], int dest[4][3])
{
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; j++)
dest[j][i] = src[i][j];
}
矩阵乘法:
void multiply(int a[2][3], int b[3][2], int result[2][2])
{
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
result[i][j] = 0;
for (int k = 0; k < 3; k++)
result[i][j] += a[i][k] * b[k][j];
}
}
}
常见错误
维度不匹配:
void func(int arr[][3]);
int mat[2][4];
func(mat); /* 错误:第二维不匹配 */
混淆行和列:
int mat[3][4];
mat[3][0] = 1; /* 越界!行索引范围是 0-2 */
mat[0][4] = 1; /* 越界!列索引范围是 0-3 */
sizeof 计算错误:
void func(int mat[][4])
{
int rows = sizeof(mat) / sizeof(mat[0]); /* 错误!sizeof(mat) = sizeof(int*) */
}
/* 正确:传递行数 */
void func(int mat[][4], int rows)
最佳实践
- 二维数组参数中,除第一维外都显式指定
- 始终传递数组的维度信息
- 利用 C99 VLA 提高灵活性
- 复杂矩阵运算考虑使用专门的数学库(如 BLAS)
- 注意行优先存储对缓存性能的影响(按行遍历比按列遍历快)