詳解C語言中指針和數(shù)組的聯(lián)系與區(qū)別
最早在大學(xué)里學(xué)c語言的時候?qū)χ羔樅蛿?shù)組這兩個概念混淆不清,經(jīng)?;鞛橐徽?,相信很多學(xué)友和我的情況類似。在這里,我會將自身對c語言的了解以及對這本書中相關(guān)內(nèi)容的理解,把指針和數(shù)組的聯(lián)系與區(qū)別總結(jié)一下。
一.指針與數(shù)組的聯(lián)系:
指針與數(shù)組是C語言中很重要的兩個概念,它們之間有著密切的關(guān)系,利用這種關(guān)系,可以增強(qiáng)處理數(shù)組的靈活性,加快運(yùn)行速度,本文著重討論指針與數(shù)組之間的聯(lián)系及在編程中的應(yīng)用。
1.指針與數(shù)組的關(guān)系
當(dāng)一個指針變量被初始化成數(shù)組名時,就說該指針變量指向了數(shù)組。如:
char str[20], *ptr;
ptr=str;
ptr被置為數(shù)組str的第一個元素的地址,因?yàn)閿?shù)組名就是該數(shù)組的首地址,也是數(shù)組第一個元素的地址。此時可以認(rèn)為指針ptr就是數(shù)組str(反之不成立),這樣原來對數(shù)組的處理都可以用指針來實(shí)現(xiàn)。如對數(shù)組元素的訪問,既可以用下標(biāo)變量訪問,也可以用指針訪問。
2.指向數(shù)組元素的指針
若有如下定義:
int a[10], *pa;
pa=a;
則p=&a[0]是將數(shù)組第1個元素的地址賦給了指針變量p。
實(shí)際上,C語言中數(shù)組名就是數(shù)組的首地址,所以第一個元素的地址可以用兩種方法獲得:p=&a[0]或p=a。
這兩種方法在形式上相像,其區(qū)別在于:pa是指針變量,a是數(shù)組名。值得注意的是:pa是一個可以變化的指針變量,而a是一個常數(shù)。因?yàn)閿?shù)組一經(jīng)被說明,數(shù)組的地址也就是固定的,因此a是不能變化的,不允許使用a++、++a或語句a+=10,而pa++、++pa、pa+=10則是正確的。由此可見,此時指針與數(shù)組融為一體。
3.指針與一維數(shù)組
理解指針與一維數(shù)組的關(guān)系,首先要了解在編譯系統(tǒng)中,一維數(shù)組的存儲組織形式和對數(shù)組元素的訪問方法。
一維數(shù)組是一個線形表,它被存放在一片連續(xù)的內(nèi)存單元中。C語言對數(shù)組的訪問是通過數(shù)組名(數(shù)組的起始地址)加上相對于起始地址的相對量(由下標(biāo)變量給出),得到要訪問的數(shù)組元素的單元地址,然后再對計算出的單元地址的內(nèi)容進(jìn)行訪問。通常把數(shù)據(jù)類型所占單元的字節(jié)個數(shù)稱為擴(kuò)大因子。
實(shí)際上編譯系統(tǒng)將數(shù)組元素的形式a[i]轉(zhuǎn)換成*(a+i),然后才進(jìn)行運(yùn)算。對于一般數(shù)組元素的形式:<數(shù)組名>[<下標(biāo)表達(dá)式>],編譯程序?qū)⑵滢D(zhuǎn)換成:*(<數(shù)組名>+<下標(biāo)表達(dá)式>),其中下標(biāo)表達(dá)式為:下標(biāo)表達(dá)式*擴(kuò)大因子。整個式子計算結(jié)果是一個內(nèi)存地址,最后的結(jié)果為:*<地址>=<地址所對應(yīng)單元的地址的內(nèi)容>。由此可見,C語言對數(shù)組的處理,實(shí)際上是轉(zhuǎn)換成指針地址的運(yùn)算。
數(shù)組與指針暗中結(jié)合在一起。因此,任何能由下標(biāo)完成的操作,都可以用指針來實(shí)現(xiàn),一個不帶下標(biāo)的數(shù)組名就是一個指向該數(shù)組的指針。
4.指針與多維數(shù)組
用指針變量可以指向一維數(shù)組,也可以指向多維數(shù)組。但在概念上和使用上,多維數(shù)組的指針比一維數(shù)組的指針要復(fù)雜一些。
例如,在一個三維數(shù)組中,引用元素c[i][j][k]的地址計算最終將換成:*(*(*(c+i)+j)+k)。了解了多維數(shù)組的存儲形式和訪問多維數(shù)組元素的內(nèi)部轉(zhuǎn)換公式后,再看當(dāng)一個指針變量指向多維數(shù)組及其元素的情況。
1)指向數(shù)組元素的指針變量
若有如下說明:
int a[3][4];
int *p;
p=a;
p是指向整型變量的指針;p=a使p指向整型二維數(shù)組a的首地址。
*(*(p+1)+2)表示取a[1][2]的內(nèi)容;*p表示取a[0][1]的內(nèi)容,因?yàn)閜是指向整型變量的指針;p++表示p的內(nèi)容加1,即p中存放的地址增加一個整型量的字節(jié)數(shù)2,從而使p指向下一個整型量a[0][1]。
2)指向由j個整數(shù)組成的一維數(shù)組的指針變量
當(dāng)指針變量p不是指向整型變量,而是指向一個包含j個元素的一維數(shù)組。如果p=a[0],則p++不是指向a[0][1],而是指向a[1]。這時p的增值以一維數(shù)組的長度為單位。
5.指針與字符數(shù)組
C語言中許多字符串操作都是由指向字符數(shù)組的指針及指針的運(yùn)算來實(shí)現(xiàn)的。因?yàn)閷τ谧址畞碚f,一般都是嚴(yán)格的順序存取方式,使用指針可以打破這種存取方式,更為靈活地處理字符串。
另外由于字符串以′\0′作為結(jié)束符,而′\0′的ASCII碼是0,它正好是C語言的邏輯假值,所以可以直接用它作為判斷字符串結(jié)束的條件,而不需要用字符串的長度來判斷。C語言中類似的字符串處理函數(shù)都是用指針來完成,使程序運(yùn)行速度更快、效率更高,而且更易于理解。
二.指針與數(shù)組的區(qū)別:
1.把數(shù)組作為參數(shù)傳遞的時候,會退化為指針
數(shù)組名作為函數(shù)形參時,在函數(shù)體內(nèi),其失去了本身的內(nèi)涵,僅僅只是一個指針;很遺憾,在失去其內(nèi)涵的同時,它還失去了其常量特性,可以作自增、自減等操作,可以被修改。
所以,數(shù)組名作為函數(shù)形參時,其淪落為一個普通指針!它的貴族身份被剝奪,成了一個地地道道的只擁有4個字節(jié)的平民。
典型的情況是
void func(int A[])
{
//sizeof(A)得到的是4bytes
}
int main()
{
int a[10]; //sizeof(a) 得到的結(jié)果是40bytes
funct(a);
}
2、數(shù)組名可作為指針常量
根據(jù)結(jié)論2,數(shù)組名可以轉(zhuǎn)換為指向其指代實(shí)體的指針,所以程序1中的第5行數(shù)組名直接賦值給指針,程序2第7行直接將數(shù)組名作為指針形參都可成立。
下面的程序成立嗎?
int intArray[10];
intArray++;
讀者可以編譯之,發(fā)現(xiàn)編譯出錯。原因在于,雖然數(shù)組名可以轉(zhuǎn)換為指向其指代實(shí)體的指針,但是它只能被看作一個指針常量,不能被修改。
而指針,不管是指向結(jié)構(gòu)體、數(shù)組還是基本數(shù)據(jù)類型的指針,都不包含原始數(shù)據(jù)結(jié)構(gòu)的內(nèi)涵,在WIN32平臺下,sizeof操作的結(jié)果都是4。
順便糾正一下許多程序員的另一個誤解。許多程序員以為sizeof是一個函數(shù),而實(shí)際上,它是一個操作符,不過其使用方式看起來的確太像一個函數(shù)了。語句sizeof(int)就可以說明sizeof的確不是一個函數(shù),因?yàn)楹瘮?shù)接納形參(一個變量),世界上沒有一個C/C++函數(shù)接納一個數(shù)據(jù)類型(如int)為"形參"。
3.對于問題:為什么用strcpy()函數(shù)時,
char a[3] = "abc";
strcopy(a,"end");
-------------------沒有錯。
用-----------------
char *a = "abc";
strcopy(a,"end");
------------------運(yùn)行時就有錯呢?
解釋如下:
char *a = "abc"; abc是一個字符串常量,有它自己的存儲空間,因?yàn)榉峙湓谥蛔x數(shù)據(jù)塊,我們無法直接訪問。這樣賦值后,a只能讀,不能寫
所以strcpy(a, "end")不行,只有當(dāng)你為a分配非常量的存儲空間后才行
如:
char *a = new char[4];
strcpy(a, "end");
printf("%s", a);
delete []a;
4//main.cpp
int array[3] = {7, 8, 9}; //全局變量
int main()
{
Test1();
Test2();
return 0;
}
//Test1.cpp
extern int array[3];
void Test1()
{
cout << array[1] << endl;
}
//Test2.cpp
extern int *array; //這個地方是不同的
void Test2()
{
cout << array << endl;
cout << array[1] << endl;
}
Test1()和Test2()的輸出結(jié)果相同嗎?
編譯一下再看看,就發(fā)現(xiàn)執(zhí)行Test2會有奇怪的結(jié)果,第一條語句的輸出是7, 第二條語句會死機(jī)。而Test1()卻一切正常。
這是為什么?
原因在編譯器。在Test1.cpp中,由于使用了extern所以編譯的時候要先用占位符將array標(biāo)志一下,在連接的時候用main.cpp中的array進(jìn)行替換。當(dāng)編譯器給變量賦值的時候,他認(rèn)為這個值是該變量的地址。就好比:int i = 5;在編譯器中編譯后會把5的地址0x8291記錄
而不是5,在i需要值的時候去0x8291這個地址去取出值給i(這里的i是全局的或者靜態(tài)變量,這時候才能在編譯階段確定地址)。
所以在Test1.cpp中,把a(bǔ)rray的地址給了array,假設(shè)這個地址是0x34fe,但是由于數(shù)組的特性array == &array,所以這里是正常的。而在Test2.cpp中,array是個指針,所以會去0x34fe中取出值給array,所以array = 0x0007(數(shù)組的第一個值,這里要做地址,因?yàn)槭墙o指針用)
這就是看到的Test2()的輸出結(jié)果。顯然array[1]會死機(jī),因?yàn)?x0007地址是沒有被分配的,并且是給操作系統(tǒng)用的而不是給用戶用的。
5.數(shù)組和指針的分配
數(shù)組是開辟一塊連續(xù)的內(nèi)存空間,數(shù)組本身的標(biāo)示符代表整個數(shù)組,可以用sizeof取得真實(shí)的大小;指針則是只分配一個指針大小的內(nèi)存,并可把它的值指向某個有效的內(nèi)存空間
[全局的和靜態(tài)的]
char *p= "hello ";
一個指針,指向只讀數(shù)據(jù)塊(section)里的 "hello ",可被編譯器放入字符串池(也就是說, 你在寫一個char *q= "hello ",可能和p共享數(shù)據(jù))
char a[]= "hello ";
一個數(shù)組,分配在可寫數(shù)據(jù)塊(section),不會被放到字符串池中
[局部]
char *p= "hello ";
一個指針,指向只讀數(shù)據(jù)塊(section)里的 "hello ",可被編譯器放入字符串池(也就是說, 你在寫一個char *q= "hello ",可能和p共享數(shù)據(jù)),另外,在函數(shù)中可以返回它的地址,也就是說,指針是局部變量,他指向的數(shù)據(jù)卻是全局的.
char a[]= "hello ";
一個數(shù)組,分配在堆棧上,初始化由編譯器進(jìn)行(短的話直接用指令填充,長的就從全局字符串表拷貝),不會被放到字符串池中(但是卻可能從字符串池中拷貝過來),也不應(yīng)該返回
它的地址.
[代碼中的字面字符串]
printf( "%s\n ", "hello ");
這兩個字面常量( "%s\n "和 "hello "),都在只讀數(shù)據(jù)塊里
[用途]
全局指針
用于不需要修改內(nèi)容,卻可能會修改指針的情況(當(dāng)然,不修改也沒人反對)
全局?jǐn)?shù)組,用于不需要修改地址,卻需要修改內(nèi)容的場合
既需要修改指針,有需要修改內(nèi)容怎么辦呢?定義一個數(shù)組,在定義一個指針指向它就可以了
函數(shù)中如果不需要修改字符串的內(nèi)容,應(yīng)該盡量用char*p= "xxx "這種寫法.
初始化的局部字符數(shù)組影響效率,一般應(yīng)該盡量避開(應(yīng)該使用的情況下則不要猶豫)