




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
1、指針1 指針是什么1.1 指針是一類數(shù)據(jù)類型的統(tǒng)稱對于C語言來說,計算機的內(nèi)存由連續(xù)的字節(jié)(byte)構(gòu)成。這些連續(xù)的字節(jié)同樣被連續(xù)地編上了號碼以相互區(qū)別,這個號碼就是所謂的地址(Address),如圖9-1所示。圖9-1 內(nèi)存單元與地址指針(Pointer)是C語言中的一類數(shù)據(jù)類型的統(tǒng)稱。這種類型的數(shù)據(jù)專門用來存儲和表示內(nèi)存單元的編號,以實現(xiàn)通過地址得以完成的各種運算。這樣看來指針?biāo)坪蹙褪堑刂罚欢聦嵣蠀s并非如此。后面將會看到,地址只是指針內(nèi)涵中的一部分,甚至只是一小部分內(nèi)容而遠非其全部。片面地把地址理解為指針的全部,永遠學(xué)不好指針。為了使得語言具有廣
2、泛的適用性,C語言標(biāo)準(zhǔn)允許編譯器自行選擇指針類型數(shù)據(jù)的長度。在不同的編譯環(huán)境下,指針數(shù)據(jù)類型的長度可能不同;甚至相同的編譯環(huán)境中不同的指針數(shù)據(jù)類型,也可能有不同的大小。為了敘述的方便,本書中的指針數(shù)據(jù)類型一律假設(shè)為具有32bit的長度。這樣并不影響對指針本質(zhì)的描述,但涉及指針數(shù)據(jù)類型長度的代碼(極少)在不同的編譯環(huán)境中可能具有不同的結(jié)果,這點請讀者加以注意。C語言同樣不規(guī)定地址這種內(nèi)存單元的編號在內(nèi)存中的存儲格式,但在現(xiàn)實中目前這種編號多數(shù)是與二進制的unsigned int數(shù)據(jù)類型的存儲格式一樣,這是本章的另一個假定。這意味著程序可以訪問的內(nèi)存的大小最大為2的32次方 (4GB)。
3、但這絕對不意味著指針類型等同于 unsigned int 數(shù)據(jù)類型,因為它們的運算規(guī)則截然不同。1.2 指針是派生數(shù)據(jù)類型指針數(shù)據(jù)類型和數(shù)組、結(jié)構(gòu)體、聯(lián)合體等一樣,也是一種派生數(shù)據(jù)類型(Derived Types)。也就是說,指針數(shù)據(jù)類型是一種借助其他數(shù)據(jù)類型構(gòu)造出來的數(shù)據(jù)類型。對于任何類型 ,都可以構(gòu)造出與之相對應(yīng)的指針數(shù)據(jù)類型。因此指針數(shù)據(jù)類型實際上有無窮多種。沒有純粹的指針,正如同沒有純粹的數(shù)組一樣。數(shù)組是在其他數(shù)據(jù)類型的基礎(chǔ)上構(gòu)造出來的,指針也必須與其他數(shù)據(jù)類型一道才能構(gòu)成自己。指針讓人感到比較復(fù)雜的原因之一在于,各種不同類型的指針都有自己的運算規(guī)則,盡管它們都被叫做指針。
4、這一點請?zhí)貏e留意,不同類型的指針有不同的運算種類和不同的運算規(guī)則。綜上所述,每一種特定的指針類型都是一種派生數(shù)據(jù)類型,其值表示某個內(nèi)存單元的地址,其用途是完成與地址有關(guān)的計算。1.3 指針是一類數(shù)據(jù)的泛稱當(dāng)某個數(shù)據(jù)的數(shù)據(jù)類型是指針時,通常也簡稱這個數(shù)據(jù)是一個指針。很顯然,在這里“指針”具有“名詞”的含義。而指針表示“數(shù)據(jù)類型”含義時,顯然具有“形容詞”的意味。這種“一詞多用”的現(xiàn)象,對于熟悉C語言特點的人來說并不值得大驚小怪,C語言本身也是這樣的。比如,“”既可以作為類型說明符也可以作為運算符。1.4 指針專用的類型說明符“*”數(shù)組這種構(gòu)造性的數(shù)據(jù)類型有自己特定的類型說
5、明符“”,這種類型說明符用于定義數(shù)組或描述數(shù)組名的類型。結(jié)構(gòu)體和聯(lián)合體數(shù)據(jù)類型特定的類型說明符分別是關(guān)鍵字“struct”和“union”。指針也有自己的特定的類型說明符“*”。和僅靠“”無法完成數(shù)組的描述一樣,指針也需要“*”與其他的類型說明符一道才能完成對指針類型的完整描述。由于“其他的類型說明符”有無限多種,所以指針的類型也有無限種可能。可以構(gòu)造出“int *”類型的指針、“char *”類型的指針、“double *”類型的指針、“void *”類型的指針。指針的一個重要特點是,它總是和另外一種數(shù)據(jù)類型聯(lián)系在一起的。1.5 指針的分類盡管有無窮多種指針類型,但從指針?biāo)P(guān)聯(lián)的
6、數(shù)據(jù)類型方面看,指針可以分為3類:指向數(shù)據(jù)對象的指針(Object Pointer)、指向函數(shù)的指針(Function Pointer)、指向虛無的指針(“void *”類型)。前兩者都與內(nèi)存中的實體(數(shù)據(jù)和一段函數(shù)的執(zhí)行代碼)有關(guān),而“void *”類型的指針則僅僅是一個值,是純粹的地址。“指針就是地址”這樣的說法對于“void *”這種類型的指針是成立的。但對于與一段具體內(nèi)存實體相關(guān)聯(lián)的指針類型來說,這種說法是極其片面的,甚至片面到了幾乎完全忽略了指針的本質(zhì)而只剩下了指針的皮毛的地步。正確的說法是,指針的值(右值)是地址,這與“指針就是地址”是完全不同的概念。學(xué)習(xí)指針最重要的內(nèi)容通常是關(guān)心
7、指針的值以外的東西,而指針的值下面將會看到,那幾乎倒是無關(guān)緊要的 。從所具有的運算方面看,這3類指針各自擁有不同的運算種類的集合。有的運算種類多些,有的少些。2 指向數(shù)據(jù)對象的指針2.1 什么是“數(shù)據(jù)對象”所謂“數(shù)據(jù)對象”(Object),含義如下。(1)是內(nèi)存中一段定長的、以byte為基本單位的連續(xù)區(qū)域。(2)這段內(nèi)存區(qū)域中的內(nèi)容表示具有某種類型的一個數(shù)據(jù)。數(shù)據(jù)對象的類型不一定是簡單數(shù)據(jù)類型(int、long、double等),也可以是派生類型,比如數(shù)組,甚至指針等。而所謂的“指向”(Pointer to)的含義是指針與這塊具有類型含義的整體的關(guān)聯(lián)。例如,對于int
8、i;“i”可以表示它所占據(jù)的內(nèi)存塊,當(dāng)說到某個指針指向“i”時,其確切的含義是指向“i”所占據(jù)內(nèi)存的整體。顯然這里提到的“i”是左值意義上的“i”。函數(shù)類型不屬于數(shù)據(jù)對象。2.2 一元“&”運算盡管前面各章從來沒有提到指針,但實際上在前面編程的過程中已經(jīng)和指針打過無數(shù)次交道了。這可能令人感到吃驚,但卻是事實。比如,在調(diào)用scanf()函數(shù)輸入變量值的時候,在實參中經(jīng)常可以看到的“&”,實際上就是在求一個指向某個數(shù)據(jù)對象的指針。對于下面的變量定義double d;表達式“&d”就是一個指針類型的數(shù)據(jù),類型是“double *”,這種類型的指針被稱為是指向“do
9、uble”類型數(shù)據(jù)的指針。前面講過,作為二元運算符,“&”是按位與運算。當(dāng)“&”作為一個一元運算符時,要求它的運算對象是一個左值表達式(一塊內(nèi)存),得到的是指向這塊內(nèi)存(類型)的指針。而一個變量的名字的含義之一就是這個變量所占據(jù)的內(nèi)存。大多數(shù)人在多數(shù)情況下關(guān)心的只是變量名的另一個含義值,這可能是學(xué)不好指針以及C語言的一個主要原因。在此,簡要地復(fù)習(xí)一下C語言的一些最基本的內(nèi)容。假如有如下定義:double d=3.0;那么,應(yīng)該如何理解表達式“d = d + 5.0”呢?這是一個賦值表達式,表示的確切含義是“取出變量d的值與常量5.0相加,然后把結(jié)果放到變量d所在的內(nèi)存中去”。請
10、特別注意在賦值號“=”的左邊和右邊,“d”這個標(biāo)識符的含義是不同的:在賦值號“=”右邊的“d”表示的是“d”的值,計算機的動作是取出這個值(本質(zhì)上是在運算器中建立“d”的副本),并不關(guān)心“d”存放在內(nèi)存中的什么地方;而在賦值號“=”左邊的“d”表示的是“d”所在的內(nèi)存空間,是把一個值放入這塊內(nèi)存中去,后一個動作與“d”中的值沒有什么關(guān)系(只是把原來的值擦除),“d”中原來有什么值都不妨礙把一個新的值放入其中,也對新的值沒有任何影響。由此可見,同一個變量名確實有兩種含義。針對兩種不同的含義,計算機能進行的操作也不同。換句話說,對于某些運算,變量名的含義是其右值;而對于另一些運算,變量名的含義是其
11、左值。編譯器根據(jù)上下文來分辨變量名究竟是哪種含義。對于用C語言編程的人來說,不分辨清楚這兩種含義就不可能透徹地理解C語言。再舉個例子,在“sizeof d”這個表達式中,“d”的含義也是“d”占據(jù)的內(nèi)存而不是“d”的值無論“d”的值是多少,表達式“sizeof d”的值都為8。在表達式“&d”中,“d”的含義也是“d”所在的內(nèi)存而不是“d”的值,“d”的值是多少都對“&”的運算結(jié)果沒有任何影響。有一種說法稱一元“&”運算是求地址運算,這種說法既是片面的,也是不嚴(yán)格的,同時對于學(xué)習(xí)指針有很大的負(fù)面作用。理由如下。在C語言中根本沒有“地址”這種數(shù)據(jù)類型,只有“指針”數(shù)據(jù)類型
12、,而指針的值才是一個地址。用地址即指針的值的概念偷換指針的概念,顯然是以偏概全。更為嚴(yán)重的是,這種說法使得許多人根本就不知道“&d”是個指針,也掩蓋了“&d”指向一塊內(nèi)存的事實,因為“&d”的值僅僅是“d”所占據(jù)的那塊內(nèi)存單元中第一個byte的編號。那么“&d”的值是多少呢?實際上多數(shù)情況下,尤其是對于初學(xué)者來說,根本沒必要關(guān)心這個值是多少,也不可能事先知道這個值。因為為變量“d”安排存儲空間是編譯器的工作,編譯器是根據(jù)程序運行時內(nèi)存中的實際情況“隨機”為變量“d”安排內(nèi)存的。源程序的作者是永遠不可能為變量“指定”一塊特定的存儲空間,同樣也不可能改變“d”在內(nèi)存
13、中的存儲位置。這樣,“&d”就是一個既不可能通過代碼被賦值也不可能通過代碼被改變的值,因而是個常量,叫做指針常量 ,類型是“double *”。這樣的常量不可以被賦值也不可以進行類似“+”、“ ”之類的運算,因為改變“&d”的值就相當(dāng)于改變了變量“d”的存儲空間的位置,然而這是根本不可能的。當(dāng)然,在程序運行之后,具體來說是“d”的存儲空間確定之后(也就是定義了變量“d”之后,因為這時“d”才開始存在),“&d”的值是確實可以知道的(其實知道了也沒什么用)。如果想查看一下,可以通過調(diào)用printf()函數(shù)用“%p”格式輸出(指針類型數(shù)據(jù)的輸出格式是“%p”)。如下面所示。
14、程序代碼9-1#include <stdio.h>#include <stdlib.h>int main( void ) double d; printf("%pn", & d ); system("PAUSE"); return 0;這段代碼的程序運行結(jié)果并不能事先確定,這和程序運行的具體環(huán)境有關(guān)。在作者的計算機上,其運行結(jié)果如圖9-2所
15、示。圖9-2 一元“&”運算這個運行結(jié)果表示的含義如圖9-3所示。圖9-3 指針與地址應(yīng)該注意到“d”沒有被賦值,但程序沒有任何問題。這再次說明了“&d”與“d”的值沒有任何關(guān)系,在表達式“&d”中的“d”表示的僅僅是變量所在的內(nèi)存而不是這塊內(nèi)存的值。一元“&”運算符的優(yōu)先級和其他一元運算符(比如邏輯非“!”)一樣,次于“()”、“”等運算符,結(jié)合性為從右向左。這個運算符叫做關(guān)聯(lián)運算符(Referencing Operator)。其確切的含義是,運算所得到與運算對象所占據(jù)的那塊內(nèi)存相關(guān)聯(lián)的指針,其值為那塊內(nèi)存單元中起始byte的地址,也可
16、以將之稱為求指針運算符。大多數(shù)情況下,“&”的運算對象是一個變量名(或數(shù)組名、函數(shù)名)。但一般的,它的運算對象可以是一個表達式,只要這個表達式能夠表示一塊內(nèi)存 ,比如對于數(shù)組long a100;“a0”就是一個表達式,由于這個表達式既可以表示“a0”的值,也可以表示“a0”所占據(jù)的內(nèi)存,所以“&a0”是合法的、有意義的C語言運算,結(jié)果就是一個“l(fā)ong *”類型的指針。而另一些表達式,比如“a0+3”,由于只有值(右值)的含義而不代表一塊內(nèi)存,所以“&( a0+3)”是沒有意義的非法的表達式。代碼中的常量,由于只有右值的含義,因而不可以進行“&”運算。比如“&a
17、mp;5”,是沒有意義的非法的表達式。對于符號常量也同樣不可以做“&”運算。 練習(xí) 編寫程序驗證一下“&d”不可以被賦值也不可以進行類似“+ +”、“ ”之類的運算。2.3 數(shù)據(jù)指針變量的定義數(shù)據(jù)指針變量的定義,是指用完整的指針類型說明符(這里所謂的“完整”是指用*和另一種完整數(shù)據(jù)類型的名稱共同的意思)來說明一個變量標(biāo)識符的性質(zhì),并為這個變量標(biāo)識符開辟存儲空間。比如:int *p_i;這樣就定義了一個指向“int”類型數(shù)據(jù)的指針變量“p_i”。其中“int”是另一種數(shù)據(jù)對象的類型的名稱,“*”是指針類型說明符。類似地,定義:cha
18、r *p_c;double *p_d;分別被稱為定義了一個指向“char類型”數(shù)據(jù)的指針變量“p_c”和定義了一個指向“double類型”數(shù)據(jù)的指針變量“p_d”。至于所謂“指向int類型數(shù)據(jù)”的含義,是指:如果“p_i”的值為3456H,那么“p_i”指向的是3456H、3457H、3458H、3459H這4個字節(jié),因為“int”類型數(shù)據(jù)占據(jù)的內(nèi)存空間的大小是“sizeof(int)”,即4,如圖9-4所示。圖9-4 數(shù)據(jù)指針類型的含義由此可見“指向int類型數(shù)據(jù)”的確切含義是指向一塊大小為“sizeof(int)”的內(nèi)存空間(但是指針的值只記錄最前面一個byte的地址而不是記錄
19、所指向的全部內(nèi)存單元的地址),這比指針的值要重要得多,指針具體的值對掌握指針這種數(shù)據(jù)類型通常沒有什么意義。學(xué)習(xí)指針最重要的是要時刻關(guān)注指針指向一塊多大的或者一塊什么樣的內(nèi)存。因為這將決定這個指針的幾乎所有運算。對于任何一種數(shù)據(jù)類型(除了某些不完全類型),都可以用和上面相仿的方式定義相應(yīng)的指針變量,指向?qū)?yīng)類型數(shù)據(jù)所占據(jù)的內(nèi)存空間的大小。練習(xí) 畫一下“p_c”、“p_d”這兩個指針變量在內(nèi)存中的存儲情況和指向的含義的示意圖。假設(shè)“p_c”、“p_d”的定義為:char *p_c;double *p_d;2.4 指針的賦值運算對于指針類型的數(shù)據(jù),唯一
20、一個普遍可以進行的運算是賦值運算,各種指針都可以用來賦值,指針變量都可以被賦值(除非用const關(guān)鍵字限制),其余的指針運算都沒有普遍性。對于下面的代碼片段:程序代碼9-2(片段) int *p_i; int i; p_i = & i ;在表達式“p_i = & i”中,“& i ”是一個指向“int”類型數(shù)據(jù)的指針常量,“p_i”是一個指向“int”類型數(shù)據(jù)的指針變量。對指針變量進行賦值運算的一般原則是,應(yīng)該(本章所提到的“應(yīng)該”的含義指的是普遍認(rèn)同的、良好的編程風(fēng)格,而不是語法的必須要求)用同樣類型的指針進行賦值。例如下面的賦值就是似是
21、而非的,盡管有的編譯器是能容忍的。程序代碼9-3(片段)double d;long *p_l;p_l = & d;/這兩個指針的類型是不同的本質(zhì)上,不同類型的指針是不可以互相賦值的。但是對于表達式“p_l = & d”,編譯器會對這個不合邏輯的賦值表達式做一個隱式的類型轉(zhuǎn)換。如果不是精確清醒地知道編譯器會進行什么樣的轉(zhuǎn)化,就不要寫這種連自己都不清楚確切含義的語句。如果一定要類型轉(zhuǎn)換,不如顯式地表達出來。比如:p_l = ( long * ) & d;一種不多見的對指針變量的賦值是把一個“地址常數(shù)”賦值給它,這時一般也應(yīng)該把“地址常數(shù)”
22、用“類型轉(zhuǎn)換”運算轉(zhuǎn)換為一個“指針常數(shù)”再進行賦值,如:int *p_i=(int *)0XABCD;2.5 不是乘法的“*”運算“*”是指針類型說明符,同時也可以充當(dāng)“乘法”運算符(作為二元運算符時),此外“*”也可以是一個一元運算符。這是C語言中典型的“一詞多義”的現(xiàn)象(變量名也是如此),符號具體的含義需要由符號所處的語境代碼的上下文確定。這是C語言的一個特點,也是難點。一元“*”運算是指針特有的一個運算,下面通過具體的例子講述“*”運算的含義。對于變量定義:int i ;根據(jù)前面所講,對“int”類型變量“i”做“&”運算可得到一個指向“int”類型變量“
23、i”的指針,這個指針的數(shù)據(jù)類型是“int *”。而對于“int *”類型的指針“&i”,*(&i)的含義就是“&i”所指向的那塊內(nèi)存或者是那塊內(nèi)存的值,換句話說“*(&i)”就是“i”可以作為左值使用也可以作為右值使用。因此,對“i”的一切操作也都可以通過指向“i”的指針與“*”來實現(xiàn)。例如對“i”這塊內(nèi)存賦值:i = 2 ; 另一種完全等效的方式是:*(&i) = 2 ;如果需要取得“i”的值也是一樣,比如對于表達式“i*3”(這里“i”的意義是“i”的值),完全等價的表達式是“( * ( &i ) ) * 3 ”。這里出現(xiàn)的第二個“*”運算符
24、,由于前后都有運算對象,因此是乘法運算。而“( &i )”前面的“*”則不是乘法運算。這也是在不同語境上下文中一詞多義的例子。此外由于“*”作為一個一元運算優(yōu)先級與“&”相同,且一元運算符的結(jié)合性為從右向左,所以表達式“( * ( &i ) ) * 3 ”的另一種等價寫法是“ * &i * 3 ”。“*”運算符叫做“間接引用運算符”(Indirection Operator或Dereferencing Operator),其運算對象是一個指針,運算結(jié)果得到的是指針?biāo)赶虻哪菈K內(nèi)存(左值)或那塊內(nèi)存中數(shù)據(jù)的值(右值)。從“&”和“*”運算的含義中完全可以發(fā)
25、現(xiàn)這樣的事實:對于任何一個變量“v”,“*&v”就是“v”;反過來,對于任何一個指針“p”,只要“p”指向一個變量(可以進行“*”運算),那么,“&*p”就是“p”。前面兩條結(jié)論還可以適當(dāng)推廣。實際上,這對透徹地理解指針非常有幫助。比如第一條規(guī)律,不僅僅對變量成立,實際上對任何內(nèi)存中的有完整意義的實體“st”(一段連續(xù)的內(nèi)存空間,可能代表某種類型的一個數(shù)據(jù)或者是一個函數(shù)的執(zhí)行代碼 )都成立:“*&st”就是“st”,反過來只要一個指針“p”不是“void *”類型,那么“&*p”就是“p”。由此可見,“&”與“*”是一對逆運算(Referencing 與
26、Dereferencing)。3 指針的應(yīng)用與誤用3.1 指針有什么用在了解了指針的一些基本概念之后,自然而然會想到的一個問題就是指針究竟有什么用處。如果對于變量定義int i ;既然“i = 2”與“*&i = 2”是完全等價的操作,那么兩個完全等價的操作中難道不是必然會有一個是多余的嗎?想到這些問題非常自然。實際上指針非常有用,指針是C語言的精華。下面將逐步介紹如何應(yīng)用指針。指針的用途之一是通過函數(shù)改變函數(shù)調(diào)用處本地局部變量的值。如果沒有指針的話,改變本地局部變量的值,只能通過把函數(shù)返回值賦值給這個本地局部變量的辦法。但是由于函數(shù)只能返回一個值,
27、所以這種辦法有很大的局限性。首先看一個簡單的例子。程序代碼9-6#include <stdio.h>#include <stdlib.h> void f(int);int main(void) int i=5; f(i); printf("i=%dn",i); system("PAUSE"); return 0;void f(int n) n+; printf("n=%dn" , n ); return ;這段程序的
28、輸出是:n=6i=5請按任意鍵繼續(xù). . .可以看到在f()函數(shù)中,形參“n”的值的改變對main()函數(shù)中的i沒有影響。這是因為在C語言中,實參與形參之間是“傳值”的關(guān)系,形參“n”是把“i”的值(右值)而不是“i”本身作為自己的初始值。在計算實參時求出的“i”的值可能被放在運算器中,也可能被放在內(nèi)存中的另一個地方,這樣無論“n”如何變化都不會使得“i”發(fā)生改變。這個過程如圖9-6所示。也就是說,盡管在f()函數(shù)中,可以獲得main()中當(dāng)?shù)刈兞俊癷”的值(右值),然而由于“i”是main()中的局部變量,f()函數(shù)并不能直接使用這個變量的左值。 圖9-6 n與i是兩個
29、不同作用區(qū)域的變量 如果在main()中希望通過函數(shù)調(diào)用改變本地局部變量的值,也就是說在f()函數(shù)中改變main()中的局部變量“i”的值,應(yīng)該如何實現(xiàn)呢?答案是通過指針和間接引用運算。程序代碼9-7#include <stdio.h>#include <stdlib.h> void f(int *);int main(void) int i=5; f( &i ); printf("i=%dn",i); system("Pause"); return 0;void f(
30、int *p) (*p)+; printf("*p=%dn" ,*p ); return ;這段程序的輸出是:*p=6i=6請按任意鍵繼續(xù). . .在這段程序中,函數(shù)調(diào)用以指向“i”的指針“&i”作為實參,可以實現(xiàn)“p”指向變量“i”。這樣在f()函數(shù)中對“*p”的操作,也就是對main()中局部變量“i”的操作,因而實現(xiàn)了通過對f()函數(shù)的調(diào)用改變函數(shù)調(diào)用處,即main()中的局部變量“i”的值的目的,如圖9-7所示。理解了這個道理,就不難明白為什么調(diào)用scanf()時經(jīng)常需要寫“&”這個運算符了。此外要注意在f(
31、)函數(shù)中(*p)+不可以寫成*p+,原因在于+比*優(yōu)先級高,*p+的含義是*(p+),也就是說是對指針p做“+”運算而不是對“*p”做“+”運算。當(dāng)然對于上個例子來說,把“(*p)+”寫成“+*p”最后的執(zhí)行效果是一樣的。 圖9-7 f()中的(*p)+表示的是對main()中的i的運算3.2 C代碼中的“XXX到此一游”“XXX到此一游”,這種不分場合胡寫亂畫的事情在C代碼中也常常出現(xiàn)。比如int *p;*p=10;這是個典型的誤用指針錯誤。這個錯誤在于,定義了指針變量“p”之后并沒有給“p”賦值。由于“p”是個auto類別的局部變量,所以定義之后“p”的值
32、是個“垃圾值”,說不清楚“p”指向哪塊內(nèi)存,這樣“*p=10”就會導(dǎo)致把數(shù)據(jù)寫在內(nèi)存中一個未知的、不當(dāng)?shù)摹㈠e誤的位置。這會使應(yīng)用程序發(fā)生錯誤甚至是災(zāi)難性的后果(更壞的后果是你可能根本無法馬上察覺)。這種對“*”運算的誤用的后果通常會比對變量的誤用嚴(yán)重得多。為了盡量避免這種情況,在定義指針變量時直接將其賦值為“0”被普遍認(rèn)為是一種良好的編程習(xí)慣。例如:程序代碼9-8(片段)#include <stdio.h>int *p_i = NULL ;其中NULL是文本文件“stdio.h”中定義的一個符號常量,其值為“0”,指針被賦值為“0”值時,這個“0”一般是不用進行類型轉(zhuǎn)換的。“0”這
33、個地址的寫入操作是被禁止的,這樣可以很大程度地防止應(yīng)用程序在內(nèi)存中錯誤地“隨處亂寫”。3.3 分桔子問題例題:父親將2 520個桔子分給6個兒子。分完后父親說:“老大將分給你的桔子的1/8分給老二;老二拿到后連同原先的桔子分1/7給老三;老三拿到后連同原先的桔子分1/6給老四;老四拿到后連同原先的桔子分1/5給老五;老五拿到后連同原先的桔子分1/4給老六;老六拿到后連同原先的桔子分1/3給老大”。在分桔子的過程中并不存在分得分?jǐn)?shù)個桔子的情形,結(jié)果大家手中的桔子正好一樣多。問六兄弟原來手中各有多少桔子。每次分桔子都有兩個人的桔子數(shù)目發(fā)生改變。由于函數(shù)只能返回一個值,所以無法通過函數(shù)一
34、次求得兩個人在分之前的數(shù)目,但是利用指針可以完成這樣的功能。問題由6個相同的小問題組成,其中的任一個小問題的提法都可以描述如下。甲把自己的桔子分給乙“1/n”之后,甲和乙各有桔子若干,求甲把自己的桔子分給乙之前兩人桔子的數(shù)目。若通過函數(shù)完成這個任務(wù),顯然需要知道甲分給乙之后兩人桔子的數(shù)目和“1/n”。由于要求函數(shù)改變兩個數(shù)據(jù)的值,所以函數(shù)原型可以描述為:void 求甲分給乙之前各自的數(shù)目(int * pointer_to_甲的數(shù)目,int * pointer_to_乙的數(shù)目,const int n);由于這樣的函數(shù)的前兩個參數(shù)是指針,所以在函數(shù)中不但可以知道“甲的數(shù)目”和“乙的數(shù)目
35、”(“* pointer_to_甲的數(shù)目”和“* pointer_to_乙的數(shù)目”),也可以通過這一次函數(shù)調(diào)用同時改變“甲的數(shù)目”和“乙的數(shù)目”值,即同時求出甲把自己的桔子分給乙之前兩人桔子的數(shù)目。程序代碼9-9/ * * 父親將2 520個桔子分給六個兒子。
36、60; * * 分完后父親說:"老大將分給你的桔子的1/8分給老二; * * 老二拿到后連同原先的桔子分1/7給老三;
37、 * * 老三拿到后連同原先的桔子分1/6給老四; * * 老四拿到
38、后連同原先的桔子分1/5給老五; * * 老五拿到后連同原先的桔子分1/4給老六;
39、 * * 老六拿到后連同原先的桔子分1/3給老大"。 * * 在分桔子
40、的過程中并不存在分得分?jǐn)?shù)個桔子的情形, * * 結(jié)果大家手中的桔子正好一樣多。問六兄弟原來手中各有多少桔子。 * */ #include <stdio.h>#include <stdlib.h>#define ZS 2520
41、 /總數(shù)#define ZRS 6 /總?cè)藬?shù) /求分前數(shù)目 void qiufqsm(int * ,int * ,const int );int main(void) int l1sm,l2sm,l3sm,l4sm,l5sm,l6sm ; /最后每個人的桔子數(shù) l1sm = l2sm = l3sm = l4sm = l5sm =l6sm = ZS / ZRS ; /求老六分給老大前各自數(shù)目 qiufqsm( &l6sm , &a
42、mp;l1sm , 3 ) ; /逐步前推 qiufqsm( &l5sm , &l6sm , 4 ) ; qiufqsm( &l4sm , &l5sm , 5 ) ; qiufqsm( &l3sm , &l4sm , 6 ) ; qiufqsm( &l2sm , &l3sm , 7 ) ; qiufqsm( &l1sm , &l2sm , 8 ) ; printf ("最初個人桔子數(shù)為:%d,%d,%d,%d,%d,%dn&q
43、uot; , l1sm , l2sm , l3sm , l4sm , l5sm , l6sm ); system("Pause"); return 0;/求甲把自己桔子分給乙之前兩人桔子的數(shù)目void qiufqsm(int *p_jia , int * p_yi , const int n )
44、; int jiazqs ;/甲之前數(shù) jiazqs = *p_jia * n / ( n - 1 ) ; /前后的差值 * p_yi -= ( jiazqs - *p_jia ) ; /乙之前的為減去差值 *p_jia += ( jiazqs - *p_jia ) ; /甲之前的為加上差
45、值運行結(jié)果如圖9-8所示。 圖9-8 分桔子問題練習(xí) 1寫一個能實現(xiàn)交換兩個“int”變量的值的函數(shù),并通過程序驗證函數(shù)的功能。2改寫求調(diào)和級數(shù)例題,約分部分用一個函數(shù)實現(xiàn)。 4 指針與一維數(shù)組4.1 數(shù)據(jù)指針與整數(shù)的加減法指向數(shù)據(jù)類型的指針,可以進行加法、減法運算。但C語言對另一個運算對象有嚴(yán)格的限制。數(shù)據(jù)指針可以與一個整數(shù)類型數(shù)據(jù)做加法運算。為了考察這個加法的含義,首先看一下下面代碼的輸出。程序代碼9-10#include <stdio.h>#include <stdlib.h>int
46、 main(void) int i; printf("%p %p",& i , &i + 1 ); system("Pause"); return 0;在作者的計算機上的輸出是0023FF74 0023FF78這個結(jié)果可能因為運行環(huán)境(編譯器及計算機)的改變而有所不同。但有一點是確定的,那就是輸出的“&i+1”的值在數(shù)值上比“&i”的值大“sizeof(int)”。這表明一個數(shù)據(jù)指針加1的含義是得到另一個同樣類型的指針,這個指針剛好指向內(nèi)存中后一個同類型的量。對更一般的數(shù)據(jù)類型T,指
47、向T類型的指針加1的含義是,得到指向內(nèi)存中緊鄰的后一個T類型量的指針,在數(shù)值上相當(dāng)于加了sizeof(T)。如圖9-9所示。 圖9-9 數(shù)據(jù)指針+1的含義加1的含義清楚了之后,加上其他整數(shù)的含義不難推之,減1的含義也就是得到指向內(nèi)存中緊鄰的前一個同類型量的指針。然而道理上雖然可以這樣理解,但實際上C語言對指針加上或減去一個整數(shù)是有嚴(yán)格限制的。比如對于int i;“& i+ 1”是有意義的運算,因為“& i + 1”恰好指向“i”后面第一個“int”類型的數(shù)據(jù),但“& i + 2”是沒有意義的,除非確信“& i + 2”確實指向了一個“int
48、”類型數(shù)據(jù)。只有在數(shù)組內(nèi)部才可能確信如此。此外,盡管“& i + 1”是有意義的運算,但是“*(& i + 1)”并沒有意義。同理,除非是在數(shù)組內(nèi)部,在確認(rèn)一個指針減1確實指向某個數(shù)據(jù)對象的前提下,否則指針減1的運算是沒有意義的。這里,存在著指針加減法“不對稱”的現(xiàn)象。對于一個數(shù)據(jù)對象(如前面的“i”),“&i+1”是有意義的,而“&i-1”是沒有定義的。也就是說,除非通過運算得到的指針的值為0或者指向一個確實的數(shù)據(jù)對象,或者指向緊鄰某個數(shù)據(jù)對象之后的一個“虛擬”的同類型的數(shù)據(jù)對象,否則這個指針是沒有意義的,其行為是未定義的。例題:編寫函數(shù),求一個一維“int”
49、數(shù)組中元素的最大值。假設(shè)這個數(shù)組的數(shù)組名為“a”,共“n”個元素。那么顯然“&a0”是指向這個數(shù)組起始元素的指針,而且“&a0+1”、“&a0+2”顯然依次指向a1、a2。這樣只要把“&a0”和“n”作為實參傳遞給函數(shù),函數(shù)就可以完成對數(shù)組的遍歷。“&a0”和“n”的類型分別為“int *”和“unsigned”,求得的最大值為函數(shù)返回值,因此函數(shù)原型為int qiuzd ( int * , unsigned ) ;完整的代碼如下。程序代碼9-11#include <stdio.h>#include <stdlib.h> int
50、qiuzd ( int * , unsigned ) ;int main( void ) int a3= 5 , 9 , 7 ; /測試數(shù)據(jù) printf("%dn", qiuzd ( &a0 , sizeof a / sizeof *a) ); /測試 system("PAUSE"); return 0;int qiuzd ( int *p , unsigned n) int i , zd = * p;&
51、#160; for ( i = 0 ; i < n ; i + ) if( * ( p + i ) > zd )
52、60; zd = * ( p + i ) ;
53、0; return zd ; 練習(xí) 1編寫函數(shù),求一個一維“int”數(shù)組中各元素之和。 2如果把3.3分桔子問題中每人的桔子數(shù)目l1sm,l2sm,l3sm,l4sm,l5sm,l6sm用一個數(shù)組表示,那么無論是“求最后每個人的桔子數(shù)”還是“逐步前推”的過程都可以用循環(huán)描述,代碼將更為簡潔。請自行完成之。4.2 數(shù)據(jù)指針的減法兩個同類型的數(shù)據(jù)指針可以做減法 ,而且它們應(yīng)該 是指向同一個數(shù)組的數(shù)組元素,或者是指向這個數(shù)組
54、最后一個元素的下一個同類型的量。這個運算是指針與整數(shù)加減法的逆運算。所得到的結(jié)果是兩個指針之間有幾個這樣類型的量,也就是它們所指向的數(shù)組元素的下標(biāo)的差,結(jié)果的正負(fù)號表示兩個指針的前后關(guān)系。請說出下面程序的運行結(jié)果,然后再自己運行程序驗證一下。程序代碼9-12#include <stdio.h>#include <stdlib.h>int main(void) char c10; printf("%d %d",&c2-&c9,&c10-&c7); system("Pause&quo
55、t;); return 0;注意,這里出現(xiàn)了一個c10子表達式,但由于代碼中并不涉及對c10的讀寫,只是求出指向這個char的指針,這個指針恰恰是c數(shù)組之后第一個指向char的指針,這在C代碼中沒有任何問題,不屬于越界訪問。4.3 數(shù)據(jù)指針的關(guān)系運算兩個指針做“<”、“<=”、“>”、“>=”這些關(guān)系運算的前提,與兩個指針做減法的前提類似。最后的結(jié)果要么是0、要么是1,含義是兩個指針在內(nèi)存中哪個在前、哪個在后,或者是哪個不在另一個之前、哪個不在另一個之后。兩個不同類型的指針的比較及其規(guī)則或潛規(guī)則,基本上是個鉆牛角尖的問題。如果有這個愛好及精力,請獨
56、立鉆研C89/C99標(biāo)準(zhǔn)關(guān)于兼容性(Compatible Type)方面的闡述。事實上,在真正寫代碼的時候,正如記不清楚運算優(yōu)先級可以加括號避開優(yōu)先級問題、不同的類型之間的賦值可以通過類型轉(zhuǎn)換避開轉(zhuǎn)換規(guī)則一樣,如果一定要在不同類型的指針之間進行關(guān)系運算,也完全可以通過類型轉(zhuǎn)換避開令人煩惱的兼容性問題。畢竟,程序要解決的問題才是最重要的問題。4.4 數(shù)據(jù)指針的判等運算兩個相同類型的數(shù)據(jù)指針做“=”或“!=”這兩個等式運算的含義十分明顯,無非是它們所指向的數(shù)據(jù)是否為同一個。兩個指針可以進行“=”、“!=”運算對操作數(shù)所要求的前提條件比做關(guān)系運算對操作數(shù)所要求的前提條件更為寬泛,具體的規(guī)
57、則在后面將詳細介紹。4.5 “”運算和多數(shù)運算符不同,下標(biāo)運算(Subscripting Operator)“”的含義實際上是由另一個運算定義的。C語言規(guī)定下面兩個表達式表達式1表達式2 與 ( * ( (表達式1) +(表達式2 ) ) )是完全等價的。這可能多少令人出乎意料,但事實的確如此。進一步想下去的推論可能更加令人驚奇:比如,由于+具有可交換性,如果表達式1表達式2 與( * ( (表達式1) +(表達式2 ) ) )完全等價,那么是否可以說“Ex1Ex2”與“Ex2Ex1”也完全等價呢?的確如此。請看一下下面的代碼。程序代碼9-13#include <stdio.
58、h>#include <stdlib.h>int main(void) int i1=7; printf("i0=%d n0i= %dn", i 0 , 0i ); system("Pause"); return 0;它運行的結(jié)果會輸出:i0=70i= 7請按任意鍵繼續(xù). . .而且沒有任何語法問題,你相信嗎?如果你不相信,自己運行一下程序好了。結(jié)論是,“i0”與“0i”這兩個表達式是完全等價的,它們都等價于“(*(i)+(0)”,也就是“*(i+0)”。如果理解這一點沒有什么問題,說明你對數(shù)據(jù)指
59、針的理解已經(jīng)很有深度了。測驗:以上面的代碼為背景,表達式“(i+1)-1 * (-1)i+1”的值是多少?請在一分鐘之內(nèi)給出答案并上機驗證。此外我要鄭重聲明,“(i+1)-1 + (-1)i+1”這種顯得有幾分詭異的表達式,只是為了測驗?zāi)銓χ羔樃拍畹恼莆蘸屠斫猓谠闯绦蛑腥绻麤]有特別正當(dāng)?shù)睦碛桑€是寫堂堂正正、平易近人的代碼為好。如果你順利地閱讀到了這里,表明你對數(shù)據(jù)指針的概念非常清晰。指針這個令很多人感到頭疼的東西,對你來說只會感到輕松愉快。甚至,下一小節(jié)的內(nèi)容,你可能現(xiàn)在已經(jīng)懂了。4.6 數(shù)組名是指針任意定義一個一維數(shù)組,比如:double d6=2;從C語言數(shù)組的理論中可以知
60、道,“d0”是這個數(shù)組的第一個元素,而且這個元素的類型是double類型。從上一小節(jié)中可以得知,“d0”這個表達式等價于“(*(d)+(0)”,也就是等價于“*d”。而“*”作為一元運算符時,它的運算對象是指針。那么數(shù)組名“d”除了是指針還能是什么呢?顯然,“d”是一個“double *”類型的指針,而且是指向這個數(shù)組起始元素的指針。這個結(jié)論非常重要,理解了這一點,指針部分就幾乎不存在什么難點了。當(dāng)然,這里所謂的“理解”是要能夠自然而然地根據(jù)指針的概念自己得到這個結(jié)論,而不是死記硬背。如果理解這一點很吃力,請暫時不要繼續(xù)讀后面的內(nèi)容,重讀幾遍前面的內(nèi)容。既然“d”是“double *”類型的指
61、針,那么顯然可以把它的值賦給一個同類型的指針變量。假設(shè)有:double *p;那么顯然可以:p = d ;而且既然“p”與“d”類型相同,值也相同,而“d0”或“*d”是這個數(shù)組的起始元素,那么“p0”或“*p”顯然也是同一個數(shù)據(jù)對象。那么“p”與“d”的區(qū)別何在呢?答案是:“d”是個常量。這從“d”的意義就可以推知。由于“d”是指向“d”數(shù)組的起始元素的指針,而“d”數(shù)組的存儲空間是編譯器而不是代碼編寫者負(fù)責(zé)安排的,那么這意味著代碼書寫者也不可能通過代碼確定或改變起始元素在內(nèi)存中的位置。這樣,對于代碼書寫者來說,“d”就是一個不可以改變的量,也就是“常量”。而“p”的值是可以改變的,它可以被
62、賦值為“d”,可以被賦值為其他的值,也可以進行“+”、“ ”等常量不可以進行的運算。下面的代碼演示了數(shù)組名與指針的這種等價性。程序代碼9-14#include <stdio.h>#include <stdlib.h>int main(void) int a2=5,7; int *p = a ; /這是對p初始化,不是對*p初始化。等價于“int *p ; p = a ;” int i ; for ( i = 0 ; i < 2 ; i + )
63、 printf("a%d=%d *(a+%d)= %d" "tp%d=%d *(p+%d)= %dn",
64、; i , ai , i , *( a + i) , i , pi , i , *( p + i) ); system("Pause"); retu
65、rn 0;它運行的結(jié)果如圖9-10所示。 圖9-10 數(shù)組名是指針注意代碼中“int *p = a ;”的含義是對p初始化而非對*p初始化。它等價于:int *p ;p = a;因為在“int *p = a ;”中定義的變量是“p”,“*”在變量定義時只是一個類型說明符,不是運算符。4.7 數(shù)組名不僅僅是指針理解數(shù)據(jù)指針,最重要的也是最不容易弄清楚的并非指針變量,而是數(shù)組名這樣遮遮掩掩著的指針常量。因為這種指針常量的類型往往并不那么明顯。而如果不清楚一個數(shù)據(jù)的類型,那就表明對這個數(shù)據(jù)幾乎一無所知。數(shù)組名不但具有指針的性質(zhì),同時也具有一些
66、本身獨有的性質(zhì)。下面的代碼用于演示數(shù)組名的特性。程序代碼9-15#include <stdio.h>#include <stdlib.h> int main(void) int a 6 ; printf (" a = %p n sizeof a = %dn" , a ,sizeof a ) ; system("Pause"); return 0;這段程序的輸出如圖9-11所示。 圖9-11 數(shù)組名不僅僅是指針輸出的前一項表明數(shù)組名是個指針,但是后一項“sizeof a=
67、24”,卻表明“a”同時也代表“a”數(shù)組所占據(jù)的那塊內(nèi)存(大小為“6*sizeof(int)”個字節(jié)),如圖9-12所示。這個說法聽起來似乎有些自相矛盾,但其實不然。所有的數(shù)據(jù)類型的變量名標(biāo)識符都有兩種解釋:變量的值以及變量所在的內(nèi)存,即右值和左值。比如下面的代碼。 圖9-12 數(shù)組名的兩種含義程序代碼9-16#include <stdio.h>#include <stdlib.h> int main(void) int i = 3 ; printf (" i = %d n sizeof i = %dn" ,
68、 i ,sizeof i ) ; system("Pause"); return 0;輸出如圖9-13所示。 圖9-13 變量名標(biāo)識符的兩種解釋前一個結(jié)果中“i”表示“i”所在的那塊內(nèi)存中的內(nèi)容所代表的值,而后一項結(jié)果中,“i”明顯表示它自身所占據(jù)的那塊內(nèi)存。因此數(shù)組名一方面是個指針,而另一方面又代表數(shù)組所占據(jù)的內(nèi)存,這并沒有什么矛盾。那么數(shù)組名的特殊性體現(xiàn)在哪里呢?數(shù)組名的特殊性在于它的“值”(右值)并不是數(shù)組所占據(jù)的內(nèi)存所代表的值。事實上,數(shù)組所占據(jù)的內(nèi)存作為一個整體也沒有“值”(右值)的含義(這點和結(jié)構(gòu)體或聯(lián)合體也不一樣),數(shù)組名的“值”是指向數(shù)組起始元素的指針常量。另一方面,數(shù)組名作為內(nèi)存(左值)看待時,也不像前面的“i”那樣可以被賦值,因為在C語言中沒有數(shù)組的整體賦值這樣的運算。用術(shù)語來說就是,數(shù)組名不可以
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 新質(zhì)生產(chǎn)力知識
- 孤立性直腸潰瘍綜合征的臨床護理
- 手術(shù)室醫(yī)用氣體管理
- 長征勝利八十周年主題發(fā)言稿模版
- 語文教師考試試題及答案
- 銀行在線面試題目及答案
- 水系滅火劑生產(chǎn)工藝流程圖
- 學(xué)校消防廣播測試題及答案
- 宣傳消防面試題及答案
- 人力資源服務(wù)與員工關(guān)系處理及調(diào)解協(xié)議
- 2024年貴州省貴陽市南明區(qū)中考一模考試物理試題
- 電梯維護保養(yǎng)規(guī)則(TSG T5002-2017)
- 六年級擇校考試卷
- 髂總動脈瘤的護理查房
- 紅色美術(shù)鑒賞智慧樹知到期末考試答案2024年
- 量化考研-2024中國大學(xué)生考研白皮書-新東方
- 施工固定總價合同
- 《施工現(xiàn)場消防》課件
- T-NMAAA.0002-2021 營運機動車停運損失鑒定評估規(guī)范
- 七年級下冊語文必背常考全冊重點知識匯總(打印版)
- 血液透析護理質(zhì)量敏感指標(biāo)
評論
0/150
提交評論