1. 程式人生 > >C語言高維陣列&陣列指標&指標陣列

C語言高維陣列&陣列指標&指標陣列

高維陣列

如二維陣列: int a[10][20]
陣列名是陣列的首地址,就是第一個元素的地址,是一個常量,常量是不能放在=左邊的,陣列和指標的區別在於,指標是變數,是用來儲存指向資料的地址的變數,而陣列名是常量。一般情況下申明一個數組,比如char a[10]; 之後 陣列名a都是陣列的首地址,是一個地址常量。但是在函式申明的形參列表中除外,比如:

int fo(char []);

在這種情況下的申明與

int fo(char a[]);
int fo(char *a);

是等價的,就是說在這種情況下,就算你寫的是陣列的形式,編譯器實際處理的時候也是當做指標來處理

的,(此時,指標是變數,形參也是變數,二者剛好對應。)所以在函式fo內部,我們隊a ++ – += -= = 之類的帶賦值的操作是完全合法的,因此a就是一個指標,不是陣列名,當我們向fo裡面傳入我們一個已經事先申明的陣列的首地址,指標a裡面的值就是已經申明的陣列進行操作。

指標陣列

元素為指標陣列,如:int* p[n],p是一個數組,陣列中元素為int*,且有n個元素。

陣列指標(行指標)

指向陣列指標,如:int (*p)[n]p是一個指標,指向陣列的指標,且陣列中有nint元素。由於[]的優先順序比*高,所以需要加括號,不加括號就變成了指標陣列。p+1要跨過n個元素。

int num[3];	//num的值與&num的值相同,但是兩者型別不同,
				//num 為指向第一個元素的指標,num+1表示下一個元素的地址,
				//&num 為陣列指標,int (*) [3], &num+1就會跳過這三個元素。
				//num++ 或者num = num + 1;會報錯。
				//&(num + 1) 也會報錯。
int a[3][4];	
int (*p)[4];
p = a;		// a自身為陣列指標, int (*)[4],指向a[0]那一行;
			// &a : 仍為陣列指標,只不過是一個指向二維陣列的指標, int (*) [3][4]。
// a + i : 陣列指標, int (*) [4]。 // *(a + i):陣列,int *。 // *(*(a + i) + j):數字 int ,a[i][j]。 // *(a + i) = a[i]; // *(*(a + i) + j) = *(a[i] + j) = a[i][j]; p = p + 1; // 此時p仍是一個數組指標,指向a[1]那一行。 int *p1[3]; //p1為指標陣列,含有3個整形指標的陣列。 for(i = 0; i < 3; i++) p1[i] = a[i];

記憶體佈局:

在這裡插入圖片描述

int main() {
   char a[5]={'A','B','C','D'};
   char (*p3)[5] = &a;
   char (*p4)[5] = a;
   return 0;
}

上面對p3 和p4 的使用,哪個正確呢?p3+1 的值會是什麼?p4+1 的值又會是什麼?毫無疑問,p3 和p4 都是陣列指標,指向的是整個陣列。&a 是整個陣列的首地址,a是陣列首元素的首地址,其值相同但意義不同。在C 語言裡,賦值符號“=”號兩邊的資料型別必須是相同的,如果不同需要顯示或隱式的型別轉換。p3 這個定義的“=”號兩邊的資料型別完全一致,而p4 這個定義的“=”號兩邊的資料型別就不一致了。左邊的型別是指向整個陣列的指標,右邊的資料型別是指向單個字元的指標。在Visual C++6.0 上給出如下警告:

warning C4047: ‘initializing’ : ‘char (*)[5]’ differs in levels of indirection from ‘char *’。

還好,這裡雖然給出了警告,但由於 &a 和a 的值一樣,而變數作為右值時編譯器只是取變數的值,所以執行並沒有什麼問題。不過我仍然警告你別這麼用。
既然現在清楚了p3 和p4 都是指向整個陣列的,那p3+1 和p4+1 的值就很好理解了。
甚至還可以把程式碼再修改,把陣列大小改大點:

int main()
{
   char a[5]={'A','B','C','D'};
   char (*p3)[10] = &a;
   char (*p4)[10] = a;
   return 0;
}

這個時候又會有什麼樣的問題?p3+1 和p4+1 的值又是多少?
上述幾個問題,希望讀者能仔細考慮考慮,並且上機測試看看結果。
測試結果:
(1).char (*p2)[5]=a;必須使用強制轉換,如:char (p2)[5]=(char ()[5])a;
(2).把陣列大小改變,都會編譯不通過,提示:

error C2440: ‘initializing’ : cannot convert from ‘char ()[5]’ to 'char ()[3]’
error C2440: ‘initializing’ : cannot convert from ‘char ()[5]’ to 'char ()[10]’

地址的強制轉換

先看下面這個例子:

struct Test
{
   int Num;
   char *pcName;
   short sDate;
   char cha[2];
   short sBa[4];
}*p;

假設p 的值為0x100000。如下表表示式的值分別為多少?

p + 0x1 = 0x___ ?
(unsigned long)p + 0x1 = 0x___?
(unsigned int*)p + 0x1 = 0x___?

還記得前面表示式“a+1”與“&a+1”之間的區別嗎?其實這裡也一樣。指標變數與一個整數相加減並不是用指標變數裡的地址直接加減這個整數。這個整數的單位不是byte 而是元素的個數。所以:p + 0x1 的值為0x100000+sizof(Test)*0x1。至於此結構體的大小為20byte,前面的章節已經詳細講解過。所以p +0x1 的值為:0x100014。

(unsigned long)p + 0x1 的值呢?這裡涉及到強制轉換,將指標變數p 儲存的值強制轉換成無符號的長整型數。任何數值一旦被強制轉換,其型別就改變了。所以這個表示式其實就是一個無符號的長整型數加上另一個整數。所以其值為:0x100001。

(unsigned int*)p + 0x1 的值呢?這裡的p 被強制轉換成一個指向無符號整型的指標。所以其值為:0x100000+sizof(unsigned int)*0x1,等於0x100004。

上面這個問題似乎還沒啥技術含量,下面就來個有技術含量的:在x86 系統下,其值為多少?

int main()
{
   int a[4]={1,2,3,4};
   int *ptr1=(int *)(&a+1);//指向a陣列後面的記憶體單元,&a+1表示向後移16個儲存單元
   int *ptr2=(int *)((int)a+1);//表示a的儲存單元的地址增加一個位元組
   printf("%x,%x",ptr1[-1],*ptr2);//ptr1[-1]其實指向的是a陣列的最後一個單元,
   					//*ptr1則表示a陣列的地址後移一個位元組之後的4個連續儲存單元所儲存的值
   return 0;
}

根據上面的講解,&a+1 與a+1 的區別已經清楚。
ptr1:將&a+1 的值強制轉換成int型別,賦值給int 型別的變數ptr,ptr1 肯定指到陣列a 的下一個int 型別資料了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往後退4 個byte。所以其值為0x4。

ptr2:按照上面的講解,(int)a+1 的值是元素a[0]的第二個位元組的地址。然後把這個地址強制轉換成int型別的值賦給ptr2,也就是說ptr2 的值應該為元素a[0]的第二個位元組開始的連續4 個byte 的內容
其記憶體佈局如下圖:
在這裡插入圖片描述
好,問題就來了,這連續4 個byte 裡到底存了什麼東西呢?也就是說元素a[0],a[1]裡面的值到底怎麼儲存的。這就涉及到系統的大小端模式了,如果懂彙編的話,這根本就不是問題。既然不知道當前系統是什麼模式,那就得想辦法測試。大小端模式與測試的方法在第一章講解union 關鍵字時已經詳細討論過了,請翻到彼處參看,這裡就不再詳述。我們可以用下面這個函式來測試當前系統的模式。

int checkSystem()
{
  union check
  {
      int i;
      char ch;
  } c;
  c.i = 1;
  return (c.ch == 1);//如果當前系統為大端模式這個函式返回0;如果為小端模式,函式返回1。
}

如果當前系統為大端模式這個函式返回0;如果為小端模式,函式返回1。也就是說如果此函式的返回值為1 的話,*ptr2 的值為0x2000000。如果此函式的返回值為0 的話,*ptr2 的值為0x100。
參考原文:https://blog.csdn.net/u013634862/article/details/26853789
參考原文:http://c.biancheng.net/cpp/html/476.html