多维数组:二维数组的定义与初始化
说到多维数组,我个人觉得这是C语言里一个非常有意思的话题。很多初学者一听到「多维」两个字就觉得头大,其实说白了,多维数组就是「数组的数组」。嗯,咱们先从最常用的二维数组说起。
二维数组的定义
二维数组的定义格式很简单:
类型说明符 数组名[行数][列数];
举个例子:
int matrix[3][4]; // 3行4列的整型二维数组
这个定义告诉编译器:我要一个3行4列的矩阵,每个元素都是int类型。总共3×4=12个元素。
关键点:行数和列数必须是常量表达式,不能是变量。这一点和一维数组是一样的。
二维数组的初始化
初始化方式有好几种,我给大家梳理一下:
方式一:按行初始化
int arr[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
这种方式最直观,每一行用一对花括号括起来。我在项目中一直用这种方式,可读性最好。
方式二:线性初始化
int arr[2][3] = {1, 2, 3, 4, 5, 6};
编译器会按行优先的顺序依次填充。说白了就是先填完第一行,再填第二行。我个人不太推荐这种方式,因为一旦数据多了,很容易搞混哪个元素在哪个位置。
方式三:部分初始化
int arr[2][3] = {
{1},
{4, 5}
};
未显式初始化的元素会被自动赋值为0。上面的代码等价于:
int arr[2][3] = {
{1, 0, 0},
{4, 5, 0}
};
注意:我曾经在项目里吃过这个亏——以为部分初始化后剩下的元素是随机值,结果调试了半天才发现C语言标准规定未初始化的静态和局部数组元素会被自动清零。嗯,这个坑我帮大家踩过了。
方式四:省略行数
int arr[][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
编译器会根据初始化数据的行数自动推断行数。但注意:列数是不能省略的!为什么?因为编译器需要知道每行有多少个元素才能正确计算偏移量。
二维数组的存储方式
二维数组在内存中是怎么存的?这个问题我当年也困惑过。其实很简单——行优先存储。
什么意思?就是先把第一行的所有元素按顺序存好,再存第二行,以此类推。你想想看,内存是一维的线性空间,二维数组只是逻辑上的概念,物理上还是连续的一维序列。
比如:
int a[2][3] = {
{10, 20, 30},
{40, 50, 60}
};
在内存中的布局是:
地址偏移: 0 4 8 12 16 20
元素: 10 20 30 40 50 60
[-- 第0行 --] [-- 第1行 --]
每个int占4个字节,所以相邻元素地址相差4字节。
小技巧:如果你想知道某个元素a[i][j]的地址,可以用公式:&a[i][j] = a + (i * 列数 + j) * sizeof(元素类型)。这个公式我在做嵌入式底层驱动时经常用到,比如操作帧缓冲区的时候。
二维数组的遍历
遍历二维数组最常用的就是嵌套循环。外层循环控制行,内层循环控制列。
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 行优先遍历
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
return 0;
}
输出结果:
1 2 3 4
5 6 7 8
9 10 11 12
你也可以按列优先遍历,只需要交换内外层循环的顺序:
// 列优先遍历
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 3; i++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
输出:
1 5 9
2 6 10
3 7 11
4 8 12
性能提示:由于C语言采用行优先存储,行优先遍历的缓存命中率更高,性能更好。我在做图像处理项目时,曾经因为用错了遍历顺序,导致程序慢了将近3倍。所以,能行优先就别列优先。
多维数组的应用
二维数组在实际项目中应用非常广泛。我举几个常见的例子:
1. 矩阵运算
// 矩阵加法
void matrix_add(int a[3][3], int b[3][3], int result[3][3]) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
result[i][j] = a[i][j] + b[i][j];
}
}
}
2. 图像处理
灰度图像本质上就是一个二维数组,每个元素代表一个像素的亮度值。我记得在做嵌入式摄像头项目时,就是用二维数组来存储和处理图像的。
3. 游戏地图
int map[10][10] = {
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
// ... 更多行
};
1代表墙壁,0代表空地。这种表示法在迷宫游戏、贪吃蛇等项目中非常常见。
4. 三维及更高维数组
C语言支持多维数组,比如三维数组:
int cube[3][4][5]; // 3层,每层4行5列
三维数组的遍历需要三层嵌套循环。不过说实话,我在实际项目中用到三维数组的情况并不多,二维数组已经能解决绝大多数问题了。如果你发现自己需要四维、五维数组,建议先停下来想想——是不是数据结构设计得有问题?
避坑指南:多维数组作为函数参数时,除了第一维,其他维度的大小必须指定。比如:
// 正确
void func(int arr[][4], int rows);
// 错误
void func(int arr[][], int rows);
我曾经在这个问题上卡了整整一个下午,编译器报错就是找不到原因。后来才发现是函数声明时漏了列数。
知识体系总览
下面这张图总结了本章的核心内容,大家可以对照着复习:
好了,二维数组的内容就讲到这里。记住几个关键点:定义时列数不能省、存储是行优先、遍历时注意内外层循环的顺序。这些基础打牢了,后面学指针和数组的关系时会轻松很多。
公众号:蓝海资料掘金营,微信 deep3321