




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、概述 C語(yǔ)言像一把雕刻刀,鋒利,并且在技師手中非常有用。和任何鋒利的工具一樣,C會(huì)傷到那些不能掌握它的人。本文介紹C語(yǔ)言傷害粗心的人的方法,以及如何避免傷害。內(nèi)容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 你必須自己檢查外部類型4 語(yǔ)義缺陷 4.1 表達(dá)式求值順序 4.2 &、|和!運(yùn)算符 4.3 下標(biāo)從零開始 4.4
2、 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 宏不是類型定義7 可移植性缺陷 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 大小寫轉(zhuǎn)換 7.8 先釋放,再重新分配 7.9 可移植性問(wèn)題的一個(gè)實(shí)例8 這里是空閑空間 參考 腳注0 簡(jiǎn)介 C語(yǔ)言及其典型實(shí)現(xiàn)被設(shè)計(jì)為
3、能被專家們?nèi)菀椎厥褂谩_@門語(yǔ)言簡(jiǎn)潔并附有表達(dá)力。但有一些限制可以保護(hù)那些浮躁的人。一個(gè)浮躁的人可以從這些條款中獲得一些幫助。 在本文中,我們將會(huì)看到這些未可知的益處。正是由于它的未可知,我們無(wú)法為其進(jìn)行完全的分類。不過(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ā)生的事情。第五部分研究了我們的程序和它們
4、所使用的常用庫(kù)之間的關(guān)系。在第六部分中,我們注意到了我們所寫的程序也許并不是我們所運(yùn)行的程序;預(yù)處理器將首先運(yùn)行。最后,第七部分討論了可移植性問(wèn)題:一個(gè)能在一個(gè)實(shí)現(xiàn)中運(yùn)行的程序無(wú)法在另一個(gè)實(shí)現(xiàn)中運(yùn)行的原因。1 詞法缺陷 編譯器的第一個(gè)部分常被稱為詞法分析器(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ú)立于-出現(xiàn)的上下文環(huán)境。 另外一個(gè)例子,考慮下面的語(yǔ)句:if(x big
5、) 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)和組成它們的字符之間的關(guān)系。稍后我們將談到預(yù)處理器。1.1 = 不是 = 從Algol派生出來(lái)的語(yǔ)言,如Pascal和Ada,用:=表示賦值而用=表示比較。而C語(yǔ)言則是用=表示賦值而用=表示比較。這是因?yàn)橘x值的
6、頻率要高于比較,因此為其分配更短的符號(hào)。 此外,C還將賦值視為一個(gè)運(yùn)算符,因此可以很容易地寫出多重賦值(如a = b = c),并且可以將賦值嵌入到一個(gè)大的表達(dá)式中。 這種便捷導(dǎo)致了一個(gè)潛在的問(wèn)題:可能將需要比較的地方寫成賦值。因此,下面的語(yǔ)句好像看起來(lái)是要檢查x是否等于y:if(x = y) 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)?/p>
7、t不為零,這個(gè)“比較”將一直為真,因此這個(gè)循環(huán)會(huì)吃盡整個(gè)文件。這之后會(huì)發(fā)生什么取決于特定的實(shí)現(xiàn)是否允許一個(gè)程序讀取超過(guò)文件尾部的部分。如果允許,這個(gè)循環(huán)會(huì)一直運(yùn)行。 一些C編譯器會(huì)對(duì)形如e1 = e2的條件給出一個(gè)警告以提醒用戶。當(dāng)你確實(shí)需要先對(duì)一個(gè)變量進(jìn)行賦值之后再檢查變量是否非零時(shí),為了在這種編譯器中避免警告信息,應(yīng)考慮顯式給出比較符。換句話說(shuō),將:if(x = y) foo();改寫為:if(x = y) != 0) foo();這樣可以清晰地表示你的意圖。1.2 & 和 | 不是 & 和 | 容易將=錯(cuò)寫為=是因?yàn)楹芏嗥渌Z(yǔ)言使用=表示比較運(yùn)算。 其他容易寫錯(cuò)的運(yùn)算符還有&和&,以及|
8、和|,這主要是因?yàn)镃語(yǔ)言中的&和|運(yùn)算符于其他語(yǔ)言中具有類似功能的運(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)的最長(zhǎng)的字符串”(譯注即通常所說(shuō)的“最長(zhǎng)子串原則”)。因此,如果/是一個(gè)記號(hào)的第一個(gè)字符,并且/后面緊隨了一個(gè)*,則這兩個(gè)字符構(gòu)成了注釋的開始
9、,不管其他上下文環(huán)境。 下面的語(yǔ)句看起來(lái)像是將y的值設(shè)置為x的值除以p所指向的值:y = x/*p /* p 指向除數(shù) */;實(shí)際上,/*開始了一個(gè)注釋,因此編譯器簡(jiǎn)單地吞噬程序文本,直到*/的出現(xiàn)。換句話說(shuō),這條語(yǔ)句僅僅把y的值設(shè)置為x的值,而根本沒(méi)有看到p。將這條語(yǔ)句重寫為:y = x / *p /* p 指向除數(shù) */;或者干脆是y = x / (*p) /* p指向除數(shù) */;它就可以做注釋所暗示的除法了。 這種模棱兩可的寫法在其他環(huán)境中就會(huì)引起麻煩。例如,老版本的C使用=+表示現(xiàn)在版本中的+=。這樣的編譯器會(huì)將a=-1;視為a =- 1;或a = a - 1;這會(huì)讓打算寫a = -1
10、;的程序員感到吃驚。 另一方面,這種老版本的C編譯器會(huì)將a=/*b;斷句為a =/ *b;盡管/*看起來(lái)像一個(gè)注釋。1.4 例外 組合賦值運(yùn)算符如+=實(shí)際上是兩個(gè)記號(hào)。因此,a + /* strange */ = 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è)字符只是編寫整數(shù)的另一種方法。這個(gè)
11、整數(shù)是給定的字符在實(shí)現(xiàn)的對(duì)照序列中的一個(gè)對(duì)應(yīng)的值。因此,在一個(gè)ASCII實(shí)現(xiàn)中,a和0141或97表示完全相同的東西。而一個(gè)包圍在雙引號(hào)中的字符串,只是編寫一個(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, d, n, 0 ;printf(hello); 使用一個(gè)指針來(lái)代替一個(gè)整數(shù)通常會(huì)得到一個(gè)警告消息(反之亦然),使用雙引號(hào)來(lái)代替單引號(hào)也會(huì)得到一個(gè)警告消息(反之亦然)。但對(duì)于不檢查參數(shù)類型的編譯
12、器卻除外。因此,用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ù)通常足夠大,以至于能夠放下多個(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)
13、是如何構(gòu)成聲明、表達(dá)式、語(yǔ)句和程序的。盡管這些構(gòu)成通常都是定義良好的,但這些定義有時(shí)候是有悖于直覺(jué)的或混亂的。 在這一節(jié)中,我們將著眼于一些不明顯句法構(gòu)造。2.1 理解聲明 我曾經(jīng)和一些人聊過(guò)天,他們那時(shí)正在在編寫在一個(gè)小型的微處理器上單機(jī)運(yùn)行的C程序。當(dāng)這臺(tái)機(jī)器的開關(guān)打開的時(shí)候,硬件會(huì)調(diào)用地址為0處的子程序。 為了模仿電源打開的情形,我們要設(shè)計(jì)一條C語(yǔ)句來(lái)顯式地調(diào)用這個(gè)子程序。經(jīng)過(guò)一些思考,我們寫出了下面的語(yǔ)句:(*(void(*)()0)(); 這樣的表達(dá)式會(huì)令C程序員心驚膽戰(zhàn)。但是,并不需要這樣,因?yàn)樗麄兛梢栽谝粋€(gè)簡(jiǎn)單的規(guī)則的幫助下很容易地構(gòu)造它:以你使用的方式聲明它。 每個(gè)C變量聲明都
14、具有兩個(gè)部分:一個(gè)類型和一組具有特定格式的、期望用來(lái)對(duì)該類型求值的表達(dá)式。最簡(jiǎn)單的表達(dá)式就是一個(gè)變量:float f, g;說(shuō)明表達(dá)式f和g在求值的時(shí)候具有類型float。由于待求值的是表達(dá)式,因此可以自由地使用圓括號(hào):float (f);這表示(f)求值為float并且因此,通過(guò)推斷,f也是一個(gè)float。 同樣的邏輯用在函數(shù)和指針類型。例如:float ff();表示表達(dá)式ff()是一個(gè)float,因此ff是一個(gè)返回一個(gè)float的函數(shù)。類似地,float *pf;表示*pf是一個(gè)float并且因此pf是一個(gè)指向一個(gè)float的指針。 這些形式的組合聲明對(duì)表達(dá)式是一樣的。因此,float
15、*g(), (*h)();表示*g()和(*h)()都是float表達(dá)式。由于()比*綁定得更緊密,*g()和*(g()表示同樣的東西:g是一個(gè)返回指float指針的函數(shù),而h是一個(gè)指向返回float的函數(shù)的指針。 當(dāng)我們知道如何聲明一個(gè)給定類型的變量以后,就能夠很容易地寫出一個(gè)類型的模型(cast):只要?jiǎng)h除變量名和分號(hào)并將所有的東西包圍在一對(duì)圓括號(hào)中即可。因此,由于float *g();聲明g是一個(gè)返回float指針的函數(shù),所以(float *()就是它的模型。 有了這些知識(shí)的武裝,我們現(xiàn)在可以準(zhǔn)備解決(*(void(*)()0)()了。 我們可以將它分為兩個(gè)部分進(jìn)行分析。首先,假設(shè)我們有
16、一個(gè)變量fp,它包含了一個(gè)函數(shù)指針,并且我們希望調(diào)用fp所指向的函數(shù)??梢赃@樣寫:(*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可以讀入并理解類型,我們可以寫:(*0)();但這樣并不行,因?yàn)?運(yùn)算符要求必須有一個(gè)指針作為它的操作數(shù)。另外,這個(gè)操作數(shù)必須是一個(gè)指向函數(shù)的指針,以保證*的結(jié)果可以被調(diào)用。因此,我們需要將0轉(zhuǎn)換為一個(gè)可以描述“指向一個(gè)返回void的函數(shù)的指針”的類型。 如果fp是
17、一個(gè)指向返回void的函數(shù)的指針,則(*fp)()是一個(gè)void值,并且它的聲明將會(huì)是這樣的:void (*fp)();因此,我們需要寫:void (*fp)();(*fp)();來(lái)聲明一個(gè)啞變量。一旦我們知道了如何聲明該變量,我們也就知道了如何將一個(gè)常數(shù)轉(zhuǎn)換為該類型:只要從變量的聲明中去掉名字即可。因此,我們像下面這樣將0轉(zhuǎn)換為一個(gè)“指向返回void的函數(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ò)使用它,我們可以更清晰
18、地解決這個(gè)問(wèn)題:typedef void (*funcptr)();(*(funcptr)0)();2.2 運(yùn)算符并不總是具有你所想象的優(yōu)先級(jí) 假設(shè)有一個(gè)聲明了的常量FLAG,它是一個(gè)整數(shù),其二進(jìn)制表示中的某一位被置位(換句話說(shuō),它是2的某次冪),并且你希望測(cè)試一個(gè)整型變量flags該位是否被置位。通常的寫法是:if(flags & FLAG) .其意義對(duì)于很多C程序員都是很明確的:if語(yǔ)句測(cè)試?yán)ㄌ?hào)中的表達(dá)式求值的結(jié)果是否為0。出于清晰的目的我們可以將它寫得更明確:if(flags & FLAG != 0) .這個(gè)語(yǔ)句現(xiàn)在更容易理解了。但它仍然是錯(cuò)的,因?yàn)?=比&綁定得更緊密,因此它被分析為:
19、if(flags & (FLAG != 0) .這(偶爾)是可以的,如FLAG是1或0(?。┑臅r(shí)候,但對(duì)于其他2的冪是不行的2。 假設(shè)你有兩個(gè)整型變量,h和l,它們的值在0和15(含0和15)之間,并且你希望將r設(shè)置為8位值,其低位為l,高位為h。一種自然的寫法是: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è),太
20、困難了。然而,通過(guò)將它們分組可以變得容易。 綁定得最緊密的運(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)算符綁定得更緊密,你必須寫(*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)算符。需要記住的兩個(gè)重要的東西是:所有的邏輯
21、運(yùn)算符具有比所有關(guān)系運(yùn)算符都低的優(yōu)先級(jí)。 移位運(yùn)算符比關(guān)系運(yùn)算符綁定得更緊密,但又不如數(shù)學(xué)運(yùn)算符。 在這些運(yùn)算符類別中,有一些奇怪的地方。乘法、除法和求余具有相同的優(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)有任何兩個(gè)具有相同的優(yōu)先級(jí)。按位運(yùn)算符比所有順序運(yùn)算符綁定得都緊密,每種與運(yùn)算符都比相應(yīng)的或運(yùn)算符綁定得更緊密,并且按位異或()運(yùn)算符介于按位與和按位或之間。 三元運(yùn)算符的優(yōu)先級(jí)比我們提到過(guò)
22、的所有運(yùn)算符的優(yōu)先級(jí)都低。這可以保證選擇表達(dá)式中包含的關(guān)系運(yùn)算符的邏輯組合特性,如:z = a b & b aty) = STRTY) | t = UNIONTY) 這條語(yǔ)句希望給t賦一個(gè)值,然后看t是否與STRTY或UNIONTY相等。而實(shí)際的效果卻大不相同3。 C中的邏輯運(yùn)算符的優(yōu)先級(jí)具有歷史原因。B語(yǔ)言C的前輩具有和C中的&和|運(yùn)算符對(duì)應(yīng)的邏輯運(yùn)算符。盡管它們的定義是按位的 ,但編譯器在條件判斷上下文中將它們視為和&和|一樣。當(dāng)在C中將它們分開后,優(yōu)先級(jí)的改變是很危險(xiǎn)的4。2.3 看看這些分號(hào)! C中的一個(gè)多余的分號(hào)通常會(huì)帶來(lái)一點(diǎn)點(diǎn)不同:或者是一個(gè)空語(yǔ)句,無(wú)任何效果;或者編譯器可能提出一
23、個(gè)診斷消息,可以方便除去掉它。一個(gè)重要的區(qū)別是在必須跟有一個(gè)語(yǔ)句的if和while語(yǔ)句中。考慮下面的例子:if(xi big); big = xi;這不會(huì)發(fā)生編譯錯(cuò)誤,但這段程序的意義與: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)聲明的末尾(譯注這句話不太好聽,看例子就明白了)。考慮下面的程序片段:struct foo int x;f() .在緊挨著f的第一個(gè)后面丟失了一個(gè)分號(hào)。它的效果是聲明了一個(gè)函
24、數(shù)f,返回值類型是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); break;case 3: printf (blue); break;case color of1: write (red);2: write (yellow);3: write (blue);end 這兩個(gè)程序片
25、段都作相同的事情:根據(jù)變量color的值是1、2還是3打印red、yellow或blue(沒(méi)有新行符)。這兩個(gè)程序片段非常相似,只有一點(diǎn)不同:Pascal程序中沒(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)用。
26、這既是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)后就變成和加法一樣了。因此,最好可以寫出這樣的語(yǔ)句:case SUBTRACT: opnd2 = -opnd2; /* no
27、break; */case ADD: . 另外一個(gè)例子,考慮編譯器通過(guò)跳過(guò)空白字符來(lái)查找一個(gè)記號(hào)。這里,我們將空格、制表符和新行符視為是相同的,除了新行符還要引起行計(jì)數(shù)器的增長(zhǎng)外:case n: linecount+; /* no break */case t:case : .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ú)有的,但
28、它仍然傷害著那些有著多年經(jīng)驗(yàn)的C程序員。 考慮下面的程序片斷:if(x = 0) if(y = 0) error();else z = x + y; f(&z); 寫這段程序的程序員的目的明顯是將情況分為兩種: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)該這樣寫:if(x = 0) if(y = 0) error(); else z = x
29、+ y; f(&z); 換句話說(shuō),當(dāng)x != 0發(fā)生時(shí)什么也不做。如果要達(dá)到第一個(gè)例子的效果,應(yīng)該寫:if(x = 0) if(y =0) error();else z = z + y; f(&z);3 連接 一個(gè)C程序可能有很多部分組成,它們被分別編譯,并由一個(gè)通常稱為連接器、連接編輯器或加載器的程序綁定到一起。由于編譯器一次通常只能看到一個(gè)文件,因此它無(wú)法檢測(cè)到需要程序的多個(gè)源文件的內(nèi)容才能發(fā)現(xiàn)的錯(cuò)誤。 在這一節(jié)中,我們將看到一些這種類型的錯(cuò)誤。有一些C實(shí)現(xiàn),但不是所有的,帶有一個(gè)稱為lint的程序來(lái)捕獲這些錯(cuò)誤。如果具有一個(gè)這樣的程序,那么無(wú)論怎樣地強(qiáng)調(diào)它的重要性都不過(guò)分。3.1 你必須
30、自己檢查外部類型 假設(shè)你有一個(gè)C程序,被劃分為兩個(gè)文件。其中一個(gè)包含如下聲明:int n;而令一個(gè)包含如下聲明:long n;這不是一個(gè)有效的C程序,因?yàn)橐恍┩獠棵Q在兩個(gè)文件中被聲明為不同的類型。然而,很多實(shí)現(xiàn)檢測(cè)不到這個(gè)錯(cuò)誤,因?yàn)榫幾g器在編譯其中一個(gè)文件時(shí)并不知道另一個(gè)文件的內(nèi)容。因此,檢查類型的工作只能由連接器(或一些工具程序如lint)來(lái)完成;如果操作系統(tǒng)的連接器不能識(shí)別數(shù)據(jù)類型,C編譯器也沒(méi)法過(guò)多地強(qiáng)制它。 那么,這個(gè)程序運(yùn)行時(shí)實(shí)際會(huì)發(fā)生什么?這有很多可能性:實(shí)現(xiàn)足夠聰明,能夠檢測(cè)到類型沖突。則我們會(huì)得到一個(gè)診斷消息,說(shuō)明n在兩個(gè)文件中具有不同的類型。 你所使用的實(shí)現(xiàn)將int和lon
31、g視為相同的類型。典型的情況是機(jī)器可以自然地進(jìn)行32位運(yùn)算。在這種情況下你的程序或許能夠工作,好象你兩次都將變量聲明為long(或int)。但這種程序的工作純屬偶然。 n的兩個(gè)實(shí)例需要不同的存儲(chǔ),它們以某種方式共享存儲(chǔ)區(qū),即對(duì)其中一個(gè)的賦值對(duì)另一個(gè)也有效。這可能發(fā)生,例如,編譯器可以將int安排在long的低位。不論這是基于系統(tǒng)的還是基于機(jī)器的,這種程序的運(yùn)行同樣是偶然。 n的兩個(gè)實(shí)例以另一種方式共享存儲(chǔ)區(qū),即對(duì)其中一個(gè)賦值的效果是對(duì)另一個(gè)賦以不同的值。在這種情況下,程序可能失敗。 這種情況發(fā)生的里一個(gè)例子出奇地頻繁。程序的某一個(gè)文件包含下面的聲明:char filename = etc/pa
32、sswd;而另一個(gè)文件包含這樣的聲明:char *filename; 盡管在某些環(huán)境中數(shù)組和指針的行為非常相似,但它們是不同的。在第一個(gè)聲明中,filename是一個(gè)字符數(shù)組的名字。盡管使用數(shù)組的名字可以產(chǎn)生數(shù)組第一個(gè)元素的指針,但這個(gè)指針只有在需要的時(shí)候才產(chǎn)生并且不會(huì)持續(xù)。在第二個(gè)聲明中,filename是一個(gè)指針的名字。這個(gè)指針可以指向程序員讓它指向的任何地方。如果程序員沒(méi)有給它賦一個(gè)值,它將具有一個(gè)默認(rèn)的0值(NULL)(譯注實(shí)際上,在C中一個(gè)為初始化的指針通常具有一個(gè)隨機(jī)的值,這是很危險(xiǎn)的?。?。 這兩個(gè)聲明以不同的方式使用存儲(chǔ)區(qū),它們不可能共存。 避免這種類型沖突的一個(gè)方法是使用像li
33、nt這樣的工具(如果可以的話)。為了在一個(gè)程序的不同編譯單元之間檢查類型沖突,一些程序需要一次看到其所有部分。典型的編譯器無(wú)法完成,但lint可以。 避免該問(wèn)題的另一種方法是將外部聲明放到包含文件中。這時(shí),一個(gè)外部對(duì)象的類型僅出現(xiàn)一次7。4 語(yǔ)義缺陷 一個(gè)句子可以是精確拼寫的并且沒(méi)有語(yǔ)法錯(cuò)誤,但仍然沒(méi)有意義。在這一節(jié)中,我們將會(huì)看到一些程序的寫法會(huì)使得它們看起來(lái)是一個(gè)意思,但實(shí)際上是另一種完全不同的意思。 我們還要討論一些表面上看起來(lái)合理但實(shí)際上會(huì)產(chǎn)生未定義結(jié)果的環(huán)境。我們這里討論的東西并不保證能夠在所有的C實(shí)現(xiàn)中工作。我們暫且忘記這些能夠在一些實(shí)現(xiàn)中工作但可能不能在另一些實(shí)現(xiàn)中工作的東西,直
34、到第7節(jié)討論可以執(zhí)行問(wèn)題為止。4.1 表達(dá)式求值順序 一些C運(yùn)算符以一種已知的、特定的順序?qū)ζ洳僮鲾?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ù)只有在需要的時(shí)候才進(jìn)行求值。而?:運(yùn)算符中的三個(gè)操作數(shù):a、b和c,最先對(duì)a進(jìn)行求值,之后僅對(duì)b或c中
35、的一個(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)之前被求值。在某些實(shí)現(xiàn)中,這是可能的;但在另一些實(shí)現(xiàn)中卻不可能。另一種情況出于同樣的原因會(huì)失?。篿 = 0;while(i n) yi+ = xi;而下面的代碼是可以工作的:i = 0;while(i n) yi = xi;
36、 i+;當(dāng)然,這可以簡(jiǎn)寫為:for(i = 0; i n; i+) yi = xi;4.2 &、|和!運(yùn)算符 C中有兩種邏輯運(yùn)算符,在某些情況下是可以交換的:按位運(yùn)算符&、|和,以及邏輯運(yùn)算符&、|和!。一個(gè)程序員如果用某一類運(yùn)算符替換相應(yīng)的另一類運(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)?/p>
37、10和12都非零;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)的情況可以使它停下來(lái)。 首先,這兩個(gè)操作都是當(dāng)條件為假時(shí)返回0,當(dāng)條件為真時(shí)返回1。只要x和y都是1或0,x & y和x & y都具有相同的值。然而,如果當(dāng)使用了
38、除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)?不像&,總是會(huì)對(duì)所有的操作數(shù)進(jìn)行求值。因此循環(huán)的最后一次獲取tabi時(shí)i的值已經(jīng)等于tabsize了。如果tabsize是tab中元素的數(shù)量,則會(huì)取到tab中不存在的一個(gè)值。4.3 下標(biāo)從零開始 在很多語(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ǔ)言的
39、程序員應(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 10被替換成了i = 10,a中的一個(gè)編號(hào)為10的并不存在的元素被設(shè)置為了0,這樣內(nèi)存中a后面的一個(gè)字被破壞了。如果編譯該程序的編譯器按照降序地址為用戶變量分配內(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è)doub
40、le值作為它的參數(shù),但沒(méi)有得到。第二個(gè)原因是它返回一個(gè)double值但沒(méi)有這樣聲名。改正的方法只有一個(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)類型被轉(zhuǎn)換為double。所有的其它值不被轉(zhuǎn)換。確保函數(shù)參數(shù)類型的正確性是程序員的責(zé)任。 因此,一個(gè)程序員如果想使用如sqrt()這樣接受一個(gè)double類型參數(shù)的函數(shù),就必須僅傳遞給它float或double類型的參數(shù)。常數(shù)2是一個(gè)int,因此其類型是錯(cuò)誤的。 當(dāng)一個(gè)函數(shù)的值被用在表達(dá)式中時(shí)
41、,其值會(huì)被自動(dòng)地轉(zhuǎn)換為適當(dāng)?shù)念愋?。然而,為了完成這個(gè)自動(dòng)轉(zhuǎn)換,編譯器必須知道該函數(shù)實(shí)際返回的類型。沒(méi)有更進(jìn)一步聲名的函數(shù)被假設(shè)返回int,因此聲名這樣的函數(shù)并不是必須的。然而,sqrt()返回double,因此在成功使用它之前必須要聲名。 實(shí)際上,C實(shí)現(xiàn)通常允許一個(gè)文件包含include語(yǔ)句來(lái)包含如sqrt()這些庫(kù)函數(shù)的聲名,但是對(duì)那些自己寫函數(shù)的程序員來(lái)說(shuō),編寫聲名也是必要的或者說(shuō),對(duì)那些編寫非凡的C程序的人來(lái)說(shuō)是有必要的。 這里有一個(gè)更加壯觀的例子:main() int i; char c; for(i = 0; i 5; i+) scanf(%d, &c); printf(%d, i)
42、; printf(n); 表面上看,這個(gè)程序從標(biāo)準(zhǔn)輸入中讀取五個(gè)整數(shù)并向標(biāo)準(zhǔn)輸出寫入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附近確切是什么是編譯器的事;在這種情況下這有可能是i的低位。因此,每當(dāng)向c中讀入一個(gè)值,i就被
43、置零。當(dāng)程序最后到達(dá)文件結(jié)尾時(shí),scanf()不再嘗試向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)存,但這并不存在,直到你分配它。 讓我們?cè)僭囋?,為r分配一些內(nèi)存:char r100;strcpy(r, s);st
44、rcat(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ù)稱為strlen(),可以告訴我們一個(gè)字符串中有多少個(gè)字符:因此,我們可以寫:char *r, *malloc();r = malloc(strlen(s) + strlen(t);strcpy(r, s);strcat(r, t); 然而這個(gè)例子會(huì)因?yàn)閮蓚€(gè)原因而失敗。首先,malloc()可能會(huì)耗盡內(nèi)存,而這個(gè)事件僅通過(guò)靜靜地返回一個(gè)空指針來(lái)表
45、示。 其次,更重要的是,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(strlen(s) + strlen(t) + 1);if(!r) complain(); exit(1);strcpy(r, s);strcat(r, t);4.6 避免提喻法 提喻法(Synecdoche, si
46、n-ECK-duh-key)是一種文學(xué)手法,有點(diǎn)類似于明喻或暗喻,在牛津英文詞典中解釋如下:“a more comprehensive term is used for a less comprehensive or vice versa; as whole for part or part for whole, genus for species or species for genus, etc.(將全面的單位用作不全面的單位,或反之;如整體對(duì)局部或局部對(duì)整體、一般對(duì)特殊或特殊對(duì)一般,等等。)” 這可以精確地描述C中通常將指針誤以為是其指向的數(shù)據(jù)的錯(cuò)誤。正將常會(huì)在字符串中發(fā)生。例如:cha
47、r *p, *q;p = xyz;盡管認(rèn)為p的值是xyz有時(shí)是有用的,但這并不是真的,理解這一點(diǎn)非常重要。p的值是指向一個(gè)有四個(gè)字符的數(shù)組中第0個(gè)元素的指針,這四個(gè)字符是x、y、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è)例外。
48、這個(gè)例外是常數(shù)0,它可以保證被轉(zhuǎn)換為一個(gè)與其它任何有效指針都不相等的指針。這個(gè)值通常類似這樣定義:#define NULL 0但其效果是相同的。要記住的一個(gè)重要的事情是,當(dāng)用0作為指針時(shí)它決不能被解除引用。換句話說(shuō),當(dāng)你將0賦給一個(gè)指針變量后,你就不能訪問(wèn)它所指向的內(nèi)存。不能這樣寫:if(p = (char *)0) .也不能這樣寫:if(strcmp(p, (char *)0) = 0) .因?yàn)閟trcmp()總是通過(guò)其參數(shù)來(lái)查看內(nèi)存地址的。 如果p是一個(gè)空指針,這樣寫也是無(wú)效的:printf(p);或printf(%s, p);4.8 整數(shù)溢出 C語(yǔ)言關(guān)于整數(shù)操作的上溢或下溢定義得非常明確
49、。 只要有一個(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ī)器上,一個(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è)正確的方法是依賴于無(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)算符的人感到煩惱:在右移運(yùn)算中,空出的位是用0填充還是用符號(hào)位填充? 移位的數(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)
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 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ì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2023一年級(jí)數(shù)學(xué)上冊(cè) 五 位置與順序第1課時(shí) 前后配套教學(xué)實(shí)錄 北師大版
- 2《祖父的園子》教學(xué)設(shè)計(jì)-2023-2024學(xué)年語(yǔ)文五年級(jí)下冊(cè)統(tǒng)編版
- 4 猜字謎 教學(xué)設(shè)計(jì)-2024-2025學(xué)年語(yǔ)文一年級(jí)下冊(cè)統(tǒng)編版
- 2024-2025學(xué)年高中歷史 第二單元 工業(yè)文明的崛起和對(duì)中國(guó)的沖擊 第9課 改變世界的工業(yè)革命(2)教學(xué)教學(xué)實(shí)錄 岳麓版必修2
- 8鳳仙花的一生(教學(xué)設(shè)計(jì))-2023-2024學(xué)年科學(xué)三年級(jí)下冊(cè)人教鄂教版
- 商務(wù)溝通與談判技巧培訓(xùn)作業(yè)指導(dǎo)書
- 12家鄉(xiāng)的喜與憂(教學(xué)設(shè)計(jì))-統(tǒng)編版道德與法治四年級(jí)下冊(cè)
- 2023七年級(jí)數(shù)學(xué)下冊(cè) 第三章 變量之間的關(guān)系 3 用圖象表示的變量間關(guān)系第2課時(shí) 折線型圖像教學(xué)實(shí)錄 (新版)北師大版
- 向校園霸凌說(shuō)不相關(guān)研究
- 2024-2025學(xué)年新教材高中生物 第一章 走近細(xì)胞 第2節(jié) 細(xì)胞的多樣性和統(tǒng)一性(2)教學(xué)實(shí)錄 新人教版必修1
- 光伏安裝培訓(xùn)課件模板
- 應(yīng)急救援專項(xiàng)方案
- 有機(jī)化學(xué)(馮駿材編)課后習(xí)題答案
- 無(wú)人機(jī)的傳感器系統(tǒng)
- 新法律援助基礎(chǔ)知識(shí)講座
- 圖文解讀中小學(xué)教育懲戒規(guī)則(試行)全文內(nèi)容課件模板
- 起重機(jī)械安全技術(shù)規(guī)程(TSG-51-2023)宣貫解讀課件
- 《建筑攝影5構(gòu)》課件
- 《無(wú)塵室基礎(chǔ)知識(shí)》課件
- 2024虛擬電廠管理規(guī)范
- 供應(yīng)商體系稽核表QSA-Checklist
評(píng)論
0/150
提交評(píng)論