1. 程式人生 > >圖解c/c++多級指標與“多維”陣列

圖解c/c++多級指標與“多維”陣列

本文來源於https://www.cnblogs.com/chenyangyao/p/5222696.html,複製發表僅便於個人學習。

宣告:本文為原創博文,如有轉載,請註明出處。若本文有編輯錯誤、概念錯誤或者邏輯錯誤,請予以指正,謝謝。     

指標與陣列是C/C++程式設計中非常重要的元素,同時也是較難以理解的。其中,多級指標與“多維”陣列更是讓很多人云裡霧裡,其實,只要掌握一定的方法,理解多級指標和“多維”陣列完全可以像理解一級指標和一維陣列那樣簡單。     
首先,先宣告一些常識,如果你對這些常識還不理解,請先去彌補一下基礎知識:

1、實際上並不存在多維陣列,所謂的多維陣列本質上是用一維陣列模擬的。

2、陣列名是一個常量(意味著不允許對其進行賦值操作),其代表陣列首元素的首地址。

3、陣列與指標的關係是因為陣列下標操作符[],比如,int a[3][2]相當於*(*(a+3)+2) 。

4、指標是一種變數,也具有型別,其佔用記憶體空間大小和系統有關,一般32位系統下,sizeof(指標變數)=4。

5、指標可以進行加減算術運算,加減的基本單位是sizeof(指標所指向的資料型別)。

6、對陣列的陣列名進行取地址(&)操作,其型別為整個陣列型別。

7、對陣列的陣列名進行sizeof運算子操作,其值為整個陣列的大小(以位元組為單位)。

8、陣列作為函式形參時會退化為指標。
一、一維陣列與陣列指標
假如有一維陣列如下:

char a[3];

該陣列一共有3個元素,元素的型別為char,如果想定義一個指標指向該陣列,也就是如果想把陣列名a賦值給一個指標變數,那麼該指標變數的型別應該是什麼呢?前文說過,一個數組的陣列名代表其首元素的首地址,也就是相當於&a[0],而a[0]的型別為char,因此&a[0]型別為char *,因此,可以定義如下的指標變數:
char * p = a;//相當於char * p = &a[0]
以上文字可用如下記憶體模型圖表示。

大家都應該知道,a和&a[0]代表的都是陣列首元素的首地址,而如果你將&a的值打印出來,會發現該值也等於陣列首元素的首地址。請注意我這裡的措辭,也就是說,&a雖然在數值上也等於陣列首元素首地址的值,但是其型別並不是陣列首元素首地址型別,也就是char *p = &a是錯誤的。

前文第6條常識已經說過,對陣列名進行取地址操作,其型別為整個陣列,因此,&a的型別是char (*)[3],所以正確的賦值方式如下: char (*p)[3] = &a;

注:很多人對類似於a+1,&a+1,&a[0]+1,sizeof(a),sizeof(&a)等感到迷惑,其實只要搞清楚指標的型別就可以迎刃而解。比如在面對a+1和&a+1的區別時,由於a表示陣列首元素首地址,其型別為char ,因此a+1相當於陣列首地址值+sizeof(char);而&a的型別為char ()[3],代表整個陣列,因此&a+1相當於陣列首地址值+sizeof(a)。(sizeof(a)代表整個陣列大小,前文第7條說明,但是無論陣列大小如何,sizeof(&a)永遠等於一個指標變數佔用空間的大小,具體與系統平臺有關)

二、二維陣列與陣列指標

假如有如下二維陣列:
char a[3][2];

由於實際上並不存在多維陣列,因此,可以將a[3][2]看成是一個具有3個元素的一維陣列,只是這三個元素分別又是一個一維陣列。實際上,在記憶體中,該陣列的確是按照一維陣列的形式儲存的,儲存順序為(低地址在前):a[0][0]、a[0][1]、a[1][0]、a[1][1]、a[2][0]、a[2][1]。(此種方式也不是絕對,也有按列優先儲存的模式) 為了方便理解,我畫了一張邏輯上的記憶體圖,之所以說是邏輯上的,是因為該圖只是便於理解,並不是陣列在記憶體中實際的儲存模型(實際模型為前文所述)。

如上圖所示,我們可以將陣列分成兩個維度來看,

首先是第一維,將a[3][2]看成一個具有三個元素的一維陣列,元素分別為:a[0]、a[1]、a[2],其中,a[0]、a[1]、a[2]又分別是一個具有兩個元素的一維陣列(元素型別為char)。從第二個維度看,此處可以將a[0]、a[1]、a[2]看成自己代表”第二維”陣列的陣列名,以a[0]為例,a0代表的一維陣列是一個具有兩個char型別元素的陣列,而a[0]是這個陣列的陣列名(代表陣列首元素首地址),因此a[0]型別為char *,同理a[1]和a[2]型別都是char 。而a是第一維陣列的陣列名,代表首元素首地址,而首元素是一個具有兩個char型別元素的一維陣列,因此a就是一個指向具有兩個char型別元素陣列的陣列指標,也就是char()[2]。 也就是說,如下的賦值是正確的: char (p)[2] = a;//a為第一維陣列的陣列名,型別為char ()[2]

char * p = a[0];//a[0]維第二維陣列的陣列名,型別為char *

同樣,對a取地址操作代表整個陣列的首地址,型別為陣列型別(請允許我暫且這麼稱呼),也就是char (*)[3][2],

所以如下賦值是正確的:

char (*p)[3][2] = &a ;

三、三維陣列與陣列指標
假設有三維陣列:
char a[3][2][2];

同樣,為了便於理解,特意畫了如下的邏輯記憶體圖。分析方法和二維陣列類似,首先,從第一維角度看過去,a[3][2][2]是一個具有三個元素a[0]、a[1]、a[2]的一維陣列,只是這三個元素分別又是一個"二維"陣列,a作為第一維陣列的陣列名,代表陣列首元素的首地址,也就是一個指向一個二維陣列的陣列指標,其型別為char (*)[2][2]。

從第二維角度看過去,a[0]、a[1]、a[2]分別是第二維陣列的陣列名,代表第二維陣列的首元素的首地址,也就是一個指向一維陣列的陣列指標,型別為char(*)[2];

同理,從第三維角度看過去,a[0][0]、a[0][1]、a[1][0]、a[1][1]、a[2][0]、a[2][1]又分別是第三維陣列的陣列名,代表第三維陣列的首元素的首地址,也就是一個指向char型別的指標,型別為char *。

由上可知,以下的賦值是正確的:
char (*p)[3][2][2] = &a;//對陣列名取地址型別為整個陣列
char (*p)[2][2] = a;
char (*p) [2] = a[0];//或者a[1]、a[2]
char *p = a[0][0];//或者a[0][1]、a[1][0]…

  四:多級指標     
   所謂的多級指標,就是一個指向指標的指標,比如:      
   char *p = "my name is chenyang.";
   char **pp = &p;//二級指標
   char ***ppp = &pp;//三級指標    

假設以上語句都位於函式體內,則可以使用下面的簡化圖來表達多級指標之間的指向關係。
多級指標通常用來作為函式的形參,比如常見的main函式宣告如下:

   int main(int argc,char ** argv)        

    因為當陣列用作函式的形參的時候,會退化為指標來處理,所以上面的形式和下面是一樣的。    

    int mian(int argc,char* argv[])          

    argv用於接收使用者輸入的命令引數,這些引數會以字串陣列的形式傳入,類似於:    

    char * parm[] = {"parm1","parm2","parm3","parm4"};//模擬使用者傳入的引數
     main(sizeof(parm)/sizeof(char *),parm);//模擬呼叫main函式,實際中main函式是由入口函式呼叫的(glibc中的入口函式預設為_start)         

多級指標的另一種常見用法是,假設使用者想呼叫一個函式分配一段記憶體,那麼分配的記憶體地址可以有兩種方式拿到:第一種是通過函式的返回值,該種方式的函式宣告如下:    

void * get_memery(int size)
{
void *p = malloc(size);
return p;
}

第二種獲取地址的方法是使用二級指標,程式碼如下:

int get_memery(int** buf,int size)
{
*buf = (int *)malloc(size);
if(*buf == NULL)
return -1;
else
return 0;
}
int *p = NULL;
get_memery(&p,10);

關於多級指標的用法很多,尤其以二級指標應用最為廣泛,後續的有時間再進行補充。