指針和多維數組有什麼關係?爲何咱們須要知道它們之間的關係?函數是經過指針來處理多維數組的,所以在使用這樣的函數以前,您須要更多的瞭解指針。假設有以下的聲明:c++
int zippo[4][2] ; /*整數數組的數組*/數組
數組名zippo同時也是數組首元素的地址。在本例中,zippo的首元素自己又是包含兩個int的數組,所以zippo也是包含兩個int的數組的地址。下面從指針的屬性進一步分析:安全
**由於zippo是數組首元素的地址,因此zippo的值和&zippo[0]相同。另外一方面,zippo[0]自己是包含兩個整數的數組,所以zippo[0]的值同其首元素的地址&zippo[0][0]相同。簡單地說,zippo[0]是一個整數大小對象的地址,而zippo是兩個整數大小對象的地址。由於整數和兩個整數組成的數組開始於同一個地址,所以zippo和zippo[0]具備相同的值 。函數
**對一個指針加1會對原來的數值加上一個對應類型大小的數值。在這方面zippo和zippo[0]是不同的,zippo所指向對象的大小是兩個int,而zippo[0]所指向對象的大小是一個int。所以zippo+1和zippo[0]+1的結果不一樣。spa
**對一個指針取值獲得的是該指針所指向對象的數值。由於zippo[0]是其首元素zippo[0][0]的地址,因此*(zippo[0])表明存儲在zippo[0][0]中的數值,即一個int數值。一樣*zippo表明其首元素zippo[0]的值,可是zippo[0]自己就是一個int數的地址,即&zippo[0][0],所以*zippo是&zippo[0][0]。對這個表達式同時應用取值運算符將獲得**zippo等價於*&zippo[0][0],後者簡化後即爲一個int數zippo[0][0]。簡言之,zippo是地址的地址,須要兩次取值才能夠獲得一般的數值。地址的地址或指針的指針是雙重間接的典型例子。指針
顯然,增長數組維度會增長指針的複雜度。code
程序清單10.15對象
/*zippo1.c --有關zippo的信息*/ #include <stdio.h> int main(void) { int zippo[4][2]={{2,4},{6,8},{1,3},{5,7}}; printf("zippo = %p, zippo+1 = %p\n", zippo, zippo+1); printf("zippo[0] = %p, zippo[0]+1 = %p\n", zippo[0], zippo[0]+1); printf("*zippo = %p, *zippo+1 = %p\n", *zippo, *zippo+1); printf("zippo[0][0] = %d\n",zippo[0][0]); printf(" *zippo[0] = %d\n",*zippo[0]); printf(" **zippo = %d\n",**zippo); printf(" zippo[2][1] = %d\n",zippo[2][1]); printf("*(*(zippo+2)+1)=%d\n",*(*(zippo+2)+1)); return 0; }
在一個系統上輸出結果以下ip
zippo = 0022FF20, zippo+1 = 0022FF28 zippo[0] = 0022FF20, zippo[0]+1 = 0022FF24 *zippo = 0022FF20, *zippo+1 = 0022FF24 zippo[0][0] = 2 *zippo[0] = 2 **zippo = 2 zippo[2][1] = 3 *(*(zippo+2)+1)=3
輸出顯示出二維數組zippo的地址和一維數組zippo[0]的地址是相同的,均爲相應的數組首元素的地址,它的值是和&zippo[0][0]相同的。ci
然而,差異也是有的,在咱們系統上,int是4個字節長。前面咱們討論過,zippo[0]指向4字節長的數據對象,對zippo[0]加1致使它的值增長4。數組名zippo是包含兩個int數的數組的地址,所以它指向8字節長的數據對象。因此,對zippo加1致使它的值增長8。
程序顯示*zippo和zippo[0]是相同的,這點是正確的。另外一方面,二維數組名必須再次取值才能取出數組中存儲的數據。
具體地:zippo[2][1]的等價指針符號表示爲*(*(zippo+2)+1)。表10.2中分步創建了這個表達式:
分析*(*(zippo+2)+1)
zippo | 第1個大小 爲2個int的元素的地址 |
zippo+2 | 第3個大小爲2個int的元素的地址 |
*(zippo+2) | 第3個元素,即包含2個int值的數組,所以也是其第1個元素(int值)的地址 |
*(zippo+2) +1 | 包含2個Int值的數組的第2個元素(int值)的地址 |
*(*(zippo+2) +1) | 數組第3行第2個int的值(zippo[2][1]) |
當您正好有一個指向二維數組的指針並須要取值時,最好不要使用指針符號,而應當使用形式簡單的數組符號。
10.7.1 指向多維數組的指針
如何聲明指向二維數組的指針變量pz?例如,在編寫處理像zippo這樣的數組的函數時,就會用到這類指針。指向int的指針能夠勝任嗎?不能夠。這種指針只是和zippo[0]兼容。由於它們都指向一個單個的int值。可是zippo是其首元素的地址,而該首元素又是包含兩個int值的數組。所以,pz必須指向一個包含兩個int值的數組,而不是指向一個單個的int值。下面是正確的代碼:
int (*pz) [2] ; //pz指向一個包含2個int值的數組
該語句代表pz是指向包含2個int值的數組的指針。爲何使用圓括號?由於表達式中[]的優先級高於*。所以,若是咱們這樣聲明:
int * pax[2] ;
那麼首先[]與pax結合,表示pax是包含兩個某種元素的數組。而後和*結合,表示pax是兩個指針組成的數組。最後,用int來定義,表示pax是由兩個指向int值的指針構成的數組。這種聲明會建立兩個指向單個Int值的指針。程序清單10.16顯示瞭如何使用指向二維數組的指針。
程序清單10.16 zippo2.c
/*zippo2.c --經過一個指針變量獲取有關zippo的信息*/ #include <stdio.h> int main(void) { int zippo[4][2]={{2,4},{6,8},{1,3},{5,7}}; int (*pz)[2]; pz=zippo; printf(" pz = %p, pz+1 = %p\n", pz, pz+1); printf(" pz[0] = %p,pz[0]+1 = %p\n", pz[0], pz[0]+1); printf(" *pz = %p,*pz+1 = %p\n", *pz, *pz+1); printf("pz[0][0] = %d\n",pz[0][0]); printf(" *pz[0] = %d\n",*pz[0]); printf(" **pz = %d\n",**pz); printf(" pz[2][1] = %d\n",pz[2][1]); printf("*(*(pz+2)+1) = %d\n",*(*(pz+2)+1)); return 0; }
輸出結果以下:
pz = 0022FF1C, pz+1 = 0022FF24 pz[0] = 0022FF1C,pz[0]+1 = 0022FF20 *pz = 0022FF1C,*pz+1 = 0022FF20 pz[0][0] = 2 *pz[0] = 2 **pz = 2 pz[2][1] = 3 *(*(pz+2)+1) = 3
不一樣的計算機獲得的結果可能有些差異,可是相互關係是同樣的。儘管pz是一個指針,而不是數組名,仍然可使用pz[2][1]這樣的符號。
更通常地,要表示單個元素,可使用數組符號或指針符號:而且在這兩種表示中便可以使用數組名也可使用指針:
zippo[m][n] == *(*(zippo+m)+n)
pz[m][n] == *(*(pz+m)+n)
10.7.2 指針兼容性
指針之間的賦值規則比數值類型的賦值更嚴格。例如,您能夠不須要進行類型轉換就直接把一個Int數值賦給一個double類型的變量。但對於指針來講這樣的賦值是不容許的。
這些規則也適用於更復雜的類型。假設有以下聲明:
int * pt; int (*pa)[3]; int ar1[2][3]; int ar2[3][2]; int **px; //指針的指針
那麼,有以下結論:
pt = &ar1[0][0]; //都指向 int pt = ar1[0]; //都指向 int pt = ar1; //非法 pa = ar1; //都指向int [3] pa = ar2; //非法 p2 = &pt //都指向int * *p2 = ar2[0] //都指向int p2 = ar2; //非法
請注意,上面的非法賦值都包含着兩個不指向同一類型的指針。例如,Pt指向一個int數值,可是ar1是指向由3個int值構成的數組。一樣,pa指向由3個int值構成的數組,所以它與ar1的類型一致,可是和ar2的類型不一致,由於ar2指向由2個int值構成的數組。
後面的兩個例子比較費解。變量p2是指向int的指針的指針,然而,ar2是指向由2個int值構成的數組的指針(簡單一些說是指向int[2]的指針)。所以,p2和ar2的類型不一樣,不能把ar2的值賦給p2。可是*p2的類型是指向int的指針,因此它和ar2[0]是兼容的。前面講過,ar2[0]是指向其首元素ar2[0][0]的指針,所以ar2[0]也是指向int的指針。
通常地,多重間接運算不容易理解。例如,考慮下面這段代碼:
int *p1; const int *p2; const int **p2; p1=p2; //非法,把const指針賦給非const指針 p2=p1; //合法,把非const指針賦給const指針 pp2=&p1;//非法,把非const指針賦給const指針
正如前面所提到的,把const指針賦給非const指針是錯誤的,由於您可能會使用新指針來改變const數據。可是把非const指針賦給const指針是容許的。這們的賦值有一個前提:只進行一層間接運算:
p2=p1; //合法,把非const指針賦給const指針
在進行兩層間接運算時,這樣的賦值再也不安全。若是容許這樣賦值,可能會產生以下的問題:
const int **pp2; int *p1; const int n=13; pp2=&p1; //不容許,咱們假設容許 *pp2=&n; //合法,二者都是const,但同時會使p1指向n *p1=10; //合法,但這將改變const n的值
10.7.3 函數和多維數組
若是須要編寫一個處理二維數組的函數,首先須要很好的理解指針以便正確聲明函數的參數。在函數體內一般可使用數組符號來避免使用指針 。
下面咱們編寫一個處理二維數組的函數,一種方法是把處理一維數組的函數應用到二維數組的每一行上,也就是以下所示這樣處理:
int junk[3][4]={{2,4,5,8},{3,5,6,9},{12,10,8,6}}; int i,j; int total=0; for(i=0;i<3;i++) total+=sum(junk[i],4); //junk[i]是一維數組
若是junk是二維數組,junk[i]就是一維數組,能夠把它看做是二維數組的一行。函數sum()計算二維數組每行的和,而後由for循環把這些和加起來獲得「總和」。
然而,使用這種方法得不到行列信息。要具備行列信息,須要恰當地聲明形參變量以便於函數可以正確的傳遞數組。在本例中,數組junk是3行4列的int數組。如前面所討論的,這代表junk是指向由4個int值構成的數組的指針。聲明此類函數參量的方法以下所示:
void somefunction(int (*pt) [4]) ;
當且僅當pt是函數的形式參量時,也能夠做以下的聲明:
void somefunction(int pt[][4]) ;
注意到第一對方括號是空的。這個空的方括號表示pt是一個指針,這種變量的使用方法和junk同樣。程序清單10.17中的例子就將使用上面兩種聲明的方法。注意清單中展現了原型語法的3種等價形式。
//array2d.c --處理二維數組的函數*/ #include <stdio.h> #define ROWS 3 #define COLS 4 void sum_rows(int ar[][COLS],int rows); void sum_cols(int [][COLS],int); //能夠省略名稱 int sum2d(int (*ar)[COLS],int rows); //另外一種語法形式 int main(void) { int junk[ROWS][COLS]={ {2,4,6,8}, {3,5,7,9}, {12,10,8,6} }; sum_rows(junk,ROWS); sum_cols(junk,ROWS); printf("Sum of all elements = %d\n",sum2d(junk,ROWS)); return 0; } void sum_rows(int ar[][COLS],int rows) { int r ; int c ; int tot ; for (r=0;r<rows;r++) { tot=0; for(c=0;c<COLS;c++) tot+=ar[r][c]; printf("row %d: sum = %d\n",r,tot); } } void sum_cols(int ar[][COLS],int rows) { int r ; int c; int tot; for (c=0;c<COLS;c++) { tot=0; for (r=0;r<rows;r++) tot+=ar[r][c]; printf("col %d: sum = %d\n",c,tot); } } int sum2d(int ar[][COLS],int rows) { int r; int c; int tot=0; for(r=0;r<rows;r++) { for (c=0;c<COLS;c++) tot+=ar[r][c]; } return tot; }
這個函數能夠在多種狀況下工做,例如,若是把12做爲行數傳遞給函數,則它能夠處理12行4列的數組。這是由於rows是元素的數目;然而,每一個元素都是一個數組,或者看做一行,rows也就能夠看做是行數。
請注意ar的使用方式同mian()中junk的使用方式同樣。這是由於ar和junk是同一類型,它們都是指向包含4個int值的數組的指針。
請注意下面的聲明是不正確的:int sum2(int ar[][],int rows); //錯誤的聲明
回憶一下,編譯器會把數組符號轉換成指針符號。這就意味着,ar[1]會被轉換成ar+1。編譯器這樣轉換的時候,須要知道ar所指向對象的數據大小。下面的聲明:
int sum2 (int ar[][4] , int rows ) ; //合法
就表示ar指向由4個Int值構成的數組,也就是16個字節長(本系統上)的對象,因此ar+1表示「在這個地址上加上16個字節大小」。若是是空括號,編譯器將不能正確處理。
也能夠像下面這樣,在另外一對方括號中填寫大小,但編譯器將忽略之:
int sum2 ( int ar [3][4] ,int rows ) ; //合法,但3將被忽略
通常地,聲明N維數組的指針時,除了最左邊的方括號能夠留空以外,其餘都須要填寫數值。
int sum4d (int ar [][12][20][30],int rows) ;
這是由於首方括號表示這是一個指針,而其餘方括號描述的是所指向對象的數據類型。請參看下面的等效原型表示:
int sum4d ( int (*ar) [12][20][30] , int rows); //ar是一個指針
此處ar指向一個12x20x30的int數組。