先從存儲的角度對二維數組做一個全面的瞭解。二維數組在內存中的存儲,是按照先行後列依次存放的。從內存的角度看,能夠這樣說,二維數組其實就是一個一維數組,在內存中沒有二維的概念。若是把二維數組的每一行當作一個總體,即當作一個數組中的一個元素,那麼整個二維數組就是一個一維數組,它以每一行做爲它的元素,這個應該很好理解。
第一,來詳細介紹二維數組與指針的關係。-
首先定義個二維數組 array[3][4],p 爲指向數組的指針。
若p=array[0],此時p指向的是二維數組第一行的首地址,則 p+i 將指向array[0]數組中的元素array[0][i]。由以上所介紹的二維數組在內存中的存儲方式可知,對數組中的任一元素array[i][j] ,其指針的形式爲:p+i*N+j (N爲每一行的長度)。 元素相應的指針表示法爲:*(p+i*N+j) ,下標表示法爲:p[i*N+j] 。
For Example:
array[4][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
int * p=array[0];
數組array有四個元素,分別爲array[0],array[1],array[2],array[3],每一個元素爲包含3個元素的一維數組,
如array[0]的3個元素爲 array[0][0],array[0][1],array[0][2]。
元素array[2][2]對應指針爲:array+2*3+2,
指針表示法爲:*(array+2*3+2) ,
下標表示法爲:array[2*3+2] 。數組
特別注意:雖然 array[0] 與 array 都是數組首地址,但二者指向的對象不一樣,這點要很是明確。
array是一個數組名,右值退化爲指針,指向的是一個一維數組a[0]總體,也就是指向{1,2,3}這個數組總體的地址。
array[0]是一個數組名,右值退化爲指針,指向的是{1,2,3}這個數組中第一個元素的地址,也就是arrar[0][0]=1的地址.
array做爲數組名,左值表明數組,有四個元素,分別爲array[0],array[1],array[2],array[3],每一個元素爲包含3個元素的一維數組,
array[0]做爲數組名,左值表明數組,有3個元素爲 ,分別爲array[0][0],array[0][1],array[0][2]。它做爲右值轉化爲指針,指向的是一維數組array[0]的首地址。(array[0]是一維數組的名字,array[0]做爲左值表明數組,做爲右值表明的是指針。*array[0]這個表達式中,array[0]做爲右值,數據類型由數組退化爲指針(該指針指向了array[0]這個數組首元素的地址),因此*array[0]的結果就是第一行第一個元素的值,*array[0]==array[0][0]。
由上面可知:array做爲數組名(左值)表明的是二維數組總體,arr[0]做爲數組名(左值)表明的是行數組的總體。位運算符sizeof()中,數組名錶明左值,因此:
二維數組的總字節長度爲:sizeof(arr)
二維數組中每行的字節長度表示爲:sizeof(arr[0])
而 array 是二維數組的名字,它指向的是所屬元素的首地址,其每一個元素爲一個行數組。它是以‘行’來做爲指針移動單位的,如array+i 指向的是第 i 行。對 array 進行 * 運算,獲得的是一維數組 array[0] 的首地址,因此 *array 與 array[0] 爲同個值(做爲左值都是一個行數組)。【此時array先退化爲指針,該指針指向的是第一行這個一維數組(array是一個數組指針,這個指針中的值就是array中第一行這個數組的地址),*array就是對指針取值,結果就是第一行這個數組。此時若是用printf函數打印*array,*array做爲第一行這個數組,會退化爲指針,打印結果就是第一行首元素的地址】
若是定義 int* p,p爲指int類型的指針,指向int 類型,而不是地址。故如下操做 :p=array[0] (正確) ,p=array (錯誤) 。這點要很是注意。(錯誤緣由是p=array這個表達式中,array做爲指針,其類型是一個數組指針,指向了{1,2,3}這個數組,因此p和array兩個指針的類型不匹配。)
C語言中不一樣類型的指針之間如何自動轉換的??。指針存儲的是地址,地址的位寬根據不一樣的系統有區別,64位是8字節。
int (*p)[3]; 它表示,數組 *p 具備三個int類型元素,分別爲 (*p)[0] , (*p)[1] , (*p)[2] ,即 p指向的是具備三個int類型的一維數組,也就是說,p爲行指針。此時,如下運算 p=array 是正確的。函數
01.數組指針是指向數組首元素的地址的指針,其本質爲指針(這個指針存放的是數組首地址的地址,至關於2級指針,這個指針不可移動)。int (*p)[10]; p即爲指向數組的指針,又稱數組指針。(行數組指針)指針
02.指針數組是數組元素爲指針的數組,其本質爲數組。對象
int*p[2]是指針數組,實質是一個數組,裏面的兩個元素都是指針, []的優先級比*的優先級高,p先與[]結合,造成數組p[2],有兩個元素的數組,再與*結合,表示此數組是指針類型的,每一個數組元素至關於一個指針變量遞歸
int *pointer_array[3]; //指針數組,便是一個存放指針元素的數組,定義後即會有含有三個指針元素的數組,可是每一個指針元素並無初始化(相對而言,char *n[3]={"gain","much","strong"};是一個初始化了的指針數組,其數組中的元素是char型指針)有限個類型相同的變量的集合命名,那麼這個名稱爲數組名內存
根據數組的定義因此能夠知道指針數組中,全部的指針都是相同類型的指針。字符串
字符數組中存放的是字符串的首地址,不是字符串自己,字符串自己位於其餘的內存區域,和字符數組是分開的。get
03.二維數組:如char string_1[10][10]只要定義了一個二維數組,不管賦不賦值,系統都會給他分配相應空間,並且該空間必定是連續的。其每一個元素表示一個字符。咱們能夠經過指定下標對其元素進行修改。編譯器
指針數組:如char *str_B[5] 系統至少會分配5個連續的空間用來存儲5個元素,表示str_B是一個5個元素的數組,每一個元素是一個指向字符型數據的一個指針。string
若是我作這樣的定義:
char a[3][8]={"gain","much","strong"};
char *n[3]={"gain","much","strong"};
他們在內存的存儲方式分別如右圖所示,可見,系統給數組a分配了3×8的空間,而給n分配的空間則取決於具體字符串的長度。
此外,系統分配給a的空間是連續的,而給n分配的空間則不必定連續。
因而可知,相比於比二維字符數組,指針數組有明顯的優勢:一是指針數組中每一個元素所指的字符串沒必要限制在相同的字符長度;二是訪問指針數組中的一個元素是用指針間接進行的,效率比下標方式要高。 可是二維字符數組卻能夠經過下標很方便的修改某一元素的值,而指針數組卻沒法這麼作。
二維數組做爲函數參數。
二維數組做爲函數參數通常有兩種方式:(1) void func(int **array){...} (2) void func(int array[ ][N])
注意第二種方式必定要指明二維數組的列數
當二維數組名做爲函數實參時,對應的形參必須是一個行指針變量。
「數組名被改寫成一個指針參數」規則並非遞歸定義的。
數組的數組會被改寫成「數組的指針」,而不是「指針的指針」:(二維數組傳遞參數後退化位,數組指針。)
指針數組,做爲實參傳遞給函數會退化位二級指針。
實參 所匹配的形參
數組的數組 char c[8][10]; char (*c)[10]; 數組指針
指針數組 char *c[10]; char **c; 指針的指針
數組指針(行指針) char (*c)[10]; char (*c)[10]; 不改變
指針的指針 char **c; char **c; 不改變
對二維數組作以下總結:
1.二維數組和二維指針不是等價的,不能相互賦值
2.指針數組*[] 能夠轉換爲二級指針** 他們相互等價
3.二維數組名和數組指針雖然是一個指針,但編譯器並不理解,對他來講是數組類型的指針,但能夠類型強制轉換
pointer = (int *)array_pointer; array_pointer = (int(*)[3])pointer; pointer = (int *)bi_array;
4.若是想用函數傳遞二維數組,通常形參用二級指針**p或指針數組*[],能夠支持二級指針和指針數組的實參傳遞,特殊的還能夠用(*p)[N]
數組名絕對不等於指針,並且不是指針
數組名僅僅是一個符號,不是變量,它沒有本身的存儲空間,而指針實實在在的是個變量,有本身的空間:
數組名不是指針,它就是一個符號。
這個是重點分析的地方,很對多人對於數組名就是指針持贊同觀點的一個堂而皇之的證據是「數組名不能被修改,由於數組名是一個常量指針」,也就是不能執行 a = a+1;這句話對通常,錯一半,對的是,數組名確實不能被修改,錯的是,不能被修改的緣由不是由於數組名是常量指針,而是由於數組名只是一個符號,不是一個變量,所以不能做爲一個左值,所以不能被修改,這裏又涉及到左值和右值的問題,就再也不贅述,網上資料不少。
數組名和指針的本質區別:指針是一個變量,有本身對應的存儲空間,而數組名僅僅是一個符號,不是變量,於是沒有本身對應的存儲空間,到此已經能夠得出結論,「數組名永遠不等於指針」。