LV015-二维数组
一、多维数组
多维数组是一种特殊的数据结构,其数组元素本身仍是数组。这种递归嵌套的特性,使得数据能够以类似表格、矩阵乃至更高维度的形式进行组织。每个维度分别对应数据的不同属性层级,例如行、列以及更高阶的层次,从而让数据呈现更加清晰、有序的结构化特征。
多维数组根据维度数量可分为二维数组、三维数组、四维数组等。其中最常见的是二维数组,可以看作“表格”或“矩阵”。

二、二维数组
1. 怎么声明?
数据类型 数组名[ 常量表达式1 ][ 常量表达式2 ];例如:
int a[2][3]; /* 定义了一个 2 行 3 列的二维数组 */表示定义了一个 2 行 3 列的二维数组 ,元素共有 2x3 = 6 个( 元素个数 = 行数 x 列数 )。
【注意】
(1)声明时 列数不能省略,行数可以省略(定义时进行初始化,编译器可以判别出行数时)。
(2) sizeof(数组名) 可以获取整个二维数组所占据的字节数(等于元素个数 x 元素类型所占字节数)。
(3)sizeof(行数组名) 可以获取二维数组某行所占据的字节数(等于该行元素个数 x 元素类型所占字节数)。
2. 怎么访问?
2.1 访问格式
array_name[row_index][col_index] /* 数组名 [行索引][列索引] 行索引和列索引都是从 0 开始*/| storage_type | 存储类型(可以说明也可以不说明) |
| data_type | 任意有效的 C 数据类型(必须说明) |
| p_name | 指针变量名 |
int a[2][3];
int i, j;
/* 通过 for 循环进行逐个元素的访问 */
for(i=0;i<2;i++) /* 行循环 */
{
for(j=0;j<3;j++) /* 列循环 */
{
printf(“%d\t”, a[i][j]);
}
}3. 元素初始化
int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; /* 分行赋值 */
int a[2][3] = {1, 2, 3, 4, 5, 6}; /* 按数组排列顺序进行赋值 */
int a[2][3] = {{1}, {4, 5}; /* 对部分元素赋初值 */【注意】
(1)与一维数组相同,数组不初始化,其元素值为随机;对 static 数组元素不赋初值,系统会自动赋以 0 值;只给部分数组元素赋初值,剩余元素自动赋值为 0 。
(2)当采用分行赋值的方法声明二维数组时,二维数组的行数可以省略,行数会由编译器自己进行计算。
【说明】
/* 声明形式 1 (两种写法等价) */
static int a[2][3];
static int a[2][3]; a[0][0] = 0; a[0][1] = 0; a[0][2] = 0; a[1][0] = 0; a[1][1] = 0; a[1][2] = 0;
/* 声明形式 2 (两种写法等价) */
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
int a[2][3]; a[0][0] = 1; a[0][1] = 2; a[0][2] = 3; a[1][0] = 4; a[1][1] = 5; a[1][2] = 6;
/* 声明形式 3 (两种写法等价) */
int a[2][3] = {{1}, {4, 5}};
int a[2][3]; a[0][0] = 1; a[0][1] = 0; a[0][2] = 0; a[1][0] = 4; a[1][1] = 5; a[1][2] = 0;
/* 声明形式 4 (两种写法等价) */
int a[ ][3] = {{1, 2, 3}, {4, 5, 6}};
int a[2][3]; a[0][0] = 1; a[0][1] = 2; a[0][2] = 3; a[1][0] = 4; a[1][1] = 5; a[1][2] = 6;
/* 声明形式 5 (两种写法等价) */
int a[ ][3] = {{}, {4, 5}};
int a[2][3]; a[0][0] = 0; a[0][1] = 0; a[0][2] = 0; a[1][0] = 4; a[1][1] = 5; a[1][2] = 0;
/* 声明形式 6 (两种写法等价) */
int a[2][3] = {1, 2, 3, 4, 5, 6};
int a[2][3]; a[0][0] = 0; a[0][1] = 0; a[0][2] = 0; a[1][0] = 4; a[1][1] = 5; a[1][2] = 0;/* 编译器自己计算赋值,不过会有警告*/
/* 声明形式 7 (两种写法等价) */
int a[ ][3] = {1, 2, 3, 4, 5, 6};
int a[2][3]; a[0][0] = 0; a[0][1] = 0; a[0][2] = 0; a[1][0] = 4; a[1][1] = 5; a[1][2] = 0;/* 编译器自己计算赋值,不过会有警告*/4. 存储空间
4.1 逻辑分析
逻辑层面看,我们常用矩阵形式(如 3 行 4 列)来表示二维数组,这种表示方法能够直观地体现出行与列之间的关系,便于我们理解和操作数据。然而,在计算机的内存世界里,二维数组并非以二维的形式存储,而是采用线性、连续的方式存放所有元素。
在 C 语言中,二维数组的元素是按照行优先的顺序进行排列的。也就是说,编译器会先顺序存放第一行的所有元素,紧接着存放第二行的所有元素,以此类推,直至将整个二维数组的所有元素都存放在连续的内存空间中。以一个 3 行 4 列的二维数组 a[3][4] 为例,其在内存中的存放顺序如下所示:

4.2 测试实例
二维数组在内存中是一维的,存储时 行序优先。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a[][3] = {{1, 2, 3}, {4, 5, 6}};
int i, j;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
{
printf("&a[%d][%d] = %p, a[%d][%d] = %d\n",i, j, &a[i][j],i, j, a[i][j]);
}
}
return 0;
}
a[2][3] 在内存中的结构如下图所示, a[2][3] 数组为 int 类型,在 64 位平台下占 4 个字节,所以相邻元素之间地址相差 4 。
5. 数组名
与一维数组一样,二维数组的数组名也代表了该二维数组的起始地址。二维数组的另一种理解方式:二维数组可以看做是由多个元素组成的一维数组,而每个元素又是一个数组,从而合起来构成了二维数组。例如:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a[][3] = {{1, 2, 3}, {4, 5, 6}};
int i, j;
printf("a = %p\n", a);
for(i = 0; i < 2; i++)
{
printf("a + %d = %p, a[%d] = %p\n",i,a +i, i, a[i]);
for(j = 0; j < 3; j++)
{
printf("&a[%d][%d] = %p, a[%d][%d] = %d\n",i, j, &a[i][j],i, j, a[i][j]);
}
}
return 0;
}
经过打印地址发现:
(1) a 为整个二维数组的数组名,代表了整个二维数组的起始地址。
(2) a [0], a [1] 也分别代表了第一行和第二行起始数据的地址。

经过分析和验证,可以得到:一个二维数组,按行可以分为多个一维数组,以 int a[2][3] 为例,该 2 行 3 列 的二维数组就可以理解为 2 个元素 组成,每个元素都是一个一维数组组成,每个一维数组的数组名就是二维数组名加上第一个下标,即 a [0], a [1] 。
| 行名(代表了地址) | 每行元素 | ||||
| a | a [0] | &a [0][0] | a [0][0] | a [0][1] | a [0][2] |
| a + 1 | a [1] | &a [1][0] | a [1][0] | a [1][1] | a [1][2] |
三、数组元素个数
1. 计算元素个数
我们有的时候需要程序去自动计算数组的长度,有下边两种方式:
len = sizeof(数组名)/sizeof(数组类型);
len = sizeof(数组名)/sizeof(数组名[0]);已知数组元素类型的时候,我们可以使用第一种方式,当未知数组元素类型的时候,我们可以使用后边这种方式,直接使用数组中首个元素作为每个元素大小计算的标准。
2. 计算行数或者列数
行数或者列数至少要有 1 个已知才行。我们可以先计算出总元素个数,然后可以除以已知的行数或者列数就可以得到另一个未知的参数了。
参考资料:
25 字符数组与字符串及多维数组详解:定义与初始化、访问与遍历、%s 格式符、内存剖析、编程实战_多维字符串数组怎么定义-CSDN博客