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;