1.2 二维数组

1.2.1 二维数组的声明与初始化

二维数组是最常用的高维数组。一维数组可以视为一行数据,二维数组更像是一个表格——既有数据行又有数据列。

二维数组的初始化分为两种,一种是按行初始化。和处理一维数组一样,程序员可以使用由花括号括起来的初始化列表来初始化多维数组的元素。对于多维数组的每一行,可以再用花括号指定其元素的初始化式:

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

一种是顺序初始化:

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

下面的声明只初始化了每行的第一个元素:

            int  ia[3][4]={{ 0 } , { 4 } , { 8 } };

其余元素根据其元素类型用1.1节中一维数组描述的规则初始化。本例中,其余元素为0。

如果省略内嵌的花括号,结果会完全不同:

            int ia[3][4]={0, 3, 6, 9};

该声明初始化了第一行的元素,其余元素都被初始化为0。

C++规定,在声明和初始化一个二维数组时,如果对二维数组的所有元素都赋值,则第一维(行数)可以省略。比如:

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

相当于:

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

但注意第二维不能省略。同样,在声明更高维的数组时,也只有第一维可以省略。

例1:以下声明是否正确?

            int disp(int a[][]);

解答:不正确。不能省略列数。

例2:下列代码输出是什么?

            int a[3][2]={(0, 1), (2, 3), (4, 5)};//语句1
            int* p=a[0];
            printf("%d", p[0]);

解答:1。语句1等价于

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

注意上述初始化式子中,用到了“逗号运算符”。

逗号运算符:多个表达式可以用逗号分开,其中用逗号分开的表达式的值分别结算,但整个表达式的值是最后一个表达式的值。

假设:

            int b=2, c=7, d=5;
            int a1, a2;
            a1=(++b, c--, d+3);//语句1
            a2=++b, c--, d+3;  //语句2

对于语句1,有三个表达式,用逗号分开,所以最终的值应该是最后一个表达式的值,也就是d+3,为8,所以a1=8。

对于语句2,那么也是有三个表达式,这时的三个表达式为a2=++b、c--、d+3(这是因为赋值运算符比逗号运算符优先级高)。所以最终表达式的值虽然也为8,但a2=4。

1.2.2 行优先存储与列优先存储

本质上讲,所有的数组在内存中都是一维线性的。不同语言采用的存储方式不同,有的采取行优先存储,有的采取列优先存储。

二维数组的行优先存储是指在内存中,先将二维数组的第一行按顺序存储,接着就是第二行的数据,然后是第三行的数据……

二维数组的列优先存储是指在内存中,先将二维数组的第一列按顺序存储,接着就是第二列的数据,然后是第三列的数据……

在C/C++中,二维数组按照行优先顺序连续存储。

例1:有一矩阵大小为16K×16K,若对两个这样的矩阵做加法运算,行优先读取与列优先读取的区别为__________?(2012·腾讯)

A.一样快

B.行优先快

C.列优先快

D.无法判断

解答:B。C++中数组(矩阵用数组实现)是采用行优先存储的,故按照行优先读取可以顺序读出矩阵中的元素,所以比较快。

有些时候,我们觉得用二维数组来描述一样事物很方便。比如我们用二维数组来画一个迷宫地图,行下标和列下标就如同直角坐标系一样。可是在某些情况下,不能使用二维数组,或者难以制造一个二维数组。二维数组在内存中的存储情况和一维数组是相同的,所以我们只好用一个一维数组来代替它了。图1-1给出了二维数组向一维数组的转化。

图1-1 行优先存储二维数组向一维数组的转化

于是,我们不难总结出一个结果,一个二维数组元素a[x][y]在一维数组b中,是

a[x][y]=b[x*列数+y]

例2:下列程序执行后的输出结果是(  )。(2012·中兴)

            main(){
                int a[3][3], *p, i;
                p=&a[0][0];
                for(i=0; i < 9; i++)
                    p[i]=i+1;
                printf("%d \n", a[1][2]);
            }

A.3

B.6

C.9

D.随机数

解答:B。p是一个指向int型的指针,p[i]=*(p+i),故数组a被依次初始化为{1, 2, 3, 4, 5, 6, 7, 8, 9},故a[1][2]=6。此题是将二维数组当作一维数组来初始化,由于二维数组在内存中是连续存放的,故是可行的。

例3:下面的get()函数是一个用指针实现二维数组的读取函数,请完成该函数。(2013·腾讯)

            #define M 3
            #define N 4
            int get(int *p, int i, int j){
                if(NULL == p || i<0 || i>=M || j<0 || j>=N) return(0);
                return_____________;
            }
            int main(){
                int a[M][N]={{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
                printf("a[2][3] == %d\n", get(&a[0][0], 2, 3));
                return 0;
            }

解答:*(p+i×N+j)。

例4:有一个二维数组a[1...100, 1...65]有100行,65列,我们以行序为主序,如果该数组的基地址是10000,且每个元素占2个存储单元,请问a[56, 22]的存储地址是: ?(2012小米)

解答:17192。公式:10000+((56-1) × 65+(22-1))*2=17192。计算时,可将a[1, 1]代入,验证结果是否正确,注意本题下标是从1开始的。

例5:下面程序执行的结果是:(  )(2011·趋势科技)

            int main(void){
                char matrix[3][3]={{'a', 'b', 'c'}, {'d', 'e', 'f'}, {'g', 'h', 'i'}};
                printf("%c", matrix[1][4]);
                return 0;
            }

A.c

B.f

C.g

D.h

解答:D。在C/C++中,二维数组按照行优先顺序连续存储的,故元素在空间是连续分布的。则matrix[1][4]的元素存放在第1行第4列,而实际上matrix第一行首(第0个)元素为d,第1个元素为e,第二个元素为f,第三个元素超出本行范围,寻址到下一行的g,故第4个元素为h。

1.2.3 二维数组的动态声明

            int **a=new int* [m];
            for(int i=0; i < m; i++)
                a[i]=new int [n];

就相当于产生了一个二维数组a[m][n]了。

静态声明的数组可以有公式(假设也是m行n列):

b[i][j]=b[i*n+j]

这是因为数组b是连续的一片内存,而动态声明的数组任意的a[k]都是一个int*类型,即一个地址,所以只能a[i][j]或者*(*(a+i)+j)来访问数组的元素,而不能a[i*n+j]这样使用。

动态声明的数组,使用后需要释放内存,操作如下:

            for(int i=0; i < m; ++i)
                delete []a[i];
            delete []a;