10、数组(二):二维数组的定义与初始化、二维数组的遍历、多维数组的概念

好,咱们接着聊数组。上一章我们把一维数组掰开揉碎讲清楚了,这一章来看二维数组。说实话,二维数组在实际开发中比一维数组更常见——你想想看,一张图片、一个棋盘、一张Excel表格,本质上都是二维数组。我当年刚入行时,第一个正经项目就是做图像处理,那时候才真正体会到二维数组有多重要。

二维数组的定义与初始化

二维数组,说白了就是「数组的数组」。你可以把它想象成一个表格,有行有列。定义方式也很直白:

// 定义一个3行4列的二维数组
int matrix[3][4];

这里 [3] 是行数,[4] 是列数。我个人习惯把行数写在前面,列数写在后面,这样更符合「先行后列」的直觉。

初始化方式有好几种,我挑最常用的给你看:

// 方式1:按行初始化(最推荐)
int arr1[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

// 方式2:线性初始化(按内存顺序)
int arr2[2][3] = {1, 2, 3, 4, 5, 6};

// 方式3:部分初始化(未指定的元素自动补0)
int arr3[2][3] = {
    {1, 2},
    {4}
};
// 结果:{{1, 2, 0}, {4, 0, 0}}
我的习惯: 只要行数不多,我永远用方式1——大括号嵌套大括号,清晰明了。方式2虽然省字符,但一旦数据多了,你根本分不清哪个数在哪一行。

这里有个容易踩的坑:定义时可以省略行数,但不能省略列数。为什么?因为编译器需要知道每行有几个元素,才能算出每行在内存里占多少字节。

// 正确:省略行数
int arr[][3] = {{1,2,3}, {4,5,6}};

// 错误:省略列数
// int arr[2][] = {{1,2,3}, {4,5,6}};  // 编译报错

二维数组的遍历

遍历二维数组,标准做法是嵌套循环。外层循环走行,内层循环走列。我在项目中见过有人把行列搞反了,结果数据全乱套了。

#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
我曾经踩过的坑: 有一次写矩阵乘法,我把内层循环的列上限写成了行数。结果数组越界,程序跑起来时好时坏,查了整整一个下午。从那以后,我每次写嵌套循环都会反复确认:外层是行还是列?上限对不对?

遍历顺序其实有两种:行优先和列优先。C语言是行优先的——也就是说,matrix[0][0] 后面紧跟着 matrix[0][1],而不是 matrix[1][0]。这个特性在性能优化时很有用:如果你按列遍历,CPU缓存命中率会大幅下降。

// 行优先遍历(快)
for (int i = 0; i < ROWS; i++)
    for (int j = 0; j < COLS; j++)
        process(matrix[i][j]);

// 列优先遍历(慢)
for (int j = 0; j < COLS; j++)
    for (int i = 0; i < ROWS; i++)
        process(matrix[i][j]);

嗯,这里要注意:数据量小的时候感觉不出来,但一旦矩阵是 1000x1000 以上,两种遍历方式的速度差距能到好几倍。

多维数组的概念

二维数组再往上,就是三维、四维……统称多维数组。定义方式就是继续加方括号:

// 三维数组:2个平面,每个平面3行4列
int cube[2][3][4];

你可以把它想象成一个魔方,或者一摞表格。实际开发中,三维数组已经很少见了,四维以上基本只在科学计算或机器学习里出现。我做了这么多年C语言开发,用到三维数组的场景一只手数得过来。

多维数组的初始化也是逐层嵌套:

int cube[2][2][3] = {
    {
        {1, 2, 3},
        {4, 5, 6}
    },
    {
        {7, 8, 9},
        {10, 11, 12}
    }
};

遍历三维数组需要三层循环:

for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 2; j++) {
        for (int k = 0; k < 3; k++) {
            printf("cube[%d][%d][%d] = %d\n", i, j, k, cube[i][j][k]);
        }
    }
}
核心要点:
  • 二维数组是「数组的数组」,内存中按行优先存储
  • 定义时行数可省略,列数不可省略
  • 遍历时注意行列顺序,避免越界
  • 多维数组本质相同,只是维度增加

最后给你看一张图,把二维数组的内存布局和遍历逻辑串起来:

二维数组内存布局与遍历 行0 行1 行2 列0 列1 列2 列3 1 2 3 4 5 6 7 8 9 10 11 12 内存中实际存储: 1 2 3 4 5 6 7 8 9 10 11 12 遍历顺序:行优先(外层行 → 内层列) for (i = 0; i < 3; i++) → for (j = 0; j < 4; j++) → matrix[i][j] 注意:列优先遍历会严重降低缓存命中率

这张图左边是逻辑上的表格,右边是内存中实际的线性存储。你看,每一行的数据在内存里是连续挨着的,这就是行优先。遍历时按行走,CPU缓存能一次性加载一整行,效率最高。

好了,二维数组的核心内容就这些。记住:定义时列数不能省,遍历时先行后列,内存布局是行优先。把这三点刻在脑子里,二维数组这块就稳了。


专注资料整理