陣列的指標、指標陣列以及指向指標的指標
考慮陣列的指標的時候我們要同時考慮型別和維數這兩個屬性。換一句話,就是說一個數組排除在其中儲存的數值,那麼可以用型別和維數來位置表示他的種類。
一維陣列
在c和c++中陣列的指標就是陣列的起始地址(也就第一個元素的地址),而且標準文件規定陣列名代表陣列的地址(這是地址數值層面的陣列表示)。例如:
int a[10]; int *p;
p=&a[0]//和p=a是等價的。
因為a是陣列名,所以他是該陣列的地址,同時因為第一個元素為a[0],那麼&a[0]也代表了該陣列的地址。但是我們是不是就說一個數組名 和該陣列的第一個元素的&運算是一回事呢?在一維的時候當時是的,但是在高維的時候,我們要考慮到維數給陣列帶來的影響。
a[10]是一個數組,a是陣列名,它是一個包含10個int型別的陣列型別,不是一般的指標變數噢!(雖然標準文件規定在c++中從int[]到 int*直接轉換是可以的,在使用的時候似乎在函式的引數為指標的時候,我們將該陣列名賦值沒有任何異樣),a代表陣列的首地址,在數字層面和a[10] 的地址一樣。這樣我們就可以使用指標變數以及a來操作這個陣列了。
所以我們要注意以下問題:
- p[i]和a[i]都是代表該陣列的第i+1個元素;
- p+i和a+i代表了第i+1個元素的地址,所以我們也可以使用 *(p+I)和*(a+I)來引用物件元素;
- p+1不是對於指標數量上加一,而是表示從當前的位置跳過當前指標指向型別長度的空間,對於win32的int為4byte;
多維陣列
對於二維陣列a[4][6];由於陣列名代表陣列的起始地址,所以a(第一層)和第一個元素a[0][0]地址的數字是相同的,但是意義卻是不同的。 對於該陣列我們可以理解為:a的一維陣列(第一層),它有四個元素a[0]、a[1]、a[2]、a[3](第二層),而每個元素又含有6個元素a[0] [0],a[0][1],a[0][2],a[0][3],a[0][4],a[0][5](第三層),…到此我們終於訪問到了每個元素了,這個過程我們 經歷了:a->a[0]->a[0][0];
整體來講:a是一個4行5列的二維陣列,a表示它指向的陣列的首地址(第一個元素地址&a[0]),同時a[0]指向一行,它是這個行的名字 (和該行的第一個元素的首地址相同(第一個元素為地址&a[0][0]))。所以從數字角度說:a、a[0]、&a[0][0]是相同 的,但是他們所處的層次是不同的。
既然a代表二維陣列,那麼a+i就表示它的第i+1個元素*(a+i)的地址,而在二維陣列中
*(a+i)又指向一個數組,*(a+i)+j表示這個陣列的第j+1個元素的地址,所以要訪問這個元素可以使用 *(*(a+i)+j)(也就是a[i][j])。
他們的示意圖為(虛線代表不是實際存在的):
對照這個圖,如下的一些說法都是正確的(對於a[4][6]):
- a是一個數組型別,*a指向一個數組;
- a+i指向一個數組;
- a、*a和&a[0][0]數值相同;
- a[i]+j和*(a+i)+j是同一個概念;
總結一下就是:我們對於二維指標a,他指向陣列a[0,1,2,3],使用*,可以使他降級到第二層次,這樣*a就指向了第一個真正的陣列。對於其他的情況我們也可以採用相同的方式,對於其他維數和型別的陣列我們可以採用相類似的思想。
說到指向陣列的指標,我們還可以宣告一個指標變數讓它指向一個數組。例如:
int (*p)[5];
這時p就是一個指標,要指向一個含有5個int型別元素的陣列,指向其他的就會出現問題。這個時候我們可以使用上面的什麼東西來初始化呢?我們可以使用*a,*(a+1),a[2]等。原因很簡單:我們在一個二維的陣列中,那麼表達方式有上面的相互類似的意義呢?只有 *a,*(a+1),a[2]等。
指標陣列
一個指標陣列是指一個數組中的每個元素都是一個指標,例如:
int *p[10];//而不能是int (*p)[10]
或者
char *p[10];
此時p是一個指標(數值上和&p[0]一樣);
在前面有int t[10];
int * pt=t;//使用pt指向t
那麼這裡我們用什麼指向int *t[10]中的t呢?我們要使用一個指標的指標:
int **pt=t;
這是因為:在int *t[10]中,每個元素是指標,那麼同時t又指向這個陣列,陣列上和&t[0]相同,也就是指向t[0],指向一個指標變數,可以說是一個指標的指標了,所以自然要用
int **pt;
指標的指標
一個指標變數內部可以儲存一個值,這個值是另外一個物件的地址,所以我們說一個指標變數可以指向一個普通變數,同樣這個指標變數也有一個地址,也就是 說有一個東西可以指向這個指標變數,然後再通過這個指標變數指向這個物件。那麼如何來指向這個指標變數呢?由於指標變數本身已經是一個指標了(右值),那 麼我們這裡就不能用一般的指標了,需要在指標上體現出來這些特點,我們需要定義指標的指標(二重指標)。
int *p1=&i; int**p2=&p1;
綜合以上的所有點,下面是我們常常看到一些匹配(也是經常出錯的地方):
int a[3],b[2][3],c,*d[3]; void fun1(int *p); void fun2(int (*p)[3]); void fun3(int **p); void fun4(int p[3]); void fun5(int p[]); void fun6(int p[2][3]); void fun7(int (&p)[3]);
函式 不會產生編譯時刻的可能值(但邏輯上不一定都對)--這裡我覺得應該是有效的值
函式 |
不會產生編譯時刻的可能值(但邏輯上不一定都對) |
fun1 |
a, &a[i], *b ,b[i],&b[i][j] ,&c ,d[i] |
fun2 |
b,b+i,--(這裡我覺得應該是*b,*(b+1),b[1]) |
fun3 |
d |
fun4 |
a, &a[i], *b ,b[i],&b[i][j] ,&c ,d[i] |
fun5 |
a, &a[i], *b ,b[i],&b[i][j] ,&c ,d[i] |
fun6 |
b |
fun7 |
a |
為什麼可以有這樣的搭配,原因如下:
- 對於fun1 fun4 fun 5: 在編譯器看來fun1,fun4,fun5的宣告是一樣,在編譯時候,編譯器把陣列的大小捨去不考慮,只考慮它是一個指標,也就是說有沒有大小說明是一樣的,所以三者的形式都是fun1的形式(其實只要提供了int*指標就可以了);
- 對於fun7 :以上的解釋對於引用是不適用的,如果變數被宣告為陣列的引用,那麼編譯器就要考慮陣列的大小了,那麼必須和宣告一模一樣(所以fun7就只有a合適);
- 對於fun2:p是一個指向一個含有3個元素的陣列,這樣b和b+i正好合適,而a卻不是(它是指向a[0]的,不是指向這個陣列的);
- 對於fun3:p是一個指標的指標,而d指向d[0],同時d[0]又是一個指標,所以d就是一個指標的指標。但是b卻不是(它是一個2*3的矩陣也就是年int [2][3]型別);
- 對於fun6,p是一個2*3的陣列型別,和b恰好完全匹配;