第1章:数组(二)——二维数组与矩阵运算
1.1 二维数组的本质:一维数组的数组
很多初学者一听到“二维数组”就发怵,觉得它是个平面、是个表格。其实没那么玄乎。
二维数组,说白了就是一维数组的数组。
你想想看,一维数组是排成一排的变量。那二维数组呢?就是每个元素本身又是一个一维数组。我当年刚学C语言时,老师画了个表格,行和列标得清清楚楚。但我真正理解它,是在一次项目中需要处理图像像素数据时——图像就是典型的二维数组,每个像素点就是一个元素。
定义语法很简单:
// 定义一个3行4列的二维数组
int matrix[3][4];
这里的 matrix 有3个元素,每个元素是一个长度为4的整型一维数组。内存中它们是连续存放的,按行优先排列。
关键理解:
matrix的类型是int[3][4]matrix[0]的类型是int[4],即第一行matrix[0][0]是第一个元素
1.2 二维数组的初始化
初始化方式有好几种,我习惯用最直观的那种——按行分组:
// 方式1:完全初始化,按行分组
int arr[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 方式2:连续初始化(编译器会自动按行分配)
int arr[2][3] = {1, 2, 3, 4, 5, 6};
// 方式3:部分初始化,未指定的元素自动补0
int arr[2][3] = {
{1, 2},
{4}
};
// 结果:{{1,2,0}, {4,0,0}}
我的习惯: 我建议始终使用按行分组的方式。代码可读性更好,别人一看就知道每行有几个元素。我曾经接手过一个项目,前人用连续初始化写了个4×5的数组,我数了半天括号才确认数据对应关系……
还有一种情况——省略第一维大小:
// 编译器根据初始化数据自动推断行数
int arr[][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
}; // 自动推断为3行
注意:只有第一维可以省略,第二维必须明确指定。为什么?因为编译器需要知道每行有多少个元素,才能正确计算偏移地址。
1.3 二维数组的遍历
遍历二维数组,标准做法是双重循环。外层控制行,内层控制列:
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("%4d", matrix[i][j]);
}
printf("\n"); // 每行结束后换行
}
输出效果:
1 2 3 4
5 6 7 8
9 10 11 12
嗯,这里要注意:行和列不要搞反。我见过太多新手把 matrix[i][j] 写成 matrix[j][i],结果数组越界或者数据错乱。调试时一脸懵。
避坑指南: 我曾经在一个嵌入式项目中,用二维数组存储传感器校准参数。因为遍历时行列写反了,导致校准表读取错误,整批产品校准失败。排查了整整两天才发现是 i 和 j 的顺序问题。从那以后,我写双重循环时都会在注释里标明“行”和“列”。
1.4 二维数组的应用:矩阵运算
二维数组最经典的应用就是矩阵运算。这里我以矩阵加法和乘法为例。
1.4.1 矩阵加法
两个同型矩阵相加,对应元素相加即可:
#define ROWS 3
#define COLS 3
void matrix_add(int a[ROWS][COLS], int b[ROWS][COLS], int result[ROWS][COLS]) {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
result[i][j] = a[i][j] + b[i][j];
}
}
}
1.4.2 矩阵乘法
矩阵乘法稍微复杂一点。A(m×n) 乘以 B(n×p) 得到 C(m×p)。C[i][j] 等于 A的第i行与B的第j列对应元素乘积之和:
#define M 2
#define N 3
#define P 2
void matrix_multiply(int a[M][N], int b[N][P], int result[M][P]) {
// 先初始化结果矩阵为0
for (int i = 0; i < M; i++) {
for (int j = 0; j < P; j++) {
result[i][j] = 0;
}
}
// 三重循环实现乘法
for (int i = 0; i < M; i++) {
for (int j = 0; j < P; j++) {
for (int k = 0; k < N; k++) {
result[i][j] += a[i][k] * b[k][j];
}
}
}
}
性能提示: 矩阵乘法是O(n³)的复杂度。在嵌入式系统中,如果矩阵较大,建议考虑优化循环顺序——按 i→k→j 的顺序访问,能更好地利用CPU缓存。我曾在DSP上做过矩阵运算优化,循环顺序调整后性能提升了近30%。
1.5 多维数组简介
二维以上统称为多维数组。三维数组的定义:
// 三维数组:2个平面,每个平面3行4列
int cube[2][3][4];
三维数组可以理解为“数组的数组的数组”。内存中依然是连续存储,按最左边的下标变化最慢的原则排列。
实际项目中,三维数组用得不多。我见过的一些场景:
- 图像处理中的RGB三通道数据(宽×高×通道)
- 3D游戏中的体素数据
- 科学计算中的时空数据
四维及以上?说实话,我在20年职业生涯中几乎没用过。如果你需要四维数组,大概率是设计上可以优化的。
我的建议: 超过三维的数据,考虑用结构体或者链表来组织,可读性会好很多。别为了省几行代码把团队其他人搞晕。
1.6 本章知识体系
下面这张图帮你理清二维数组的核心脉络:
这张图把二维数组的四个核心知识点串起来了。从定义初始化,到遍历,再到矩阵运算,最后延伸到多维数组。你跟着这个脉络走,二维数组这块基本就稳了。
公众号:蓝海资料掘金营,微信 deep3321