二維數組和指針

要用指針處理二維數組,首先要解決從存儲的角度對二維數組的認識問題。我們知道,一個二維數組在計算機中存儲時,是按照先行後列的順序依次存儲的,當把每一行看作一個整體,即視爲一個大的數組元素時,這個存儲的二維數組也就變成了一個一維數組了。而每個大數組元素對應二維數組的一行,我們就稱之爲行數組元素,顯然每個行數組元素都是一個一維數組


下面我們討論指針和二維數組元素的對應關係,清楚了二者之間的關係,就能用指針處理二維數組了。
設p是指向數組a的指針變量,若有:
p=a[0];
則p+j將指向a[0]數組中的元素a[0][j]。
由於a[0]、a[1]┅a[M-1]等各個行數組依次連續存儲,則對於a數組中的任一元素a[i][j],指針的一般形式如下:
p+i*N+j
元素a[i][j]相應的指針表示爲:
*( p+i*N+j)
同樣,a[i][j]也可使用指針下標法表示,如下:
p[i*N+j]
例如,有如下定義:
int a[3][4]={{10,20,30,40,},{50,60,70,80},{90,91,92,93}};
則數組a有3個元素,分別爲a[0]、a[1]、a[2]。而每個元素都是一個一維數組,各包含4個元素,如a[1]的4個元素是a[1][0]、a[1][1]、a[1]2]、a[1][3]。
若有:
int *p=a[0];
則數組a的元素a[1][2]對應的指針爲:p+1*4+2
元素a[1][2]也就可以表示爲:*( p+1*4+2)
用下標表示法,a[1][2]表示爲:p[1*4+2]



特別說明:
對上述二維數組a,雖然a[0]、a都是數組首地址,但二者指向的對象不同,a[0]是一維數組的名字,它指向的是a[0]數組的首元素,對其進行“*”運算,得到的是一個數組元素值,即a[0]數組首元素值,*a等價於a[0]    a[0]等價於&a[0][0],因此,*a[0]與a[0][0]是同一個值;而a是一個二維數組的名字,它指向的是它所屬元素的首元素,它的每一個元素都是一個行數組,因此,它的指針移動單位是“行”,所以a+i指向的是第i個行數組,即指向a[i]。對a進行“*”運算,得到的是一維數組a[0]的首地址,即*a與a[0]是同一個值。當用int *p;定義指針p時,p的指向是一個int型數據,而不是一個地址,因此,用a[0]對p賦值是正確的,而用a對p賦值是錯誤的。這一點請讀者務必注意。
⑵ 用二維數組名作地址表示數組元素。
另外,由上述說明,我們還可以得到二維數組元素的一種表示方法:
對於二維數組a,其a[0]數組由a指向,a[1]數組則由a+1指向,a[2]數組由a+2指向,以此類推。因此,*a與a[0]等價、*(a+1)與a[1]等價、*(a+2)與a[2]等價,┅,即對於a[i]數組,由*(a+i)指向。由此,對於數組元素a[i][j],用數組名a的表示形式爲:
*(*(a+i)+j)
指向該元素的指針爲:
*(a+i)+j
數組名雖然是數組的地址,但它和指向數組的指針變量不完全相同。指針變量的值可以改變,即它可以隨時指向不同的數組或同類型變量,而數組名自它定義時起就確定下來,不能通過賦值的方式使該數組名指向另外一個數組。
例4 求二維數組元素的最大值。


該問題只需對數組元素遍歷,即可求解。因此,可以通過順序移動數組指針的方法實現。
main()
{
int a[3][4]={{3,17,8,11},{66,7,8,19},{12,88,7,16}};
int *p,max;
for(p=a[0],max=*p;p<a[0]+12;p++)
   if(*p>max)
      max=*p;
printf("MAX=%d/n",max);
}
執行結果:
MAX=88
這個程序的主要算法都是在for語句中實現的:p是一個int型指針變量;p=a[0]是置數組的首元素地址爲指針初值;max=*p將數組的首元素值a[0][0]作爲最大值初值;p<a[0]+12是將指針的變化範圍限制在12個元素的位置內;p++使得每比較一個元素後,指針後移一個元素位置。


例5 求二維數組元素的最大值,並確定最大值元素所在的行和列。
本例較之上例有更進一步的要求,需要在比較的過程中,把較大值元素的位置記錄下來,顯然僅用上述指針移動方法是不行的,需要使用能提供行列數據的指針表示方法。
main()
{
int a[3][4]={{3,17,8,11},{66,7,8,19},{12,88,7,16}};
int *p=a[0],max,i,j,row,col;
max=a[0][0];
row=col=0;
for(i=0;i<3;i++)
   for(j=0;j<4;j++)
     if(*(p+i*4+j)>max)
      {
        max=*(p+i*4+j);
        row=i;
        col=j;
      }
printf("a[%d][%d]=%d/n",row,col,max);
}
程序運行結果:
a[2][1]=88


⑶ 行數組指針
在上面的說明中我們已經知道,二維數組名是指向行的,它不能對如下說明的指針變量p直接賦值:
int a[3][4]={{10,11,12,13},{20,21,22,23},{30,31,32,33}},*p;
其原因就是p與a的對象性質不同,或者說二者不是同一級指針。C語言可以通過定義行數組指針的方法,使得一個指針變量與二維數組名具有相同的性質。行數組指針的定義方法如下:
數據類型 (*指針變量名)[二維數組列數];
例如,對上述a數組,行數組指針定義如下:
int (*p)[4];
它表示,數組*p有4個int型元素,分別爲(*p)[0]、(*p)[1]、(*p)[2]、(*p)[3] ,亦即p指向的是有4個int型元素的一維數組,即p爲行指針 
    指針p爲指向一個由4個元素所組成的整型數組指針。在定義中, 圓括號是不能少的, 否則它是指針數組。這種數組的指針不同於前面介紹的整型指針, 當整型指針指向一個整型數組的元素時, 進行指針(地址)加1運算, 表示指向數組的下一個元素, 此時地址值增加了4(因爲放大因子爲4), 而如上所定義的指向一個由3個元素組成的數組指針, 進行地址加1運算時, 其地址值增加了16(放大因子爲4x4=16), 這種數組指針使用得較少, 但在處理二維數組時, 還是很方便的。例如:
          int a[3][4], (*p)[4];
          p=a;
    開始時p指向二維數組第0行, 當進行p+1運算時, 根據地址運算規則, 此時放大因子爲4x4=16, 所以此時正好指向二維數組的第1行。和二維數組元素地址計算的規則一樣, *p+1指向a[0][1], *(p+i)+j則指向數組元素a[i][j]。
     例1
     int a[3] [4]={
     {1,3,5,7},
     {9,11,13,15},
     {17,19,21,23}
    };
    main()
    {
         int i,(*b)[4];
           b=a+1;                  /* b指向二維數組的第1行, 此時*b[0]或 **b是a[1][0] */
           for(i=1;i<=4;b=b[0]+2,i++)/* 修改b的指向, 每次增加2 */
           printf("%d\t",*b[0]);
           printf("\n");
           for (i=0; i<2; i++) {
           b=a+i;                  /* 修改b的指向, 每次跳過二維數組的 一行 */
           printf("%d\t",*(b[i]+1));
        }
         printf ("\n");
     }
    程序運行結果如下:
     9    13   17   21
     3    11   19

*****************************************************************************************************************************************************************************

下面說明一下,我碰到的問題,我們定義了一下如下的函數:void function(double** array,int width,int height)

然後我們定義了一個二維數組 double p[3][3]={{1,2,3},{4,5,6},{7,8,9}};

當我們調用function時,即function(p,3,3),編譯器會報錯:

error C2664: 'function' : cannot convert parameter 1 from 'double [3][3]' to 'double **'
參數傳遞實際上是一個賦值的過程,爲了便於說明我們底下都以賦值的方式加以說明。

由分割線當中的知識我們知道p是數組首地址,地址指向的是第一個行數組,在某種程度上來說可以把二維數組名理解爲指針的指針,但是這兩者是有區別的。我們回顧一下一維數組和指針變量:

double p[]={1,2,3};

double* pp=p;

這個是沒有任何問題,正是因爲這個操作,使得很多人認爲數組名就是指針,是同一種類型。其實這是不對的。在這裏很多人喜歡糾結於他們的硬件實現,認爲它們的實現是差不多的,所以沒有差別,這種自以爲是的想當然的想法是不可取的。基本類型是C++提供,如果C++認爲這是兩種不同的類型,那它們就是兩種不同的類型,因爲我們基於C++編程,而不是基於硬件。實際上上述的賦值之所以能夠實現是因爲C++中提供了一種自動型轉化的機制,數組在運算時會自動轉化爲指針類型,所以上述的賦值過程實際上是p先轉爲指針類型之後才賦值給pp的。另外需要注意的是這種自動轉化是單向的,指針是無法自動轉化爲數組的,所以用指針給數組變量賦值是錯誤的。

這裏又出現了一個新的問題,那既然一維數組可以轉化爲指針變量,那爲什麼二維數組就不可以轉化爲指向指針的指針變量呢?難道C++只提供了直接的轉化,而不支持間接的轉化(這裏的二維數組的轉化和一維數組相比好像是又多了一層轉化)?

在回答這個問題之前,我們先看看如下兩種類型:

double *p[3]和double(*p)[3]

‍double *p[3]是一個指針數組,它是一個數組,裏面存放了3個指針;‍double(*p)[3]它是一個數組指針,它是一個指針,這個指針指向的是一個數組,它和二維數組有相同的性質,由此我們可以知道如下的賦值是可行的:

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

double(*pp)[3]=p;

這裏實際上的話也是應該執行了一次上面所說的自動轉化,p轉化了一個指向行數組的指針,然後賦值給了數組指針變量pp;

另外,我們發現底下的賦值也是可行的:

double *p[3];

double **pp=p;

這裏實際上也是執行了一次上面所說的轉化,p轉化了一個指向指針變量的指針,然後賦值給了pp;

這裏看下,上面兩次轉化後唯一的的區別於一個是指向數組的指針,一個是指向指針的指針,而C++不允許接下來的再次轉化,說明C++只支持數組到指針的一次轉化,而二次轉化沒有支持。這樣就回答了上面的問題。那爲什麼C++不支持呢?(這個問題好難回答!)

底下我們再來看下,double *p[3]和double(*p)[3],這兩個是不同的類型,如果C++支持二次轉化的話,那麼double *p[3]和double(*p)[3]就可以視爲一種類型了,所以看來C++不支持兩次轉化,也是出於多方面的考慮。這個可能也是C++考慮的因素之一吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章