2007年7月18日 星期三

C語言中陣列與指標的結合(一)

[轉錄自 http://blog.chinaunix.net/u1/35100/showart_338097.html]
對於C語言中指標和陣列的認識和看法

1. 指標變數也只是普通的變數
很多C語言的初學者都將指標變數看的很神秘,實際上,就像其他的普通變數

(比如int類型的),指標變數也是一種普通變數,他具有其他變數的一切特徵。
例如:int main(){int q=10;int *pi=0;pi = &q;printf ("%d\n", *pi);}
main中聲明並定義了一個自動變數p,他的類型是pointer-to-int.一旦定義了p,

編譯器就要給p分配記憶體空間。main結束後,p被自動釋放。
pi和q在這些方面沒有絲毫不同。
結論:指標變數沒有那麼神秘,指標變數只是一個普通變數


2. 指標變數與其他變數的關係
那麼,指針的特殊性表現在什麼地方呢?
指標特殊就特殊在對他所存儲的值的解釋上和對他的使用上。
在上例中,pi的值會被解釋成記憶體中的一個位址,
以這個位址開始的一塊記憶體則表示一個int型的數.
但是,即使在執行了pi = &q之後,pi和q也沒有什麼直接的關係:
改變pi的值不會影響q,改變q的值也不會影響pi,
他倆是兩個獨立的變數,有各自的存儲空間。
C語言賦予了指標特殊的本領就是:可以存儲別的變數的記憶體位址,
也可以利用指標變數本身的值去間接的操作別的變數.
這兩種能力是通過兩個操作符來完成的 : &和*。
pi = &q;
//利用q的記憶體位址對pi進行賦值printf ("%d\n", *pi); //利用pi的值去間接的讀q的值
sizeof(pi)跟sizeof(q)根本就是兩碼事. pi不會自動根據自己去尋找q的,只有你顯示的使用*pi才可以.
結論:假設指標pi存儲的是q的位址。pi和q沒有任何直接的關係。只有*pi才和q有直接的關係。

3. C語言中的函數參數傳遞方式
很多人的另外一種誤解是,C語言中有兩種函數傳遞方式:按值傳遞和按地址傳遞。
造成這種誤解的原因就是對上面所說的兩點理解的不夠。
void swap1( int a, int b){int temp;temp = a;a = b;b = temp;}
void swap2( int* pa, int* pb){int temp;temp = *pa;*pa = *pb;*pb = temp;}
int main(){
int i=10, j=5;
int *pi, *pj;
pi = &i;
pj = &j;
swap1(i, j);printf("i=%d, j=%d\n", i, j);
swap2(pi, pj);printf("i=%d, j=%d\n", i, j);
}
很多人會認為swap1是按值傳遞,而swap2是按指針傳遞。
其實,C語言只有一種函數參數傳遞方式:按值傳遞
swap1大家都明白,我說一下swap2。
實際上,我在第一點中已經指出,指標只是一個普通變數而已。
在對swap2的調用swap2(pi, pj)中, pi和pj的值分別被copy到swap2中的pa和pb中。
現在,i,pi和pa是3個完全不同的變數,但pi和pa的值相同,他們存的都是i的地址。
在swap2中,利用*pa就可以讀寫i了,同樣利用*pb就可以讀寫j了,
所以swap2就可以完成i和j的交換了。但是,pi和pj的值通過 swap2是無法改變的。
也就是說, swap2使用的參數傳遞方式仍然是按值傳遞,只不過傳遞的是指標的值,
然後利用指標有可以間接訪問其他變數而已.
結論:C語言只有一種函數參數傳遞方式:按值傳遞.

4. 指標與陣列
4.1 陣列名稱的類型
在C語言中,指標與陣列千絲萬縷的聯繫.看下面的例子:
int a[5];int b[3][5];int *pa1[5];int (*pa2)[5];
那麼a,b,pa1,pa2的類型到底是什麼呢?
很多人將a的類型誤解成為一級指標,即const pointer to int,
而將b的類型誤解成為二級指標,即const pointer to the pointer to int;
a不是const pointer to int類型的,我們是可以從下面這個事實推出來的:
sizeof(a)跟sizeof(int*)是不同的.
只能說,a是array of 5 ints類型的
而b則是array of 3 arrays of 5 ints類型的
這裏之所以把pa1和pa2列出來,主要是給大家區別一下:
pa1是array of 5 int pointers類型的,
而pa2是pointer to array of 5 ints類型的

4.2 陣列名稱的運算
大家經常會遇到關於陣列名稱的運算問題,比如
int a[5]={1, 2, 3, 4, 5};
int b[3][5]={{1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}};
printf("%d", *(a+2));
printf("%d, %d\n", **(b+2), *(*b+2));
在進行上面的運算時,有下面的似非而是的結論:

&a可以看作是pointer to array of 5 ints類型的所以&a+1,這裏的“1”是指5*sizeof(int)

a是array of 5 ints類型的,但是a的值是什麼呢?
a的值其實是一個pointer to int

*a的值則是一個int,即a[0];

&b可以看作是pointer to array of 3 arrays of 5 ints類型的
所以&b+1, 這裏的“1”是指3*5*sizeof(int)

b 是array of 3 arrays of 5 ints類型的,
但是b的值卻是一個pointer to array of 5 ints

*b是array of 5 ints類型的,
但是*b的值卻是pointer to int類型的

**b的值則是int類型的,即b[0][0];

推而廣之,則對於 int c[n1][n2]...[nm]的m維陣列(m後面的數位為下標),
有下面的結論(或者說計算方法):
先將c擴展為int c[n1][n2]...[nm][1]; (最後一個是數位1,不是字母l)
1.&c的單位“1”為n1*n2*...*nm*1*sizeof(int)

sizeof(&c)等於存儲一個指標的大小,
其值與sizeof(int*)相同;

2.令c1代表*...*c,共有 i 個 (0< color="#3333ff">則他的單位“1”為n(i+2)*...*nm*1*sizeof(int),

其中(i+2)等是下標sizeof(c1)=n(i+1)*...*nm*sizeof(int) 3.令c2代表*...*c,共有m個*,
表示陣列中第一個整數sizeof(c2)=sizeof(int)

例如:int c[3][4][5][6];先轉換成int c[3][4][5][6][1];
&c+1 相當於地址加3*4*5*6*1*sizeof(int), sizeof(&c)的值等於sizeof(int*);
c+1 相當於地址加4*5*6*1*sizeof(int), sizeof(c)=3*4*5*6*1*sizeof(int);
**c+1 相當於地址加6*sizeof(int), sizeof(**c)=5*6*1*sizeof(int);
****c 就是陣列中第一個整數

4.3 一種常見錯誤
int b[3][5];int **p=b;
有些人誤認為p+1的值和b+1是相同的,其實不然
類似於
T1 b1;T2* b2=(T2*)(&b1); //T1,T2為兩種不同的類型
這樣的話,b2+1是按sizeof(T2)進行增加的而&b1+1
是按照sizeof(T1)進行增加的指標進行類型轉換後,
其運算跟他自身的類型有關,而與他指向的東東無關