第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],结果数组越界或者数据错乱。调试时一脸懵。

避坑指南: 我曾经在一个嵌入式项目中,用二维数组存储传感器校准参数。因为遍历时行列写反了,导致校准表读取错误,整批产品校准失败。排查了整整两天才发现是 ij 的顺序问题。从那以后,我写双重循环时都会在注释里标明“行”和“列”。

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 本章知识体系

下面这张图帮你理清二维数组的核心脉络:

二维数组 定义与初始化 int arr[3][4] = {{...}}; 按行分组 / 连续初始化 部分初始化自动补0 遍历 双重循环:外层行,内层列 注意 i/j 顺序不要搞反 矩阵运算 矩阵加法:对应元素相加 矩阵乘法:三重循环 性能优化:循环顺序 多维数组(三维及以上) int cube[2][3][4]; // 2个平面,每个3行4列

这张图把二维数组的四个核心知识点串起来了。从定义初始化,到遍历,再到矩阵运算,最后延伸到多维数组。你跟着这个脉络走,二维数组这块基本就稳了。


公众号:蓝海资料掘金营,微信 deep3321