多维数组:二维数组的定义与初始化

说到多维数组,我个人觉得这是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);

我曾经在这个问题上卡了整整一个下午,编译器报错就是找不到原因。后来才发现是函数声明时漏了列数。

知识体系总览

下面这张图总结了本章的核心内容,大家可以对照着复习:

二维数组知识体系 二维数组 定义与初始化 类型 数组名[行][列] 按行/线性/部分初始化 可省略行数,不可省略列数 存储方式 行优先存储 内存连续线性排列 地址计算公式 遍历方式 嵌套循环 行优先 vs 列优先 缓存命中率影响性能 典型应用场景 矩阵运算 | 图像处理 | 游戏地图 | 多维数据 核心:二维数组就是「数组的数组」

好了,二维数组的内容就讲到这里。记住几个关键点:定义时列数不能省、存储是行优先、遍历时注意内外层循环的顺序。这些基础打牢了,后面学指针和数组的关系时会轻松很多。


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