版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、· 0 簡(jiǎn)介 · 1 詞法缺陷 · 1.1 = 不是 = · 1.2 & 和 | 不是 && 和 | · 1.3 多字符記號(hào) · 1.4 例外 · 1.5 字符串和字符· 2 句法缺陷 · 2.1 理解聲明 · 2.2 運(yùn)算符并不總是具有你所想象的優(yōu)先級(jí) · 2.3 看看這些分號(hào)! · 2.4 switch語(yǔ)句 · 2.5 函數(shù)調(diào)用 · 2.6 懸掛else問(wèn)題· 3 連接 · 3.1 你必須自己檢查外部類(lèi)型
2、183; 4 語(yǔ)義缺陷 · 4.1 表達(dá)式求值順序 · 4.2 &&、|和!運(yùn)算符 · 4.3 下標(biāo)從零開(kāi)始 · 4.4 C并不總是轉(zhuǎn)換實(shí)參 · 4.5 指針不是數(shù)組 · 4.6 避免提喻法 · 4.7 空指針不是空字符串 · 4.8 整數(shù)溢出 · 4.9 移位運(yùn)算符· 5 庫(kù)函數(shù) · 5.1 getc()返回整數(shù) · 5.2 緩沖輸出和內(nèi)存分配· 6 預(yù)處理器 · 6.1 宏不是函數(shù) · 6.2 宏不是類(lèi)型定義· 7
3、可移植性缺陷 · 7.1 一個(gè)名字中都有什么? · 7.2 一個(gè)整數(shù)有多大? · 7.3 字符是帶符號(hào)的還是無(wú)符號(hào)的? · 7.4 右移位是帶符號(hào)的還是無(wú)符號(hào)的? · 7.5 除法如何舍入? · 7.6 一個(gè)隨機(jī)數(shù)有多大? · 7.7 大小寫(xiě)轉(zhuǎn)換 · 7.8 先釋放,再重新分配 · 7.9 可移植性問(wèn)題的一個(gè)實(shí)例· 8 這里是空閑空間 · 參考 · 腳注0 簡(jiǎn)介 C語(yǔ)言及其典型實(shí)現(xiàn)被設(shè)計(jì)為能被專(zhuān)家們?nèi)菀椎厥褂?。這門(mén)語(yǔ)言簡(jiǎn)潔并附有表達(dá)力。但有
4、一些限制可以保護(hù)那些浮躁的人。一個(gè)浮躁的人可以從這些條款中獲得一些幫助。 在本文中,我們將會(huì)看到這些未可知的益處。正是由于它的未可知,我們無(wú)法為其進(jìn)行完全的分類(lèi)。不過(guò),我們?nèi)匀煌ㄟ^(guò)研究為了一個(gè)C程序的運(yùn)行所需要做的事來(lái)做到這些。我們假設(shè)讀者對(duì)C語(yǔ)言至少有個(gè)粗淺的了解。 第一部分研究了當(dāng)程序被劃分為記號(hào)時(shí)會(huì)發(fā)生的問(wèn)題。第二部分繼續(xù)研究了當(dāng)程序的記號(hào)被編譯器組合為聲明、表達(dá)式和語(yǔ)句時(shí)會(huì)出現(xiàn)的問(wèn)題。第三部分研究了由多個(gè)部分組成、分別編譯并綁定到一起的C程序。第四部分處理了概念上的誤解:當(dāng)一個(gè)程序具體執(zhí)行時(shí)會(huì)發(fā)生的事情。第五部分研
5、究了我們的程序和它們所使用的常用庫(kù)之間的關(guān)系。在第六部分中,我們注意到了我們所寫(xiě)的程序也許并不是我們所運(yùn)行的程序;預(yù)處理器將首先運(yùn)行。最后,第七部分討論了可移植性問(wèn)題:一個(gè)能在一個(gè)實(shí)現(xiàn)中運(yùn)行的程序無(wú)法在另一個(gè)實(shí)現(xiàn)中運(yùn)行的原因。1 詞法缺陷 編譯器的第一個(gè)部分常被稱(chēng)為詞法分析器(lexical analyzer)。詞法分析器檢查組成程序的字符序列,并將它們劃分為記號(hào)(token)一個(gè)記號(hào)是一個(gè)由一個(gè)或多個(gè)字符構(gòu)成的序列,它在語(yǔ)言被編譯時(shí)具有一個(gè)(相關(guān)地)統(tǒng)一的意義。在C中, 例如,記號(hào)->的意義和組成它的每個(gè)獨(dú)立的字符具有明顯的區(qū)別,而且其意義獨(dú)立于-&
6、gt;出現(xiàn)的上下文環(huán)境。 另外一個(gè)例子,考慮下面的語(yǔ)句:if(x > big) big = x;該語(yǔ)句中的每一個(gè)分離的字符都被劃分為一個(gè)記號(hào),除了關(guān)鍵字if和標(biāo)識(shí)符big的兩個(gè)實(shí)例。 事實(shí)上,C程序被兩次劃分為記號(hào)。首先是預(yù)處理器讀取程序。它必須對(duì)程序進(jìn)行記號(hào)劃分以發(fā)現(xiàn)標(biāo)識(shí)宏的標(biāo)識(shí)符。它必須通過(guò)對(duì)每個(gè)宏進(jìn)行求值來(lái)替換宏調(diào)用。最后,經(jīng)過(guò)宏替換的程序又被匯集成字符流送給編譯器。編譯器再第二次將這個(gè)流劃分為記號(hào)。 在這一節(jié)中,我們將探索對(duì)記號(hào)的意義的普遍的誤解以及記號(hào)和組成它們的字符之間
7、的關(guān)系。稍后我們將談到預(yù)處理器。1.1 = 不是 = 從Algol派生出來(lái)的語(yǔ)言,如Pascal和Ada,用:=表示賦值而用=表示比較。而C語(yǔ)言則是用=表示賦值而用=表示比較。這是因?yàn)橘x值的頻率要高于比較,因此為其分配更短的符號(hào)。 此外,C還將賦值視為一個(gè)運(yùn)算符,因此可以很容易地寫(xiě)出多重賦值(如a = b = c),并且可以將賦值嵌入到一個(gè)大的表達(dá)式中。 這種便捷導(dǎo)致了一個(gè)潛在的問(wèn)題:可能將需要比較的地方寫(xiě)成賦值。因此,下面的語(yǔ)句好像看起來(lái)是要檢查x是否等于y:if(x = y)
8、0; foo();而實(shí)際上是將x設(shè)置為y的值并檢查結(jié)果是否非零。再考慮下面的一個(gè)希望跳過(guò)空格、制表符和換行符的循環(huán):while(c = ' ' | c = 't' | c = 'n') c = getc(f);在與't'進(jìn)行比較的地方程序員錯(cuò)誤地使用=代替了=。這個(gè)“比較”實(shí)際上是將't'賦給c,然后判斷c的(新的)值是否為零。因?yàn)?#39;t'不為零,這個(gè)“比較”將一直為真,因此這個(gè)循環(huán)會(huì)吃盡整個(gè)文件。這之后會(huì)發(fā)生什么取決于特定的實(shí)現(xiàn)是否允許一個(gè)
9、程序讀取超過(guò)文件尾部的部分。如果允許,這個(gè)循環(huán)會(huì)一直運(yùn)行。 一些C編譯器會(huì)對(duì)形如e1 = e2的條件給出一個(gè)警告以提醒用戶(hù)。當(dāng)你確實(shí)需要先對(duì)一個(gè)變量進(jìn)行賦值之后再檢查變量是否非零時(shí),為了在這種編譯器中避免警告信息,應(yīng)考慮顯式給出比較符。換句話(huà)說(shuō),將:if(x = y) foo();改寫(xiě)為:if(x = y) != 0) foo();這樣可以清晰地表示你的意圖。1.2 & 和 | 不是 && 和 | 容易將=錯(cuò)寫(xiě)為=是因?yàn)楹芏嗥渌?/p>
10、語(yǔ)言使用=表示比較運(yùn)算。 其他容易寫(xiě)錯(cuò)的運(yùn)算符還有&和&&,以及|和|,這主要是因?yàn)镃語(yǔ)言中的&和|運(yùn)算符于其他語(yǔ)言中具有類(lèi)似功能的運(yùn)算符大為不同。我們將在第4節(jié)中貼近地觀察這些運(yùn)算符。1.3 多字符記號(hào) 一些C記號(hào),如/、*和=只有一個(gè)字符。而其他一些C記號(hào),如/*和=,以及標(biāo)識(shí)符,具有多個(gè)字符。當(dāng)C編譯器遇到緊連在一起的/和*時(shí), 它必須能夠決定是將這兩個(gè)字符識(shí)別為兩個(gè)分離的記號(hào)還是一個(gè)單獨(dú)的記號(hào)。C語(yǔ)言參考手冊(cè)說(shuō)明了如何決定:“如果輸入流到一個(gè)給定的字符串為止已經(jīng)被識(shí)別為 記號(hào),則應(yīng)該包含下一個(gè)字符以組成能夠構(gòu)成記號(hào)的最
11、長(zhǎng)的字符串”(譯注即通常所說(shuō)的“最長(zhǎng)子串原則”)。因此,如果/是一個(gè)記號(hào)的第一個(gè)字符,并且/后面緊隨了一個(gè)*,則這兩個(gè)字符構(gòu)成了注釋的開(kāi)始,不管其他上下文環(huán)境。 下面的語(yǔ)句看起來(lái)像是將y的值設(shè)置為x的值除以p所指向的值:y = x/*p /* p 指向除數(shù) */;實(shí)際上,/*開(kāi)始了一個(gè)注釋?zhuān)虼司幾g器簡(jiǎn)單地吞噬程序文本,直到*/的出現(xiàn)。換句話(huà)說(shuō),這條語(yǔ)句僅僅把y的值設(shè)置為x的值,而根本沒(méi)有看到p。將這條語(yǔ)句重寫(xiě)為:y = x / *p /* p 指向除數(shù) */;或者干脆是y = x /
12、(*p) /* p指向除數(shù) */;它就可以做注釋所暗示的除法了。 這種模棱兩可的寫(xiě)法在其他環(huán)境中就會(huì)引起麻煩。例如,老版本的C使用=+表示現(xiàn)在版本中的+=。這樣的編譯器會(huì)將a=-1;視為a =- 1;或a = a - 1;這會(huì)讓打算寫(xiě)a = -1;的程序員感到吃驚。 另一方面,這種老版本的C編譯器會(huì)將a=/*b;斷句為a =/ *b;盡管/*看起來(lái)像一個(gè)注釋。1.4 例外 組合賦值運(yùn)算符如+=實(shí)際上是兩個(gè)記號(hào)。因此,a + /* strange */
13、= 1和a += 1是一個(gè)意思??雌饋?lái)像一個(gè)單獨(dú)的記號(hào)而實(shí)際上是多個(gè)記號(hào)的只有這一個(gè)特例。特別地,p - > a是不合法的。它和p -> a不是同義詞。 另一方面,有些老式編譯器還是將=+視為一個(gè)單獨(dú)的記號(hào)并且和+=是同義詞。1.5 字符串和字符 單引號(hào)和雙引號(hào)在C中的意義完全不同,在一些混亂的上下文中它們會(huì)導(dǎo)致奇怪的結(jié)果而不是錯(cuò)誤消息。 包圍在單引號(hào)中的一個(gè)字符只是編寫(xiě)整數(shù)的另一種方法。這個(gè)整數(shù)是給定的字符在實(shí)現(xiàn)的對(duì)照序列中的一個(gè)對(duì)應(yīng)的值。因此,在一個(gè)ASCII實(shí)現(xiàn)中,
14、39;a'和0141或97表示完全相同的東西。而一個(gè)包圍在雙引號(hào)中的字符串,只是編寫(xiě)一個(gè)有雙引號(hào)之間的字符和一個(gè)附加的二進(jìn)制值為零的字符所初始化的一個(gè)無(wú)名數(shù)組的指針的一種簡(jiǎn)短方法。 下面的兩個(gè)程序片斷是等價(jià)的:printf("Hello worldn");char hello = 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l'
15、;, 'd', 'n', 0 ;printf(hello); 使用一個(gè)指針來(lái)代替一個(gè)整數(shù)通常會(huì)得到一個(gè)警告消息(反之亦然),使用雙引號(hào)來(lái)代替單引號(hào)也會(huì)得到一個(gè)警告消息(反之亦然)。但對(duì)于不檢查參數(shù)類(lèi)型的編譯器卻除外。因此,用printf('n');來(lái)代替printf("n");通常會(huì)在運(yùn)行時(shí)得到奇怪的結(jié)果。(譯注提示:正如上面所說(shuō),'n'表示一個(gè)整數(shù),它被轉(zhuǎn)換為了一個(gè)指針,這個(gè)指針?biāo)赶虻膬?nèi)容是沒(méi)有意義的。) 由于一個(gè)整數(shù)通常足夠大,以至于
16、能夠放下多個(gè)字符,一些C編譯器允許在一個(gè)字符常量中存放多個(gè)字符。這意味著用'yes'代替"yes"將不會(huì)被發(fā)現(xiàn)。后者意味著“分別包含y、e、s和一個(gè)空字符的四個(gè)連續(xù)存儲(chǔ)器區(qū)域中的第一個(gè)的地址”,而前者意味著“在一些實(shí)現(xiàn)定義的樣式中表示由字符y、e、s聯(lián)合構(gòu)成的一個(gè)整數(shù)”。這兩者之間的任何一致性都純屬巧合。2 句法缺陷 要理解C語(yǔ)言程序,僅了解構(gòu)成它的記號(hào)是不夠的。還要理解這些記號(hào)是如何構(gòu)成聲明、表達(dá)式、語(yǔ)句和程序的。盡管這些構(gòu)成通常都是定義良好的,但這些定義有時(shí)候是有悖于直覺(jué)的或混亂的。
17、 在這一節(jié)中,我們將著眼于一些不明顯句法構(gòu)造。2.1 理解聲明 我曾經(jīng)和一些人聊過(guò)天,他們那時(shí)正在在編寫(xiě)在一個(gè)小型的微處理器上單機(jī)運(yùn)行的C程序。當(dāng)這臺(tái)機(jī)器的開(kāi)關(guān)打開(kāi)的時(shí)候,硬件會(huì)調(diào)用地址為0處的子程序。 為了模仿電源打開(kāi)的情形,我們要設(shè)計(jì)一條C語(yǔ)句來(lái)顯式地調(diào)用這個(gè)子程序。經(jīng)過(guò)一些思考,我們寫(xiě)出了下面的語(yǔ)句:(*(void(*)()0)(); 這樣的表達(dá)式會(huì)令C程序員心驚膽戰(zhàn)。但是,并不需要這樣,因?yàn)樗麄兛梢栽谝粋€(gè)簡(jiǎn)單的規(guī)則的幫助下很容易地構(gòu)造它:以你使用的方式聲明它。
18、0; 每個(gè)C變量聲明都具有兩個(gè)部分:一個(gè)類(lèi)型和一組具有特定格式的、期望用來(lái)對(duì)該類(lèi)型求值的表達(dá)式。最簡(jiǎn)單的表達(dá)式就是一個(gè)變量:float f, g;說(shuō)明表達(dá)式f和g在求值的時(shí)候具有類(lèi)型float。由于待求值的是表達(dá)式,因此可以自由地使用圓括號(hào):float (f);這表示(f)求值為float并且因此,通過(guò)推斷,f也是一個(gè)float。 同樣的邏輯用在函數(shù)和指針類(lèi)型。例如:float ff();表示表達(dá)式ff()是一個(gè)float,因此ff是一個(gè)返回一個(gè)float的函數(shù)。類(lèi)似地,float *pf;表示*pf是一個(gè)float并且因此pf是一個(gè)指向一個(gè)fl
19、oat的指針。 這些形式的組合聲明對(duì)表達(dá)式是一樣的。因此,float *g(), (*h)();表示*g()和(*h)()都是float表達(dá)式。由于()比*綁定得更緊密,*g()和*(g()表示同樣的東西:g是一個(gè)返回指float指針的函數(shù),而h是一個(gè)指向返回float的函數(shù)的指針。 當(dāng)我們知道如何聲明一個(gè)給定類(lèi)型的變量以后,就能夠很容易地寫(xiě)出一個(gè)類(lèi)型的模型(cast):只要?jiǎng)h除變量名和分號(hào)并將所有的東西包圍在一對(duì)圓括號(hào)中即可。因此,由于float *g();聲明g是一個(gè)返回float指針的函數(shù),所以(float *()就
20、是它的模型。 有了這些知識(shí)的武裝,我們現(xiàn)在可以準(zhǔn)備解決(*(void(*)()0)()了。 我們可以將它分為兩個(gè)部分進(jìn)行分析。首先,假設(shè)我們有一個(gè)變量fp,它包含了一個(gè)函數(shù)指針,并且我們希望調(diào)用fp所指向的函數(shù)??梢赃@樣寫(xiě):(*fp)();如果fp是一個(gè)指向函數(shù)的指針,則*fp就是函數(shù)本身,因此(*fp)()是調(diào)用它的一種方法。(*fp)中的括號(hào)是必須的,否則這個(gè)表達(dá)式將會(huì)被分析為*(fp()。我們現(xiàn)在要找一個(gè)適當(dāng)?shù)谋磉_(dá)式來(lái)替換fp。 這個(gè)問(wèn)題就是我們的第二步分析。如果C可以讀入并理解類(lèi)型,我們可以寫(xiě):(*0)();但這樣
21、并不行,因?yàn)?運(yùn)算符要求必須有一個(gè)指針作為它的操作數(shù)。另外,這個(gè)操作數(shù)必須是一個(gè)指向函數(shù)的指針,以保證*的結(jié)果可以被調(diào)用。因此,我們需要將0轉(zhuǎn)換為一個(gè)可以描述“指向一個(gè)返回void的函數(shù)的指針”的類(lèi)型。 如果fp是一個(gè)指向返回void的函數(shù)的指針,則(*fp)()是一個(gè)void值,并且它的聲明將會(huì)是這樣的:void (*fp)();因此,我們需要寫(xiě):void (*fp)();(*fp)();來(lái)聲明一個(gè)啞變量。一旦我們知道了如何聲明該變量,我們也就知道了如何將一個(gè)常數(shù)轉(zhuǎn)換為該類(lèi)型:只要從變量的聲明中去掉名字即可。因此,我們像下面這樣將0轉(zhuǎn)換為一個(gè)“指向返回vo
22、id的函數(shù)的指針”:(void(*)()0接下來(lái),我們用(void(*)()0來(lái)替換fp:(*(void(*)()0)();結(jié)尾處的分號(hào)用于將這個(gè)表達(dá)式轉(zhuǎn)換為一個(gè)語(yǔ)句。 在這里,我們解決這個(gè)問(wèn)題時(shí)沒(méi)有使用typedef聲明。通過(guò)使用它,我們可以更清晰地解決這個(gè)問(wèn)題:typedef void (*funcptr)();(*(funcptr)0)();2.2 運(yùn)算符并不總是具有你所想象的優(yōu)先級(jí) 假設(shè)有一個(gè)聲明了的常量FLAG,它是一個(gè)整數(shù),其二進(jìn)制表示中的某一位被置位(換句話(huà)說(shuō),它是2的某次冪),并且你希望測(cè)試一個(gè)整型變量fl
23、ags該位是否被置位。通常的寫(xiě)法是:if(flags & FLAG) .其意義對(duì)于很多C程序員都是很明確的:if語(yǔ)句測(cè)試?yán)ㄌ?hào)中的表達(dá)式求值的結(jié)果是否為0。出于清晰的目的我們可以將它寫(xiě)得更明確:if(flags & FLAG != 0) .這個(gè)語(yǔ)句現(xiàn)在更容易理解了。但它仍然是錯(cuò)的,因?yàn)?=比&綁定得更緊密,因此它被分析為:if(flags & (FLAG != 0) .這(偶爾)是可以的,如FLAG是1或0(!)的時(shí)候,但對(duì)于其他2的冪是不行的2。 假設(shè)你有兩個(gè)整型變量,h和l,它們的值在0和15(含0和15)之間,并且你希望將
24、r設(shè)置為8位值,其低位為l,高位為h。一種自然的寫(xiě)法是:r = h << 4 + 1;不幸的是,這是錯(cuò)誤的。加法比移位綁定得更緊密,因此這個(gè)例子等價(jià)于:r = h << (4 + l);正確的方法有兩種:r = (h << 4) + l;r = h << 4 | l; 避免這種問(wèn)題的一個(gè)方法是將所有的東西都用括號(hào)括起來(lái),但表達(dá)式中的括號(hào)過(guò)度就會(huì)難以理解,因此最好還是是記住C中的優(yōu)先級(jí)。 不幸的是,這有15個(gè),太困難了。然而,通過(guò)將它們分組可以變得容易。
25、 綁定得最緊密的運(yùn)算符并不是真正的運(yùn)算符:下標(biāo)、函數(shù)調(diào)用和結(jié)構(gòu)選擇。這些都與左邊相關(guān)聯(lián)。 接下來(lái)是一元運(yùn)算符。它們具有真正的運(yùn)算符中的最高優(yōu)先級(jí)。由于函數(shù)調(diào)用比一元運(yùn)算符綁定得更緊密,你必須寫(xiě)(*p)()來(lái)調(diào)用p指向的函數(shù);*p()表示p是一個(gè)返回一個(gè)指針的函數(shù)。轉(zhuǎn)換是一元運(yùn)算符,并且和其他一元運(yùn)算符具有相同的優(yōu)先級(jí)。一元運(yùn)算符是右結(jié)合的,因此*p+表示*(p+),而不是(*p)+。 在接下來(lái)是真正的二元運(yùn)算符。其中數(shù)學(xué)運(yùn)算符具有最高的優(yōu)先級(jí),然后是移位運(yùn)算符、關(guān)系運(yùn)算符、邏輯運(yùn)算符、賦值運(yùn)算符,最后是條件運(yùn)算
26、符。需要記住的兩個(gè)重要的東西是:1. 所有的邏輯運(yùn)算符具有比所有關(guān)系運(yùn)算符都低的優(yōu)先級(jí)。 2. 移位運(yùn)算符比關(guān)系運(yùn)算符綁定得更緊密,但又不如數(shù)學(xué)運(yùn)算符。 在這些運(yùn)算符類(lèi)別中,有一些奇怪的地方。乘法、除法和求余具有相同的優(yōu)先級(jí),加法和減法具有相同的優(yōu)先級(jí),以及移位運(yùn)算符具有相同的優(yōu)先級(jí)。 還有就是六個(gè)關(guān)系運(yùn)算符并不具有相同的優(yōu)先級(jí):=和!=的優(yōu)先級(jí)比其他關(guān)系運(yùn)算符要低。這就允許我們判斷a和b是否具有與c和d相同的順序,例如:a < b = c < d 在邏輯運(yùn)算符中,沒(méi)有任何兩
27、個(gè)具有相同的優(yōu)先級(jí)。按位運(yùn)算符比所有順序運(yùn)算符綁定得都緊密,每種與運(yùn)算符都比相應(yīng)的或運(yùn)算符綁定得更緊密,并且按位異或()運(yùn)算符介于按位與和按位或之間。 三元運(yùn)算符的優(yōu)先級(jí)比我們提到過(guò)的所有運(yùn)算符的優(yōu)先級(jí)都低。這可以保證選擇表達(dá)式中包含的關(guān)系運(yùn)算符的邏輯組合特性,如:z = a < b && b < c ? d : e 這個(gè)例子還說(shuō)明了賦值運(yùn)算符具有比條件運(yùn)算符更低的優(yōu)先級(jí)是有意義的。另外,所有的復(fù)合賦值運(yùn)算符具有相同的優(yōu)先級(jí)并且是自右至左結(jié)合的,因此a = b = c和b = c; a = b;
28、是等價(jià)的。 具有最低優(yōu)先級(jí)的是逗號(hào)運(yùn)算符。這很容易理解,因?yàn)槎禾?hào)通常在需要表達(dá)式而不是語(yǔ)句的時(shí)候用來(lái)替代分號(hào)。 賦值是另一種運(yùn)算符,通常具有混合的優(yōu)先級(jí)。例如,考慮下面這個(gè)用于復(fù)制文件的循環(huán):while(c = getc(in) != EOF) putc(c, out);這個(gè)while循環(huán)中的表達(dá)式看起來(lái)像是c被賦以getc(in)的值,接下來(lái)判斷是否等于EOF以結(jié)束循環(huán)。不幸的是,賦值的優(yōu)先級(jí)比任何比較操作都低,因此c的值將會(huì)是getc(in)和EOF比較的結(jié)果,并且會(huì)被拋棄。因此,“
29、復(fù)制”得到的文件將是一個(gè)由值為1的字節(jié)流組成的文件。 上面這個(gè)例子正確的寫(xiě)法并不難:while(c = getc(in) != EOF) putc(c, out);然而,這種錯(cuò)誤在很多復(fù)雜的表達(dá)式中卻很難被發(fā)現(xiàn)。例如,隨UNIX系統(tǒng)一同發(fā)布的lint程序通常帶有下面的錯(cuò)誤行:if (t = BTYPE(pt1->aty) = STRTY) | t = UNIONTY) 這條語(yǔ)句希望給t賦一個(gè)值,然后看t是否與STRTY或UNIONTY相等。而實(shí)際的效果卻大不相同3。 C中的邏輯運(yùn)算
30、符的優(yōu)先級(jí)具有歷史原因。B語(yǔ)言C的前輩具有和C中的&和|運(yùn)算符對(duì)應(yīng)的邏輯運(yùn)算符。盡管它們的定義是按位的 ,但編譯器在條件判斷上下文中將它們視為和&&和|一樣。當(dāng)在C中將它們分開(kāi)后,優(yōu)先級(jí)的改變是很危險(xiǎn)的4。2.3 看看這些分號(hào)! C中的一個(gè)多余的分號(hào)通常會(huì)帶來(lái)一點(diǎn)點(diǎn)不同:或者是一個(gè)空語(yǔ)句,無(wú)任何效果;或者編譯器可能提出一個(gè)診斷消息,可以方便除去掉它。一個(gè)重要的區(qū)別是在必須跟有一個(gè)語(yǔ)句的if和while語(yǔ)句中??紤]下面的例子:if(xi > big); big = xi;這不會(huì)發(fā)生編譯錯(cuò)誤,但這
31、段程序的意義與:if(xi > big) big = xi;就大不相同了。第一個(gè)程序段等價(jià)于:if(xi > big) big = xi;也就是等價(jià)于:big = xi;(除非x、i或big是帶有副作用的宏)。 另一個(gè)因分號(hào)引起巨大不同的地方是函數(shù)定義前面的結(jié)構(gòu)聲明的末尾(譯注這句話(huà)不太好聽(tīng),看例子就明白了)。考慮下面的程序片段:struct foo int x;f() .在緊挨著f的第一個(gè)后面丟失了一個(gè)分號(hào)。它的效果是聲明了一個(gè)函數(shù)f
32、,返回值類(lèi)型是struct foo,這個(gè)結(jié)構(gòu)成了函數(shù)聲明的一部分。如果這里出現(xiàn)了分號(hào),則f將被定義為具有默認(rèn)的整型返回值5。2.4 switch語(yǔ)句 通常C中的switch語(yǔ)句中的case段可以進(jìn)入下一個(gè)。例如,考慮下面的C和Pascal程序片斷:switch(color) case 1: printf ("red"); break;case 2: printf ("yellow");
33、160; break;case 3: printf ("blue"); break;case color of1: write ('red');2: write ('yellow');3: write ('blue');end 這兩個(gè)程序片段都作相同的事情:根據(jù)變量color的值是1、2還是3打印red、yellow或blue(沒(méi)有新行符)。這兩個(gè)程序片段非常相似,只有一點(diǎn)不同:Pas
34、cal程序中沒(méi)有C中相應(yīng)的break語(yǔ)句。C中的case標(biāo)簽是真正的標(biāo)簽:控制流程可以無(wú)限制地進(jìn)入到一個(gè)case標(biāo)簽中。 看看另一種形式,假設(shè)C程序段看起來(lái)更像Pascal:switch(color) case 1: printf ("red");case 2: printf ("yellow");case 3: printf ("blue");并且假設(shè)color的值是2。則該程序?qū)⒋蛴ellowblue,因?yàn)榭刂谱匀坏剞D(zhuǎn)入到下一個(gè)printf()的調(diào)用。 這既
35、是C語(yǔ)言switch語(yǔ)句的優(yōu)點(diǎn)又是它的弱點(diǎn)。說(shuō)它是弱點(diǎn),是因?yàn)楹苋菀淄浺粋€(gè)break語(yǔ)句,從而導(dǎo)致程序出現(xiàn)隱晦的異常行為。說(shuō)它是優(yōu)點(diǎn),是因?yàn)橥ㄟ^(guò)故意去掉break語(yǔ)句,可以很容易實(shí)現(xiàn)其他方法難以實(shí)現(xiàn)的控制結(jié)構(gòu)。尤其是在一個(gè)大型的switch語(yǔ)句中,我們經(jīng)常發(fā)現(xiàn)對(duì)一個(gè)case的處理可以簡(jiǎn)化其他一些特殊的處理。 例如,設(shè)想有一個(gè)程序是一臺(tái)假想的機(jī)器的翻譯器。這樣的一個(gè)程序可能包含一個(gè)switch語(yǔ)句來(lái)處理各種操作碼。在這樣一臺(tái)機(jī)器上,通常減法在對(duì)其第二個(gè)運(yùn)算數(shù)進(jìn)行變號(hào)后就變成和加法一樣了。因此,最好可以寫(xiě)出這樣的語(yǔ)句:case SUBTRACT: &
36、#160; opnd2 = -opnd2; /* no break; */case ADD: . 另外一個(gè)例子,考慮編譯器通過(guò)跳過(guò)空白字符來(lái)查找一個(gè)記號(hào)。這里,我們將空格、制表符和新行符視為是相同的,除了新行符還要引起行計(jì)數(shù)器的增長(zhǎng)外:case 'n': linecount+; /* no break */case 't':case ' ':
37、160; .2.5 函數(shù)調(diào)用 和其他程序設(shè)計(jì)語(yǔ)言不同,C要求一個(gè)函數(shù)調(diào)用必須有一個(gè)參數(shù)列表,但可以沒(méi)有參數(shù)。因此,如果f是一個(gè)函數(shù),f();就是對(duì)該函數(shù)進(jìn)行調(diào)用的語(yǔ)句,而f;什么也不做。它會(huì)作為函數(shù)地址被求值,但不會(huì)調(diào)用它6。2.6 懸掛else問(wèn)題 在討論任何語(yǔ)法缺陷時(shí)我們都不會(huì)忘記提到這個(gè)問(wèn)題。盡管這一問(wèn)題不是C語(yǔ)言所獨(dú)有的,但它仍然傷害著那些有著多年經(jīng)驗(yàn)的C程序員。 考慮下面的程序片斷:if(x = 0) if(y = 0) erro
38、r();else z = x + y; f(&z); 寫(xiě)這段程序的程序員的目的明顯是將情況分為兩種:x = 0和x != 0。在第一種情況中,程序段什么都不做,除非y = 0時(shí)調(diào)用error()。第二種情況中,程序設(shè)置z = x + y并以z的地址作為參數(shù)調(diào)用f()。 然而, 這段程序的實(shí)際效果卻大為不同。其原因是一個(gè)else總是與其最近的if相關(guān)聯(lián)。如果我們希望這段程序能夠按照實(shí)際的情況運(yùn)行,應(yīng)該這樣寫(xiě):if(x = 0)
39、160; if(y = 0) error(); else z = x + y; f(&z); 換句話(huà)說(shuō),當(dāng)x != 0發(fā)生時(shí)什么也不做。如果要達(dá)到第一個(gè)例子的效果,應(yīng)該寫(xiě):if(x = 0) if(y =0)
40、0; error();else z = z + y; f(&z);3 連接 一個(gè)C程序可能有很多部分組成,它們被分別編譯,并由一個(gè)通常稱(chēng)為連接器、連接編輯器或加載器的程序綁定到一起。由于編譯器一次通常只能看到一個(gè)文件,因此它無(wú)法檢測(cè)到需要程序的多個(gè)源文件的內(nèi)容才能發(fā)現(xiàn)的錯(cuò)誤。 在這一節(jié)中,我們將看到一些這種類(lèi)型的錯(cuò)誤。有一些C實(shí)現(xiàn),但不是所有的,帶有一個(gè)稱(chēng)為lint的程序
41、來(lái)捕獲這些錯(cuò)誤。如果具有一個(gè)這樣的程序,那么無(wú)論怎樣地強(qiáng)調(diào)它的重要性都不過(guò)分。3.1 你必須自己檢查外部類(lèi)型 假設(shè)你有一個(gè)C程序,被劃分為兩個(gè)文件。其中一個(gè)包含如下聲明:int n;而令一個(gè)包含如下聲明:long n;這 不是一個(gè)有效的C程序,因?yàn)橐恍┩獠棵Q(chēng)在兩個(gè)文件中被聲明為不同的類(lèi)型。然而,很多實(shí)現(xiàn)檢測(cè)不到這個(gè)錯(cuò)誤,因?yàn)榫幾g器在編譯其中一個(gè)文件時(shí)并不知道另一 個(gè)文件的內(nèi)容。因此,檢查類(lèi)型的工作只能由連接器(或一些工具程序如lint)來(lái)完成;如果操作系統(tǒng)的連接器不能識(shí)別數(shù)據(jù)類(lèi)型,C編譯器也沒(méi)法過(guò)多地強(qiáng)制 它。 那么,這
42、個(gè)程序運(yùn)行時(shí)實(shí)際會(huì)發(fā)生什么?這有很多可能性:1. 實(shí)現(xiàn)足夠聰明,能夠檢測(cè)到類(lèi)型沖突。則我們會(huì)得到一個(gè)診斷消息,說(shuō)明n在兩個(gè)文件中具有不同的類(lèi)型。 2. 你所使用的實(shí)現(xiàn)將int和long視為相同的類(lèi)型。典型的情況是機(jī)器可以自然地進(jìn)行32位運(yùn)算。在這種情況下你的程序或許能夠工作,好象你兩次都將變量聲明為long(或int)。但這種程序的工作純屬偶然。 3. n的兩個(gè)實(shí)例需要不同的存儲(chǔ),它們以某種方式共享存儲(chǔ)區(qū),即對(duì)其中一個(gè)的賦值對(duì)另一個(gè)也有效。這可能發(fā)生,例如,編譯器可以將int安排在long的低位。不論這是基于系統(tǒng)的還是基于機(jī)器的,這種程序的運(yùn)行同樣是偶然。 4. n的兩個(gè)實(shí)例以另一種方式共享存
43、儲(chǔ)區(qū),即對(duì)其中一個(gè)賦值的效果是對(duì)另一個(gè)賦以不同的值。在這種情況下,程序可能失敗。 這種情況發(fā)生的里一個(gè)例子出奇地頻繁。程序的某一個(gè)文件包含下面的聲明:char filename = "etc/passwd"而另一個(gè)文件包含這樣的聲明:char *filename; 盡管在某些環(huán)境中數(shù)組和指針的行為非常相似,但它們是不同的。在第一個(gè)聲明中,filename是一個(gè)字符數(shù)組的名字。盡管使用數(shù)組的名字可以產(chǎn)生數(shù)組第一個(gè)元素的指針,但這個(gè)指針只有在需要的時(shí)候才產(chǎn)生并且不會(huì)持續(xù)。在第二個(gè)聲明中,filename是一
44、個(gè)指針的名字。這個(gè)指針可以指向程序員讓它指向的任何地方。如果程序員沒(méi)有給它賦一個(gè)值,它將具有一個(gè)默認(rèn)的0值(NULL)(譯注實(shí)際上,在C中一個(gè)為初始化的指針通常具有一個(gè)隨機(jī)的值,這是很危險(xiǎn)的?。?。 這兩個(gè)聲明以不同的方式使用存儲(chǔ)區(qū),它們不可能共存。 避免這種類(lèi)型沖突的一個(gè)方法是使用像lint這樣的工具(如果可以的話(huà))。為了在一個(gè)程序的不同編譯單元之間檢查類(lèi)型沖突,一些程序需要一次看到其所有部分。典型的編譯器無(wú)法完成,但lint可以。 避免該問(wèn)題的另一種方法是將外部聲明放到包含文件中。這時(shí)
45、,一個(gè)外部對(duì)象的類(lèi)型僅出現(xiàn)一次7。4 語(yǔ)義缺陷 一個(gè)句子可以是精確拼寫(xiě)的并且沒(méi)有語(yǔ)法錯(cuò)誤,但仍然沒(méi)有意義。在這一節(jié)中,我們將會(huì)看到一些程序的寫(xiě)法會(huì)使得它們看起來(lái)是一個(gè)意思,但實(shí)際上是另一種完全不同的意思。 我們還要討論一些表面上看起來(lái)合理但實(shí)際上會(huì)產(chǎn)生未定義結(jié)果的環(huán)境。我們這里討論的東西并不保證能夠在所有的C實(shí)現(xiàn)中工作。我們暫且忘記這些能夠在一些實(shí)現(xiàn)中工作但可能不能在另一些實(shí)現(xiàn)中工作的東西,直到第7節(jié)討論可以執(zhí)行問(wèn)題為止。4.1 表達(dá)式求值順序 一些C運(yùn)算符以一種已知的、特定的順序?qū)ζ洳?/p>
46、作數(shù)進(jìn)行求值。但另一些不能。例如,考慮下面的表達(dá)式:a < b && c < dC語(yǔ)言定義規(guī)定a < b首先被求值。如果a確實(shí)小于b,c < d必須緊接著被求值以計(jì)算整個(gè)表達(dá)式的值。但如果a大于或等于b,則c < d根本不會(huì)被求值。 要對(duì)a < b求值,編譯器對(duì)a和b的求值就會(huì)有一個(gè)先后。但在一些機(jī)器上,它們也許是并行進(jìn)行的。 C中只有四個(gè)運(yùn)算符&&、|、?:和,指定了求值順序。&&和|最先對(duì)左邊的操作數(shù)進(jìn)行求值,而右邊的操作數(shù)只有在需要的
47、時(shí)候才進(jìn)行求值。而?:運(yùn)算符中的三個(gè)操作數(shù):a、b和c,最先對(duì)a進(jìn)行求值,之后僅對(duì)b或c中的一個(gè)進(jìn)行求值,這取決于a的值。,運(yùn)算符首先對(duì)左邊的操作數(shù)進(jìn)行求值,然后拋棄它的值,對(duì)右邊的操作數(shù)進(jìn)行求值8。 C中所有其它的運(yùn)算符對(duì)操作數(shù)的求值順序都是未定義的。事實(shí)上,賦值運(yùn)算符不對(duì)求值順序做出任何保證。 出于這個(gè)原因,下面這種將數(shù)組x中的前n個(gè)元素復(fù)制到數(shù)組y中的方法是不可行的:i = 0;while(i < n) yi = xi+;其中的問(wèn)題是yi的地址并不保證在i增長(zhǎng)之前被求值。在某
48、些實(shí)現(xiàn)中,這是可能的;但在另一些實(shí)現(xiàn)中卻不可能。另一種情況出于同樣的原因會(huì)失?。篿 = 0;while(i < n) yi+ = xi;而下面的代碼是可以工作的:i = 0;while(i < n) yi = xi; i+;當(dāng)然,這可以簡(jiǎn)寫(xiě)為:for(i = 0; i < n; i+) yi = xi;4.2 &&、|和!運(yùn)算符 C中有兩種邏輯運(yùn)算符,在某些情況下是可以交換的:按位
49、運(yùn)算符&、|和,以及邏輯運(yùn)算符&&、|和!。一個(gè)程序員如果用某一類(lèi)運(yùn)算符替換相應(yīng)的另一類(lèi)運(yùn)算符會(huì)得到某些奇怪的效果:程序可能會(huì)正確地工作,但這純屬偶然。 &&、|和!運(yùn)算符將它們的參數(shù)視為僅有“真”或“假”,通常約定0代表“假”而其它的任意值都代表“真”。這些運(yùn)算符返回1表示“真”而返回0表示“假”,而且&&和|運(yùn)算符當(dāng)可以通過(guò)左邊的操作數(shù)確定其返回值時(shí),就不會(huì)對(duì)右邊的操作數(shù)進(jìn)行求值。 因此!10是零,因?yàn)?0非零;10 && 12是1,因?yàn)?0和12
50、都非零;10 | 12也是1,因?yàn)?0非零。另外,最后一個(gè)表達(dá)式中的12不會(huì)被求值,10 | f()中的f()也不會(huì)被求值。 考慮下面這段用于在一個(gè)表中查找一個(gè)特定元素的程序:i = 0;while(i < tabsize && tabi != x) i+;這段循環(huán)背后的意思是如果i等于tabsize時(shí)循環(huán)結(jié)束,元素未被找到。否則,i包含了元素的索引。 假設(shè)這個(gè)例子中的&&不小心被替換為了&,這個(gè)循環(huán)可能仍然能夠工作,但只有兩種幸運(yùn)的情況可以
51、使它停下來(lái)。 首先,這兩個(gè)操作都是當(dāng)條件為假時(shí)返回0,當(dāng)條件為真時(shí)返回1。只要x和y都是1或0,x & y和x && y都具有相同的值。然而,如果當(dāng)使用了除1之外的非零值表示“真”時(shí)互換了這兩個(gè)運(yùn)算符,這個(gè)循環(huán)將不會(huì)工作。 其次,由于數(shù)組元素不會(huì)改變,因此越過(guò)數(shù)組最后一個(gè)元素前進(jìn)一個(gè)位置時(shí)是無(wú)害的,循環(huán)會(huì)幸運(yùn)地停下來(lái)。失誤的程序會(huì)越過(guò)數(shù)組的結(jié)尾,因?yàn)?amp;不像&&,總是會(huì)對(duì)所有的操作數(shù)進(jìn)行求值。因此循環(huán)的最后一次獲取tabi時(shí)i的值已經(jīng)等于tabsize了。如果tabsize是t
52、ab中元素的數(shù)量,則會(huì)取到tab中不存在的一個(gè)值。4.3 下標(biāo)從零開(kāi)始 在很多語(yǔ)言中,具有n個(gè)元素的數(shù)組其元素的號(hào)碼和它的下標(biāo)是從1到n嚴(yán)格對(duì)應(yīng)的。但在C中不是這樣。 一個(gè)具有n個(gè)元素的C數(shù)組中沒(méi)有下標(biāo)為n的元素,其中的元素的下標(biāo)是從0到n - 1。因此從其它語(yǔ)言轉(zhuǎn)到C語(yǔ)言的程序員應(yīng)該特別小心地使用數(shù)組:int i, a10;for(i = 1; i <= 10; i+) ai = 0;這個(gè)例子的目的是要將a中的每個(gè)元素都設(shè)置為0,但沒(méi)有期望的效果。因?yàn)閒or語(yǔ)句中的比較i <
53、; 10被替換成了i <= 10,a中的一個(gè)編號(hào)為10的并不存在的元素被設(shè)置為了0,這樣內(nèi)存中a后面的一個(gè)字被破壞了。如果編譯該程序的編譯器按照降序地址為用戶(hù)變量分配內(nèi)存,則a后面就是i。將i設(shè)置為零會(huì)導(dǎo)致該循環(huán)陷入一個(gè)無(wú)限循環(huán)。4.4 C并不總是轉(zhuǎn)換實(shí)參 下面的程序段由于兩個(gè)原因會(huì)失?。篸ouble s;s = sqrt(2);printf("%gn", s); 第一個(gè)原因是sqrt()需要一個(gè)double值作為它的參數(shù),但沒(méi)有得到。第二個(gè)原因是它返回一個(gè)double值但沒(méi)有這樣聲名。改正的方法
54、只有一個(gè):double s, sqrt();s = sqrt(2.0);printf("%gn", s); C中有兩個(gè)簡(jiǎn)單的規(guī)則控制著函數(shù)參數(shù)的轉(zhuǎn)換:(1)比int短的整型被轉(zhuǎn)換為int;(2)比double短的浮點(diǎn)類(lèi)型被轉(zhuǎn)換為double。所有的其它值不被轉(zhuǎn)換。確保函數(shù)參數(shù)類(lèi)型的正確性是程序員的責(zé)任。 因此,一個(gè)程序員如果想使用如sqrt()這樣接受一個(gè)double類(lèi)型參數(shù)的函數(shù),就必須僅傳遞給它float或double類(lèi)型的參數(shù)。常數(shù)2是一個(gè)int,因此其類(lèi)型是錯(cuò)誤的。
55、60; 當(dāng)一個(gè)函數(shù)的值被用在表達(dá)式中時(shí),其值會(huì)被自動(dòng)地轉(zhuǎn)換為適當(dāng)?shù)念?lèi)型。然而,為了完成這個(gè)自動(dòng)轉(zhuǎn)換,編譯器必須知道該函數(shù)實(shí)際返回的類(lèi)型。沒(méi)有更進(jìn)一步聲名的函數(shù)被假設(shè)返回int,因此聲名這樣的函數(shù)并不是必須的。然而,sqrt()返回double,因此在成功使用它之前必須要聲名。 實(shí)際上,C實(shí)現(xiàn)通常允許一個(gè)文件包含include語(yǔ)句來(lái)包含如sqrt()這些庫(kù)函數(shù)的聲名,但是對(duì)那些自己寫(xiě)函數(shù)的程序員來(lái)說(shuō),編寫(xiě)聲名也是必要的或者說(shuō),對(duì)那些編寫(xiě)非凡的C程序的人來(lái)說(shuō)是有必要的。 這里有一個(gè)更加壯觀的例子:main()
56、160; int i; char c; for(i = 0; i < 5; i+) scanf("%d", &c); printf("%d", i); printf("n");
57、; 表面上看,這個(gè)程序從標(biāo)準(zhǔn)輸入中讀取五個(gè)整數(shù)并向標(biāo)準(zhǔn)輸出寫(xiě)入0 1 2 3 4。實(shí)際上,它并不總是這么做。譬如在一些編譯器中,它的輸出為0 0 0 0 0 1 2 3 4。 為什么?因?yàn)閏的聲名是char而不是int。當(dāng)你令scanf()去讀取一個(gè)整數(shù)時(shí),它需要一個(gè)指向一個(gè)整數(shù)的指針。但這里它得到的是一個(gè)字符的指針。但scanf()并不知道它沒(méi)有得到它所需要的:它將輸入看作是一個(gè)指向整數(shù)的指針并將一個(gè)整數(shù)存貯到那里。由于整數(shù)占用比字符更多的內(nèi)存,這樣做會(huì)影響到c附近的內(nèi)存。 c附近確切是什么是編譯器的事;在這
58、種情況下這有可能是i的低位。因此,每當(dāng)向c中讀入一個(gè)值,i就被置零。當(dāng)程序最后到達(dá)文件結(jié)尾時(shí),scanf()不再?lài)L試向c中放入新值,i才可以正常地增長(zhǎng),直到循環(huán)結(jié)束。4.5 指針不是數(shù)組 C程序通常將一個(gè)字符串轉(zhuǎn)換為一個(gè)以空字符結(jié)尾的字符數(shù)組。假設(shè)我們有兩個(gè)這樣的字符串s和t,并且我們想要將它們連接為一個(gè)單獨(dú)的字符串r。我們通常使用庫(kù)函數(shù)strcpy()和strcat()來(lái)完成。下面這種明顯的方法并不會(huì)工作:char *r;strcpy(r, s);strcat(r, t);這是因?yàn)閞沒(méi)有被初始化為指向任何地方。盡管r可能潛在地表示某一塊內(nèi)存,但這并不存在,
59、直到你分配它。 讓我們?cè)僭囋?,為r分配一些內(nèi)存:char r100;strcpy(r, s);strcat(r, t);這只有在s和t所指向的字符串不很大的時(shí)候才能夠工作。不幸的是,C要求我們?yōu)閿?shù)組指定的大小是一個(gè)常數(shù),因此無(wú)法確定r是否足夠大。然而,很多C實(shí)現(xiàn)帶有一個(gè)叫做malloc()的庫(kù)函數(shù),它接受一個(gè)數(shù)字并分配這么多的內(nèi)存。通常還有一個(gè)函數(shù)稱(chēng)為strlen(),可以告訴我們一個(gè)字符串中有多少個(gè)字符:因此,我們可以寫(xiě):char *r, *malloc();r = malloc(strlen(s) + strlen(t);strcpy(r, s);str
60、cat(r, t); 然而這個(gè)例子會(huì)因?yàn)閮蓚€(gè)原因而失敗。首先,malloc()可能會(huì)耗盡內(nèi)存,而這個(gè)事件僅通過(guò)靜靜地返回一個(gè)空指針來(lái)表示。 其次,更重要的是,malloc()并沒(méi)有分配足夠的內(nèi)存。一個(gè)字符串是以一個(gè)空字符結(jié)束的。而strlen()函數(shù)返回其字符串參數(shù)中所包含字符的數(shù)量,但不包括結(jié)尾的空字符。因此,如果strlen(s)是n,則s需要n + 1個(gè)字符來(lái)盛放它。因此我們需要為r分配額外的一個(gè)字符。再加上檢查malloc()是否成功,我們得到:char *r, *malloc();r = malloc(strle
61、n(s) + strlen(t) + 1);if(!r) complain(); exit(1);strcpy(r, s);strcat(r, t);4.6 避免提喻法 提喻法(Synecdoche, sin-ECK-duh-key)是一種文學(xué)手法,有點(diǎn)類(lèi)似于明喻或暗喻,在牛津英文詞典中解釋如下:“a more comprehensive term is used for a less comprehensive or vice versa; as whole for part or
62、part for whole, genus for species or species for genus, etc.(將全面的單位用作不全面的單位,或反之;如整體對(duì)局部或局部對(duì)整體、一般對(duì)特殊或特殊對(duì)一般,等等。)” 這可以精確地描述C中通常將指針誤以為是其指向的數(shù)據(jù)的錯(cuò)誤。正將常會(huì)在字符串中發(fā)生。例如:char *p, *q;p = "xyz"盡管認(rèn)為p的值是xyz有時(shí)是有用的,但這并不是真的,理解這一點(diǎn)非常重要。p的值是指向一個(gè)有四個(gè)字符的數(shù)組中第0個(gè)元素的指針,這四個(gè)字符是'x'、'y'、'
63、;z'和'0'。因此,如果我們現(xiàn)在執(zhí)行:q = p;p和q會(huì)指向同一塊內(nèi)存。內(nèi)存中的字符沒(méi)有因?yàn)橘x值而被復(fù)制。這種情況看起來(lái)是這樣的: 要記住的是,復(fù)制一個(gè)指針并不能復(fù)制它所指向的東西。 因此,如果之后我們執(zhí)行:q1 = 'Y'q所指向的內(nèi)存包含字符串xYz。p也是,因?yàn)閜和q指向相同的內(nèi)存。4.7 空指針不是空字符串 將一個(gè)整數(shù)轉(zhuǎn)換為一個(gè)指針的結(jié)果是實(shí)現(xiàn)相關(guān)的(implementation-dependent),除了一個(gè)例外。這個(gè)例外是常數(shù)0,它
64、可以保證被轉(zhuǎn)換為一個(gè)與其它任何有效指針都不相等的指針。這個(gè)值通常類(lèi)似這樣定義:#define NULL 0但其效果是相同的。要記住的一個(gè)重要的事情是,當(dāng)用0作為指針時(shí)它決不能被解除引用。換句話(huà)說(shuō),當(dāng)你將0賦給一個(gè)指針變量后,你就不能訪(fǎng)問(wèn)它所指向的內(nèi)存。不能這樣寫(xiě):if(p = (char *)0) .也不能這樣寫(xiě):if(strcmp(p, (char *)0) = 0) .因?yàn)閟trcmp()總是通過(guò)其參數(shù)來(lái)查看內(nèi)存地址的。 如果p是一個(gè)空指針,這樣寫(xiě)也是無(wú)效的:printf(p);或printf("%s", p);4.8 整數(shù)溢出
65、60; C語(yǔ)言關(guān)于整數(shù)操作的上溢或下溢定義得非常明確。 只要有一個(gè)操作數(shù)是無(wú)符號(hào)的,結(jié)果就是無(wú)符號(hào)的,并且以2n為模,其中n為字長(zhǎng)。如果兩個(gè)操作數(shù)都是帶符號(hào)的,則結(jié)果是未定義的。 例如,假設(shè)a和b是兩個(gè)非負(fù)整型變量,你希望測(cè)試a + b是否溢出。一個(gè)明顯的辦法是這樣的:if(a + b < 0) complain();通常,這是不會(huì)工作的。 一旦a + b發(fā)生了溢出,對(duì)于結(jié)果的任何賭注都是沒(méi)有意義的。例如,在某些機(jī)器上,一
66、個(gè)加法運(yùn)算會(huì)將一個(gè)內(nèi)部寄存器設(shè)置為四種狀態(tài):正、負(fù)、零或溢出。 在這樣的機(jī)器上,編譯器有權(quán)將上面的例子實(shí)現(xiàn)為首先將a和b加在一起,然后檢查內(nèi)部寄存器狀態(tài)是否為負(fù)。如果該運(yùn)算溢出,內(nèi)部寄存器將處于溢出狀態(tài),這個(gè)測(cè)試會(huì)失敗。 使這個(gè)特殊的測(cè)試能夠成功的一個(gè)正確的方法是依賴(lài)于無(wú)符號(hào)算術(shù)的良好定義,即要在有符號(hào)和無(wú)符號(hào)之間進(jìn)行轉(zhuǎn)換:if(int)(unsigned)a + (unsigned)b) < 0) complain();4.9 移位運(yùn)算符 兩個(gè)原因會(huì)令使用移位運(yùn)算符的人感到煩惱:
67、1. 在右移運(yùn)算中,空出的位是用0填充還是用符號(hào)位填充? 2. 移位的數(shù)量允許使用哪些數(shù)? 第一個(gè)問(wèn)題的答案很簡(jiǎn)單,但有時(shí)是實(shí)現(xiàn)相關(guān)的。如果要進(jìn)行移位的操作數(shù)是無(wú)符號(hào)的,會(huì)移入0。如果操作數(shù)是帶符號(hào)的,則實(shí)現(xiàn)有權(quán)決定是移入0還是移入符號(hào)位。如果在一個(gè)右移操作中你很關(guān)心空位,那么用unsigned來(lái)聲明變量。這樣你就有權(quán)假設(shè)空位被設(shè)置為0。 第二個(gè)問(wèn)題的答案同樣簡(jiǎn)單:如果待移位的數(shù)長(zhǎng)度為n,則移位的數(shù)量必須大于等于0并且嚴(yán)格地小于n。因此,在一次單獨(dú)的操作中不可能將所有的位從變量中移出。
68、例如,如果一個(gè)int是32位,且n是一個(gè)int,寫(xiě)n << 31和n << 0是合法的,但n << 32和n << -1是不合法的。 注意,即使實(shí)現(xiàn)將符號(hào)為移入空位,對(duì)一個(gè)帶符號(hào)整數(shù)的右移運(yùn)算和除以2的某次冪也不是等價(jià)的。為了證明這一點(diǎn),考慮(-1) >> 1的值,這是不可能為0的。譯注:(-1) / 2的結(jié)果是0。5 庫(kù)函數(shù) 每個(gè)有用的C程序都會(huì)用到庫(kù)函數(shù),因?yàn)闆](méi)有辦法把輸入和輸出內(nèi)建到語(yǔ)言中去。在這一節(jié)中,我們將會(huì)看到一些廣泛使用的庫(kù)函數(shù)在某種情況下會(huì)出現(xiàn)的一
69、些非預(yù)期行為。5.1 getc()返回整數(shù) 考慮下面的程序:#include main() char c; while(c = getchar() != EOF) putchar(c); 這段程序看起來(lái)好像要將標(biāo)準(zhǔn)輸入復(fù)制到標(biāo)準(zhǔn)輸出。實(shí)際上,它并不完全會(huì)做這些。 原因是c被聲明為字符而不是整數(shù)。這意味著它將不能接收可能出現(xiàn)的所有字符包括EO
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 幼兒教師反思心得500字(8篇)
- 安全在我心中演講稿大全(9篇)
- 區(qū)域養(yǎng)老中心入住協(xié)議書(shū)
- 商鋪消防施工協(xié)議
- 勞務(wù)服務(wù)項(xiàng)目風(fēng)險(xiǎn)評(píng)估與控制
- 施工期間成本預(yù)算最終協(xié)議
- 電子商務(wù)平臺(tái)招投標(biāo)流程
- 房地產(chǎn)企業(yè)融資策略
- 保健食品企業(yè)參股管理建議
- 市政道路外圍圍墻施工協(xié)議
- 初中藝術(shù)鄂教七年級(jí)上冊(cè)(2022年新編) 漫步藝術(shù)長(zhǎng)廊舞劇欣賞《永不消逝的電波》教學(xué)設(shè)計(jì)
- python學(xué)習(xí)課件(共73張PPT)
- 中考數(shù)學(xué)復(fù)習(xí)專(zhuān)題課件:瓜豆原理之直線(xiàn)型
- 大同重力儲(chǔ)能設(shè)備項(xiàng)目可行性研究報(bào)告
- 樁基及基坑質(zhì)量通病防治講義PPT(105頁(yè))
- 精品堆垛機(jī)安裝指導(dǎo)書(shū)
- 前臺(tái)月度績(jī)效考核表(KPI)
- 雞的飼養(yǎng)管理-優(yōu)質(zhì)課件
- 德育課(共19張PPT)
- 化學(xué)微生物學(xué)第7章 微生物轉(zhuǎn)化
- 《少年正是讀書(shū)時(shí)》-完整版PPT課件
評(píng)論
0/150
提交評(píng)論