4.2 二维数组的定义及引用
前面介绍了一维数组,接下来介绍如何定义和使用二维数组。
二维数组定义的一般形式如下:
类型说明符 数组名[常量表达式1][常量表达式2];
与一维数组的定义唯一的不同是多了一个常量表达式2,其中,常量表达式1为第一维的长度,常量表达式2为第二维的长度。通常在处理二维数组的时候,为了便于理解,都将数组视为一个矩阵,常量表达式1表示矩阵的行数,而常量表达式2表示矩阵的列数。与一维数组一样,在定义二维数组时,常量表达式同样不能为变量。下面先通过一段代码来看二维数组的定义。
include<stdio.h>
define M 4
define N 3
void main(void)
{
int i,j;
int arr[M][N];
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
printf("&arr[%d][%d]=%d\t",i,j,&arr[i][j]);
}
printf("\n");
}
return;
}
运行结果:
&arr[0][0]=1245000&arr[0][1]=1245004&arr[0][2]=1245008
&arr[1][0]=1245012&arr[1][1]=1245016&arr[1][2]=1245020
&arr[2][0]=1245024&arr[2][1]=1245028 arr[2][2]=1245032
&arr[3][0]=1245036&arr[3][1]=1245040&arr[3][2]=1245044
将二维数组arr视为一个矩阵,图4-3显示了数组中每个元素在矩阵中的存放位置。
由图4-3可知,数组中各个元素在矩阵中对应的位置由二维数组的两个下标决定。我们可以将定义的二维数组int arr[4][3]视为由arr[4]和int[3]两部分构成,将arr[4]视为一个整型一维数组,其中含有4个元素arr[0]、arr[1]、arr[2]、arr[3],每个元素都是int[3]类型的,也就是说,每个元素又是一个一维数组,每个一维数组含有3个元素,如arr[0]含有arr[0][1]、arr[0][1]、arr[0][2]三个元素。
图 4-3 二维数组arr元素的矩阵存放方式
知道了二维数组的这种特殊结构之后,接下来通过图4-4来了解二维数组在内存中的存储结构。
图 4-4 二维数组arr在内存中的存储结构
通过上述二维数组在内存中的存储结构图可以发现,二维数组中的所有元素都存储在一片连续的内存单元中,所占用的内存大小为元素类型所占用的内存大小乘以第一维及第二维的长度。如果以矩阵的方式来分析二维数组的存储方式,那么先从矩阵第一行从左往右依次存储完所有元素,然后按照同样的方法存储第二行的所有元素,直到存储完所有数组元素为止。
接下来看二维数组的一般引用形式:
数组名[表达式1][表达式2]
如果用矩阵的方式来描述二维数组,那么这里的“表达式1”就是行下标,“表达式2”就是列下标,它们的取值均从0开始。例如定义一个二维数组a[M][N],它的行下标的取值范围为0~M-1,列下标的取值范围为0~N-1。与之前讲解的一维数组一样,在使用二维数组元素的过程中也要避免下标越界的情况出现,看看下面的代码:
include<stdio.h>
define M 4
define N 3
void main(void)
{
int i,j;
int arr[M][N];
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
arr[i][j]=i+j;
printf("arr[%d][%d]=%d\t",i,j,arr[i][j]);
}
printf("\n");
}
return;
}
运行结果:
arr[0][0]=0 arr[0][1]=1 arr[0][2]=2
arr[1][0]=1 arr[1][1]=2 arr[1][2]=3
arr[2][0]=2 arr[2][1]=3 arr[2][2]=4
arr[3][0]=3 arr[3][1]=4 arr[3][2]=5
在上面的代码中,虽然在数组定义的过程中不含有变量,但是在数组引用的时候却含有变量。对数组中元素的引用只需采用“数组名[行下标][列下标]”即可,值得注意的是行下标和列下标的取值范围,使用数组元素时同样可以将数组中的每个元素看成一个简单的变量进行处理。在上面代码的二重循环中,内层循环控制的是二维数组的列下标,外层循环控制的是二维数组的行下标。通过循环变量的改变来逐一取出二维数组中的每个元素,并对其进行赋值和打印操作。接下来看看二维数组的初始化方法,以下代码是在定义二维数组的过程中对二维数组元素进行赋值操作。
include<stdio.h>
define M 4
define N 3
void main(void)
{
int i,j;
int arr[M][N]={
{1,2,3},
{4,5,6},
{7,8,9},
{10,11,12}};
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
printf("arr[%d][%d]=%d\t",i,j,arr[i][j]);
}
printf("\n");
}
return;
}
运行结果:
arr[0][0]=1 arr[0][1]=2 arr[0][2]=3
arr[1][0]=4 arr[1][1]=5 arr[1][2]=6
arr[2][0]=7 arr[2][1]=8 arr[2][2]=9
arr[3][0]=10 arr[3][1]=11 arr[3][2]=12
上面代码对二维数组进行初始化时,将每一行数组元素的初始化值都放到一个花括号内,在花括号内采用逗号来分隔数组元素的初始化值,这是一种直观的初始化方法。就如前面分析的,二维数组在内存中的存储方式与一维数组是相同的,所有元素都存储在同一片连续的内存单元中,所以也可以采用一维数组的方式来对其进行初始化,例如:
include<stdio.h>
define M 4
define N 3
void main(void)
{
int i,j;
int arr[M][N]={1,2,3,4,5,6,7,8,9,10,11,12};
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
printf("arr[%d][%d]=%d\t",i,j,arr[i][j]);
}
printf("\n");
}
return;
}
运行结果:
arr[0][0]=1 arr[0][1]=2 arr[0][2]=3
arr[1][0]=4 arr[1][1]=5 arr[1][2]=6
arr[2][0]=7 arr[2][1]=8 arr[2][2]=9
arr[3][0]=10 arr[3][1]=11 arr[3][2]=12
两种初始化方法的运行结果完全一致。当然,在初始化的过程中也可以对其中的部分元素进行赋值,只不过采用这两种方法对部分元素进行赋值操作所产生的效果是不同的,下面通过代码来进行简单对比。
include<stdio.h>
define M 4
define N 3
void main(void)
{
int i,j;
int arr[M][N]={1,2,3,4,5,6,7};
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
printf("arr[%d][%d]=%d\t",i,j,arr[i][j]);
}
printf("\n");
}
return;
}
运行结果:
arr[0][0]=1 arr[0][1]=2 arr[0][2]=3
arr[1][0]=4 arr[1][1]=5 arr[1][2]=6
arr[2][0]=7 arr[2][1]=0 arr[2][2]=0
arr[3][0]=0 arr[3][1]=0 arr[3][2]=0
采用一维数组的初始化方式对二维数组的部分元素进行赋值,如图4-5所示。
图 4-5 二维数组的初始化方式(1)
采用这种方式对二维数组进行赋值操作时,按照如图4-5所示的方法从arr[0][0]开始依次对数组中的每个元素进行赋值,如果赋值的个数小于数组元素的个数,那么剩余部分的元素初始值为0;而如果给定的初始值的个数大于二维数组元素的个数,那么编译时就会出现“errorC2078:too many initializers”错误,所以在初始化时要注意给定的初始值个数不要多于二维数组本身所能容纳的元素的个数。下面来看采用括号的方式对二维数组中的每行元素进行赋值的情况。
include<stdio.h>
define M 4
define N 3
void main(void)
{
int i,j;
int arr[M][N]={{1,2,3},
{4,5},
{7},
{10}};
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
printf("arr[%d][%d]=%d\t",i,j,arr[i][j]);
}
printf("\n");
}
return;
}
运行结果:
arr[0][0]=1 arr[0][1]=2 arr[0][2]=3
arr[1][0]=4 arr[1][1]=5 arr[1][2]=0
arr[2][0]=7 arr[2][1]=0 arr[2][2]=0
arr[3][0]=10 arr[3][1]=0 arr[3][2]=0
采用花括号的方法来对每行的数组元素单独赋值时要注意,花括号中的内容不能为空,至少要给定一个初始值,同时,初始值的个数也不能多于每行元素的个数。如图4-6所示为用花括号的方法对每行的数组元素单独赋值的初始化方式。
图 4-6 二维数组arr的初始化(2)
由图4-6可知,采用这种方式对二维数组中的元素进行赋值时,花括号中的每个值与相对应行的数组元素对应,初始值的个数小于行中数组元素的个数时,将剩余数组元素的初始值赋值为0。
当然,还可以采用一种特殊的方式来定义并初始化二维数组,那就是在定义的过程中可以不指定表达式1,即不指定行的长度,但是必须通过表达式2来指定列的长度。同样可以使用前面介绍的两种方法对这种不指定表达式1的二维数组进行初始化,例如:
include<stdio.h>
define M 4
define N 3
void main(void)
{
int i,j;
int arr[][N]={{1,2,3},{4},{7,8},{10}};
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
printf("arr[%d][%d]=%d\t",i,j,arr[i][j]);
}
printf("\n");
}
return;
}
运行结果:
arr[0][0]=1 arr[0][1]=2 arr[0][2]=3
arr[1][0]=4 arr[1][1]=0 arr[1][2]=0
arr[2][0]=7 arr[2][1]=8 arr[2][2]=0
arr[3][0]=10 arr[3][1]=0 arr[3][2]=0
如果在定义的时候没有指定第一维的长度,编译器会在编译时根据花括号的个数来判断第一维的长度。以下代码为没有使用花括号的情况。
include<stdio.h>
define N 3
void main(void)
{
int i,j,M;
int arr[][N]={1,2,3,4,5,6,7,8,9,10};
if(0==10%N)
M=10/N;
else
M=10/N+1;
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
printf("arr[%d][%d]=%d\t",i,j,arr[i][j]);
}
printf("\n");
}
return;
}
运行结果:
arr[0][0]=1 arr[0][1]=2 arr[0][2]=3
arr[1][0]=4 arr[1][1]=5 arr[1][2]=6
arr[2][0]=7 arr[2][1]=8 arr[2][2]=9
arr[3][0]=10 arr[3][1]=0 arr[3][2]=0
由于既没有指定第一维的长度,又没有给出与行相对应的花括号的数目,因此编译器会根据给出的初始值来判断行数,如果给出的初始值的个数为n,指定的二维数组的第二维的长度为N,那么此时的行数可以通过以下方式得到。
if(0==n%N)
M=n/N;
else
M=n/N+1;
通过初始值的个数n与第二维的长度N之间关系就可以知道二维数组的第一维长度M。