《C和指针》读书笔记-第六章指针
寫在前面,由于學(xué)過C語言,導(dǎo)致想要跳躍式地翻閱《C和指針》,后來發(fā)現(xiàn)這實(shí)為錯(cuò)誤,對(duì)于這本經(jīng)典著作,要做的是從頭到尾保持體系的完整性。
《C和指針》配套代碼請(qǐng)移步網(wǎng)站:Pointers on C
作者Kenneth A. Reek的個(gè)人網(wǎng)站
文章目錄
- 6.1 內(nèi)存和地址
- 地址和內(nèi)容
- 6.2 值和類型
- 6.3 指針變量的內(nèi)容
- 6.4 間接訪問操作符
- 6.5 未初始化和非法的指針
- 6.6 NULL指針
- 6.7 指針、間接訪問和左值
- 6.8 指針、間接訪問和變量
- 6.9 指針常量
- 6.10指針的指針
- 6.11指針表達(dá)式
- 6.12 實(shí)例
- 6.13 指針運(yùn)算
- 6.13.1 算術(shù)運(yùn)算
- 6.13.2 關(guān)系運(yùn)算
- 6.14 警告的總結(jié)
- 6.15 編程提示的總結(jié)
6.1 內(nèi)存和地址
我們可以把計(jì)算機(jī)的內(nèi)存看作是一條長街上的一排房屋。每座房子都可以容納數(shù)據(jù),并通過一個(gè)房號(hào)來標(biāo)識(shí)。
這個(gè)比喻頗為有用,但也存在局限性。計(jì)算機(jī)的內(nèi)存由數(shù)以億萬計(jì)的位(bit)組成,每個(gè)位可以容納0或1.由于一個(gè)位所能表示的值的范圍太有限,所以單獨(dú)的位用處不大,通常許多位合成一組作為一個(gè)單位,這樣就可以存儲(chǔ)范圍較大的值。這里有一幅圖,展示了現(xiàn)實(shí)機(jī)器中的一些內(nèi)存位置。
這些位置的每一個(gè)都被稱為字節(jié)(byte) ,每個(gè)字節(jié)都包含了存儲(chǔ)一個(gè)字符所需要的位數(shù)。在許多現(xiàn)代機(jī)器上,每個(gè)字節(jié)包含8個(gè)位,可以存儲(chǔ)無符號(hào)值0~255,或者有符號(hào)值-128 ~127.上面這張圖并沒有顯示這些位置的內(nèi)容,但內(nèi)存中的每個(gè)位置總是包含一些值。每個(gè)字節(jié)通過地址來標(biāo)識(shí),如上圖方框上面的數(shù)字所示。
為了存儲(chǔ)更大的值,我們把兩個(gè)或更多個(gè)字節(jié)合在一起作為一個(gè)更大的內(nèi)存單位。例如,許多機(jī)器以字為單位存儲(chǔ)整數(shù),每個(gè)字一般由2個(gè)或4個(gè)字節(jié)組成。下面這張圖所示的內(nèi)存位置與上面這張圖相同,但這次它以4個(gè)字節(jié)的字表示。
由于它們包含了更多的位,每個(gè)字可以容納的無符號(hào)整數(shù)的范圍是從0至4294967295(232?12^{32}-1232?1),可以容納的有符號(hào)整數(shù)的范圍是從-2147483648(?231-2^{31}?231)至2147483647(231?12^{31}-1231?1).
注意,盡管一個(gè)字包含了4個(gè)字節(jié),它仍然只有一個(gè)地址。至于它的地址是從它最左邊那個(gè)字節(jié)的位置還是最右邊那個(gè)字節(jié)的位置,不同的機(jī)器有不同的規(guī)定。另一個(gè)需要注意的硬件事項(xiàng)是邊界對(duì)齊(boundary alignment)。在要求邊界對(duì)齊的機(jī)器上,整型值存儲(chǔ)的起始位置只能是某些特定的字節(jié),通常是2或4的倍數(shù)。但這些問題是硬件設(shè)計(jì)者的事情,它們很少影響C程序員。我們只對(duì)兩件事情感興趣:
地址和內(nèi)容
這里有另外一個(gè)例子,這次它顯示了內(nèi)存中5個(gè)字的內(nèi)容。
這里顯示了5個(gè)整數(shù),每個(gè)都位于自己的字中。如果你記住了一個(gè)值的存儲(chǔ)地址,以后可以根據(jù)這個(gè)地址取得這個(gè)值。
但是,要記住所有這些地址太笨拙了,所以高級(jí)語言所提供的特性之一就是通過名字而不是地址來訪問內(nèi)存的位置。下面這張圖與上圖相同,但這次使用名字來代替地址。
當(dāng)然,這些名字就是我們所稱的變量。有一點(diǎn)非常重要,你必須記住,名字與內(nèi)存位置之間的關(guān)聯(lián)并不是硬件所提供的,它是由編譯器為我們實(shí)現(xiàn)的。所有這些變量給了我們一種更方便的方法記住地址-------硬件仍然通過地址訪問內(nèi)存位置。
6.2 值和類型
現(xiàn)在讓我們來看一下存儲(chǔ)于這些位置的值。頭兩個(gè)位置所存儲(chǔ)的是整數(shù)。第三個(gè)位置所存儲(chǔ)的是一個(gè)非常大的整數(shù),第4、5個(gè)位置所存儲(chǔ)的也是整數(shù)。下面是這些變量的聲明
int a=112,b=-1; float c=3.14; int *d=&a; float *e=&c;在這些聲明中,變量a和b確實(shí)用于存儲(chǔ)整型值。但是,它聲明的所存儲(chǔ)的是浮點(diǎn)值。可是,在上圖中c的值卻是一個(gè)整數(shù)。那么到底它是哪個(gè)呢? 整數(shù)還是浮點(diǎn)數(shù)?
答案是該變量包含了一序列內(nèi)容為0或1的位。它們可以被解釋為整數(shù),也可以被解釋為浮點(diǎn)數(shù),這取決于它們被使用的方式。如果使用的是整型算術(shù)指令,這個(gè)值就被解釋為整型,如果使用的是浮點(diǎn)型指令,它就是個(gè)浮點(diǎn)型。
這個(gè)事實(shí)引出了一個(gè)重要結(jié)論:不能簡單地通過檢查一個(gè)值的位來判斷它的類型。 為了判斷值的類型(以及它的值),必須觀察程序中這個(gè)值的使用方式。考慮下面這個(gè)二進(jìn)制形式表示的32位值:
01100111011011000110111101100010下面是這些位可能被解釋的許多結(jié)果中的幾種。這些值都是從一個(gè)基于Motorola 68000 的處理器上得到的。如果換個(gè)系統(tǒng),使用不同的數(shù)據(jù)格式和指令,對(duì)這些位的解釋又將有所不同。
這里,一個(gè)單一的值可以被解釋為5個(gè)不同的類型。顯然,值的類型并非值本身所固有的一種特性,而是取決于它的使用方式。
6.3 指針變量的內(nèi)容
讓我們把話題返回到指針,看看變量d和e 的聲明。它們都被聲明為指針,并用其他變量的地址予以初始化。指針的初始化是用&操作符完成的,它用于產(chǎn)生操作數(shù)的內(nèi)存地址。
d和e的內(nèi)容是地址而不是整型或浮點(diǎn)數(shù)數(shù)值。事實(shí)上,從圖中可以容易地看到,d 的內(nèi)容與a 的存儲(chǔ)地址一致,而e 的內(nèi)容和c的存儲(chǔ)地址一致,這也正是我們對(duì)這兩個(gè)指針進(jìn)行初始化時(shí)所期望的結(jié)果。 區(qū)分變量d的地址(112)和它的內(nèi)容(100)是非常重要的,同時(shí)也必須意識(shí)到100這個(gè)數(shù)值用于標(biāo)識(shí)其他位置(是……的地址)。
在我們轉(zhuǎn)到下一步之前,先看一些涉及這些變量的表達(dá)式。
int a=112,b=-1; float c=3.14; int *d=&a; float *e=&c;下面這些表達(dá)式的值分別是什么呢?
a b c d e前三個(gè)非常容易,a=112,b=-1,c=3.14.指針變量其實(shí)也很容易,d的值是100,e的值是108.
6.4 間接訪問操作符
通過一個(gè)指針訪問它所指向的地址的過程稱為間接訪問(indirection) 或解引用指針(dereferencing the pointer) 。這個(gè)用于執(zhí)行間接訪問的操作符是單目運(yùn)算符*。
下面的聲明和前面相同
d的值是100.當(dāng)我們對(duì)d使用間接訪問操作符時(shí),它表示訪問內(nèi)存位置100并察看那里的值。因此,*d的右值是112-----位置100的內(nèi)容,它的左值是位置100本身。
注意上面列表中各個(gè)表達(dá)式的類型:d是一個(gè)指向整型的指針,對(duì)它進(jìn)行解引用操作將產(chǎn)生一個(gè)整型值。類似地,對(duì)float* 進(jìn)行間接訪問將產(chǎn)生一個(gè)float型值。
正常情況下,我們并不知道編譯器為每個(gè)變量所選擇的存儲(chǔ)位置,所以我們事先無法預(yù)測它們的地址。這樣,當(dāng)我們繪制內(nèi)存中的指針圖時(shí),用實(shí)際數(shù)值表示地址是不方便的。所以,絕大部分書改用箭頭來代替,如下所示:
但是,這種記法可能會(huì)引起誤解,因?yàn)榧^可能會(huì)使你誤以為執(zhí)行了間接訪問操作,但事實(shí)上,它并不一定會(huì)執(zhí)行這個(gè)操作。例如,根據(jù)上圖,你會(huì)推斷表達(dá)式d的值是什么?
如果你的答案是112,那么你就被這個(gè)箭頭誤導(dǎo)了。正確的答案是a 的地址,而不是它的內(nèi)容。但是,這個(gè)箭頭似乎會(huì)把你的注意力吸引到a上。要使你的思維不受箭頭的影響是不容易的,這也是問題所在:除非存在間接訪問操作符,否則不要被箭頭所誤導(dǎo)。
下面這個(gè)修正后的箭頭記法試圖消除這個(gè)問題。
這種記法的意圖是既顯示指針的值,但又不給你強(qiáng)烈的視覺線索,以為這個(gè)箭頭是我們必須遵從的路徑。事實(shí)上,如果不對(duì)指針變量進(jìn)行間接訪問操作,它的值只是簡單的一些位的集合。當(dāng)執(zhí)行間接訪問操作時(shí),這種記法才使用實(shí)線箭頭表示實(shí)際發(fā)生的內(nèi)存訪問。
注意箭頭起始位置在方框內(nèi)部,因?yàn)樗硎敬鎯?chǔ)于該變量的值。同樣,箭頭指向一個(gè)位置,而不是存儲(chǔ)于該位置的值。這種記法提示跟隨箭頭執(zhí)行間接訪問操作的結(jié)果將是一個(gè)左值。
盡管這種箭頭記法很有用,但為了正確使用它,你必須記住指針變量的值就是一個(gè)數(shù)字。箭頭顯示了這個(gè)數(shù)字的值,但箭頭記法并未改變它本身就是個(gè)數(shù)字的事實(shí)。指針并不存在內(nèi)建的間接訪問屬性,所以除非表達(dá)式中存在間接訪問操作符,否則你不能按箭頭所示實(shí)際訪問它所指向的位置。
6.5 未初始化和非法的指針
下面這個(gè)代碼段說明了一個(gè)極為常見的錯(cuò)誤:
int *a; *a=12;這個(gè)聲明創(chuàng)建了一個(gè)名為a的指針變量,后面那條賦值語句把12存儲(chǔ)在a所指向的內(nèi)存位置。
警告:
但是究竟a指向哪里呢?我們聲明了這個(gè)變量,但從未對(duì)它進(jìn)行初始化,所以我們沒有辦法預(yù)測12這個(gè)值將存儲(chǔ)于什么地方。從這一點(diǎn)看,指針變量和其他變量并無區(qū)別。如果變量是靜態(tài)的,它會(huì)被初始化為0;但如果變量是自動(dòng)的,它根本不會(huì)被初始化。無論是哪種情況,聲明一個(gè)指向整型的指針都不會(huì)“創(chuàng)建”用于存儲(chǔ)整形值的內(nèi)存空間。
所以,如果程序執(zhí)行這個(gè)賦值操作,會(huì)發(fā)生什么情況呢? 如果你運(yùn)氣好,a的初始值會(huì)是個(gè)非法地址,這樣賦值語句將會(huì)出錯(cuò),從而終止程序。在UNIX系統(tǒng)上,這個(gè)錯(cuò)誤被稱為“段違例(segmentation violation)”或“內(nèi)存錯(cuò)誤(memory fault)”。它提示程序試圖訪問一個(gè)并未分配給程序的內(nèi)存地址。在一臺(tái)運(yùn)行Windows的PC上,對(duì)未初始化或非法指針進(jìn)行間接的訪問操作是一般保護(hù)性異常(General Protection Exception)的根源之一。
對(duì)于那些要求整數(shù)必須存儲(chǔ)于特定邊界的機(jī)器而言,如果這種類型的數(shù)據(jù)在內(nèi)存中的存儲(chǔ)地址處于錯(cuò)誤的邊界上,那么對(duì)這個(gè)地址進(jìn)行訪問時(shí)將會(huì)產(chǎn)生一個(gè)錯(cuò)誤。這種錯(cuò)誤在UNIX系統(tǒng)中被稱為“總線錯(cuò)誤(bus error)”。
一個(gè)更為嚴(yán)重的情況是:這個(gè)指針偶爾可能包含了一個(gè)合法的地址。接下來的事情很簡單:位于那個(gè)位置的值被修改,雖然你并無意去修改它。像這種類型的錯(cuò)誤非常難以捕捉,因?yàn)橐l(fā)錯(cuò)誤的代碼可能與原先用于操作那個(gè)值的代碼完全不相干。所以,在你對(duì)指針進(jìn)行間接訪問之前,必須非常小心,確保它們已被初始化!!!
6.6 NULL指針
標(biāo)準(zhǔn)定義了NULL指針,它作為一個(gè)特殊的指針變量,表示不指向任何東西。要使一個(gè)指針變量為NULL,你可以給它賦一個(gè)零值。為了測試一個(gè)指針變量是否為NULL,你可以將它與零值進(jìn)行比較。之所以選擇零這個(gè)值是因?yàn)橐环N源代碼約定。就機(jī)器內(nèi)部而言,NULL指針的實(shí)際值可能與此不同。在這種情況下,編譯器將負(fù)責(zé)零值和內(nèi)部值之間的翻譯轉(zhuǎn)換。
NULL指針的概念是非常有用的,因?yàn)樗o了你一種方法,表示某個(gè)特定的指針目前并未指向任何東西。例如,一個(gè)用于在某個(gè)數(shù)組中查找某個(gè)特定值的函數(shù)可能返回一個(gè)指向查找到的數(shù)組元素的指針。如果該數(shù)組不包含指定條件的值,函數(shù)就返回一個(gè)NULL指針。這個(gè)技巧允許返回值傳達(dá)兩個(gè)不同片段的信息。首先,有沒有找到元素?其次,如果找到,它是哪個(gè)元素?
提示:
盡管這個(gè)技巧在C程序中極為常用,但它違背了軟件工程的原則。用一個(gè)單一的值表示兩種不同的意思是件危險(xiǎn)的事情,因?yàn)閷砗苋菀谉o法弄清哪個(gè)才是它真正的用意。在大型的程序中,這個(gè)問題更為嚴(yán)重,因?yàn)槟悴豢赡茉陬^腦中對(duì)整個(gè)設(shè)計(jì)一覽無余。一種更為安全的策略是讓函數(shù)返回兩個(gè)獨(dú)立的值:首先是個(gè)狀態(tài)值,用于提示查找是否成功;其次是個(gè)指針,當(dāng)狀態(tài)值提示查找成功時(shí),它所指向的就是查找到的元素。
對(duì)指針進(jìn)行解引用操作可以獲得它所指向的值。但從定義上來看,NULL指針并未指向任何東西。因此,對(duì)一個(gè)NULL指針進(jìn)行解引用操作是非法的。在對(duì)指針進(jìn)行解引用操作之前,你首先必須確保它并非NULL指針。
警告:
如果對(duì)一個(gè)NULL指針進(jìn)行間接訪問操作會(huì)發(fā)生什么情況呢?\color{red}{如果對(duì)一個(gè)NULL指針進(jìn)行間接訪問操作會(huì)發(fā)生什么情況呢?}如果對(duì)一個(gè)NULL指針進(jìn)行間接訪問操作會(huì)發(fā)生什么情況呢?它的結(jié)果因編譯器而異。在有些機(jī)器上,它會(huì)訪問內(nèi)存位置零。編譯器能偶確保內(nèi)存位置零沒有存儲(chǔ)任何變量,但機(jī)器并未妨礙你訪問或修改這個(gè)位置。這種行為是非常不幸的,因?yàn)槌绦虬艘粋€(gè)錯(cuò)誤,但機(jī)器卻隱藏了它的癥狀,這樣就使這個(gè)錯(cuò)誤更加難以尋找。
在其他機(jī)器上,對(duì)NULL指針進(jìn)行間接訪問將引發(fā)一個(gè)錯(cuò)誤,并終止程序。宣布這個(gè)錯(cuò)誤比隱藏這個(gè)錯(cuò)誤要好得多,因?yàn)槌绦騿T能夠更容易修正它。
提示
如果所有的指針變量能夠被自動(dòng)初始化為NULL,那實(shí)在是一件幸事,但事實(shí)并非如此。不論你的機(jī)器對(duì)解引用NULL指針這種行為作何反應(yīng),對(duì)所有的指針變量進(jìn)行顯示的初始化是種好的做法。如果你已經(jīng)知道指針將被初始化為什么地址,就把它初始化為該地址,否則就把它初始化為NULL。風(fēng)格良好的程序會(huì)在指針解引用之前對(duì)它進(jìn)行檢查,這種初始化策略可以節(jié)省大量的調(diào)試時(shí)間。
6.7 指針、間接訪問和左值
涉及指針的表達(dá)式能不能作為左值?如果能,又是哪些呢?對(duì)表5.1優(yōu)先級(jí)表格進(jìn)行快速查閱后可以發(fā)現(xiàn),間接訪問操作符所需要的操作數(shù)是個(gè)右值,但這個(gè)操作符所產(chǎn)生的結(jié)果是個(gè)左值。
讓我們回到早些時(shí)候的例子。給定下面這些聲明
int a; int *d=&a;考慮下面的表達(dá)式:
指針變量可以作為左值,并不是因?yàn)樗鼈兪侵羔?#xff0c;而是因?yàn)樗鼈兪亲兞俊?duì)指針變量進(jìn)行間接訪問表示我們應(yīng)該訪問指針?biāo)赶虻奈恢谩?間接訪問指定了一個(gè)特定的內(nèi)存位置,這樣我們可以把間接訪問表達(dá)式的結(jié)果作為左值使用。在下面這兩條語句中
*d=10-*d; //OK d=10-*d; //ERROR第一條語句包含了兩個(gè)間接訪問操作。右邊的間接訪問作為右值使用,所以它的值是d所指向的位置所存儲(chǔ)的值(a的值)。左邊的間接訪問作為左值使用,所以d所指向的位置(a)把賦值符右側(cè)的表達(dá)式的計(jì)算結(jié)果作為它的新值。
第二條語句是非法的,因?yàn)樗硎景岩粋€(gè)整型數(shù)量(10-*d)存儲(chǔ)于一個(gè)指針變量中。當(dāng)我們實(shí)際使用的變量類型和應(yīng)該使用的變量類型不一致時(shí),編譯器會(huì)發(fā)出抱怨,幫組我們判斷這種情況。這些警告和錯(cuò)誤信息是我們的朋友,編譯器通過產(chǎn)生這些信息向我們提供幫助。d=10-*d; 在devc++編譯器中返回錯(cuò)誤
[Error] invalid conversion from 'int' to 'int*' [-fpermissive]可運(yùn)行代碼如下:
#include<bits/stdc++.h> using namespace std;int main(){int a=12; //int 占用4字節(jié) int *d =&a; //指針d指向變量a // *d 就是 a *d= 10 - *d;cout<<a<<endl; //&a 是a的地址,對(duì)地址間接訪問*&a 就是a *&a=25; // 即 a =25; cout<<a<<endl; }程序輸出結(jié)果:
-2 256.8 指針、間接訪問和變量
如果你自以為精通了指針,不妨看一下這個(gè)表達(dá)式,看看你是否明白它的意思。
*&a=25;如果你的答案是把25賦值給變量a,那么恭喜你,你答對(duì)了。讓我們來分析這個(gè)表達(dá)式,首先,&操作符產(chǎn)生a的地址,它是一個(gè)指針常量,接著,*操作符訪問其操作數(shù)所表示的地址。在這個(gè)表達(dá)式中,操作數(shù)是a的地址,所以值25就存儲(chǔ)于a中。
這條語句和簡單地使用a=25;有什么區(qū)別嗎?從功能上來說,它們是相同的。
6.9 指針常量
讓我們分析一個(gè)表達(dá)式。假定變量a存儲(chǔ)于位置100,下面這條語句的作用是什么?
*100=25;它看上去好像是把25賦值給a,因?yàn)閍是位置100所存儲(chǔ)的變量。但是,這是錯(cuò)的!這句語句實(shí)際上是非法的,因?yàn)?strong>字面值100的類型是整型,而間接訪問只能作用于指針類型表達(dá)式。如果想要把25存儲(chǔ)于位置100,必須使用強(qiáng)制類型轉(zhuǎn)換。
*(int * )100=25;強(qiáng)制類型轉(zhuǎn)換把值100從整型變成指向整型的指針,這樣對(duì)它進(jìn)行間接訪問就是合法的。如果a存儲(chǔ)在位置100,那么這條語句的作用就是把值25存儲(chǔ)于a。但是,需要使用這種技巧的機(jī)會(huì)是絕無僅有的!為什么?因?yàn)橥ǔo法預(yù)測編譯器會(huì)把某個(gè)特定的變量放在內(nèi)存中的什么位置,所以無法預(yù)先知道它的地址。
這個(gè)技巧的唯一用處是偶爾需要通過地址訪問內(nèi)存中某個(gè)特定的位置,它并不是用于訪問某個(gè)變量,而是訪問硬件本身。
6.10指針的指針
看下面的例子
int a=12; int *b=&a; int **c=&b;看一下內(nèi)存分配
問題是,c是什么類型?顯然它是一個(gè)指針,但它指向的是什么?變量b是一個(gè)“指向整型的指針”,所以任何指向b的類型必須是指向“指向整型的指針”的指針,更通俗地說,是一個(gè)指針的指針。
它合法嗎?是的!指針變量和其他變量一樣,占據(jù)內(nèi)存中某個(gè)特定的位置,所以用&操作符取得它的地址是合法的。
指針的指針如何聲明?
int ** c;表示表達(dá)式**c 的類型是int。
對(duì)表達(dá)式int **c=&b;進(jìn)行分析:
int a=12; int *b=&a; int **c=&b;分析:*操作符具有從右向左的結(jié)合性,所以這個(gè)表達(dá)式相當(dāng)于*(*c),必須從里向外逐層求值。*c訪問c所指向的位置,我們知道這是變量b。第二個(gè)間接訪問操作符訪問這個(gè)位置所指向的地址,也就是變量a。
上面的表達(dá)式的值各是多少呢?a的值是12,b的值是變量a的地址,c的值是變量b的地址。
*b的值是什么呢? *b作為右值,表示b所指向的地址里面的內(nèi)容,也就是a,所以 *b=12;
*c的值是什么呢?*c作為右值,表示c所指向的地址里面的內(nèi)容,也就是b,所以 *c=&a;
**c的值是什么呢?**c作為右值,表示(*c)所指向的地址里面的內(nèi)容,即 **c= *&b,表示b這個(gè)地址里面的內(nèi)容,也就是a,即 **c=12;
總結(jié)如下表
| a | 12 |
| b | &a |
| *b | a ,12 |
| c | &b |
| *c | b ,&a |
| **c | *b, a, 12 |
測試代碼
#include<iostream> using namespace std;int main(){int a=12;int *b=&a;int **c=&b;cout<<"a的值是: "<<a<<endl;cout<<"*b的值是:"<<*b<<endl;cout<<endl;cout<<"b的值是: "<<b<<endl;cout<<"*c的值是:"<<*c<<endl;cout<<endl;cout<<"a的地址是:"<<&a<<endl;cout<<"b的值是: "<<b<<endl;cout<<endl;cout<<"b的地址是:"<<&b<<endl;cout<<"c的值是: "<<c<<endl;cout<<endl;cout<<"*c的值=b的值:"<<*c<<endl;cout<<"**c的值=a的值:"<<**c<<endl; }6.11指針表達(dá)式
首先看一些聲明
char ch='a'; char *cp=&ch;現(xiàn)在我們有了兩個(gè)變量,它們初始化如下
圖中還顯示了ch后面的那個(gè)內(nèi)存位置,因?yàn)槲覀兯笾档挠行┍磉_(dá)式將訪問它(盡管在錯(cuò)誤的情況下才會(huì)對(duì)它進(jìn)行訪問)。由于我們不知道它的初始值,所以用一個(gè)問號(hào)來代替。
首先來個(gè)簡單的作為開始,如下面這個(gè)表達(dá)式
ch當(dāng)它作為右值使用時(shí),表達(dá)式的值為’a’,如下圖所示
這個(gè)粗橢圓提示變量ch的值就是表達(dá)式的值。但是,當(dāng)這個(gè)表達(dá)式作為左值使用時(shí),它是這個(gè)內(nèi)存的地址而不是該地址所包含的值,所以它的圖示方式有所不同
此時(shí)該位置用粗方框標(biāo)記,提示這個(gè)位置就是表達(dá)式的結(jié)果。另外,它的值并沒有顯示,因?yàn)樗⒉恢匾J聦?shí)上,這個(gè)值將被某個(gè)新值代替。接下來的表達(dá)式將以表格的形式出現(xiàn)。每個(gè)表的后面是表達(dá)式求值過程的描述。
作為右值,這個(gè)表達(dá)式的值是變量ch的地址。注意這個(gè)值同變量cp中所存儲(chǔ)的值一樣。但這個(gè)表達(dá)式并未提到cp,所以這個(gè)結(jié)果值并不是因?yàn)樗a(chǎn)生的。 第二個(gè)問題是,為什么這個(gè)表達(dá)式不是一個(gè)合法的左值? 優(yōu)先級(jí)表格顯示&操作符的結(jié)果是個(gè)右值,它不能當(dāng)作左值使用。但是為什么呢? 答案很簡單,當(dāng)表達(dá)式&ch進(jìn)行求值時(shí),它的結(jié)果應(yīng)該存儲(chǔ)于計(jì)算機(jī)的什么地方呢?它肯定會(huì)位于某個(gè)地方,但你無法知道它位于何處。這個(gè)表達(dá)式并未標(biāo)識(shí)任何機(jī)器內(nèi)存的特定位置,所以它不是一個(gè)合法的左值。
這個(gè)表達(dá)式前面見到過。它的右值就是cp的值。它的左值就是cp所處的內(nèi)存位置。由于這個(gè)表達(dá)式并不進(jìn)行間接訪問操作,所以不必依箭頭所示方向進(jìn)行間接訪問。
這個(gè)例子與&ch類似,不過這次我們所取的是指針變量的地址。這個(gè)結(jié)果的類型是指向字符的指針的指針。同樣,這個(gè)值的存儲(chǔ)位置并未清晰定義,所以這個(gè)表達(dá)式不是一個(gè)合法的左值。
現(xiàn)在我們加入了間接訪問操作,所以它的結(jié)果應(yīng)該不會(huì)令人驚奇。*cp作為右值表示 cp的內(nèi)容,即‘a(chǎn)’;*cp作為左值表示cp的內(nèi)容,也就是cp所存的地址。
需要記住的是
前提:cp是一個(gè)指針變量
cp作為左值,表示指針變量cp在內(nèi)存中的位置
*cp作為左值,表示cp的內(nèi)容,也就是存的地址
下面幾個(gè)表達(dá)式就比較有意思。
這個(gè)圖涉及的東西更多,所以讓我們一步一步研究它。這里有兩個(gè)操作符。*操作符的優(yōu)先級(jí)高于+,所以首先執(zhí)行的是間接訪問操作(如圖中cp到ch的實(shí)線箭頭所示),我們可以得到它的值(如虛線橢圓所示)。我們?nèi)〉眠@個(gè)值的一份拷貝并把它與1相加,表達(dá)式的最終結(jié)果是字符’b’. 圖中虛線表示表達(dá)式求值時(shí)數(shù)據(jù)的移動(dòng)過程。這個(gè)表達(dá)式的最終結(jié)果的存儲(chǔ)位置并未清晰定義,所以它不是一個(gè)合法的左值。優(yōu)先級(jí)表格證實(shí)+的結(jié)果不能作為左值。
在這個(gè)例子中,我們在前面那個(gè)表達(dá)式中增加了一個(gè)括號(hào)。這個(gè)括號(hào)使得表達(dá)式先執(zhí)行加法運(yùn)算,就是把1和cp中所存儲(chǔ)的地址相加。此時(shí)的結(jié)果值是圖中虛線橢圓所示的指針。接下來的間接訪問操作隨著箭頭訪問緊隨ch之后的內(nèi)存位置。這樣,這個(gè)表達(dá)式的右值就是這個(gè)位置的值,而它的左值就是這個(gè)位置本身。
在這里我們需要學(xué)習(xí)的很重要的一點(diǎn)。注意指針加法運(yùn)算的結(jié)果是個(gè)右值,因?yàn)樗拇鎯?chǔ)位置并未清晰定義。如果沒有間接訪問操作,這個(gè)表達(dá)式將不是一個(gè)合法的左值。 然而,間接訪問跟隨指針訪問一個(gè)特定的位置。這樣*(cp+1)就可以作為左值使用,盡管cp+1本身并不是左值。間接訪問操作符是少數(shù)幾個(gè)其結(jié)果為左值的操作符之一。
但是,這個(gè)表達(dá)式所訪問的是ch后面的那個(gè)內(nèi)存位置,我們?nèi)绾沃涝却鎯?chǔ)于那個(gè)地方的是什么東西?一般而言,我們無法得知,所以像這樣的表達(dá)式是非法的。
++和- -操作符在指針變量中使用的相當(dāng)頻繁,所致在這總上下文環(huán)境中理解它們是非常重要的。在這個(gè)表達(dá)式中,我們增加了指針變量cp的值。(為了讓圖更清楚,我們省略了加法)。表達(dá)式的結(jié)果是增值后的指針的一份拷貝,因?yàn)榍熬Y++先增加它的操作數(shù)的值再返回這個(gè)結(jié)果。這份拷貝的存儲(chǔ)位置并未清晰定義,所以它不是一個(gè)合法的左值。
后綴++操作符同樣增加cp的值,但它先返回cp值的一份拷貝然后再增加cp的值。這樣,這個(gè)表達(dá)式的值就是cp原來的值的一份拷貝。
前面兩個(gè)表達(dá)式的值都不是合法的左值。但如果我們在表達(dá)式中增加了間接訪問操作符,它們就可以成為合法的左值,如下圖的兩個(gè)表達(dá)式所示。
這里,間接訪問操作符作用域增值后的指針的拷貝上,所以的它的右值是ch后面那個(gè)內(nèi)存地址的值,而它的左值就是那個(gè)位置本身。
下面這個(gè)例子很重要。
?*cp++
使用后綴++操作符所產(chǎn)生的結(jié)果不同: 它的右值和左值分別是ch的值和ch的內(nèi)存位置,也就是cp原先所指\color{red}{它的右值和左值分別是ch的值和ch的內(nèi)存位置,也就是cp原先所指}它的右值和左值分別是ch的值和ch的內(nèi)存位置,也就是cp原先所指。同樣,后綴++操作符在周圍的表達(dá)式中使用其原先操作數(shù)的值。間接訪問操作符和后綴++操作符的組合常常令人誤解。 優(yōu)先級(jí)表格顯示后綴++操作符的優(yōu)先級(jí)高于*操作符,但表達(dá)式的結(jié)果看上去像是先執(zhí)行間接訪問操作,實(shí)際上不是\color{red}{但表達(dá)式的結(jié)果看上去像是先執(zhí)行間接訪問操作,實(shí)際上不是}但表達(dá)式的結(jié)果看上去像是先執(zhí)行間接訪問操作,實(shí)際上不是。事實(shí)上,這里涉及三個(gè)步驟:
(1)++操作符產(chǎn)生cp的一份拷貝
(2)然后++操作符增加cp的值
(3)最后在cp的拷貝上執(zhí)行間接訪問操作。
后綴表達(dá)式cp++:先返回cp的值的一份拷貝,然后再增加cp的值。這樣cp++的值就是cp原來的值的一份拷貝。
這個(gè)表達(dá)式常常在循環(huán)中出現(xiàn),首先用一個(gè)數(shù)組的地址初始化指針,然后使用這種表達(dá)式就可以依次訪問該數(shù)組的內(nèi)容。
在這個(gè)表達(dá)式中,由于這兩個(gè)操作符的結(jié)合性都是自右向左,所以首先執(zhí)行的是間接訪問操作。然后,cp所指向的位置的值加1(由‘a(chǎn)’變成‘b’),表達(dá)式的結(jié)果是這個(gè)增值后的值的一份拷貝。
和前面一些表達(dá)式相比,最后3個(gè)表達(dá)式在實(shí)際應(yīng)用中使用的較少。但是,對(duì)它們有一個(gè)透徹的理解有助于提高你的技能。
使用后綴++操作符,我們必須加上括號(hào),使它首先執(zhí)行間接訪問操作。這個(gè)表達(dá)式的執(zhí)行結(jié)果和前一個(gè)表達(dá)式相似,但它的結(jié)果值是ch增值前的原先值。
這個(gè)表達(dá)式看上去相當(dāng)詭異,但事實(shí)上并不復(fù)雜。這個(gè)表達(dá)式共有3個(gè)操作符,這些操作符的結(jié)合性都是從右向左的,所以首先執(zhí)行的是++cp。cp下面的虛橢圓表示第一個(gè)中間結(jié)果。接著,我們對(duì)這個(gè)拷貝值進(jìn)行間接訪問,它使我們訪問ch后面那個(gè)內(nèi)存位置。第二個(gè)中間結(jié)果用虛線方框表示,因?yàn)橄乱粋€(gè)操作符把它當(dāng)作一個(gè)左值使用。最后,我們在這個(gè)位置執(zhí)行++操作,也就是增加它的值。我們之所以把結(jié)果值顯示為?+1是因?yàn)槲覀儾⒉恢肋@個(gè)位置原先的值。
這個(gè)表達(dá)式和前一個(gè)表達(dá)式的區(qū)別在于這次第一個(gè)++操作符是后綴形式而不是前綴形式。由于它的優(yōu)先級(jí)較高,所以先執(zhí)行它。 間接訪問操作所訪問的是cp所指向的位置,而不是cp所指向那個(gè)位置后面那個(gè)位置。
解釋: 對(duì)于后面的*cp++,先執(zhí)行++操作符產(chǎn)生cp的一份拷貝,然后++操作符增加cp的值(cp現(xiàn)在指向下一個(gè)位置),最后,在cp的拷貝上(原位置)執(zhí)行間接訪問操作,所以 ,作為右值得到的是ch里面的值‘a(chǎn)’。然后’a’執(zhí)行前綴++,得到的是‘b’。 只不過此時(shí)cp已經(jīng)指向下一個(gè)位置。
6.12 實(shí)例
用法舉例:字符串長度函數(shù)strlen
#include<stdlib.h> #include<iostream> using namespace std;size_t strlen(char *string){int length=0;while(*string++ != '\0'){ //(此處作為右值)取值,并++//再復(fù)習(xí)一下*string++的執(zhí)行過程// 1.++操作符生成string 的一個(gè)拷貝//2.++操作符增加string的值(新值)//3在string的拷貝(原先值)上執(zhí)行間接訪問length+=1;}return length; } int main(){char a[10]={1,2,4};cout <<strlen(a)<<endl; }在指針到達(dá)字符串末尾的NUL字節(jié)之前,while語句中*string++表達(dá)式的值一直為真。它同時(shí)增加指針的值,用于下一次測試。這個(gè)表達(dá)式甚至可以正確地處理空字符串。
警告:
如果這個(gè)函數(shù)調(diào)用時(shí)傳遞給它的是一個(gè)NULL指針,那么while語句中的間接訪問將會(huì)失敗。函數(shù)是不是應(yīng)該在解引用指針前檢查這個(gè)條件? 從絕對(duì)安全的角度來看,應(yīng)該如此。但是,這個(gè)函數(shù)并不負(fù)責(zé)創(chuàng)建字符串。如果它發(fā)現(xiàn)參數(shù)為NULL,它肯定發(fā)現(xiàn)了一個(gè)出現(xiàn)在程序其他地方的錯(cuò)誤。當(dāng)指針創(chuàng)建時(shí)檢查它有效是符合邏輯的,移位這樣只需要檢查一次。這個(gè)函數(shù)采用的就是這種方法。如果函數(shù)失敗是因?yàn)榇中拇笠獾恼{(diào)用者懶得檢查參數(shù)的有效性而引起的,那是他活該如此。
程序6.2和6.3增加了一層間接訪問。它們在一些字符串中搜索某個(gè)特定的字符值,但我們使用指針數(shù)組來表示這些字符串,如圖6.1所示。
函數(shù)的參數(shù)是strings和value,strings是一個(gè)指向指針數(shù)組的指針,value是我們所查找的字符值。注意指針數(shù)組以一個(gè)NULL指針結(jié)束。函數(shù)將檢查這個(gè)值來判斷循環(huán)何時(shí)結(jié)束。下面這行表達(dá)式
完成3項(xiàng)任務(wù):
(1)它把strings當(dāng)前所指向的指針復(fù)制到變量string中
(2)它增加strings的值,使它指向下一個(gè)值
(3)它測試string是否是NULL。當(dāng)string指向當(dāng)前字符串中作為終止標(biāo)志的NUL字節(jié)時(shí),內(nèi)層的while循環(huán)就終止。
程序6.2:在一組字符串中查找:版本1
// 給定一個(gè)指向以NULL結(jié)尾的指針列表的指針,在列表中的字符串中查找一個(gè)特定的字符#include<stdio.h>#define TRUE 1 #define FALSE 0int find_char(char **strings , char value){char *string; //我們當(dāng)前正在查找的字符串//對(duì)于列表中的每個(gè)字符串while( ( string = *strings++ ) != NULL){//觀察字符串中的每個(gè)字符,看看它是不是我們需要查找的那個(gè)while( *string != '\0'){if( *string ++ == value)return TRUE;}}return FALSE;}如果string尚未到達(dá)其結(jié)尾的NUL字節(jié),就執(zhí)行下面這條語句
if( *string ++ == value)它測試當(dāng)前的字符是否與需要查找的字符匹配,然后增加指針的值,使它指向下一個(gè)字符。
程序6.3實(shí)現(xiàn)相同的功能,但它不需要對(duì)指向每個(gè)字符串的指針做一份拷貝。但是,由于存在副作用,這個(gè)程序?qū)⑵茐倪@個(gè)指針數(shù)組。這個(gè)副作用使得該函數(shù)不如前面那個(gè)版本有用,因?yàn)樗贿m用于字符串只需要查找一次的情況。
程序6.3 在一組字符串中查找:版本2
#include<stdio.h> #include<assert.h>#define TRUE 1 #define FALSE 0int find_char (char ** strings ,int value){assert( strings!=NULL);//對(duì)列表中的每個(gè)字符串while( *strings !=NULL){while( ** strings !='\0'){if( *( *strings )++ ==value) return TRUE;}strings++;}return FALSE; }但是,在程序6.3中有兩個(gè)有趣的表達(dá)式。第一個(gè)是 **strings。第1個(gè)間接訪問操作訪問指針數(shù)組中的當(dāng)前指針,第2個(gè)間接訪問操作隨該指針訪問字符串中的當(dāng)前字符。內(nèi)層的while語句測試這個(gè)字符的值并觀察是否到達(dá)了字符串的末尾。
第二個(gè)有趣的表達(dá)式是*(*strings)++.括號(hào)是需要的,這樣才能使表達(dá)式以正確的順序進(jìn)行求值。 第一個(gè)間接訪問操作訪問列表中的當(dāng)前指針。增值操作把該指針?biāo)赶虻哪莻€(gè)位置的值加1,但第二個(gè)間接訪問操作作用于原先那個(gè)值的拷貝上。這個(gè)表達(dá)式的直接作用是對(duì)當(dāng)前字符串中的當(dāng)前字符進(jìn)行測試,看看是否到達(dá)了字符串的末尾。作為副作用,指向當(dāng)前字符串字符的指針值將增加1.
6.13 指針運(yùn)算
指針加上一個(gè)整數(shù)的結(jié)果是另一個(gè)指針。問題是,它指向哪里?如果你將一個(gè)字符指針+1,運(yùn)算結(jié)果產(chǎn)生的指針指向內(nèi)存中的下一個(gè)字符。float占據(jù)的內(nèi)存空間不止1個(gè)字節(jié),如果你將一個(gè)指向float的指針加1,將會(huì)發(fā)生什么呢? 它會(huì)不會(huì)指向該float值內(nèi)部的某個(gè)字節(jié)呢?
幸運(yùn)的是,答案是否定的。當(dāng)一個(gè)指針和一個(gè)整數(shù)量執(zhí)行算術(shù)運(yùn)算時(shí),整數(shù)在執(zhí)行加法運(yùn)算前始終會(huì)根據(jù)合適的大小進(jìn)行調(diào)整。這個(gè)“合適的大小”就是指針?biāo)赶蝾愋偷拇笮?#xff0c;“調(diào)整”就是把整數(shù)值和“合適的大小”相乘。 為了更好地說明,試想在某臺(tái)機(jī)器上,float占據(jù)4個(gè)字節(jié)。在計(jì)算float型指針加3的表達(dá)式時(shí),這個(gè)3根據(jù)float類型的大小(此例中為4)進(jìn)行調(diào)整(相乘)。這樣,實(shí)際加到指針上的整型值為12.
把3與指針相加使指針的值增加3個(gè)float的大小,而不是3個(gè)字節(jié)。 這個(gè)行為較之獲得一個(gè)指向一個(gè)float值內(nèi)部某個(gè)位置的指針更為合理。下圖中有一些加法運(yùn)算的例子。調(diào)整的美感在于指針?biāo)惴ú⒉灰蕾囉谥羔樀念愋汀Q句話說,如果p是一個(gè)指向char的指針,那么表達(dá)式p+1就指向下一個(gè)char。如果p是個(gè)指向float的指針,那么p+1就指向下一個(gè)float,其他類型也是如此。
6.13.1 算術(shù)運(yùn)算
C的指針運(yùn)算只限于兩種形式。
第一種形式是 指針 ± 整數(shù)
標(biāo)準(zhǔn)定義這種形式只能用于指向數(shù)組中某個(gè)元素的指針,如下圖所示。
并且這類表達(dá)式的結(jié)果類型也是指針。這種形式也適用于使用malloc函數(shù)動(dòng)態(tài)分配獲得的內(nèi)存。
對(duì)指針執(zhí)行加法或減法運(yùn)算之后如果結(jié)果指針?biāo)傅奈恢迷跀?shù)組的第1個(gè)元素的前面或在數(shù)組最后一個(gè)元素的后面,那么其效果就是未定義的。 讓指針指向數(shù)組最后一個(gè)元素后面的那個(gè)位置是合法的,但對(duì)這個(gè)指針執(zhí)行間接訪問可能會(huì)失敗。
是該舉個(gè)例子的時(shí)候了。 這里有個(gè)循環(huán),把數(shù)組中所有的元素都初始化為0.
#define N_VALUES 5 float values[N_VALUES]; float *vp;for(vp=&values[0];vp<&values[N_VALUES];)*vp++=0;for語句的初始部分把vp指向數(shù)組的第一個(gè)元素。
這個(gè)例子中的指針運(yùn)算是用++操作符完成的。 增加值1與float長度相乘,其結(jié)果加到指針vp上。經(jīng)過第1次循環(huán)之后,指針在內(nèi)存中的位置如下:
經(jīng)過5次循環(huán)之后,vp就指向數(shù)組最后一個(gè)元素后面的那個(gè)內(nèi)存位置
此時(shí)循環(huán)終止。由于下標(biāo)從零開始,所以具有5個(gè)元素的數(shù)組的最后一個(gè)元素的下標(biāo)值為4.這樣,&values[N_VALUES] 表示數(shù)組最后一個(gè)元素后面那個(gè)內(nèi)存位置的地址。當(dāng)vp到達(dá)這個(gè)值時(shí),我們就知道到達(dá)了數(shù)組的末尾,故循環(huán)終止。
第2種類型的指針運(yùn)算具有如下形式:指針-指針
只有當(dāng)兩個(gè)指針都指向同一個(gè)數(shù)組中的元素時(shí),才允許從一個(gè)指針減去另一個(gè)指針,如下所示
兩個(gè)指針相減的結(jié)果類型是ptrdiff_t,它是一種有符號(hào)整數(shù)類型。減法運(yùn)算的值是兩個(gè)指針在內(nèi)存中的距離(以數(shù)組元素的長度為單位,而不是以字節(jié)為單位),因?yàn)闇p法運(yùn)算的結(jié)果將除以數(shù)組元素類型的長度。 例如,如果p1指向array[i] 而 p2指向 array[j], 那么p2-p1的值就是j-i的值。
讓我們看一下它是如何作用于某個(gè)特定類型的。假定前圖中數(shù)據(jù)元素 的類型為float,每個(gè)元素占據(jù)4個(gè)字節(jié)的內(nèi)存空間。如果數(shù)組的起始位置為1000,p1的值是1004,p2的值是1024,但表達(dá)式p2-p1的值將是5,因?yàn)閮蓚€(gè)指針的差值(20)將除以每個(gè)元素的長度(4)。
同樣,這種對(duì)差值的調(diào)整使指針的運(yùn)算結(jié)果與數(shù)據(jù)的類型無關(guān)。不論數(shù)組包含的元素類型如何,這個(gè)指針減法運(yùn)算的值總是5.
那么,表達(dá)式p1-p2是否合法呢? 是的,如果兩個(gè)指針都指向同一個(gè)數(shù)組中的元素,這個(gè)表達(dá)式就是合法的。在前一個(gè)例子中,這個(gè)值將是-5.
如果兩個(gè)指針?biāo)赶虻牟皇峭粋€(gè)數(shù)組中的元素,那么它們之間相減的結(jié)果是未定義的。
警告:
實(shí)際上,絕大多數(shù)編譯器都不會(huì)檢查指針表達(dá)式的結(jié)果是否位于合法的邊界之內(nèi)。因此,程序員應(yīng)該負(fù)起責(zé)任,確保這一點(diǎn)。越界指針和指向未知值的指針是兩個(gè)常見的錯(cuò)誤根源。
6.13.2 關(guān)系運(yùn)算
對(duì)指針執(zhí)行關(guān)系運(yùn)算也是有限制的。用下列關(guān)系操作符對(duì)兩個(gè)指針值進(jìn)行比較是可能的:
< <= > >=不過前提是它們都是指向同一個(gè)數(shù)組中的元素。 根據(jù)你所使用的操作符,比較表達(dá)式將告訴你哪個(gè)指針指向數(shù)組中更前或更后的元素。 標(biāo)準(zhǔn)并未定義如果兩個(gè)任意的指針進(jìn)行比較會(huì)產(chǎn)生什么結(jié)果。
然而,你可以在兩個(gè)任意的指針間執(zhí)行相等或不相等的測試,因?yàn)檫@類比較的結(jié)果和編譯器閑則在何處存儲(chǔ)數(shù)據(jù)并無關(guān)系—指針要不指向同一個(gè)地址,要不指向不同的地址。
讓我們再觀察一個(gè)循環(huán)。它用于清楚一個(gè)數(shù)組中所有的元素。
#define N_VALUES 5 float values[N_VALUES]; float *vp;for(vp=&values[0];vp<&values[N_VALUES];)*vp++=0;for語句使用了一個(gè)關(guān)系測試來決定是否結(jié)束循環(huán)。這個(gè)測試是合法的,因?yàn)関p和指針變量都指向同意數(shù)組中的元素。
現(xiàn)在考慮下面這個(gè)循環(huán)
for( vp =&values[N_VALUES];vp>&values[0];)*--vp=0;它和前面那個(gè)循環(huán)所執(zhí)行的任務(wù)相同,但數(shù)組元素將以相反的順序清除。我們讓vp指向數(shù)組元素最后那個(gè)元素后面的內(nèi)存位置,但對(duì)它進(jìn)行間接訪問之前先執(zhí)行自減操作。當(dāng)vp指向數(shù)組第一個(gè)元素時(shí),循環(huán)便告結(jié)束,不過這發(fā)生在第一個(gè)元素被清除之后。
有些人可能反對(duì)像*–vp這樣的表達(dá)式,覺得它的可讀性差。但是,如果對(duì)其簡化,看看這個(gè)循環(huán)會(huì)發(fā)生什么:
for( vp = &values[N_VALUES-1];vp>= &values[0];vp--)*vp=0;現(xiàn)在vp指向數(shù)組最后一個(gè)元素,它的自減操作放在for語句的調(diào)整部分進(jìn)行。這個(gè)循環(huán)存在一個(gè)問題,你能發(fā)現(xiàn)它嗎?
警告:
在數(shù)組第一個(gè)元素被清除之后,vp的值還將減去1,而接下去的一次比較運(yùn)算是用于結(jié)束循環(huán)的。但這就是問題所在:比較表達(dá)式 vp >= &values[0] 的值是未定義的,因?yàn)?strong>vp移到了數(shù)組的邊界之外。標(biāo)準(zhǔn)允許指向數(shù)組元素的指針與指向數(shù)組最后一個(gè)元素后面的那個(gè)內(nèi)存位置的指針進(jìn)行比較,但不允許與指向數(shù)組第一個(gè)元素之前的那個(gè)內(nèi)存位置的指針進(jìn)行比較。
實(shí)際上,在絕大多數(shù)C編譯器中,這個(gè)循環(huán)將順利完成任務(wù)。然而,你還是應(yīng)該避免使用它,因?yàn)闃?biāo)準(zhǔn)并不保證它可行。你遲早可能遇到一臺(tái)這個(gè)循環(huán)將失敗的機(jī)器。對(duì)于負(fù)責(zé)可以指代碼的程序員而言,這類問題簡直是噩夢。
6.14 警告的總結(jié)
1 錯(cuò)誤地對(duì)一個(gè)未初始化的指針變量進(jìn)行解引用。
2 錯(cuò)誤地對(duì)一個(gè)NULL指針進(jìn)行解引用
3 向函數(shù)錯(cuò)誤地傳遞NULL指針。
4 未檢測到指針表達(dá)式的錯(cuò)誤,從而導(dǎo)致不可預(yù)料的錯(cuò)誤
5 對(duì)一個(gè)指針進(jìn)行加減運(yùn)算,使它非法指向了數(shù)組第一個(gè)元素的前面的內(nèi)存位置。
6.15 編程提示的總結(jié)
1 一個(gè)值應(yīng)該只具有一個(gè)意思.
2 如果指針并不指向任何有意義的東西,就把它設(shè)置為NULL.
總結(jié)
以上是生活随笔為你收集整理的《C和指针》读书笔记-第六章指针的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《图解HTTP》读书笔记--第8章 确认
- 下一篇: 《大话数据结构》读书笔记-串