終于說到了指針。指針是C語言的精華部分,如果沒有指針,c語言對底層的許多操作將無法完成。也是因為指針的存在,使得c語言看起來并不那么高級,因為指針操作的對象的是內(nèi)存地址,想要熟練地進行指針操作,必須考慮到內(nèi)存等偏硬件方面的東西。當然,也不需要了解過多。但是,數(shù)據(jù)結(jié)構(gòu)這一關(guān)還是要過的。我對數(shù)據(jù)結(jié)構(gòu)方面了解尚淺,就不多說了。數(shù)組與指針的關(guān)系如此復雜,讓我不得不照著書來寫這一篇筆記了。
一、數(shù)組不等于指針
C語言中,對數(shù)組的操作,是仿照指針的模式進行的。但是需要記住一點,數(shù)組不等于指針。對于一維數(shù)組a[],指向數(shù)組的指針p=a來說,他們之間最大的區(qū)別在于,數(shù)組方式使用數(shù)組名a(同時也是數(shù)組的首地址)對數(shù)組進行直接的訪問和操作,而指針方式使用指針名p對數(shù)組進行的是間接的訪問和操作。在多數(shù)情況下,他們操作結(jié)果是相同的,但是也有例外。
如果我們在文件外定義了一個指針p,int *p=a(a是一個整型數(shù)組)。在文件內(nèi)用到p時,需要用extern聲明一下,表明p是個外部變量,在外部定義好了。如果我們聲明為指針 extern int * p,然后去使用,肯定是沒有問題的。但是如果我們聲明為數(shù)組extern int p[] ,問題就出現(xiàn)了,編譯系統(tǒng)處理的時候會使用數(shù)組方式對指針進行操作。也就是說,當我們想得到*p,也就是*a,a[0]的值的時候,因為系統(tǒng)把p當作了數(shù)組首地址,所以*p并不能得到a[0],得到的是p所存放的地址值,也就是a[0]的地址。這種情況下,一定要使extern聲明與定義相匹配。
數(shù)組的直接訪問數(shù)據(jù)模式與指針的間接訪問數(shù)據(jù)模式,是兩者之間最根本的不同。數(shù)組不等于指針。數(shù)組通常用于存儲固定數(shù)目且數(shù)據(jù)類型相同的元素,數(shù)組所占用的內(nèi)存是隱式分配和刪除的,數(shù)組中保存數(shù)據(jù),并且數(shù)組中的每個元素都有唯一且明確的變量名來標識數(shù)據(jù),使用數(shù)組可以直接訪問數(shù)據(jù)也就是說a[i]只是簡單地以a+i為地址取得數(shù)據(jù)。而指針通常用于動態(tài)數(shù)據(jù)結(jié)構(gòu),指針變量保存的是數(shù)據(jù)的地址(其中包括變量的地址,也包括不匿名數(shù)據(jù)),使用指針訪問數(shù)據(jù)采用的是間接訪問模式,即首先取得指針的內(nèi)容,把它作為地址,然后從這個地址提取數(shù)據(jù)。如果指針有下標,p[i]就是先去的指針p的內(nèi)容,然后把指針p的內(nèi)容加上i作為地址,從中提取數(shù)據(jù)。指針可以指向匿名數(shù)據(jù),所以要學會用指針操作匿名內(nèi)存,c語言中與內(nèi)存空間相關(guān)的函數(shù)為malLOC(), free()。
在定義指針時,編譯器并不為指針所指的對象分配內(nèi)存空間,只是分配指針本身的空間,除非在定義字符指針(必須是指向字符型的)的同時用字符串常量進行初始化。其實就算是這種情況,也可當作是編譯器為此字符串常量分配了內(nèi)存空間后,在為字符指針本身分配了空間,并使字符指針指向字符串常量的首地址。在ANSI C中,初始化指針所創(chuàng)建的字符串常量被定義為只讀(?這一點,我用turbo C試了試,好像可以修改)。數(shù)組也可以用字符串常量進行初始化,但與指針襄樊,由字符串常量初始化的數(shù)組是可以修改的,原因很簡單,由字符串常量初始化的數(shù)組本來就是一個字符數(shù)組,每個字符都有確定的變量名與之對應,所以當然可以修改單個字符了。而初始化指針所創(chuàng)建的字符串常量,其實是匿名的數(shù)據(jù),如果你把指向字符串常量的字符指針賦予了其他地址,這個字符串常量顯然再也找不到了。
由此可見,數(shù)組和指針在編譯器處理時是不同的,在運行時的表示形式也是不一樣的,并且可能產(chǎn)生不同的代碼。對編譯器而言,一個數(shù)組就是一個地址,一個指針就是一個地址的地址。所以,在外部數(shù)組的聲明時,在數(shù)組的定義(因為數(shù)組的定義必然要分配內(nèi)存空間)時,不能用指針來替代數(shù)組。
還有,在下列情況下,對數(shù)組的引用不能用指向該數(shù)組第一個元素的指針來替代:
1. 數(shù)組名作為sizeof()的操作數(shù),因為此時需要的是整個數(shù)組的大小,而不是指針所指向的第一個元素的大??;
2. 使用&操作符取數(shù)組的地址。&操作符的主要用途是實現(xiàn)傳址調(diào)用。指針本身就是地址,所以對指針使用&意義不大。
3. 數(shù)組是一個字符串常量初始值。這一點上面已經(jīng)提到,不多說了。字符串常量初始化數(shù)組必須一氣呵成,不能分成兩了語句。也就是說,字符串常量只能對數(shù)組進行聲明初始化,不能用字符串常量對數(shù)組賦值。這點是因為C語言中只能夠在數(shù)組聲明時對它進行初始化(這點沒啥原因,也許是因為數(shù)組名是不可修改的左值吧),不能對數(shù)組名賦值,只能對數(shù)組元素逐個賦值。
4. 數(shù)組名是不可修改的左值,它的值是數(shù)組第一個元素的地址,不可以改變。
二、什么時候數(shù)組與指針相同
大多數(shù)的時候,數(shù)組和指針可以互換。
1. 除了個別情況,表達式中的數(shù)組名(數(shù)組在使用中,而不是聲明中)就是指針
假如我們聲明:int a[10], *p, i=2;
就可以通過以下方法來訪問a[i] :
a[i];
*(a+i);
p=a ; p[i] ;
p=a ; *(p+i) ;
p=a+i ; *p ;
實際上,對數(shù)組的引用如a[i]在編譯時總是被編譯器改寫成*(a+i)的形式。所以如加法一樣,取下標操作符的操作數(shù)是可以交換的(a[i] 與i[a] 都是正確的,等價的)。
編譯器自動把下標值得步長調(diào)整到數(shù)組元素的大小。對起始地址執(zhí)行加法操作之前,編譯器會負責計算每次增加的步長。這就是為什么指針總是有類型限制,每個指針只能指向一種類型的原因所在。
2. 數(shù)組下標總是與指針的偏移量相同
數(shù)組下標總是與指針的偏移量相同,所以程序員完全可以使用指針來訪問數(shù)組,從而繞過下標操作符。在這種情況下,對數(shù)組下標范圍檢查并不能檢測所有對數(shù)組的訪問情況。因此,C語言并不進行下標范圍檢測。但是我們編寫程序的時候,可要小心,不要越界。
在處理以為數(shù)組時,指針并不比數(shù)組更快。C語言吧數(shù)組下標改寫成指針偏移量的根本原因是指針和偏移量是底層硬件所使用的基本模型。
3. 作為函數(shù)參數(shù)的數(shù)組名等同于指針
吧作為形參的數(shù)組和指針等同起來是出于效率原因的考慮。在函數(shù)參數(shù)的聲明中,數(shù)組名被編譯器當中指向該數(shù)據(jù)第一個元素的指針。編譯器只向函數(shù)傳遞數(shù)組的地址,而不是整個數(shù)字的拷貝。隱性轉(zhuǎn)換意味著下面三種函數(shù)定義形式是完全相同的:
fun(int *p){...}
fun(int p[]) {...}
fun(int p[10]) {...}
在函數(shù)的聲明和調(diào)用上,使用數(shù)組或者指向數(shù)組第一個元素的指針都是合法的。
注意,這里第一個元素的含義,這第一個元素可以是數(shù)值變量、字符變量、數(shù)組、結(jié)構(gòu)體、指針。
三、總結(jié)
數(shù)組不等于指針,但是數(shù)組可以用指針等效;指針始終是指針,指針絕對不可以改寫為數(shù)組。你可以使用下標方式訪問指針,一般是指針作為函數(shù)參數(shù)時,一般是指針指向的是數(shù)組元素時。
在外部聲明和定義時,數(shù)組不能拿指針來等效,除了數(shù)組作為函數(shù)參數(shù)之外,定義和聲明必須匹配。
在使用時,數(shù)組基本可以與指針互換。a[i]總是被編譯器解釋為*(a+i)。
在作為函數(shù)參數(shù)時,數(shù)組可以與指針互換。編譯器總是把函數(shù)參數(shù)的數(shù)組修改為指向數(shù)組第一個元素的指針。在函數(shù)內(nèi)部獲得的事實上都是一個指針。
c語言實際上沒有多維數(shù)組。只有數(shù)組元素是數(shù)組的數(shù)組。多維數(shù)組作為函數(shù)參數(shù),傳遞的指針類型是指向數(shù)組的指針。