7.3 二维数组

开发工具

  7.3 二维数组

  上一节主要通过实例讲解了一维数组使用。本节主要介绍绍二维数组的定义、存储和数组元素的使用,然后用具体实例展示二维数组的应用。

  一维数组可以表示一组数据,因此有时可把一维数组看成向量。当把向量看作是特殊的数组元素时,可以定义一个特殊的一维数组,数组中的每一个数组元素是一个向量,这样就形成了二维数组。二维数组可以用来存储矩阵、图像等。因此,二维数组有着非常广泛而重要的作用。

  二维数组的定义如下:

  数据类型 二维数组名[行数][列数]数据类型用以说明二维数组中每个变量的类型,行数和列数都是整型常量表达式,与一维数组一样,行数列数表达式不能出现变量。如:

  int a[3][4];定义了一个3行4列的二维数组,如果把一行当成一个数组元素,则数组a可以看成是一个具有3个元素的一维数组,其中第i个元素a[i]又是一个具有4个元素的一维数组。所以二维数组是一维数组的特例。由于二维数组名也是一个地址常量(a的第0行的首地址),因此,不能整体对二维数组进行输入和输出,只能通过对二维数组中的每个变量的操作来实现。如以下是错误的二维数组输入、输出和赋值:

  short int a[3][4],b[3][4];scanf("%d",a);printf("%d",a);b=a; (X)使用二维数组也要遵从先定义后使用的原则,并且只能通过二维数组的每个变量来实现,二维数组变量也称为数组元素或下标变量,用数组名、行下标和列下标共同表示,如a[2][2]表示数组a中第2行第2列的元素,对于3行4列的数组a,正确的行下标是0、1、2,列下标是0、1、2、3,也就是说行下标最大值是最行数减1,列下标最大值是列数-1。可以用下图逻辑上表示二维数组:

  a[0][0]

  a[0][1]

  a[0][2]

  a[0][3]

  a[1][0]

  a[1][1]

  a[1][2]

  a[1][3]

  a[2][0]

  a[2][1]

  a[2][2]

  a[2][3]

  图中,直接把内存块看成是一个由行和列构成的方阵。二维数组元素的使用也与普通变量相同,普通变量可以使用的地方,二维数组元素都可使用,只不过这个“变量”有点长而已。如:

  a[0][0]=10; //给数组元素a[0][0]赋10a[1][1]=a[0][0]+10; //a[0][0]的值加10后给a[1][1]赋值scanf("%d%d",&a[1][1],&a[1][2]); //输入数据给a[1][1]、a[1][2]赋值当然,二维数组的行下标和列下标既可以是常量,也可以是变量或表达式。如a[i][j]表示第i行第j列的元素,而a[i][i]表示行下标和列下标都相同的元素,对于方阵而言,a[i][i]刚好表示了主对角线上的元素。

  二维数组在内存如何存放呢?并不像上面的方阵那样存放,而是按照“行优先顺序”存放,即存完二维数组的第一行,再存第二行,...,直到存最后一行。如3行4列的short型数组a在内存布局如图:

  地址

  内空空间

  编号

  xxx18

  a[2][3]

  11

  xxx16

  a[2][2]

  10

  xxx14

  a[2][1]

  9

  xxx12

  a[2][0]

  8

  xxx0F

  a[1][3]

  7

  xxx0D

  a[1][2]

  6

  xxx0B

  a[1][1]

  5

  xxx09

  a[1][0]

  4

  xxx07

  a[0][3]

  3

  xxx05

  a[0][2]

  2

  xxx03

  a[0][1]

  1

  xxx01

  a[0][0]

  0

  从图中可以看出,第0行最后一个元素a[0][3]与第1行第一个元素a[1][0],第1行最后一个元素a[1][3]与第2行第一个元素a[2][0]刚好相邻。所以,二维数组在内存中的存储从本质上与一维数组一样,都是按顺序存储的变量序列。

  如果知道a[0][0]的地址为loc(a[0][0]),可以快速计算出a[i][j]元素的地址,只要计算前面i行的元素个数(i*列数)再加上第j列前面的元素个数j,其和再乘以每个元素的长度d(这里是2)即可,公式如下:

  loc(a[i][j)=((i*列数)+j))*d

  如想计算a[2][2]的地址,把行下标和列下标代入后,公式为:loc(a[2][2])=((2*4)+2)*2=20。20对应的十六进制为0x14,刚好对应序号为10的存储单元。

  给二维数组赋初值的格式为:

  数据类型 二维数组名[行数][列数]={{第0行},{第1行},...}

  或:

  数据类型 二维数组名[行数][列数]={数据列表}

  两种赋初值的方式不同,第一种方式是按行赋初值的,第二种方式是按顺序先给第0行赋初值,再给第1行赋初值,直到最后一行。如:

  int a[3][4]={{1,2,3,4},{2,3,4,5},{3,4,5,6}};

  int b[3][4]={{1,2},{3,4,5},{6,7,8,9}};

  int c[3][4]={1,2,3,4,5,6,7,8,9};

  int d[][4]={1,2,3,4,5,6,7,8,9};

  其中,第一行代码是给a中的每个元素赋初值;第二行代码给b的第1行赋1、2、0、0,第2行赋3、4、5、0,第3行赋6、7、8、9;第三行代码给c的第1行赋1、2、3、4,第2行赋5、6、7、8,第3行赋9、0、0、0;最后一行代码没有指定数组行数,计算机根据赋初值的数自动获得行数3,赋初值结果与c相同。

  1

  2

  3

  4

  2

  3

  4

  5

  3

  4

  5

  6

  a

  1

  2

  0

  0

  3

  4

  5

  0

  5

  6

  7

  8

  b

  1

  2

  3

  4

  5

  6

  7

  8

  9

  0

  0

  0

  c

  1

  2

  3

  4

  5

  6

  7

  8

  9

  0

  0

  0

  d

  接下来,我们看一下如何给二维数组输入数据、如何按矩阵输出二维数组的每个元素。

  由于二维数组由多行构成的,我们只要一行一行地输入数组元素就可以了,因此,可以写出输入行的伪代码如下:

  for(i=0;i<n;i++){ //n表示行数 输入第i行所有元素; //即数组行下标为i的元素}要输入第i行的元素,即要固定行下标i,让j从0变化到列数减1,重复输入a[i][j]。程序代码为:

  for(j=0;j<n;j++){ scanf("%d",&a[i][j]);}把“输入第i行所有元素”的代码替换前面的伪代码,得到了二维数组输入的双重循环:

  for(i=0;i<n;i++){ //n表示行数 for(j=0;j<n;j++){ scanf("%d",&a[i][j]); }}同样的分析,我们可以得到二维数组输出的双重循环结构,即内层循环用以控制输出一行的每个元素,外层循环用以控制输出所有行。代码如下:

  for(i=0;i<n;i++){ //n表示行数 for(j=0;j<n;j++){ printf("%4d",&a[i][j]); } printf("\n");}与二维数组的输入不同的是:每行输出结束后要加一个换行符。

  例1:将一个3*4的矩阵存入一个3*4的二维数组中,输出该矩阵。然后输出矩阵的最大值以及它的行下标和列下标。

  设数组为a,最大值为max,最大值行下标为row、列下标为col。求最大值的算法与前面一维数组求最小值“打擂台”思路一致,即在输入数组元素a[i][j]后,立即用a[i][j]与最大值max打擂台,如果a[i][j]比max大,则让a[i][j]成为擂主,即max=a[i][j],同时记录行下标i与列下标j。代码如下:

  scanf("%d",&a[i][j])if(i==0&&j==0) //先假设a[0][0]为最大值,即把a[0][0]赋给max{ max=a[0][0]; row=col=0;}else if(max < a[i][j]){ max=a[i][j]; row=i;row=j;}当i与j都是0时,擂台max初值设为a[0][0],最大值行下标和列下标皆为0。其它情况下用max与a[i][j]进行打擂。

  完整程序如下:

  #include <stdio.h>int main(void){int a[3][4];int max,row,col,i,j;for(i=0;i<3;i++) for(j=0;j<4;j++){ scanf("%d",&a[i][j]); if(i==0&&j==0) { max=a[0][0]; row=col=0; } else if(max < a[i][j]) { max=a[i][j]; row=i;row=j; } }for(i=0;i<3;i++){ for(j=0;j<4;j++) printf("%4d ",a[i][j]); printf("\n");}printf("max=%d,row=%d,col=%d\n",max,row,col);return 0;}例2:输入一个4*4的矩阵A,并原地转置后输出该矩阵。

  所谓矩阵转置,即把一个矩阵的列变成行(或行变成列),如图所示:

  //矩阵A:1 2 3 45 6 7 89 10 11 1213 14 15 16//A转置后的矩阵B:1 5 9 132 6 10 143 7 11 154 8 12 16在上图中,矩阵A的第i行元素在矩阵B中变成了第i列,如矩阵的第2行9、10、11、12变成了矩阵B中的第2列。即有A[2][0]=B[0][2]、A[2][1]=B[1][2]、A[2][2]=B[2][2]、A[2][3]=A[3][2]。因此有A[i][j]与B[j][i]相等。所以实现转置可以用以下代码:

  for(i=0;i<3;i++) for(j=0;j<3;j++) //把A的第i行变到B的第i更 B[j][i]=A[i][j];但题目要求就地转置。由上面两个矩阵发现,原来的数组元素A[i][j]与转置后A'[j][i]相同,而原来的A[j][i]与转置后的A'[i][j]。即:

  A'[j][i]==A[i][j];A'[i][j]==A[j][i];说明,原地转置只需要交换A[i][j]与A[i][j]即可:

  t=A[i][j];A[i][j]=A[j][i];A[j][i]=t;以上三个赋值语句中,等号左边的A对应的是新矩阵A',即A'[i][j]存放了A[j][i]的值,而A'[i][j]存放了A[j][i]的值。因此,完整的程序如下:

  #include <stdio.h>int main( ){int i,j,t;int A[4][4];for(i=0;i<4;i++) for(j=0;j<4;j++) scanf("%d",&A[i][j]);for(i=0;i<4;i++) for(j=0;j<i;j++) {t=A[i][j];A[i][j]=A[j][i];A[j][i]=t;}for(=0;i<4;i++){ for(j=0;j<4;j++) printf("%4d ",A[i][j]); printf("\n");}return 0;}因为一次交换即得到了两个新的数组元素的A'[i][j]与A'[j][i],而主对角线元素不需要交换,因此只需要交换矩阵元素个数减主对角线元素个数的一半即可((4*4-4)/2=6),。所以只要用矩阵主对角线下的的元素与其对称位置的数组元素交换。要注意,第二个循环的终值是i,而不是4。需要交换的数对如图所示:

矩阵转置交换元素示意图

  本节介绍了二维数组的定义、初始化、内存映象及二维数组元素的使用,通过两个矩阵运算的实例深入讲解了二维数组的输入、输出和使用。本节就讲到这里,下次再见!

标签: 开发工具