C Primer Plus 第10章 數組和指針 10.7 指針和多維數組

指針和多維數組有什麼關係?爲何咱們須要知道它們之間的關係?函數是經過指針來處理多維數組的,所以在使用這樣的函數以前,您須要更多的瞭解指針。假設有以下的聲明: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數組。

相關文章
相關標籤/搜索