【轉】 http://blog.chinaunix.net/uid-21411227-id-1826897.html
一.指針與數組的聯系:
指針與數組是C語言中很重要的兩個概念,它們之間有着密切的關系,利用這種關系,可以增強處理數組的靈活性,加快運行速度,本文着重討論指針與數組之間的聯系及在編程中的應用。
1.指針與數組的關系
當一個指針變量被初始化成數組名時,就說該指針變量指向了數組。如:
1 char str[20], *ptr; 2 3 ptr=str;
ptr被置為數組str的第一個元素的地址,因為數組名就是該數組的首地址,也是數組第一個元素的地址。此時可以認為指針ptr就是數組str(反之不成立),這樣原來對數組的處理都可以用指針來實現。如對數組元素的訪問,既可以用下標變量訪問,也可以用指針訪問。
2.指向數組元素的指針
若有如下定義:
int a[10], *pa; pa=a;
則p=&a[0]是將數組第1個元素的地址賦給了指針變量p。
實際上,C語言中數組名就是數組的首地址,所以第一個元素的地址可以用兩種方法獲得:p=&a[0]或p=a。
這兩種方法在形式上相像,其區別在於:pa是指針變量,a是數組名。值得注意的是:pa是一個可以變化的指針變量,而a是一個常數。因為數組一經被說明,數組的地址也就是固定的,因此a是不能變化的,不允許使用a++、++a或語句a+=10,而pa++、++pa、pa+=10則是正確的。由此可見,此時指針與數組融為一體。
3.指針與一維數組
理解指針與一維數組的關系,首先要了解在編譯系統中,一維數組的存儲組織形式和對數組元素的訪問方法。
一維數組是一個線形表,它被存放在一片連續的內存單元中。C語言對數組的訪問是通過數組名(數組的起始地址)加上相對於起始地址的相對量(由下標變量給出),得到要訪問的數組元素的單元地址,然后再對計算出的單元地址的內容進行訪問。通常把數據類型所占單元的字節個數稱為擴大因子。
實際上編譯系統將數組元素的形式a[i]轉換成*(a+i),然后才進行運算。對於一般數組元素的形式:<數組名>[<下標表達式>],編譯程序將其轉換成:*(<數組名>+<下標表達式>),其中下標表達式為:下標表達式*擴大因子。整個式子計算結果是一個內存地址,最后的結果為:*<地址>=<地址所對應單元的地址的內容>。由此可見,C語言對數組的處理,實際上是轉換成指針地址的運算。
數組與指針暗中結合在一起。因此,任何能由下標完成的操作,都可以用指針來實現,一個不帶下標的數組名就是一個指向該數組的指針。
4.指針與多維數組
用指針變量可以指向一維數組,也可以指向多維數組。但在概念上和使用上,多維數組的指針比一維數組的指針要復雜一些。
例如,在一個三維數組中,引用元素c[i][j][k]的地址計算最終將換成:*(*(*(c+i)+j)+k)。了解了多維數組的存儲形式和訪問多維數組元素的內部轉換公式后,再看當一個指針變量指向多維數組及其元素的情況。
1)指向數組元素的指針變量
若有如下說明:
1 int a[3][4]; 2 3 int *p; 4 5 p=a;
p是指向整型變量的指針;p=a使p指向整型二維數組a的首地址。
*(*(p+1)+2)表示取a[1][2]的內容;*p表示取a[0][1]的內容,因為p是指向整型變量的指針;p++表示p的內容加1,即p中存放的地址增加一個整型量的字節數2,從而使p指向下一個整型量a[0][1]。
2)指向由j個整數組成的一維數組的指針變量
當指針變量p不是指向整型變量,而是指向一個包含j個元素的一維數組。如果p=a[0],則p++不是指向a[0][1],而是指向a[1]。這時p的增值以一維數組的長度為單位。
5.指針與字符數組
C語言中許多字符串操作都是由指向字符數組的指針及指針的運算來實現的。因為對於字符串來說,一般都是嚴格的順序存取方式,使用指針可以打破這種存取方式,更為靈活地處理字符串。
另外由於字符串以′\0′作為結束符,而′\0′的ASCII碼是0,它正好是C語言的邏輯假值,所以可以直接用它作為判斷字符串結束的條件,而不需要用字符串的長度來判斷。C語言中類似的字符串處理函數都是用指針來完成,使程序運行速度更快、效率更高,而且更易於理解。
二.指針與數組的區別:
1.把數組作為參數傳遞的時候,會退化為指針
數組名作為函數形參時,在函數體內,其失去了本身的內涵,僅僅只是一個指針;很遺憾,在失去其內涵的同時,它還失去了其常量特性,可以作自增、自減等操作,可以被修改。
所以,數組名作為函數形參時,其淪落為一個普通指針!它的貴族身份被剝奪,成了一個地地道道的只擁有4個字節的平民。
典型的情況是
1 void func(int A[]) 2 3 { 4 5 //sizeof(A)得到的是4bytes 6 7 } 8 9 int main() 10 11 { 12 13 int a[10]; //sizeof(a) 得到的結果是40bytes 14 15 funct(a); 16 17 }
2、數組名可作為指針常量
根據結論2,數組名可以轉換為指向其指代實體的指針,所以程序1中的第5行數組名直接賦值給指針,程序2第7行直接將數組名作為指針形參都可成立。
下面的程序成立嗎?
1 int intArray[10]; 2 3 intArray++;
讀者可以編譯之,發現編譯出錯。原因在於,雖然數組名可以轉換為指向其指代實體的指針,但是它只能被看作一個指針常量,不能被修改。
而指針,不管是指向結構體、數組還是基本數據類型的指針,都不包含原始數據結構的內涵,在WIN32平台下,sizeof操作的結果都是4。
順便糾正一下許多程序員的另一個誤解。許多程序員以為sizeof是一個函數,而實際上,它是一個操作符,不過其使用方式看起來的確太像一個函數了。語句sizeof(int)就可以說明sizeof的確不是一個函數,因為函數接納形參(一個變量),世界上沒有一個C/C++函數接納一個數據類型(如int)為"形參"。
3.對於問題:
1 char a[3] = "abc"; 2 strcpy(a,"end");//it's ok 3 4 char *a = "abc"; 5 strcpy(a,"end"); //it's wrong
解釋如下:
char *a = "abc"; abc是一個字符串常量,有它自己的存儲空間,因為分配在只讀數據塊,我們無法直接訪問。這樣賦值后,a只能讀,不能寫
所以strcpy(a, "end")不行,只有當你為a分配非常量的存儲空間后才行
如:
1 char *a = new char[4]; 2 3 strcpy(a, "end"); 4 5 printf("%s", a); 6 7 delete []a;
4.數組和指針的分配
數組是開辟一塊連續的內存空間,數組本身的標示符代表整個數組,可以用sizeof取得真實的大小;指針則是只分配一個指針大小的內存,並可把它的值指向某個有效的內存空間
[全局的和靜態的]
1 char *p= "hello ";//一個指針,指向只讀數據塊(section)里的 "hello ",可被編譯器放入字符串池(也就是說, 你在寫一個char *q= "hello ",可能和p共享數據) 2 3 char a[]= "hello ";//一個數組,分配在可寫數據塊(section),不會被放到字符串池中
[局部]
char *p= "hello ";
一個指針,指向只讀數據塊(section)里的 "hello ",可被編譯器放入字符串池(也就是說, 你在寫一個char *q= "hello ",可能和p共享數據),另外,在函數中可以返回它的地址,也就是說,指針是局部變量,他指向的數據卻是全局的.
char a[]= "hello ";
一個數組,分配在堆棧上,初始化由編譯器進行(短的話直接用指令填充,長的就從全局字符串表拷貝),不會被放到字符串池中(但是卻可能從字符串池中拷貝過來),也不應該返回
它的地址.
[代碼中的字面字符串]
printf( "%s\n ", "hello ");
這兩個字面常量( "%s\n "和 "hello "),都在只讀數據塊里
[用途]
全局指針
用於不需要修改內容,卻可能會修改指針的情況(當然,不修改也沒人反對)
全局數組,用於不需要修改地址,卻需要修改內容的場合
既需要修改指針,有需要修改內容怎么辦呢?定義一個數組,在定義一個指針指向它就可以了
函數中如果不需要修改字符串的內容,應該盡量用char*p= "xxx "這種寫法.
初始化的局部字符數組影響效率,一般應該盡量避開(應該使用的情況下則不要猶豫)