欧美色欧美亚洲高清在线观看,国产特黄特色a级在线视频,国产一区视频一区欧美,亚洲成a 人在线观看中文

  1. <ul id="fwlom"></ul>

    <object id="fwlom"></object>

    <span id="fwlom"></span><dfn id="fwlom"></dfn>

      <object id="fwlom"></object>

      C語言缺陷與陷阱

      時(shí)間:2019-05-14 02:12:16下載本文作者:會(huì)員上傳
      簡(jiǎn)介:寫寫幫文庫小編為你整理了多篇相關(guān)的《C語言缺陷與陷阱》,但愿對(duì)你工作學(xué)習(xí)有幫助,當(dāng)然你在寫寫幫文庫還可以找到更多《C語言缺陷與陷阱》。

      第一篇:C語言缺陷與陷阱

      C語言陷阱和缺陷

      [譯序]

      那些自認(rèn)為已經(jīng)“學(xué)完”C語言的人,請(qǐng)你們仔細(xì)讀閱讀這篇文章吧。路還長(zhǎng),很多東西要學(xué)。我也是??

      [概述]

      C語言像一把雕刻刀,鋒利,并且在技師手中非常有用。和任何鋒利的工具一樣,C會(huì)傷到那些不能掌握它的人。本文介紹C語言傷害粗心的人的方法,以及如何避免傷害。

      [內(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語句 2.5 函數(shù)調(diào)用

      2.6 懸掛else問題 3 鏈接

      3.1 你必須自己檢查外部類型 4 語義缺陷

      4.1 表達(dá)式求值順序 4.2 &&、||和!運(yùn)算符 4.3 下標(biāo)從零開始

      4.4 C并不總是轉(zhuǎn)換實(shí)參 4.5 指針不是數(shù)組 4.6 避免提喻法

      4.7 空指針不是空字符串 4.8 整數(shù)溢出 4.9 移位運(yùn)算符 5 庫函數(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)的還是無符號(hào)的? 7.4 右移位是帶符號(hào)的還是無符號(hào)的? 7.5 除法如何舍入? 7.6 一個(gè)隨機(jī)數(shù)有多大? 7.7 大小寫轉(zhuǎn)換

      7.8 先釋放,再重新分配 7.9 可移植性問題的一個(gè)實(shí)例 8 這里是空閑空間 參考 腳注

      0 簡(jiǎn)介

      C語言及其典型實(shí)現(xiàn)被設(shè)計(jì)為能被專家們?nèi)菀椎厥褂?。這門語言簡(jiǎn)潔并附有表達(dá)力。但有一些限制可以保護(hù)那些浮躁的人。一個(gè)浮躁的人可以從這些條款中獲得一些幫助。

      在本文中,我們將會(huì)看一看這些未可知的益處。這是由于它的未可知,我們無法為其進(jìn)行完全的分類。不過,我們?nèi)匀煌ㄟ^研究為了一個(gè)C程序的運(yùn)行所需要做的事來做到這些。我們假設(shè)讀者對(duì)C語言至少有個(gè)粗淺的了解。

      第一部分研究了當(dāng)程序被劃分為記號(hào)時(shí)會(huì)發(fā)生的問題。第二部分繼續(xù)研究了當(dāng)程序的記號(hào)被編譯器組合為聲明、表達(dá)式和語句時(shí)會(huì)出現(xiàn)的問題。第三部分研究了由多個(gè)部分組成、分別編譯并綁定到一起的C程序。第四部分處理了概念上的誤解:當(dāng)一個(gè)程序具體執(zhí)行時(shí)會(huì)發(fā)生的事情。第五部分研究了我們的程序和它們所使用的常用庫之間的關(guān)系。在第六部分中,我們注意到了我們所寫的程序也不并不是我們所運(yùn)行的程序;預(yù)處理器將首先運(yùn)行。最后,第七部分討論了可移植性問題:一個(gè)能在一個(gè)實(shí)現(xiàn)中運(yùn)行的程序無法在另一個(gè)實(shí)現(xiàn)中運(yùn)行的原因。詞法缺陷

      編譯器的第一個(gè)部分常被稱為詞法分析器(lexical analyzer)。詞法分析器檢查組成程序的字符序列,并將它們劃分為記號(hào)(token)一個(gè)記號(hào)是一個(gè)有一個(gè)或多個(gè)字符的序列,它在語言被編譯時(shí)具有一個(gè)(相關(guān)地)統(tǒng)一的意義。在C中,例如,記號(hào)->的意義和組成它的每個(gè)獨(dú)立的字符具有明顯的區(qū)別,而且其意義獨(dú)立于->出現(xiàn)的上下文環(huán)境。

      另外一個(gè)例子,考慮下面的語句:

      if(x > big)big = x;

      該語句中的每一個(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í)符。它必須通過對(duì)每個(gè)宏進(jìn)行求值來替換宏調(diào)用。最后,經(jīng)過宏替換的程序又被匯集成字符流送給編譯器。編譯器再第二次將這個(gè)流劃分為記號(hào)。

      在這一節(jié)中,我們將探索對(duì)記號(hào)的意義的普遍的誤解以及記號(hào)和組成它們的字符之間的關(guān)系。稍后我們將談到預(yù)處理器。

      1.1 = 不是 == 從Algol派生出來的語言,如Pascal和Ada,用:=表示賦值而用=表示比較。而C語言則是用=表示賦值而用==表示比較。這是因?yàn)橘x值的頻率要高于比較,因此為其分配更短的符號(hào)。

      此外,C還將賦值視為一個(gè)運(yùn)算符,因此可以很容易地寫出多重賦值(如a = b = c),并且可以將賦值嵌入到一個(gè)大的表達(dá)式中。

      這種便捷導(dǎo)致了一個(gè)潛在的問題:可能將需要比較的地方寫成賦值。因此,下面的語句好像看起來是要檢查x是否等于y:

      if(x = y)foo();

      而實(shí)際上是將x設(shè)置為y的值并檢查結(jié)果是否非零。在考慮下面的一個(gè)希望跳過空格、制表符和換行符的循環(huán):

      while(c == ' ' || c = 't' || c == 'n')c = getc(f);

      在與't'進(jìn)行比較的地方程序員錯(cuò)誤地使用=代替了==。這個(gè)“比較”實(shí)際上是將't'賦給c,然后判斷c的(新的)值是否為零。因?yàn)?t'不為零,這個(gè)“比較”將一直為真,因此這個(gè)循環(huán)會(huì)吃盡整個(gè)文件。這之后會(huì)發(fā)生什么取決于特定的實(shí)現(xiàn)是否允許一個(gè)程序讀取超過文件尾部的部分。如果允許,這個(gè)循環(huán)會(huì)一直運(yùn)行。

      一些C編譯器會(huì)對(duì)形如e1 = e2的條件給出一個(gè)警告以提醒用戶。當(dāng)你趨勢(shì)需要先對(duì)一個(gè)變量進(jìn)行賦值之后再檢查變量是否非零時(shí),為了在這種編譯器中避免警告信息,應(yīng)考慮顯式給出比較符。換句話說,將:

      if(x = y)foo();

      改寫為:

      if((x = y)!= 0)foo();

      這樣可以清晰地表示你的意圖。

      1.2 & 和 | 不是 && 和 || 容易將==錯(cuò)寫為=是因?yàn)楹芏嗥渌Z言使用=表示比較運(yùn)算。其他容易寫錯(cuò)的運(yùn)算符還有&和&&,或|和||,這主要是因?yàn)镃語言中的&和|運(yùn)算符于其他語言中具有類似功能的運(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語言參考手冊(cè)說明了如何決定:“如果輸入流到一個(gè)給定的字符串為止已經(jīng)被識(shí)別為記號(hào),則應(yīng)該包含下一個(gè)字符以組成能夠構(gòu)成記號(hào)的最長(zhǎng)的字符串”。因此,如果/是一個(gè)記號(hào)的第一個(gè)字符,并且/后面緊隨了一個(gè)*,則這兩個(gè)字符構(gòu)成了注釋的開始,不管其他上下文環(huán)境。

      下面的語句看起來像是將y的值設(shè)置為x的值除以p所指向的值:

      y = x/*p /* p 指向除數(shù) */;

      實(shí)際上,/*開始了一個(gè)注釋,因此編譯器簡(jiǎn)單地吞噬程序文本,直到*/的出現(xiàn)。換句話說,這條語句僅僅把y的值設(shè)置為x的值,而根本沒有看到p。將這條語句重寫為:

      y = x / *p /* p 指向除數(shù) */;

      或者干脆是

      y = x /(*p)/* p指向除數(shù) */;

      它就可以做注釋所暗示的除法了。

      這種模棱兩可的寫法在其他環(huán)境中就會(huì)引起麻煩。例如,老版本的C使用=+表示現(xiàn)在版本中的+=。這樣的編譯器會(huì)將

      a=-1;

      視為

      a =-1;或

      a = a> a

      是不合法的。它和

      p-> a

      不是同義詞。

      另一方面,有些老式編譯器還是將=+視為一個(gè)單獨(dú)的記號(hào)并且和+=是同義詞。

      1.5 字符串和字符

      單引號(hào)和雙引號(hào)在C中的意義完全不同,在一些混亂的上下文中它們會(huì)導(dǎo)致奇怪的結(jié)果而不是錯(cuò)誤消息。

      包圍在單引號(hào)中的一個(gè)字符只是書寫整數(shù)的另一種方法。這個(gè)整數(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è)無名數(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è)指針來代替一個(gè)整數(shù)通常會(huì)得到一個(gè)警告消息(反之亦然),使用雙引號(hào)來代替單引號(hào)也會(huì)得到一個(gè)警告消息(反之亦然)。但對(duì)于不檢查參數(shù)類型的編譯器卻除外。因此,用

      printf('n');

      來代替

      printf(“n”);

      通常會(huì)在運(yùn)行時(shí)得到奇怪的結(jié)果。

      由于一個(gè)整數(shù)通常足夠大,以至于能夠放下多個(gè)字符,一些C編譯器允許在一個(gè)字符常量中存放多個(gè)字符。這意味著用'yes'代替“yes”將不會(huì)被發(fā)現(xiàn)。后者意味著“分別包含y、e、s和一個(gè)空字符的四個(gè)連續(xù)存貯器區(qū)域中的第一個(gè)的地址”,而前者意味著“在一些實(shí)現(xiàn)定義的樣式中表示由字符y、e、s聯(lián)合構(gòu)成的一個(gè)整數(shù)”。這兩者之間的任何一致性都純屬巧合。句法缺陷

      要理解C語言程序,僅了解構(gòu)成它的記號(hào)是不夠的。還要理解這些記號(hào)是如何構(gòu)成聲明、表達(dá)式、語句和程序的。盡管這些構(gòu)成通常都是定義良好的,但這些定義有時(shí)候是有悖于直覺的或混亂的。

      在這一節(jié)中,我們將著眼于一些不明顯句法構(gòu)造。

      2.1 理解聲明

      我曾經(jīng)和一些人聊過天,他們那時(shí)在書寫在一個(gè)小型的微處理器上單機(jī)運(yùn)行的C程序。當(dāng)這臺(tái)機(jī)器的開關(guān)打開的時(shí)候,硬件會(huì)調(diào)用地址為0處的子程序。

      為了模仿電源打開的情形,我們要設(shè)計(jì)一條C語句來顯式地調(diào)用這個(gè)子程序。經(jīng)過一些思考,我們寫出了下面的語句:

      (*(void(*)())0)();

      這樣的表達(dá)式會(huì)令C程序員心驚膽戰(zhàn)。但是,并不需要這樣,因?yàn)樗麄兛梢栽谝粋€(gè)簡(jiǎn)單的規(guī)則的幫助下很容易地構(gòu)造它:以你使用的方式聲明它。

      每個(gè)C變量聲明都具有兩個(gè)部分:一個(gè)類型和一組具有特定格式的期望用來對(duì)該類型求值的表達(dá)式。最簡(jiǎn)單的表達(dá)式就是一個(gè)變量:

      float f, g;

      說明表達(dá)式f和g——在求值的時(shí)候——具有類型float。由于待求值的時(shí)表達(dá)式,因此可以自由地使用圓括號(hào): float((f));

      者表示((f))求值為float并且因此,通過推斷,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 *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è)我們有一個(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á)式來替換fp。

      這個(gè)問題就是我們的第二步分析。如果C可以讀入并理解類型,我們可以寫:

      (*0)();

      但這樣并不行,因?yàn)?運(yùn)算符要求必須有一個(gè)指針作為他的操作數(shù)。另外,這個(gè)操作數(shù)必須是一個(gè)指向函數(shù)的指針,以保證*的結(jié)果可以被調(diào)用。因此,我們需要將0轉(zhuǎn)換為一個(gè)可以描述“指向一個(gè)返回void的函數(shù)的指針”的類型。如果fp是一個(gè)指向返回void的函數(shù)的指針,則(*fp)()是一個(gè)void值,并且它的聲明將會(huì)是這樣的:

      void(*fp)();

      因此,我們需要寫:

      void(*fp)();(*fp)();

      來聲明一個(gè)啞變量。一旦我們知道了如何聲明該變量,我們也就知道了如何將一個(gè)常數(shù)轉(zhuǎn)換為該類型:只要從變量的聲明中去掉名字即可。因此,我們像下面這樣將0轉(zhuǎn)換為一個(gè)“指向返回void的函數(shù)的指針”:

      (void(*)())0

      接下來,我們用(void(*)())0來替換fp:

      (*(void(*)())0)();

      結(jié)尾處的分號(hào)用于將這個(gè)表達(dá)式轉(zhuǎn)換為一個(gè)語句。

      在這里,我們就解決了這個(gè)問題時(shí)沒有使用typedef聲明。通過使用它,我們可以更清晰地解決這個(gè)問題:

      typedef void(*funcptr)();(*(funcptr)0)();

      2.2 運(yùn)算符并不總是具有你所想象的優(yōu)先級(jí)

      假設(shè)有一個(gè)聲明了的常量FLAG是一個(gè)整數(shù),其二進(jìn)制表示中的某一位被置位(換句話說,它是2的某次冪),并且你希望測(cè)試一個(gè)整型變量flags該位是否被置位。通常的寫法是:

      if(flags & FLAG)...其意義對(duì)于很多C程序員都是很明確的:if語句測(cè)試?yán)ㄌ?hào)中的表達(dá)式求值的結(jié)果是否為0。出于清晰的目的我們可以將它寫得更明確:

      if(flags & FLAG!= 0)...這個(gè)語句現(xiàn)在更容易理解了。但它仍然是錯(cuò)的,因?yàn)?=比&綁定得更緊密,因此它被分析為:

      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;

      避免這種問題的一個(gè)方法是將所有的東西都用括號(hào)括起來,但表達(dá)式中的括號(hào)過度就會(huì)難以理解,因此最好還是是記住C中的優(yōu)先級(jí)。

      不幸的是,這有15個(gè),太困難了。然而,通過將它們分組可以變得容易。

      綁定得最緊密的運(yùn)算符并不是真正的運(yùn)算符:下標(biāo)、函數(shù)調(diào)用和結(jié)構(gòu)選擇。這些都與左邊相關(guān)聯(lián)。

      接下來是一元運(yùn)算符。它們具有真正的運(yùn)算符中的最高優(yōu)先級(jí)。由于函數(shù)調(diào)用比一元運(yùn)算符綁定得更緊密,你必須寫(*p)()來調(diào)用p指向的函數(shù);*p()表示p是一個(gè)返回一個(gè)指針的函數(shù)。轉(zhuǎn)換是一元運(yùn)算符,并且和其他一元運(yùn)算符具有相同的優(yōu)先級(jí)。一元運(yùn)算符是右結(jié)合的,因此*p++表示*(p++),而不是(*p)++。

      在接下來是真正的二元運(yùn)算符。其中數(shù)學(xué)運(yùn)算符具有最高的優(yōu)先級(jí),然后是移位運(yùn)算符、關(guān)系運(yùn)算符、邏輯運(yùn)算符、賦值運(yùn)算符,最后是條件運(yùn)算符。需要記住的兩個(gè)重要的東西是:

      所有的邏輯運(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)算符中,沒有任何兩個(gè)具有相同的優(yōu)先級(jí)。按位運(yùn)算符比所有順序運(yùn)算符綁定得都緊密,每種與運(yùn)算符都比相應(yīng)的或運(yùn)算符綁定得更緊密,并且按位異或(^)運(yùn)算符介于按位與和按位或之間。

      三元運(yùn)算符的優(yōu)先級(jí)比我們提到過的所有運(yùn)算符的優(yōu)先級(jí)都低。這可以保證選擇表達(dá)式中包含的關(guān)系運(yùn)算符的邏輯組合特性,如:

      z = a < b && b < c ? d : e

      這個(gè)例子還說明了賦值運(yùn)算符具有比條件運(yùn)算符更低的優(yōu)先級(jí)是有意義的。另外,所有的復(fù)合賦值運(yùn)算符具有相同的優(yōu)先級(jí)并且是自右至左結(jié)合的,因此

      a = b = c 和

      b = c;a = b;

      是等價(jià)的。

      具有最低優(yōu)先級(jí)的是逗號(hào)運(yùn)算符。這很容易理解,因?yàn)槎禾?hào)通常在需要表達(dá)式而不是語句的時(shí)候用來替代分號(hào)。

      賦值是另一種運(yùn)算符,通常具有混合的優(yōu)先級(jí)。例如,考慮下面這個(gè)用于復(fù)制文件的循環(huán):

      while(c = getc(in)!= EOF)putc(c, out);

      這個(gè)while循環(huán)中的表達(dá)式看起來像是c被賦以getc(in)的值,接下來判斷是否等于EOF以結(jié)束循環(huán)。不幸的是,賦值的優(yōu)先級(jí)比任何比較操作都低,因此c的值將會(huì)是getc(in)和EOF比較的結(jié)果,并且會(huì)被拋棄。因此,“復(fù)制”得到的文件將是一個(gè)由值為1的字節(jié)流組成的文件。

      上面這個(gè)例子正確的寫法并不難:

      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){

      這條語句希望給t賦一個(gè)值,然后看t是否與STRTY或UNIONTY相等。而實(shí)際的效果卻大不相同[3]。

      C中的邏輯運(yùn)算符的優(yōu)先級(jí)具有歷史原因。B——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ì)帶來一點(diǎn)點(diǎn)不同:或者是一個(gè)空語句,無任何效果;或者編譯器可能提出一個(gè)診斷消息,可以方便除去掉它。一個(gè)重要的區(qū)別是在必須跟有一個(gè)語句的if和while語句中??紤]下面的例子:

      if(x > big);big = x;這不會(huì)發(fā)生編譯錯(cuò)誤,但這段程序的意義與: if(x > big)big = x;就大不相同了。第一個(gè)程序段等價(jià)于: if(x > big){ } big = x;也就是等價(jià)于: big = x;(除非x、i或big是帶有副作用的宏)。另一個(gè)因分號(hào)引起巨大不同的地方是函數(shù)定義前面的結(jié)構(gòu)聲明的末尾[譯注:這句話不太好聽,看例子就明白了]??紤]下面的程序片段: struct foo { int x;} f(){...} 在緊挨著f的第一個(gè)}后面丟失了一個(gè)分號(hào)。它的效果是聲明了一個(gè)函數(shù)f,返回值類型是struct foo,這個(gè)結(jié)構(gòu)成了函數(shù)聲明的一部分。如果這里出現(xiàn)了分號(hào),則f將被定義為具有默認(rèn)的整型返回值[5]。2.4 switch語句 通常C中的switch語句中的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 of 1: write('red');2: write('yellow');3: write('blue');end 這兩個(gè)程序片斷都作相同的事情:根據(jù)變量color的值是1、2還是3打印red、yellow或blue(沒有新行符)。這兩個(gè)程序片斷非常相似,只有一點(diǎn)不同:Pascal程序中沒有C中相應(yīng)的break語句。C中的case標(biāo)簽是真正的標(biāo)簽:控制流程可以無限制地進(jìn)入到一個(gè)case標(biāo)簽中??纯戳硪环N形式,假設(shè)C程序段看起來更像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)用。這既是C語言switch語句的優(yōu)點(diǎn)又是它的弱點(diǎn)。說它是弱點(diǎn),是因?yàn)楹苋菀淄浺粋€(gè)break語句,從而導(dǎo)致程序出現(xiàn)隱晦的異常行為。說它是優(yōu)點(diǎn),是因?yàn)橥ㄟ^故意去掉break語句,可以很容易實(shí)現(xiàn)其他方法難以實(shí)現(xiàn)的控制結(jié)構(gòu)。尤其是在一個(gè)大型的switch語句中,我們經(jīng)常發(fā)現(xiàn)對(duì)一個(gè)case的處理可以簡(jiǎn)化其他一些特殊的處理。例如,設(shè)想有一個(gè)程序是一臺(tái)假想的機(jī)器的翻譯器。這樣的一個(gè)程序可能包含一個(gè)switch語句來處理各種操作碼。在這樣一臺(tái)機(jī)器上,通常減法在對(duì)其第二個(gè)運(yùn)算數(shù)進(jìn)行變號(hào)后就變成和加法一樣了。因此,最好可以寫出這樣的語句: case SUBTRACT: opnd2 =-opnd2;/* no break;*/ case ADD:...另外一個(gè)例子,考慮編譯器通過跳過空白字符來查找一個(gè)記號(hào)。這里,我們將空格、制表符和新行符視為是相同的,除了新行符還要引起行計(jì)數(shù)器的增長(zhǎng)外: case 'n': linecount++;/* no break */ case 't': case ' ':...2.5 函數(shù)調(diào)用 和其他程序設(shè)計(jì)語言不同,C要求一個(gè)函數(shù)調(diào)用必須有一個(gè)參數(shù)列表,但可以沒有參數(shù)。因此,如果f是一個(gè)函數(shù),f();就是對(duì)該函數(shù)進(jìn)行調(diào)用的語句,而 f;什么也不做。它會(huì)作為函數(shù)地址被求值,但不會(huì)調(diào)用它[6]。2.6 懸掛else問題 在討論任何語法缺陷時(shí)我們都不會(huì)忘記提到這個(gè)問題。盡管這一問題不是C語言所獨(dú)有的,但它仍然傷害著那些有著多年經(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 + y;f(&z);} } 換句話說,當(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è)文件,因此它無法檢測(cè)到需要程序的多個(gè)源文件的內(nèi)容才能發(fā)現(xiàn)的錯(cuò)誤。在這一節(jié)中,我們將看到一些這種類型的錯(cuò)誤。有一些C實(shí)現(xiàn),但不是所有的,帶有一個(gè)稱為lint的程序來捕獲這些錯(cuò)誤。如果具有一個(gè)這樣的程序,那么無論怎樣地強(qiáng)調(diào)它的重要性都不過分。3.1 你必須自己檢查外部類型 假設(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)來完成;如果操作系統(tǒng)的鏈接器不能識(shí)別數(shù)據(jù)類型,C編譯器也沒法過多地強(qiáng)制它。那么,這個(gè)程序運(yùn)行時(shí)實(shí)際會(huì)發(fā)生什么?這有很多可能性: 實(shí)現(xiàn)足夠聰明,能夠檢測(cè)到類型沖突。則我們會(huì)得到一個(gè)診斷消息,說明n在兩個(gè)文件中具有不同的類型。你所使用的實(shí)現(xiàn)將int和long視為相同的類型。典型的情況是機(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/passwd”;而另一個(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è)指針可以指向程序員讓它指向的任何地方。如果程序員沒有給它賦一個(gè)值,它將具有一個(gè)默認(rèn)的0值(null)[譯注:實(shí)際上,在C中一個(gè)為初始化的指針通常具有一個(gè)隨機(jī)的值,這是很危險(xiǎn)的!]。這兩個(gè)聲明以不同的方式使用存儲(chǔ)區(qū),他們不可能共存。避免這種類型沖突的一個(gè)方法是使用像lint這樣的工具(如果可以的話)。為了在一個(gè)程序的不同編譯單元之間檢查類型沖突,一些程序需要一次看到其所有部分。典型的編譯器無法完成,但lint可以。避免該問題的另一種方法是將外部聲明放到包含文件中。這時(shí),一個(gè)外部對(duì)象的類型僅出現(xiàn)一次[7]。4 語義缺陷 一個(gè)句子可以是精確拼寫的并且沒有語法錯(cuò)誤,但仍然沒有意義。在這一節(jié)中,我們將會(huì)看到一些程序的寫法會(huì)使得它們看起來是一個(gè)意思,但實(shí)際上是另一種完全不同的意思。我們還要討論一些表面上看起來合理但實(shí)際上會(huì)產(chǎn)生未定義結(jié)果的環(huán)境。我們這里討論的東西并不保證能夠在所有的C實(shí)現(xiàn)中工作。我們暫且忘記這些能夠在一些實(shí)現(xiàn)中工作但可能不能在另一些實(shí)現(xiàn)中工作的東西,直到第7節(jié)討論可以執(zhí)行問題為止。4.1 表達(dá)式求值順序 一些C運(yùn)算符以一種已知的、特定的順序?qū)ζ洳僮鲾?shù)進(jìn)行求值。但另一些不能。例如,考慮下面的表達(dá)式: a < b && c < d C語言定義規(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中的一個(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)y = x[i++];其中的問題是y的地址并不保證在i增長(zhǎng)之前被求值。在某些實(shí)現(xiàn)中,這是可能的;但在另一些實(shí)現(xiàn)中卻不可能。另一種情況出于同樣的原因會(huì)失?。? i = 0;while(i < n)y[i++] = x;而下面的代碼是可以工作的: i = 0;while(i < n){ y = x;i++;} 當(dāng)然,這可以簡(jiǎn)寫為: for(i = 0;i < n;i++)y = x;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)可以通過左邊的操作數(shù)確定其返回值時(shí),就不會(huì)對(duì)右邊的操作數(shù)進(jìn)行求值。因此!10是零,因?yàn)?0非零;10 && 12是1,因?yàn)?0和12都非零;10 || 12也是1,因?yàn)?0非零。另外,最后一個(gè)表達(dá)式中的12不會(huì)被求值,10 || f()中的f()也不會(huì)被求值??紤]下面這段用于在一個(gè)表中查找一個(gè)特定元素的程序: i = 0;while(i < tabsize && tab!= x)i++;這段循環(huán)背后的意思是如果i等于tabsize時(shí)循環(huán)結(jié)束,元素未被找到。否則,i包含了元素的索引。假設(shè)這個(gè)例子中的&&不小心被替換為了&,這個(gè)循環(huán)可能仍然能夠工作,但只有兩種幸運(yùn)的情況可以使它停下來。首先,這兩個(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ì)改變,因此越過數(shù)組最后一個(gè)元素進(jìn)一個(gè)位置時(shí)是無害的,循環(huán)會(huì)幸運(yùn)地停下來。失誤的程序會(huì)越過數(shù)組的結(jié)尾,因?yàn)?不像&&,總是會(huì)對(duì)所有的操作數(shù)進(jìn)行求值。因此循環(huán)的最后一次獲取tab時(shí)i的值已經(jīng)等于tabsize了。如果tabsize是tab中元素的數(shù)量,則會(huì)取到tab中不存在的一個(gè)值。4.3 下標(biāo)從零開始 在很多語言中,具有n個(gè)元素的數(shù)組其元素的號(hào)碼和它的下標(biāo)是從1到n嚴(yán)格對(duì)應(yīng)的。但在C中不是這樣。一個(gè)具有n個(gè)元素的C數(shù)組中沒有下標(biāo)為n的元素,其中的元素的下標(biāo)是從0到n'a';return c;} 在很多C實(shí)現(xiàn)中,為了減少比實(shí)際計(jì)算還要多的調(diào)用開銷,通常將其實(shí)現(xiàn)為宏: #define toupper(c)((c)>= 'a' &&(c)<= 'z' ?(c)+('A''a')#define tolower(c)((c)+ 'A''a' :(c))#define tolower(c)((c)>= 'A' &&(c)<= 'Z' ?(c)+ 'a''a';return c;} tolower()類似。這個(gè)改變帶來更多的問題,每次使用這些函數(shù)的時(shí)候都會(huì)引入函數(shù)調(diào)用開銷。我們的英雄認(rèn)為一些人可能不愿意支付這些開銷,因此他們將這個(gè)宏重命名為: #define _toupper(c)((c)+ 'A''A')這就允許用戶選擇方便或速度。這里面其實(shí)只有一個(gè)問題:伯克利的人們和其他的C實(shí)現(xiàn)者并沒有跟著這么做。這意味著一個(gè)在AT&T系統(tǒng)上編寫的使用了toupper()或tolower()的程序,如果沒有為其傳遞正確大小寫字母參數(shù),在其他C實(shí)現(xiàn)中可能不會(huì)正常工作。如果不知道這些歷史,可能很難對(duì)這類錯(cuò)誤進(jìn)行跟蹤。7.8 先釋放,再重新分配 很多C實(shí)現(xiàn)為用戶提供了三個(gè)內(nèi)存分配函數(shù):malloc()、realloc()和free()。調(diào)用malloc(n)返回一個(gè)指向有n個(gè)字符的新分配的內(nèi)存的指針,這個(gè)指針可以由程序員使用。給free()傳遞一個(gè)指向由malloc()分配的內(nèi)存的指針可以使這塊內(nèi)存得以重用。通過一個(gè)指向已分配區(qū)域的指針和一個(gè)新的大小調(diào)用realloc()可以將這塊內(nèi)存擴(kuò)大或縮小到新尺寸,這個(gè)過程中可能要復(fù)制內(nèi)存。也許有人會(huì)想,真相真是有點(diǎn)微妙啊。下面是System V接口定義中出現(xiàn)的對(duì)realloc()的描述: realloc改變一個(gè)由ptr指向的size個(gè)字節(jié)的塊,并返回該塊(可能被移動(dòng))的指針。在新舊尺寸中比較小的一個(gè)尺寸之下的內(nèi)容不會(huì)被改變。而UNIX系統(tǒng)第七版的參考手冊(cè)中包含了這一段的副本。此外,還包含了描述realloc()的另外一段: 如果在最后一次調(diào)用malloc、realloc或calloc后釋放了ptr所指向的塊,realloc依舊可以工作;因此,free、malloc和realloc的順序可以利用malloc壓縮存貯的查找策略。因此,下面的代碼片段在UNIX第七版中是合法的: free(p);p = realloc(p, newsize);這一特性保留在從UNIX第七版衍生出來的系統(tǒng)中:可以先釋放一塊存儲(chǔ)區(qū)域,然后再重新分配它。這意味著,在這些系統(tǒng)中釋放的內(nèi)存中的內(nèi)容在下一次內(nèi)存分配之前可以保證不變。因此,在這些系統(tǒng)中,我們可以用下面這種奇特的思想來釋放一個(gè)鏈表中的所有元素: for(p = head;p!= NULL;p = p->next)free((char *)p);而不用擔(dān)心調(diào)用free()會(huì)導(dǎo)致p->next不可用。不用說,這種技術(shù)是不推薦的,因?yàn)椴皇撬蠧實(shí)現(xiàn)都能在內(nèi)存被釋放后將它的內(nèi)容保留足夠長(zhǎng)的時(shí)間。然而,第七版的手冊(cè)遺留了一個(gè)未聲明的問題:realloc()的原始實(shí)現(xiàn)實(shí)際上是必須要先釋放再重新分配的。出于這個(gè)原因,一些C程序都是先釋放內(nèi)存再重新分配的,而當(dāng)這些程序移植到其他實(shí)現(xiàn)中時(shí)就會(huì)出現(xiàn)問題。7.9 可移植性問題的一個(gè)實(shí)例 讓我們來看一個(gè)已經(jīng)被很多人在很多時(shí)候解決了的問題。下面的程序帶有兩個(gè)參數(shù):一個(gè)長(zhǎng)整數(shù)和一個(gè)函數(shù)(的指針)。它將整數(shù)轉(zhuǎn)換位十進(jìn)制數(shù),并用代表其中每一個(gè)數(shù)字的字符來調(diào)用給定的函數(shù)。void printnum(long n, void(*p)()){ if(n < 0){(*p)('-');n =-n;} if(n >= 10)printnum(n / 10, p);(*p)(n % 10 + '0');} 這個(gè)程序非常簡(jiǎn)單。首先檢查n是否為負(fù)數(shù);如果是,則打印一個(gè)符號(hào)并將n變?yōu)檎龜?shù)。接下來,測(cè)試是否n >= 10。如果是,則它的十進(jìn)制表示中包含兩個(gè)或更多個(gè)數(shù)字,因此我們遞歸地調(diào)用printnum()來打印除最后一個(gè)數(shù)字外的所有數(shù)字。最后,我們打印最后一個(gè)數(shù)字。這個(gè)程序——由于它的簡(jiǎn)單——具有很多可移植性問題。首先是將n的低位數(shù)字轉(zhuǎn)換成字符形式的方法。用n % 10來獲取低位數(shù)字的值是好的,但為它加上'0'來獲得相應(yīng)的字符表示就不好了。這個(gè)加法假設(shè)機(jī)器中順序的數(shù)字所對(duì)應(yīng)的字符數(shù)順序的,沒有間隔,因此'0' + 5和'5'的值是相同的,等等。盡管這個(gè)假設(shè)對(duì)于ASCII和EBCDIC字符集是成立的,但對(duì)于其他一些機(jī)器可能不成立。避免這個(gè)問題的方法是使用一個(gè)表: void printnum(long n, void(*p)()){ if(n < 0){(*p)('-');n =-n;} if(n >= 10)printnum(n / 10, p);(*p)(“0123456789”[n % 10]);} 另一個(gè)問題發(fā)生在當(dāng)n < 0時(shí)。這時(shí)程序會(huì)打印一個(gè)負(fù)號(hào)并將n設(shè)置為-n。這個(gè)賦值會(huì)發(fā)生溢出,因?yàn)樵谑褂?的補(bǔ)碼的機(jī)器上通常能夠表示的負(fù)數(shù)比正數(shù)要多。例如,一個(gè)(長(zhǎng))整數(shù)有k位和一個(gè)附加位表示符號(hào),則-2k可以表示而2k卻不能。解決這一問題有很多方法。最直觀的一種是將n賦給一個(gè)unsigned long值。然而,一些C便一起可能沒有實(shí)現(xiàn)unsigned long,因此我們來看看沒有它怎么辦。在第一個(gè)實(shí)現(xiàn)和第二個(gè)實(shí)現(xiàn)的機(jī)器上,改變一個(gè)正整數(shù)的符號(hào)保證不會(huì)發(fā)生溢出。問題僅出在改變一個(gè)負(fù)數(shù)的符號(hào)時(shí)。因此,我們可以通過避免將n變?yōu)檎龜?shù)來避免這個(gè)問題。當(dāng)然,一旦我們打印了負(fù)數(shù)的符號(hào),我們就能夠?qū)⒇?fù)數(shù)和正數(shù)視為是一樣的。下面的方法就強(qiáng)制在打印符號(hào)之后n為負(fù)數(shù),并且用負(fù)數(shù)值完成我們所有的算法。如果我們這么做,我們就必須保證程序中打印符號(hào)的部分只執(zhí)行一次;一個(gè)簡(jiǎn)單的方法是將這個(gè)程序劃分為兩個(gè)函數(shù): void printnum(long n, void(*p)()){ if(n < 0){(*p)('-');printneg(n, p);} else printneg(-n, p);} void printneg(long n, void(*p)()){ if(n <=-10)printneg(n / 10, p);(*p)(“0123456789”[-(n % 10)]);} printnum()現(xiàn)在只檢查要打印的數(shù)是否為負(fù)數(shù);如果是的話則打印一個(gè)符號(hào)。否則,它以n的負(fù)絕對(duì)值來調(diào)用printneg()。我們同時(shí)改變了printneg()的函數(shù)體來適應(yīng)n永遠(yuǎn)是負(fù)數(shù)或零這一事實(shí)。我們得到什么?我們使用n / 10和n % 10來獲取n的前導(dǎo)數(shù)字和結(jié)尾數(shù)字(經(jīng)過適當(dāng)?shù)姆?hào)變換)。調(diào)用整數(shù)除法的行為在其中一個(gè)操作數(shù)為負(fù)的時(shí)候是實(shí)現(xiàn)相關(guān)的。因此,n % 10有可能是正的!這時(shí),-(n % 10)是正數(shù),將會(huì)超出我們的數(shù)字字符數(shù)組的末尾。為了解決這一問題,我們建立兩個(gè)臨時(shí)變量來存放商和余數(shù)。作完除法后,我們檢查余數(shù)是否在正確的范圍內(nèi),如果不是的話則調(diào)整這兩個(gè)變量。printnum()沒有改變,因此我們只列出printneg(): void printneg(long n, void(*p)()){ long q;int r;if(r > 0){ r-= 10;q++;} if(n <=-10){ printneg(q, p);}(*p)(“0123456789”[-r]);} 8 這里是空閑空間 還有很多可能讓C程序員誤入迷途的地方本文沒有提到。如果你發(fā)現(xiàn)了,請(qǐng)聯(lián)系作者。在以后的版本中它會(huì)被包含進(jìn)來,并添加一個(gè)表示感謝的腳注。參考 《The C Programming Language》(Kernighan and Ritchie, Prentice-Hall 1978)是最具權(quán)威的C著作。它包含了一個(gè)優(yōu)秀的教程,面向那些熟悉其他高級(jí)語言程序設(shè)計(jì)的人,和一個(gè)參考手冊(cè),簡(jiǎn)潔地描述了整個(gè)語言。盡管自1978年以來這門語言發(fā)生了不少變化,這本書對(duì)于很多主題來說仍然是個(gè)定論。這本書同時(shí)還包含了本文中多次提到的“C語言參考手冊(cè)”?!禩he C Puzzle Book》(Feuer, Prentice-Hall, 1982)是一本少見的磨煉人們文法能力的書。這本書收集了很多謎題(和答案),它們的解決方法能夠測(cè)試讀者對(duì)于C語言精妙之處的知識(shí)。《C: A Referenct Manual》(Harbison and Steele, Prentice Hall 1984)是特意為實(shí)現(xiàn)者編寫的一本參考資料。其他人也會(huì)發(fā)現(xiàn)它是特別有用的——因?yàn)樗軓闹袇⒖技?xì)節(jié)。------------------腳注 1.這本書是基于圖書《C Traps and Pitfalls》(Addison-Wesley, 1989, ISBN 0-201-17928-8)的一個(gè)擴(kuò)充,有興趣的讀者可以讀一讀它。2.因?yàn)?=的結(jié)果不是1就是0。3.感謝Guy Harris為我指出這個(gè)問題。4.Dennis Ritchie和Steve Johnson同時(shí)向我指出了這個(gè)問題。5.感謝一位不知名的志愿者提出這個(gè)問題。6.感謝Richard Stevens指出了這個(gè)問題。7.一些C編譯器要求每個(gè)外部對(duì)象僅有一個(gè)定義,但可以有多個(gè)聲明。使用這樣的編譯器時(shí),我們何以很容易地將一個(gè)聲明放到一個(gè)包含文件中,并將其定義放到其它地方。這意味著每個(gè)外部對(duì)象的類型將出現(xiàn)兩次,但這比出現(xiàn)多于兩次要好。8.分離函數(shù)參數(shù)用的逗號(hào)不是逗號(hào)運(yùn)算符。例如在f(x, y)中,x和y的獲取順序是未定義的,但在g((x, y))中不是這樣的。其中g(shù)只有一個(gè)參數(shù)。它的值是通過對(duì)x進(jìn)行求值、拋棄這個(gè)值、再對(duì)y進(jìn)行求值來確定的。9.預(yù)處理器還可以很容易地組織這樣的顯式常量以能夠方便地找到它們。10.PDP-11和VAX-11是數(shù)組設(shè)備集團(tuán)(DEC)的商標(biāo)。

      第二篇:C語言陷阱和缺陷

      0 簡(jiǎn)介

      C語言及其典型實(shí)現(xiàn)被設(shè)計(jì)為能被專家們?nèi)菀椎厥褂?。這門語言簡(jiǎn)潔并附有表達(dá)力。但有一些限制可以保護(hù)那些浮躁的人。一個(gè)浮躁的人可以從這些條款中獲得一些幫助。

      在本文中,我們將會(huì)看到這些未可知的益處。正是由于它的未可知,我們無法為其進(jìn)行完全的分類。不過,我們?nèi)匀煌ㄟ^研究為了一個(gè)C程序的運(yùn)行所需要做的事來做到這些。我們假設(shè)讀者對(duì)C語言至少有個(gè)粗淺的了解。

      第一部分研究了當(dāng)程序被劃分為記號(hào)時(shí)會(huì)發(fā)生的問題。第二部分繼續(xù)研究了當(dāng)程序的記號(hào)被編譯器組合為聲明、表達(dá)式和語句時(shí)會(huì)出現(xiàn)的問題。第三部分研究了由多個(gè)部分組成、分別編譯并綁定到一起的C程序。第四部分處理了概念上的誤解:當(dāng)一個(gè)程序具體執(zhí)行時(shí)會(huì)發(fā)生的事情。第五部分研究了我們的程序和它們所使用的常用庫之間的關(guān)系。在第六部分中,我們注意到了我們所寫的程序也許并不是我們所運(yùn)行的程序;預(yù)處理器將首先運(yùn)行。最后,第七部分討論了可移植性問題:一個(gè)能在一個(gè)實(shí)現(xiàn)中運(yùn)行的程序無法在另一個(gè)實(shí)現(xiàn)中運(yùn)行的原因。詞法缺陷

      編譯器的第一個(gè)部分常被稱為詞法分析器(lexical analyzer)。詞法分析器檢查組成程序的字符序列,并將它們劃分為記號(hào)(token)一個(gè)記號(hào)是一個(gè)由一個(gè)或多個(gè)字符構(gòu)成的序列,它在語言被編譯時(shí)具有一個(gè)(相關(guān)地)統(tǒng)一的意義。在C中,例如,記號(hào)->的意義和組成它的每個(gè)獨(dú)立的字符具有明顯的區(qū)別,而且其意義獨(dú)立于->出現(xiàn)的上下文環(huán)境。

      另外一個(gè)例子,考慮下面的語句:

      if(x > big)big = x;

      該語句中的每一個(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í)符。它必須通過對(duì)每個(gè)宏進(jìn)行求值來替換宏調(diào)用。最后,經(jīng)過宏替換的程序又被匯集成字符流送給編譯器。編譯器再第二次將這個(gè)流劃分為記號(hào)。

      在這一節(jié)中,我們將探索對(duì)記號(hào)的意義的普遍的誤解以及記號(hào)和組成它們的字符之間的關(guān)系。稍后我們將談到預(yù)處理器。

      1.1 = 不是==

      從Algol派生出來的語言,如Pascal和Ada,用:=表示賦值而用=表示比較。而C語言則是用=表示賦值而用==表示比較。這是因?yàn)橘x值的頻率要高于比較,因此為其分配更短的符號(hào)。

      此外,C還將賦值視為一個(gè)運(yùn)算符,因此可以很容易地寫出多重賦值(如a = b = c),并且可以將賦值嵌入到一個(gè)大的表達(dá)式中。

      這種便捷導(dǎo)致了一個(gè)潛在的問題:可能將需要比較的地方寫成賦值。因此,下面的語句好像看起來是要檢查x是否等于y:

      if(x = y)

      foo();

      而實(shí)際上是將x設(shè)置為y的值并檢查結(jié)果是否非零。再考慮下面的一個(gè)希望跳過空格、制表符和換行符的循環(huán):

      while(c == ' ' || c = '/t' || c == '/n')

      c = getc(f);

      在與'/t'進(jìn)行比較的地方程序員錯(cuò)誤地使用=代替了==。這個(gè)“比較”實(shí)際上是將'/t'賦給c,然后判斷c的(新的)值是否為零。因?yàn)?/t'不為零,這個(gè)“比較”將一直為真,因此這個(gè)循環(huán)會(huì)吃盡整個(gè)文件。這之后會(huì)發(fā)生什么取決于特定的實(shí)現(xiàn)是否允許一個(gè)程序讀取超過文件尾部的部分。如果允許,這個(gè)循環(huán)會(huì)一直運(yùn)行。

      一些C編譯器會(huì)對(duì)形如e1 = e2的條件給出一個(gè)警告以提醒用戶。當(dāng)你確實(shí)需要先對(duì)一個(gè)變量進(jìn)行賦值之后再檢查變量是否非零時(shí),為了在這種編譯器中避免警告信息,應(yīng)考慮顯式給出比較符。換句話說,將: if(x = y)

      foo();改寫為:

      if((x = y)!= 0)

      foo();

      這樣可以清晰地表示你的意圖。

      1.2 & 和 | 不是 && 和||

      容易將==錯(cuò)寫為=是因?yàn)楹芏嗥渌Z言使用=表示比較運(yùn)算。其他容易寫錯(cuò)的運(yùn)算符還有&和&&,以及|和||,這主要是因?yàn)镃語言中的&和|運(yùn)算符于其他語言中具有類似功能的運(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語言參考手冊(cè)說明了如何決定:“如果輸入流到一個(gè)給定的字符串為止已經(jīng)被識(shí)別為記號(hào),則應(yīng)該包含下一個(gè)字符以組成能夠構(gòu)成記號(hào)的最長(zhǎng)的字符串”([譯注]即通常所說的“最長(zhǎng)子串原則”)。因此,如果/是一個(gè)記號(hào)的第一個(gè)字符,并且/后面緊隨了一個(gè)*,則這兩個(gè)字符構(gòu)成了注釋的開始,不管其他上下文環(huán)境。

      下面的語句看起來像是將y的值設(shè)置為x的值除以p所指向的值: y = x/*p

      /* p 指向除數(shù) */;

      實(shí)際上,/*開始了一個(gè)注釋,因此編譯器簡(jiǎn)單地吞噬程序文本,直到*/的出現(xiàn)。換句話說,這條語句僅僅把y的值設(shè)置為x的值,而根本沒有看到p。將這條語句重寫為: y = x / *p

      /* p 指向除數(shù) */;或者干脆是

      y = x /(*p)

      /* p指向除數(shù) */;它就可以做注釋所暗示的除法了。

      這種模棱兩可的寫法在其他環(huán)境中就會(huì)引起麻煩。例如,老版本的C使用=+表示現(xiàn)在版本中的+=。這樣的編譯器會(huì)將 a=-1;視為 a =-1;或

      a = a> a

      是不合法的。它和

      p-> a

      不是同義詞。

      另一方面,有些老式編譯器還是將=+視為一個(gè)單獨(dú)的記號(hào)并且和+=是同義詞。

      1.5 字符串和字符

      單引號(hào)和雙引號(hào)在C中的意義完全不同,在一些混亂的上下文中它們會(huì)導(dǎo)致奇怪的結(jié)果而不是錯(cuò)誤消息。

      包圍在單引號(hào)中的一個(gè)字符只是編寫整數(shù)的另一種方法。這個(gè)整數(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è)無名數(shù)組的指針的一種簡(jiǎn)短方法。

      下面的兩個(gè)程序片斷是等價(jià)的:

      printf(“Hello world/n”);

      char hello[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '/n', 0 };printf(hello);

      使用一個(gè)指針來代替一個(gè)整數(shù)通常會(huì)得到一個(gè)警告消息(反之亦然),使用雙引號(hào)來代替單引號(hào)也會(huì)得到一個(gè)警告消息(反之亦然)。但對(duì)于不檢查參數(shù)類型的編譯器卻除外。因此,用 printf('/n');來代替 printf(“/n”);通常會(huì)在運(yùn)行時(shí)得到奇怪的結(jié)果。([譯注]提示:正如上面所說,'/n'表示一個(gè)整數(shù),它被轉(zhuǎn)換為了一個(gè)指針,這個(gè)指針?biāo)赶虻膬?nè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ù)”。這兩者之間的任何一致性都純屬巧合。句法缺陷

      要理解C語言程序,僅了解構(gòu)成它的記號(hào)是不夠的。還要理解這些記號(hào)是如何構(gòu)成聲明、表達(dá)式、語句和程序的。盡管這些構(gòu)成通常都是定義良好的,但這些定義有時(shí)候是有悖于直覺的或混亂的。

      在這一節(jié)中,我們將著眼于一些不明顯句法構(gòu)造。

      2.1 理解聲明

      我曾經(jīng)和一些人聊過天,他們那時(shí)正在在編寫在一個(gè)小型的微處理器上單機(jī)運(yùn)行的C程序。當(dāng)這臺(tái)機(jī)器的開關(guān)打開的時(shí)候,硬件會(huì)調(diào)用地址為0處的子程序。

      為了模仿電源打開的情形,我們要設(shè)計(jì)一條C語句來顯式地調(diào)用這個(gè)子程序。經(jīng)過一些思考,我們寫出了下面的語句:

      (*(void(*)())0)();

      這樣的表達(dá)式會(huì)令C程序員心驚膽戰(zhàn)。但是,并不需要這樣,因?yàn)樗麄兛梢栽谝粋€(gè)簡(jiǎn)單的規(guī)則的幫助下很容易地構(gòu)造它:以你使用的方式聲明它。

      每個(gè)C變量聲明都具有兩個(gè)部分:一個(gè)類型和一組具有特定格式的、期望用來對(duì)該類型求值的表達(dá)式。最簡(jiǎn)單的表達(dá)式就是一個(gè)變量:

      float f, g;

      說明表達(dá)式f和g——在求值的時(shí)候——具有類型float。由于待求值的是表達(dá)式,因此可以自由地使用圓括號(hào):

      float((f));

      這表示((f))求值為float并且因此,通過推斷,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 *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è)我們有一個(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á)式來替換fp。

      這個(gè)問題就是我們的第二步分析。如果C可以讀入并理解類型,我們可以寫:(*0)();

      但這樣并不行,因?yàn)?運(yùn)算符要求必須有一個(gè)指針作為它的操作數(shù)。另外,這個(gè)操作數(shù)必須是一個(gè)指向函數(shù)的指針,以保證*的結(jié)果可以被調(diào)用。因此,我們需要將0轉(zhuǎn)換為一個(gè)可以描述“指向一個(gè)返回void的函數(shù)的指針”的類型。

      如果fp是一個(gè)指向返回void的函數(shù)的指針,則(*fp)()是一個(gè)void值,并且它的聲明將會(huì)是這樣的: void(*fp)();

      因此,我們需要寫:

      void(*fp)();(*fp)();

      來聲明一個(gè)啞變量。一旦我們知道了如何聲明該變量,我們也就知道了如何將一個(gè)常數(shù)轉(zhuǎn)換為該類型:只要從變量的聲明中去掉名字即可。因此,我們像下面這樣將0轉(zhuǎn)換為一個(gè)“指向返回void的函數(shù)的指針”:

      (void(*)())0

      接下來,我們用(void(*)())0來替換fp:

      (*(void(*)())0)();

      結(jié)尾處的分號(hào)用于將這個(gè)表達(dá)式轉(zhuǎn)換為一個(gè)語句。

      在這里,我們解決這個(gè)問題時(shí)沒有使用typedef聲明。通過使用它,我們可以更清晰地解決這個(gè)問題:

      typedef void(*funcptr)();(*(funcptr)0)();

      2.2 運(yùn)算符并不總是具有你所想象的優(yōu)先級(jí)

      假設(shè)有一個(gè)聲明了的常量FLAG,它是一個(gè)整數(shù),其二進(jìn)制表示中的某一位被置位(換句話說,它是2的某次冪),并且你希望測(cè)試一個(gè)整型變量flags該位是否被置位。通常的寫法是:

      if(flags & FLAG)...其意義對(duì)于很多C程序員都是很明確的:if語句測(cè)試?yán)ㄌ?hào)中的表達(dá)式求值的結(jié)果是否為0。出于清晰的目的我們可以將它寫得更明確:

      if(flags & FLAG!= 0)...這個(gè)語句現(xiàn)在更容易理解了。但它仍然是錯(cuò)的,因?yàn)?=比&綁定得更緊密,因此它被分析為: 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;

      避免這種問題的一個(gè)方法是將所有的東西都用括號(hào)括起來,但表達(dá)式中的括號(hào)過度就會(huì)難以理解,因此最好還是是記住C中的優(yōu)先級(jí)。

      不幸的是,這有15個(gè),太困難了。然而,通過將它們分組可以變得容易。

      綁定得最緊密的運(yùn)算符并不是真正的運(yùn)算符:下標(biāo)、函數(shù)調(diào)用和結(jié)構(gòu)選擇。這些都與左邊相關(guān)聯(lián)。

      接下來是一元運(yùn)算符。它們具有真正的運(yùn)算符中的最高優(yōu)先級(jí)。由于函數(shù)調(diào)用比一元運(yùn)算符綁定得更緊密,你必須寫(*p)()來調(diào)用p指向的函數(shù);*p()表示p是一個(gè)返回一個(gè)指針的函數(shù)。轉(zhuǎn)換是一元運(yùn)算符,并且和其他一元運(yùn)算符具有相同的優(yōu)先級(jí)。一元運(yùn)算符是右結(jié)合的,因此*p++表示*(p++),而不是(*p)++。

      在接下來是真正的二元運(yùn)算符。其中數(shù)學(xué)運(yùn)算符具有最高的優(yōu)先級(jí),然后是移位運(yùn)算符、關(guān)系運(yùn)算符、邏輯運(yùn)算符、賦值運(yùn)算符,最后是條件運(yùn)算符。需要記住的兩個(gè)重要的東西是:

      ? 所有的邏輯運(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)算符中,沒有任何兩個(gè)具有相同的優(yōu)先級(jí)。按位運(yùn)算符比所有順序運(yùn)算符綁定得都緊密,每種與運(yùn)算符都比相應(yīng)的或運(yùn)算符綁定得更緊密,并且按位異或(^)運(yùn)算符介于按位與和按位或之間。

      三元運(yùn)算符的優(yōu)先級(jí)比我們提到過的所有運(yùn)算符的優(yōu)先級(jí)都低。這可以保證選擇表達(dá)式中包含的關(guān)系運(yùn)算符的邏輯組合特性,如:

      z = a < b && b < c ? d : e

      這個(gè)例子還說明了賦值運(yùn)算符具有比條件運(yùn)算符更低的優(yōu)先級(jí)是有意義的。另外,所有的復(fù)合賦值運(yùn)算符具有相同的優(yōu)先級(jí)并且是自右至左結(jié)合的,因此 a = b = c 和

      b = c;a = b;是等價(jià)的。

      具有最低優(yōu)先級(jí)的是逗號(hào)運(yùn)算符。這很容易理解,因?yàn)槎禾?hào)通常在需要表達(dá)式而不是語句的時(shí)候用來替代分號(hào)。

      賦值是另一種運(yùn)算符,通常具有混合的優(yōu)先級(jí)。例如,考慮下面這個(gè)用于復(fù)制文件的循環(huán):

      while(c = getc(in)!= EOF)

      putc(c, out);

      這個(gè)while循環(huán)中的表達(dá)式看起來像是c被賦以getc(in)的值,接下來判斷是否等于EOF以結(jié)束循環(huán)。不幸的是,賦值的優(yōu)先級(jí)比任何比較操作都低,因此c的值將會(huì)是getc(in)和EOF比較的結(jié)果,并且會(huì)被拋棄。因此,“復(fù)制”得到的文件將是一個(gè)由值為1的字節(jié)流組成的文件。

      上面這個(gè)例子正確的寫法并不難:

      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){

      這條語句希望給t賦一個(gè)值,然后看t是否與STRTY或UNIONTY相等。而實(shí)際的效果卻大不相同[3]。

      C中的邏輯運(yùn)算符的優(yōu)先級(jí)具有歷史原因。B語言——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ì)帶來一點(diǎn)點(diǎn)不同:或者是一個(gè)空語句,無任何效果;或者編譯器可能提出一個(gè)診斷消息,可以方便除去掉它。一個(gè)重要的區(qū)別是在必須跟有一個(gè)語句的if和while語句中??紤]下面的例子: if(x[i] > big);

      big = x[i];

      這不會(huì)發(fā)生編譯錯(cuò)誤,但這段程序的意義與: if(x[i] > big)

      big = x[i];

      就大不相同了。第一個(gè)程序段等價(jià)于: if(x[i] > big){ } big = x[i];也就是等價(jià)于:

      big = x[i];

      (除非x、i或big是帶有副作用的宏)。

      另一個(gè)因分號(hào)引起巨大不同的地方是函數(shù)定義前面的結(jié)構(gòu)聲明的末尾([譯注]這句話不太好聽,看例子就明白了)??紤]下面的程序片段: struct foo {

      int x;}

      f(){

      ...}

      在緊挨著f的第一個(gè)}后面丟失了一個(gè)分號(hào)。它的效果是聲明了一個(gè)函數(shù)f,返回值類型是struct foo,這個(gè)結(jié)構(gòu)成了函數(shù)聲明的一部分。如果這里出現(xiàn)了分號(hào),則f將被定義為具有默認(rèn)的整型返回值[5]。

      2.4 switch語句

      通常C中的switch語句中的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 of 1: write('red');2: write('yellow');3: write('blue');end

      這兩個(gè)程序片段都作相同的事情:根據(jù)變量color的值是1、2還是3打印red、yellow或blue(沒有新行符)。這兩個(gè)程序片段非常相似,只有一點(diǎn)不同:Pascal程序中沒有C中相應(yīng)的break語句。C中的case標(biāo)簽是真正的標(biāo)簽:控制流程可以無限制地進(jìn)入到一個(gè)case標(biāo)簽中。

      看看另一種形式,假設(shè)C程序段看起來更像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)用。

      這既是C語言switch語句的優(yōu)點(diǎn)又是它的弱點(diǎn)。說它是弱點(diǎn),是因?yàn)楹苋菀淄浺粋€(gè)break語句,從而導(dǎo)致程序出現(xiàn)隱晦的異常行為。說它是優(yōu)點(diǎn),是因?yàn)橥ㄟ^故意去掉break語句,可以很容易實(shí)現(xiàn)其他方法難以實(shí)現(xiàn)的控制結(jié)構(gòu)。尤其是在一個(gè)大型的switch語句中,我們經(jīng)常發(fā)現(xiàn)對(duì)一個(gè)case的處理可以簡(jiǎn)化其他一些特殊的處理。

      例如,設(shè)想有一個(gè)程序是一臺(tái)假想的機(jī)器的翻譯器。這樣的一個(gè)程序可能包含一個(gè)switch語句來處理各種操作碼。在這樣一臺(tái)機(jī)器上,通常減法在對(duì)其第二個(gè)運(yùn)算數(shù)進(jìn)行變號(hào)后就變成和加法一樣了。因此,最好可以寫出這樣的語句:

      case SUBTRACT:

      opnd2 =-opnd2;

      /* no break;*/ case ADD:

      ...另外一個(gè)例子,考慮編譯器通過跳過空白字符來查找一個(gè)記號(hào)。這里,我們將空格、制表符和新行符視為是相同的,除了新行符還要引起行計(jì)數(shù)器的增長(zhǎng)外: case '/n':

      linecount++;

      /* no break */ case '/t': case ' ':

      ...2.5 函數(shù)調(diào)用

      和其他程序設(shè)計(jì)語言不同,C要求一個(gè)函數(shù)調(diào)用必須有一個(gè)參數(shù)列表,但可以沒有參數(shù)。因此,如果f是一個(gè)函數(shù),f();

      就是對(duì)該函數(shù)進(jìn)行調(diào)用的語句,而

      f;

      什么也不做。它會(huì)作為函數(shù)地址被求值,但不會(huì)調(diào)用它[6]。

      2.6 懸掛else問題

      在討論任何語法缺陷時(shí)我們都不會(huì)忘記提到這個(gè)問題。盡管這一問題不是C語言所獨(dú)有的,但它仍然傷害著那些有著多年經(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 + y;

      f(&z);

      } }

      換句話說,當(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è)文件,因此它無法檢測(cè)到需要程序的多個(gè)源文件的內(nèi)容才能發(fā)現(xiàn)的錯(cuò)誤。

      在這一節(jié)中,我們將看到一些這種類型的錯(cuò)誤。有一些C實(shí)現(xiàn),但不是所有的,帶有一個(gè)稱為lint的程序來捕獲這些錯(cuò)誤。如果具有一個(gè)這樣的程序,那么無論怎樣地強(qiáng)調(diào)它的重要性都不過分。

      3.1 你必須自己檢查外部類型

      假設(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)來完成;如果操作系統(tǒng)的連接器不能識(shí)別數(shù)據(jù)類型,C編譯器也沒法過多地強(qiáng)制它。

      那么,這個(gè)程序運(yùn)行時(shí)實(shí)際會(huì)發(fā)生什么?這有很多可能性:

      ? 實(shí)現(xiàn)足夠聰明,能夠檢測(cè)到類型沖突。則我們會(huì)得到一個(gè)診斷消息,說明n在兩個(gè)文件中具有不同的類型。

      ? 你所使用的實(shí)現(xiàn)將int和long視為相同的類型。典型的情況是機(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/passwd”;而另一個(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è)指針可以指向程序員讓它指向的任何地方。如果程序員沒有給它賦一個(gè)值,它將具有一個(gè)默認(rèn)的0值(NULL)([譯注]實(shí)際上,在C中一個(gè)為初始化的指針通常具有一個(gè)隨機(jī)的值,這是很危險(xiǎn)的!)。

      這兩個(gè)聲明以不同的方式使用存儲(chǔ)區(qū),它們不可能共存。

      避免這種類型沖突的一個(gè)方法是使用像lint這樣的工具(如果可以的話)。為了在一個(gè)程序的不同編譯單元之間檢查類型沖突,一些程序需要一次看到其所有部分。典型的編譯器無法完成,但lint可以。

      避免該問題的另一種方法是將外部聲明放到包含文件中。這時(shí),一個(gè)外部對(duì)象的類型僅出現(xiàn)一次[7]。語義缺陷

      一個(gè)句子可以是精確拼寫的并且沒有語法錯(cuò)誤,但仍然沒有意義。在這一節(jié)中,我們將會(huì)看到一些程序的寫法會(huì)使得它們看起來是一個(gè)意思,但實(shí)際上是另一種完全不同的意思。

      我們還要討論一些表面上看起來合理但實(shí)際上會(huì)產(chǎn)生未定義結(jié)果的環(huán)境。我們這里討論的東西并不保證能夠在所有的C實(shí)現(xiàn)中工作。我們暫且忘記這些能夠在一些實(shí)現(xiàn)中工作但可能不能在另一些實(shí)現(xiàn)中工作的東西,直到第7節(jié)討論可以執(zhí)行問題為止。

      4.1 表達(dá)式求值順序

      一些C運(yùn)算符以一種已知的、特定的順序?qū)ζ洳僮鲾?shù)進(jìn)行求值。但另一些不能。例如,考慮下面的表達(dá)式:

      a < b && c < d

      C語言定義規(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中的一個(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)

      y[i] = x[i++];

      其中的問題是y[i]的地址并不保證在i增長(zhǎng)之前被求值。在某些實(shí)現(xiàn)中,這是可能的;但在另一些實(shí)現(xiàn)中卻不可能。另一種情況出于同樣的原因會(huì)失?。?i = 0;while(i < n)

      y[i++] = x[i];

      而下面的代碼是可以工作的: i = 0;while(i < n){

      y[i] = x[i];

      i++;}

      當(dāng)然,這可以簡(jiǎn)寫為: for(i = 0;i < n;i++)

      y[i] = x[i];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)可以通過左邊的操作數(shù)確定其返回值時(shí),就不會(huì)對(duì)右邊的操作數(shù)進(jìn)行求值。

      因此!10是零,因?yàn)?0非零;10 && 12是1,因?yàn)?0和12都非零;10 || 12也是1,因?yàn)?0非零。另外,最后一個(gè)表達(dá)式中的12不會(huì)被求值,10 || f()中的f()也不會(huì)被求值。

      考慮下面這段用于在一個(gè)表中查找一個(gè)特定元素的程序:

      i = 0;while(i < tabsize && tab[i]!= x)

      i++;

      這段循環(huán)背后的意思是如果i等于tabsize時(shí)循環(huán)結(jié)束,元素未被找到。否則,i包含了元素的索引。

      假設(shè)這個(gè)例子中的&&不小心被替換為了&,這個(gè)循環(huán)可能仍然能夠工作,但只有兩種幸運(yùn)的情況可以使它停下來。

      首先,這兩個(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ì)改變,因此越過數(shù)組最后一個(gè)元素前進(jìn)一個(gè)位置時(shí)是無害的,循環(huán)會(huì)幸運(yùn)地停下來。失誤的程序會(huì)越過數(shù)組的結(jié)尾,因?yàn)?不像&&,總是會(huì)對(duì)所有的操作數(shù)進(jìn)行求值。因此循環(huán)的最后一次獲取tab[i]時(shí)i的值已經(jīng)等于tabsize了。如果tabsize是tab中元素的數(shù)量,則會(huì)取到tab中不存在的一個(gè)值。

      4.3 下標(biāo)從零開始

      在很多語言中,具有n個(gè)元素的數(shù)組其元素的號(hào)碼和它的下標(biāo)是從1到n嚴(yán)格對(duì)應(yīng)的。但在C中不是這樣。

      一個(gè)具有n個(gè)元素的C數(shù)組中沒有下標(biāo)為n的元素,其中的元素的下標(biāo)是從0到n'a';

      return c;}

      在很多C實(shí)現(xiàn)中,為了減少比實(shí)際計(jì)算還要多的調(diào)用開銷,通常將其實(shí)現(xiàn)為宏:

      #define toupper(c)((c)>= 'a' &&(c)<= 'z' ?(c)+('A''a')#define tolower(c)((c)+ 'A''a' :(c))#define tolower(c)((c)>= 'A' &&(c)<= 'Z' ?(c)+ 'a''a';

      return c;}

      tolower()類似。

      這個(gè)改變帶來更多的問題,每次使用這些函數(shù)的時(shí)候都會(huì)引入函數(shù)調(diào)用開銷。我們的英雄認(rèn)為一些人可能不愿意支付這些開銷,因此他們將這個(gè)宏重命名為:

      #define _toupper(c)((c)+ 'A''A')這就允許用戶選擇方便或速度。

      這里面其實(shí)只有一個(gè)問題:伯克利的人們和其他的C實(shí)現(xiàn)者并沒有跟著這么做。這意味著一個(gè)在AT&T系統(tǒng)上編寫的使用了toupper()或tolower()的程序,如果沒有為其傳遞正確大小寫字母參數(shù),在其他C實(shí)現(xiàn)中可能不會(huì)正常工作。

      如果不知道這些歷史,可能很難對(duì)這類錯(cuò)誤進(jìn)行跟蹤。

      7.8 先釋放,再重新分配

      很多C實(shí)現(xiàn)為用戶提供了三個(gè)內(nèi)存分配函數(shù):malloc()、realloc()和free()。調(diào)用malloc(n)返回一個(gè)指向有n個(gè)字符的新分配的內(nèi)存的指針,這個(gè)指針可以由程序員使用。給free()傳遞一個(gè)指向由malloc()分配的內(nèi)存的指針可以使這塊內(nèi)存得以再次使用。通過一個(gè)指向已分配區(qū)域的指針和一個(gè)新的大小調(diào)用realloc()可以將這塊內(nèi)存擴(kuò)大或縮小到新尺寸,這個(gè)過程中可能要復(fù)制內(nèi)存。

      也許有人會(huì)想,真相真是有點(diǎn)微妙啊。下面是System V接口定義中出現(xiàn)的對(duì)realloc()的描述:

      realloc改變一個(gè)由ptr指向的size個(gè)字節(jié)的塊,并返回該塊(可能被移動(dòng))的指針。在新舊尺寸中比較小的一個(gè)尺寸之下的內(nèi)容不會(huì)被改變。

      而UNIX系統(tǒng)第七版的參考手冊(cè)中包含了這一段的副本。此外,還包含了描述realloc()的另外一段:

      如果在最后一次調(diào)用malloc、realloc或calloc后釋放了ptr所指向的塊,realloc依舊可以工作;因此,free、malloc和realloc的順序可以利用malloc壓縮存貯的查找策略。

      因此,下面的代碼片段在UNIX第七版中是合法的:

      free(p);p = realloc(p, newsize);

      這一特性保留在從UNIX第七版衍生出來的系統(tǒng)中:可以先釋放一塊存儲(chǔ)區(qū)域,然后再重新分配它。這意味著,在這些系統(tǒng)中釋放的內(nèi)存中的內(nèi)容在下一次內(nèi)存分配之前可以保證不變。因此,在這些系統(tǒng)中,我們可以用下面這種奇特的思想來釋放一個(gè)鏈表中的所有元素: for(p = head;p!= NULL;p = p->next)

      free((char *)p);

      而不用擔(dān)心調(diào)用free()會(huì)導(dǎo)致p->next不可用。

      不用說,這種技術(shù)是不推薦的,因?yàn)椴皇撬蠧實(shí)現(xiàn)都能在內(nèi)存被釋放后將它的內(nèi)容保留足夠長(zhǎng)的時(shí)間。然而,第七版的手冊(cè)遺留了一個(gè)未聲明的問題:realloc()的原始實(shí)現(xiàn)實(shí)際上是必須要先釋放再重新分配的。出于這個(gè)原因,一些C程序都是先釋放內(nèi)存再重新分配的,而當(dāng)這些程序移植到其他實(shí)現(xiàn)中時(shí)就會(huì)出現(xiàn)問題。

      7.9 可移植性問題的一個(gè)實(shí)例

      讓我們來看一個(gè)已經(jīng)被很多人在很多時(shí)候解決了的問題。下面的程序帶有兩個(gè)參數(shù):一個(gè)長(zhǎng)整數(shù)和一個(gè)函數(shù)(的指針)。它將整數(shù)轉(zhuǎn)換位十進(jìn)制數(shù),并用代表其中每一個(gè)數(shù)字的字符來調(diào)用給定的函數(shù)。

      void printnum(long n, void(*p)()){

      if(n < 0){

      (*p)('-');

      n =-n;

      }

      if(n >= 10)

      printnum(n / 10, p);

      (*p)(n % 10 + '0');}

      這個(gè)程序非常簡(jiǎn)單。首先檢查n是否為負(fù)數(shù);如果是,則打印一個(gè)符號(hào)并將n變?yōu)檎龜?shù)。接下來,測(cè)試是否n >= 10。如果是,則它的十進(jìn)制表示中包含兩個(gè)或更多個(gè)數(shù)字,因此我們遞歸地調(diào)用printnum()來打印除最后一個(gè)數(shù)字外的所有數(shù)字。最后,我們打印最后一個(gè)數(shù)字。

      這個(gè)程序——由于它的簡(jiǎn)單——具有很多可移植性問題。首先是將n的低位數(shù)字轉(zhuǎn)換成字符形式的方法。用n % 10來獲取低位數(shù)字的值是好的,但為它加上'0'來獲得相應(yīng)的字符表示就不好了。這個(gè)加法假設(shè)機(jī)器中順序的數(shù)字所對(duì)應(yīng)的字符數(shù)順序的,沒有間隔,因此'0' + 5和'5'的值是相同的,等等。盡管這個(gè)假設(shè)對(duì)于ASCII和EBCDIC字符集是成立的,但對(duì)于其他一些機(jī)器可能不成立。避免這個(gè)問題的方法是使用一個(gè)表:

      void printnum(long n, void(*p)()){

      if(n < 0){

      (*p)('-');

      n =-n;

      }

      if(n >= 10)

      printnum(n / 10, p);

      (*p)(“0123456789”[n % 10]);}

      另一個(gè)問題發(fā)生在當(dāng)n < 0時(shí)。這時(shí)程序會(huì)打印一個(gè)負(fù)號(hào)并將n設(shè)置為-n。這個(gè)賦值會(huì)發(fā)生溢出,因?yàn)樵谑褂?的補(bǔ)碼的機(jī)器上通常能夠表示的負(fù)數(shù)比正數(shù)要多。例如,一個(gè)(長(zhǎng))整數(shù)有k位和一個(gè)附加位表示符號(hào),則-2k可以表示而2k卻不能。

      解決這一問題有很多方法。最直觀的一種是將n賦給一個(gè)unsigned long值。然而,一些C便一起可能沒有實(shí)現(xiàn)unsigned long,因此我們來看看沒有它怎么辦。

      在第一個(gè)實(shí)現(xiàn)和第二個(gè)實(shí)現(xiàn)的機(jī)器上,改變一個(gè)正整數(shù)的符號(hào)保證不會(huì)發(fā)生溢出。問題僅出在改變一個(gè)負(fù)數(shù)的符號(hào)時(shí)。因此,我們可以通過避免將n變?yōu)檎龜?shù)來避免這個(gè)問題。

      當(dāng)然,一旦我們打印了負(fù)數(shù)的符號(hào),我們就能夠?qū)⒇?fù)數(shù)和正數(shù)視為是一樣的。下面的方法就強(qiáng)制在打印符號(hào)之后n為負(fù)數(shù),并且用負(fù)數(shù)值完成我們所有的算法。如果我們這么做,我們就必須保證程序中打印符號(hào)的部分只執(zhí)行一次;一個(gè)簡(jiǎn)單的方法是將這個(gè)程序劃分為兩個(gè)函數(shù): void printnum(long n, void(*p)()){

      if(n < 0){

      (*p)('-');

      printneg(n, p);

      }

      else

      printneg(-n, p);}

      void printneg(long n, void(*p)()){

      if(n <=-10)

      printneg(n / 10, p);

      (*p)(“0123456789”[-(n % 10)]);}

      printnum()現(xiàn)在只檢查要打印的數(shù)是否為負(fù)數(shù);如果是的話則打印一個(gè)符號(hào)。否則,它以n的負(fù)絕對(duì)值來調(diào)用printneg()。我們同時(shí)改變了printneg()的函數(shù)體來適應(yīng)n永遠(yuǎn)是負(fù)數(shù)或零這一事實(shí)。

      我們得到什么?我們使用n / 10和n % 10來獲取n的前導(dǎo)數(shù)字和結(jié)尾數(shù)字(經(jīng)過適當(dāng)?shù)姆?hào)變換)。調(diào)用整數(shù)除法的行為在其中一個(gè)操作數(shù)為負(fù)的時(shí)候是實(shí)現(xiàn)相關(guān)的。因此,n % 10有可能是正的!這時(shí),-(n % 10)是負(fù)數(shù),將會(huì)超出我們的數(shù)字字符數(shù)組的末尾。

      為了解決這一問題,我們建立兩個(gè)臨時(shí)變量來存放商和余數(shù)。作完除法后,我們檢查余數(shù)是否在正確的范圍內(nèi),如果不是的話則調(diào)整這兩個(gè)變量。printnum()沒有改變,因此我們只列出printneg():

      void printneg(long n, void(*p)()){

      long q;

      int r;

      if(r > 0){

      r-= 10;

      q++;

      }

      if(n <=-10){

      printneg(q, p);

      }

      (*p)(“0123456789”[-r]);} 這里是空閑空間

      還有很多可能讓C程序員誤入迷途的地方本文沒有提到。如果你發(fā)現(xiàn)了,請(qǐng)聯(lián)系作者。在以后的版本中它會(huì)被包含進(jìn)來,并添加一個(gè)表示感謝的腳注。

      參考

      《The C Programming Language》(Kernighan and Ritchie, Prentice-Hall 1978)是最具權(quán)威的C著作。它包含了一個(gè)優(yōu)秀的教程,面向那些熟悉其他高級(jí)語言程序設(shè)計(jì)的人,和一個(gè)參考手冊(cè),簡(jiǎn)潔地描述了整個(gè)語言。盡管自1978年以來這門語言發(fā)生了不少變化,這本書對(duì)于很多主題來說仍然是個(gè)定論。這本書同時(shí)還包含了本文中多次提到的“C語言參考手冊(cè)”。

      《The C Puzzle Book》(Feuer, Prentice-Hall, 1982)是一本少見的磨煉人們文法能力的書。這本書收集了很多謎題(和答案),它們的解決方法能夠測(cè)試讀者對(duì)于C語言精妙之處的知識(shí)。

      《C: A Referenct Manual》(Harbison and Steele, Prentice Hall 1984)是特意為實(shí)現(xiàn)者編寫的一本參考資料。其他人也會(huì)發(fā)現(xiàn)它是特別有用的——因?yàn)樗軓闹袇⒖技?xì)節(jié)。

      腳注

      1.這本書是基于圖書《C Traps and Pitfalls》(Addison-Wesley, 1989, ISBN 0-201-17928-8)的一個(gè)擴(kuò)充,有興趣的讀者可以讀一讀它。

      2.因?yàn)?=的結(jié)果不是1就是0。

      3.感謝Guy Harris為我指出這個(gè)問題。

      4.Dennis Ritchie和Steve Johnson同時(shí)向我指出了這個(gè)問題。

      5.感謝一位不知名的志愿者提出這個(gè)問題。

      6.感謝Richard Stevens指出了這個(gè)問題。

      7.一些C編譯器要求每個(gè)外部對(duì)象僅有一個(gè)定義,但可以有多個(gè)聲明。使用這樣的編譯器時(shí),我們何以很容易地將一個(gè)聲明放到一個(gè)包含文件中,并將其定義放到其它地方。這意味著每個(gè)外部對(duì)象的類型將出現(xiàn)兩次,但這比出現(xiàn)多于兩次要好。

      8.分離函數(shù)參數(shù)用的逗號(hào)不是逗號(hào)運(yùn)算符。例如在f(x, y)中,x和y的獲取順序是未定義的,但在g((x, y))中不是這樣的。其中g(shù)只有一個(gè)參數(shù)。它的值是通過對(duì)x進(jìn)行求值、拋棄這個(gè)值、再對(duì)y進(jìn)行求值來確定的。

      9.預(yù)處理器還可以很容易地組織這樣的顯式常量以能夠方便地找到它們。

      10.PDP-11和VAX-11是數(shù)組設(shè)備集團(tuán)(DEC)的商標(biāo)。

      本文來自CSDN

      客,轉(zhuǎn)

      請(qǐng)

      標(biāo)

      :http://blog.csdn.net/milan25429688/archive/2005/03/24/328944.aspx#contents

      第三篇:語言的功能和陷阱

      n

      語言的功能和陷阱

      n

      王蒙

      n

      一、焦點(diǎn)問題

      ?

      思考語言的社會(huì)功能問題,以及由語言而引發(fā)的社會(huì)問題。

      ?

      演講的基本特點(diǎn)和要求。

      n

      二、王蒙其人

      王蒙,1934年生于北京

      n

      5歲上小學(xué)。

      n

      10歲時(shí)跳級(jí)考入中學(xué)。

      n

      1948年,14歲,王蒙參加地下黨。

      n

      1949年,15歲調(diào)入新民主主義青年團(tuán)(后改名為共產(chǎn)主義青年團(tuán))北京市委工作。

      n

      1953年,19歲,長(zhǎng)篇小說《青春萬歲》獲得成功。

      n

      1956年,參加全國(guó)第一屆青年作者會(huì)議。

      n

      1956年秋,發(fā)表《組織部來了個(gè)年輕人》,引起極大反響。

      n

      1958年,24歲,被錯(cuò)劃為右派。

      n

      1958年,赴北京郊區(qū)勞動(dòng)。

      n

      1962年,赴新疆勞動(dòng)。

      n

      1963年起在伊犁地區(qū)農(nóng)村勞動(dòng)多年。

      n

      n

      三、王蒙成就

      n

      其間曾任自治區(qū)文聯(lián)編輯、維吾爾語翻譯。1979年調(diào)回北京,任北京市文聯(lián)專業(yè)作家。中國(guó)作家協(xié)會(huì)副主席。

      n

      自20世紀(jì)50年代以來,發(fā)表作品共一千余萬字。

      n

      被翻譯成英、法、德、俄、日、韓、意、西班牙、等二十余種語言文字。

      n

      曾獲意大利蒙德羅文學(xué)獎(jiǎng)、日本創(chuàng)作學(xué)會(huì)和平與文化獎(jiǎng)。

      n

      學(xué)術(shù)著作《〈紅樓夢(mèng)〉啟示錄》。

      n

      擔(dān)任十余所大學(xué)教授、名譽(yù)教授、顧問。

      n

      曾應(yīng)邀訪問世界各大洲四十多個(gè)國(guó)家。曾任哈佛大學(xué)燕京學(xué)院特邀訪問學(xué)者、美國(guó)三一學(xué)院校長(zhǎng)級(jí)學(xué)者

      n

      三、文本分析

      n

      為什么關(guān)注語言?

      “語言是存在的揭示、澄明、到達(dá)”

      “語言是存在的家,人就居住在這家中”。

      ——海德格爾

      王蒙這篇演講的前一部分主要講述語言、尤其是文學(xué)語言的基本功能。

      ?

      (一)王蒙提出語言的三種功能:

      ?

      現(xiàn)實(shí)有用的功能;

      ?

      生發(fā)和促進(jìn)的功能(推進(jìn)思想、推進(jìn)感情、推進(jìn)文化、創(chuàng)造文化);

      ?

      浪漫的功能(語言和文字離開了現(xiàn)實(shí)或者超出了現(xiàn)實(shí)的功能)。

      ?

      (二)如何理解王蒙的觀點(diǎn):

      ?

      語言創(chuàng)造了人:(反對(duì)語言工具論)提倡語言本體論,如“皎潔”。

      ?

      沒有語言就沒有記憶:(反對(duì)語言交際論)提倡語言文化論,如“我們倆困覺”。

      ?

      語言的審美化:(反對(duì)語言反映論)提倡語言形象論,如“吃葡萄”。

      ?

      (三)王蒙這篇演講的后一部分主要講述語言的陷阱。

      語言的陷阱:

      ?

      語言和現(xiàn)實(shí)和你的思想感情脫節(jié);

      ?

      脫離生活,變成反面的東西;

      ?

      異化、狗屎化效應(yīng)、被語言文字主宰,扼殺創(chuàng)造性,扼殺活潑的生機(jī)。

      n

      四、怎樣認(rèn)識(shí)語言的功能和陷阱

      ?

      (1)語言決定、生產(chǎn)意義。

      ?

      (2)語言是思想的物質(zhì)現(xiàn)實(shí):維特根斯坦說,我的語言的局限就是我的世界的局限。例如,現(xiàn)代“時(shí)間”是一種空間化的隱喻,“自……以來”;但是,在美國(guó)印第安的霍皮族那里,沒有昨天、今天、明天的概念。同樣,不同的語言體系生產(chǎn)出不同的“宇宙”,也就有了不同的生命觀、宇宙觀和哲學(xué)。

      ?

      (3)語言限定體驗(yàn):語言在瞬間體驗(yàn)中起著決定性的作用,歷史性的養(yǎng)成人們感受的習(xí)性,如明月、流水;也橫向地限定了人們的體驗(yàn)?zāi)芰头绞剑缭律⑽逦丁?/p>

      ?

      (4)語言本身也可以是美的形象。

      ?

      (5)語言可以“修改”現(xiàn)實(shí):王蒙是一個(gè)經(jīng)歷了“反右”和十年“文化大革命”的作家,特殊的經(jīng)歷和遭遇使他對(duì)“語言”的負(fù)面功能有著特殊的認(rèn)識(shí)。他說,“語言文字可以反過來主宰我們,扼殺我們的創(chuàng)造性,扼殺我們活潑的生機(jī)”。

      n

      五、“概念恐懼”與語言的權(quán)利

      存在主義哲學(xué)家基爾克郭爾提出“概念恐懼”認(rèn)為,“恐懼”和“畏懼”不同,前者是對(duì)沒有具體對(duì)象的恐懼。在十年“文化大革命”中,很多語言就是這樣造成一種“恐懼”,這些語言并沒有創(chuàng)造“實(shí)體”,比如“地富反壞右”、“牛鬼蛇神”等。“一個(gè)青年在街上走”,這說明了語言本身隱藏著權(quán)力,影響我們的認(rèn)識(shí)和思考。事實(shí)上,有“語言”的地方,就存在著權(quán)力的妥協(xié)、對(duì)立和斗爭(zhēng),就存在著“扼殺”和對(duì)“扼殺”的反抗。

      n

      六、再談?wù)勚v演

      講演稿也叫演說詞,是在較隆重的集會(huì)和會(huì)議上發(fā)表的講話文稿??梢杂脕斫涣魉枷?、感情,表達(dá)主張、見解,具有宣傳、鼓動(dòng)和教育作用。

      演講是一種溝通。在古代希臘,演講被稱之為“誘動(dòng)術(shù)”。這包含了三個(gè)意思:

      (1)廣場(chǎng)性:利用話語修辭,調(diào)動(dòng)公眾情緒的相互感染;

      (2)單向性:含有表演性質(zhì)的獨(dú)白話語行為;

      (3)

      共謀性:演講是一種修辭性的“共謀”策略的實(shí)施。

      七、王蒙講演的風(fēng)格:

      ?

      外松內(nèi)緊

      ?

      亦莊亦諧

      ?

      取譬引喻

      n

      八、思考與討論

      l

      找出表現(xiàn)演講者機(jī)智的句子。

      l

      怎樣區(qū)分演講中的幽默與噱頭?

      l

      舉例說明演講者是怎樣不斷調(diào)動(dòng)、活躍場(chǎng)內(nèi)氣氛的。

      l

      你同意演講者關(guān)于“語言陷阱”的觀點(diǎn)嗎?為什么?

      第四篇:面試中要注意的語言陷阱與應(yīng)答技巧

      就業(yè)面試經(jīng)典問題及最佳答案 工作動(dòng)機(jī) 個(gè)人愿望篇

      自考生就業(yè)面試經(jīng)典問題”見招拆招“

      工作動(dòng)機(jī)、個(gè)人愿望·

      請(qǐng)給我們談?wù)勀阕约旱囊恍┣闆r·你是哪年出生的?你是哪所大學(xué)畢業(yè)的?問題:請(qǐng)給我們談?wù)勀阕约旱囊恍┣闆r

      回答:簡(jiǎn)要的描述你的相關(guān)工作經(jīng)歷以及你的一些特征,包括與人相處的能力和個(gè)人的性格特征。如果你一下子不能夠確定面試者到底需要什么樣的內(nèi)容,你可以這樣說:”有沒有什么您特別感興趣的范圍?“

      點(diǎn)評(píng):企業(yè)以此來判斷是否應(yīng)該聘用你。通過你的談?wù)?,可以看出你想的是如何為公司效力還是那些會(huì)影響工作的個(gè)人問題。當(dāng)然,還可以知道你的一些背景。

      ·請(qǐng)談一下你對(duì)公司的看法,為什么你想來工作?

      問題:你是哪年出生的?你是哪所大學(xué)畢業(yè)的?等等

      回答:我是XXXX年出生的。我是XX大學(xué)畢業(yè)的。

      點(diǎn)評(píng):這類問題至為關(guān)鍵的是要針對(duì)每個(gè)問題簡(jiǎn)潔明了的回答,不可拖泥帶水,也 不必再加什么說明。完全不必再畫蛇添足的說”我屬X,今年XX歲“之類的話。至于專業(yè)等或許主考官接下來的問題就是針對(duì)此而言的,故而不必迫不及待和盤托出。

      ·你認(rèn)為對(duì)你來說現(xiàn)在找一份工作是不是不太容易?

      問題:你認(rèn)為對(duì)你來說現(xiàn)在找一份工作是不是不太容易,或者你很需要這份工作?

      回答:

      1.是的。

      2.我看不見得。

      點(diǎn)評(píng):

      一般按1回答,一切便大功告成。

      有些同學(xué)為了顯示自己的”不卑不亢“,強(qiáng)調(diào)個(gè)人尊嚴(yán),故按2回答。結(jié)果,用人單位打消了錄用該生的念頭,理由是:”此人比較傲“一句話,斷送了該生一次較好的就業(yè)機(jī)會(huì)。

      ·你是怎么應(yīng)聘到我們公司的?

      問題:你是怎么應(yīng)聘到我們公司的?

      回答:貴公司是國(guó)際上有名的汽車工業(yè)公司,雖然我學(xué)的專業(yè)不是汽車專業(yè),但我一直留意、關(guān)心貴公司的發(fā)展,特別是貴公司注重對(duì)員工的培訓(xùn),更讓我心動(dòng),另外象貴公司這樣大的企業(yè),我想是各種專業(yè)人才都需要的,便毅然前來應(yīng)聘。

      點(diǎn)評(píng):該畢業(yè)生的專業(yè)雖然不是該公司緊缺的專業(yè),但他分析了公司招聘職位的具體要求,認(rèn)為可以應(yīng)試該公司的某一種職位要求。(如管理、營(yíng)銷、秘書),如食品工程專業(yè)的求職面遠(yuǎn)不只局限于食品的加工企業(yè),可延伸至飲品、酒類、保健品、調(diào)味品甚至酒樓等多個(gè)行業(yè)。都會(huì)有適合自己的職位?!ふ?qǐng)你談?wù)剬?duì)我單位的看法·

      問題:請(qǐng)你談?wù)剬?duì)我單位的看法

      回答:我對(duì)貴單位還沒什么了解,故談不出看法

      點(diǎn)評(píng):象這樣的回答,一般面試不成功多,如你很想進(jìn)入該單位,就不妨實(shí)地去單位”偵察“一番,或收集有關(guān)的資料。如有一位畢業(yè)生,他有意去國(guó)家進(jìn)出口銀行工作,便通過朋友的關(guān)系弄到了一本進(jìn)出口銀行的基本業(yè)務(wù)材料,從而在面試中對(duì)答如流,贏得了招聘單位的賞識(shí)。并能以自身的優(yōu)勢(shì)來說明為何應(yīng)聘這工作,做到有的防矢,給主考官留下了深刻的印象。因此,收集資料,了解單位,可以幫助求職者認(rèn)清主要方向,更精確,更客觀地審視主聘單位,選擇適合自己發(fā)展的單位,避免走彎路。

      你完全可以到大公司任職,你怎么想到我們小企業(yè)?

      問題:以你的資歷條件,完全可以到大公司任職,你怎么想到我們小企業(yè)?回答:

      1.哎,沒辦法,一時(shí)沒有應(yīng)聘到大企業(yè),況且,畢業(yè)時(shí)間又到了,否則只能回當(dāng)?shù)鼐蜆I(yè),因此先就業(yè)再說。

      2.小企業(yè)有他自己的優(yōu)勢(shì),在用人方面非常重視,自己雖然資歷條件尚可,我想,在你們這樣的企業(yè)更能發(fā)揮自己的作用。

      點(diǎn)評(píng):一個(gè)還未工作就想以后跳槽的員工,是無論如何不能指望他盡心盡力的干好工作的,因此,即使有此想法,也不能說出來,說不定工作后受到企業(yè)重用,本人的作用也發(fā)揮的特別好,而不想再走了呢?

      ·你為什么希望到我們公司工作?

      問題:你為什么希望到我們公司工作?

      回答:我覺得貴公司力量雄厚,領(lǐng)導(dǎo)得力,上下一心,適于一切有才干的人發(fā)展。

      忌:”我是學(xué)電子的,我到這里才是專業(yè)對(duì)口。“看情況而定。

      ”我來這里上班離家近?!?/p>

      ”我喜歡你們這兒?!?/p>

      ”聽說你們公司月薪較高?!?/p>

      點(diǎn)評(píng):回答問題要從對(duì)方入題,引起對(duì)方好感,使對(duì)方感到你能尊重,關(guān)心公司的需要,愿為公司盡微薄之力。

      ·如果公司錄用你,你最希望在哪個(gè)部門工作?

      問題:如果本公司錄用你,你最希望在哪個(gè)部門工作?

      回答:

      忌:”到哪個(gè)部門都行“

      應(yīng):”本人希望 到XX部門,但也很樂意接受公司的其他安排。

      點(diǎn)評(píng):不要說得太隨意,太肯定。比較穩(wěn)妥的辦法是首先表明自己的志向和興趣,再表示服從安排。

      ·你愿意被外派工作嗎?你愿意經(jīng)常出差嗎?

      問題:你愿意被外派工作嗎?你愿意經(jīng)常出差嗎?

      回答:愿意,反正我無牽無掛,到哪兒工作都可以。

      點(diǎn)評(píng):這是主試者通過提問來透露他要找的是什么樣的人,此信息已經(jīng)很明白地告訴你,他所期待的回答是什么。對(duì)于此類問題應(yīng)聘者留意傾聽。從“話中之話”中找出應(yīng)試者實(shí)際需要的線索。

      我怎樣相信對(duì)這個(gè)職位你是最好的人選呢?

      問題:我怎樣相信對(duì)這個(gè)職位你是最好的人選呢?

      回答:根據(jù)這個(gè)職位的性質(zhì)和我們剛才的談話,我推斷你需要的是工作積極的人,能夠設(shè)定目標(biāo),不懼怕挑戰(zhàn)的人。我就具有這些品質(zhì),讓我再告訴你一些我在校時(shí)的經(jīng)歷,它們能說明我確實(shí)是你所需要的最好的人選。

      點(diǎn)評(píng):設(shè)身處地替面試官想一想,考慮一下招聘者需要什么樣的人,你又在哪些方面符合他們的要求。根據(jù)要求,談出自己應(yīng)聘的優(yōu)勢(shì)。

      ·如果我能給你任何你想要的工作,你會(huì)選擇什么?

      問題:如果我能給你任何你想要的工作,你會(huì)選擇什么?你真正想做的是什么工作?

      回答:就是這份工作。

      點(diǎn)評(píng):你可能覺得這是個(gè)怪問題,事實(shí)上常有這樣的問題。這個(gè)問題是假設(shè)每個(gè)人都有未實(shí)現(xiàn)的夢(mèng)想,都不能做他真正想做的事,亦即或多或少每個(gè)人都在妥協(xié)。若你真的談了你的夢(mèng)想,而他只會(huì)為圓你夢(mèng)想的夢(mèng),而不錄用你。因此,你確實(shí)要這份工作,那么答案只有一個(gè)。

      ·為什么你還沒有找到工作?

      問題:為什么你還沒有找到工作?

      回答:我正在謹(jǐn)慎選擇我的工作,本來我可以選擇別的工作的,可是那些工作和現(xiàn)在這一個(gè)不同,我實(shí)在看不出它們會(huì)對(duì)我的事業(yè)進(jìn)展有幫助。

      點(diǎn)評(píng):如果你真的拒絕了其他人的錄取,那是再好不過了,如果其他企業(yè)都沒有錄取你,哪也不一定有問題。別人不能只因?yàn)槟悻F(xiàn)在沒有工作,就斷定都沒有人錄取你,不要給人這樣的錯(cuò)覺。

      你對(duì)我們公司有多少了解?

      問題:你對(duì)我們公司有多少了解?

      回答:

      1.完全不了解。

      2.因?yàn)閷?duì)貴公司有關(guān)方面相當(dāng)有興趣,所以才來應(yīng)聘。

      點(diǎn)評(píng):若回答1.那就沒有必要再說下去了,但錄用的機(jī)會(huì)也就小了。最好的回答是2,這是公司想測(cè)試應(yīng)聘者對(duì)公司的興趣,關(guān)注程度,以后進(jìn)公司工作的意愿的問題,因此,最好要稍稍記住公司的簡(jiǎn)介內(nèi)容和招聘人事廣告內(nèi)容。你對(duì)公司有何印象?

      問題:你對(duì)公司有何印象?

      回答:感覺很好,在其他公司沒有這樣的感受。

      點(diǎn)評(píng):或者說出面試當(dāng)天的印象就可以了,因?yàn)檫€沒有正式進(jìn)入公司上班,所以主試者也不會(huì)太過刁難。

      ·你談?wù)勥x擇這份工作的動(dòng)機(jī)?

      問題:你談?wù)勥x擇這份工作的動(dòng)機(jī)?

      回答:“這個(gè)職位剛好是我的專業(yè)對(duì)口,能把學(xué)的書本知識(shí)在實(shí)踐中更好地應(yīng)用?!?/p>

      “我雖然學(xué)的專業(yè)與這職位有區(qū)別,但我對(duì)這方面的能力較強(qiáng),相信自己能干好這份工作。

      點(diǎn)評(píng):這是測(cè)試面試者對(duì)這份工作的理解程度及熱忱,并篩選因一時(shí)興起而來應(yīng)聘的人。

      你家在外地,單位無住宿條件,你如何看待呢?

      問題:你家在外地,我們單位無住宿,你如何看待呢?

      回答:家在外地,貴單位無住宿條件,這些都不影響我來應(yīng)聘貴公司,住宿我可以自己解決,無須單位操心,我看重貴公司的發(fā)展前途。

      點(diǎn)評(píng):不要因?yàn)閭€(gè)人生活上的小問題,而錯(cuò)失良機(jī)。主試者也想看看你對(duì)困難的看法,自信心程度。

      我們不限定固定職位,你認(rèn)為自己最適合做什么?

      問題:我們不限定固定職位,你認(rèn)為自己最適合做什么?

      回答:

      忌:”公司安排我做什么就做什么!“太隨意。

      ”理想的職位就是有機(jī)會(huì)讓我一展專長(zhǎng),為公司的發(fā)展貢獻(xiàn)自己的學(xué)識(shí)?!疤?。

      應(yīng):我學(xué)的是XX專業(yè),我認(rèn)為XX職位比較適合我。

      點(diǎn)評(píng):主試者問你問題,就是想要一個(gè)明確的答案,且明確的回答給人以有思想、有主見、有活力的印象。象上面的回答,是犯了一個(gè)錯(cuò)誤,然而幾乎每個(gè)人都會(huì)犯同樣的錯(cuò)誤,他們總是說自己干什么都可以。因此,回答這樣的問題,干脆用自己的心里話表白,實(shí)事求是,至少讓主試者聽起來感到舒服些。你希望從事什么樣的工作?

      問題:你希望從事什么樣的工作?

      回答:根據(jù)貴公司的招聘職位,我認(rèn)為**職位可能比較適合我,有利于我的能力的發(fā)揮。當(dāng)然,其他有些職位也是可做的,人貴在學(xué)習(xí)。

      點(diǎn)評(píng):應(yīng)試者可以應(yīng)聘的職位作出大致的設(shè)想,讓主試人了解自己的抱負(fù)與努力方向。由于每個(gè)單位都有自己的人事政策,其工作安排未必能完全與求職者的愿望相一致,尤其對(duì)一個(gè)初出茅廬的大學(xué)生來說,從基層做起,從小事做起也是應(yīng)該的。但是,又不能隨便回答:”到哪里工作都可以?!斑@讓人覺得像在”乞討工作“,被人看輕。所以要掌握分寸。

      你為什么要應(yīng)聘我們公司?

      問題:你為什么要應(yīng)聘我們公司?

      回答:看了貴公司的廣告及要求,感到自己比較符合公司的招聘條件,另外,對(duì)貴公司也有些了解,自己若能有幸成為貴公司的一員,是能有助于自己能力的發(fā)揮與發(fā)展的。

      點(diǎn)評(píng):這樣的回答,可顯示出自己積極進(jìn)取的態(tài)度。在談?wù)撚萌藛挝粫r(shí),態(tài)度要誠(chéng)懇、謙和。不論大單位或小單位,都有其優(yōu)勝和劣勢(shì),應(yīng)試者應(yīng)視其實(shí)際情況,提出自己的見解,不要牽強(qiáng)附會(huì),如果一味往對(duì)方臉上貼金,反而會(huì)令人反感。

      ·你在以前實(shí)習(xí)的公司從事什么樣的工作?

      問題:你在以前實(shí)習(xí)的公司從事什么樣的工作?

      回答:在具體說明對(duì)工作的理解程度和熟悉度時(shí),回答要領(lǐng)有三個(gè)方面:擔(dān)任的工作內(nèi)容、職務(wù)、成績(jī)?nèi)?xiàng)。

      點(diǎn)評(píng):這個(gè)問題可以讓公司知道面試者是否符合所要招聘的職位,以前在其他公司的職位是否重要,來判斷應(yīng)聘者的發(fā)展可能。

      你為何選擇應(yīng)聘我們公司?

      問題:你為何選擇應(yīng)聘我們公司?

      回答:我對(duì)貴公司有一定的了解,特別對(duì)公司的XX經(jīng)營(yíng)理念,產(chǎn)品質(zhì)量及員工培訓(xùn)比較看好。

      點(diǎn)評(píng):為了表明應(yīng)聘原因及工作意愿,應(yīng)聘者在回答時(shí)最好要了解企業(yè)狀況,不要籠統(tǒng)回答因?yàn)樽约簩碛邪l(fā)展,更不要回答為了安定等答案。

      ·在公司想做什么樣的工作?

      問題:在公司想做什么樣的工作?

      回答:現(xiàn)在想在某工作方面沖刺,將來則希望能在某方面努力等。朝自己想要的目標(biāo)陳述即可。

      點(diǎn)評(píng):同時(shí)招聘很多職種的公司,最有可能問到這樣的問題,這是判斷應(yīng)聘者個(gè)人的能力傾向。面試者如果不論職種都回答”可以“的話,反而會(huì)讓人懷疑工作態(tài)度。如果這家公司只招聘一個(gè)職種,還是被問到這個(gè)問題時(shí),是為了確認(rèn)應(yīng)聘者有無猶豫,應(yīng)聘者只要清楚的敘述自己想做的事就可以了。

      ·你為何要跳槽?

      問題:你為何要跳槽?

      回答:雖然在前面公司工作挺順的,同事間合作也很愉快,但我感到貴公司更適合我的發(fā)展。

      點(diǎn)評(píng):公司根據(jù)你跳槽原因,意在了解你的就業(yè)動(dòng)機(jī)。

      ·請(qǐng)問你有什么樣的工作觀?

      問題:請(qǐng)問你有什么樣的工作觀?

      回答:我認(rèn)為工作是為了實(shí)現(xiàn)自己的人生價(jià)值,發(fā)揮自己的最大潛能,解決自己的生活問題。

      點(diǎn)評(píng):此話是問工作在你的生活中意味著什么?為何而工作?從工作中得到了什么?幾年后想變成怎樣等。因此,別把它想得太復(fù)雜,可根據(jù)自己的具體情況回答。

      ·你是否可以接受加班?

      問題:你是否可以接受加班?

      回答:我愿意接受挑戰(zhàn)。在自己責(zé)任范圍內(nèi)的工作,不能算是加班。

      點(diǎn)評(píng):這是面試者針對(duì)應(yīng)聘者的工作熱忱而提的問題,因無理的加班不一定是好的。

      你認(rèn)為這份工作最重要的是什么?

      問題:你認(rèn)為這份工作最重要的是什么?

      回答:最重要的是對(duì)自己的挑戰(zhàn)和提高。

      點(diǎn)評(píng):對(duì)工作要加上自己的看法。

      第五篇:Chapter1語言的功能與陷阱

      大學(xué)語文題庫

      一.語言的功能與陷阱 大學(xué)語文主要培養(yǎng)的是(C)。?A、背誦 ?B、書寫 ?C、語感 ?D、文采

      (往年考過)2.(鏡像問題)王蒙的(A)這部作品給使他被錯(cuò)劃為右派。?A、《組織部來了個(gè)年輕人》 ?B、《青春萬歲》 ?C、《春盡江南》 ?D、《中國(guó)天機(jī)》

      王蒙寫作的新中國(guó)歷史上第一部校園小說是(C)。A、《組織部來了個(gè)年輕人》 B、《語言的功能和陷阱》 C、《青春萬歲》 D、《戀愛的季節(jié)》

      王蒙的第一部作品是()。A A.青春萬歲

      B.組織部來了個(gè)年輕人 C.班主任 D.青春之歌

      3.《人論》是(D)的作品。?A、笛卡爾 ?B、黑格爾 ?C、笛卡爾 ?D、卡西爾

      4.“幸?!币辉~在中國(guó)的廣泛使用源于(C)國(guó)家的影響。?A、美國(guó) ?B、德國(guó) ?C、蘇聯(lián) ?D、日本

      5.(鏡像問題)“春心莫共花爭(zhēng)發(fā),一寸相思一寸灰”是(C)的作品。?A、李白 ?B、李賀 ?C、李商隱 ?D、李隆基

      “春心莫共花爭(zhēng)發(fā),一寸相思一寸灰”是李商隱的作品。(是)

      (往年考過)6.“言不盡意”最早是(B)意識(shí)到的問題。?A、孔子 ?B、老子 ?C、孟子 ?D、屈原

      下面(C)最早提出了言不盡意的觀點(diǎn)。A、王蒙 B、蘇軾 C、老子 D、孔子

      (往年考過)7.紅色文學(xué)的主題是(C)。?A、愛情 ?B、青春 ?C、革命 ?D、農(nóng)村

      8.(鏡像問題)“寫小說就是寫語言”是(B)的名言。?A、巴金 ?B、汪曾祺 ?C、郭沫若 ?D、矛盾

      (往年考過)“想象一種語言,就是想象一種社會(huì)生活”是(C)的觀點(diǎn)。?A、康德 ?B、薩特

      ?C、維特根斯坦 ?D、尼采

      (往年考過)“語言的局限就是我們?nèi)渴澜绲木窒蕖笔牵―)的觀點(diǎn)。?A、馬克思 ?B、費(fèi)爾巴哈 ?C、黑格爾 ?D、恩格斯

      (D)提出了“如果說不清楚就說明沒有想清楚,如果寫不清楚就說明沒有說清楚”。A、王蒙 B、徐志摩 C、臧克家 D、聞一多

      (A)曾說過寫小說就是寫語言。A、汪曾祺 B、王蒙 C、普羅普 D、林風(fēng)眠

      “語言是思想的物質(zhì)的、直接的現(xiàn)實(shí)”是(D)的觀點(diǎn)。A、弗洛伊德 B、王蒙 C、郭沫若 D、恩格斯 9.(鏡像問題)辨認(rèn)色彩最強(qiáng)的是(B)人。?A、亞洲 ?B、歐洲 ?C、非洲 ?D、美洲

      中國(guó)人辨別色彩的能力強(qiáng)于歐洲人,是因?yàn)闈h語中表示色彩的詞匯非常豐富。()我的答案:×

      10.《團(tuán)結(jié)一切抗日力量,反對(duì)反共頑固派》是毛澤東的作品,這篇文章的語言是面向(B)群體。

      ?A、知識(shí)分子 ?B、農(nóng)民 ?C、工人 ?D、官方

      (往年考過)11.小品《主角和配角》反應(yīng)了(C)時(shí)期兩種群體力量的博弈。?A、抗戰(zhàn) ?B、大躍進(jìn) ?C、改革開放 ?D、現(xiàn)代化建設(shè)

      12.小品《主角和配角》體現(xiàn)了語言和(C)的關(guān)系。?A、思想 ?B、意義 ?C、權(quán)力 ?D、情感

      13.蔣介石在大陸第一次作為正面形象出現(xiàn)的電視劇是(D)。?A、《闖關(guān)東》 ?B、《席卷大西南》 ?C、《亮劍》

      ?D、《長(zhǎng)沙保衛(wèi)戰(zhàn)》

      (往年考過)14.《熱血、辛勞、汗水和眼淚》是(D)的演講。?A、希特勒 ?B、斯大林 ?C、毛澤東 ?D、丘吉爾

      《熱血、辛勞、汗水和眼淚》是()時(shí)期的演講。C ? A 法國(guó)大革命 ? B 一戰(zhàn) ? C 二戰(zhàn)

      ? D 解放戰(zhàn)爭(zhēng)

      15.(D)真正帶來了長(zhǎng)篇小說的繁榮。A、毛筆寫作 B、鋼筆寫作 C、沾筆寫作 D、電腦寫作

      16.(B)因?qū)懽骶﹦ 渡臣忆骸繁徽袅擞遗傻拿弊?。A、王蒙 B、汪曾祺 C、丁玲 D、艾青

      17.下面詩句中的月亮不代表思鄉(xiāng)之情的是(A)。A、月明星稀,烏鵲南飛 B、海上生明月,天涯共此時(shí) C、舉頭望明月,低頭思故鄉(xiāng) D、露從今夜白,月是故鄉(xiāng)明

      18.王蒙總結(jié)了語言的三種功能,其中不包括(B)。A、交流功能 B、區(qū)別功能 C、推動(dòng)思想功能 D、浪漫功能

      19.《俠客行》中,只有不識(shí)字的小孩認(rèn)出了蝌蚪文,這體現(xiàn)了語言的(D)。A、言不盡意 B、言過其實(shí) C、可替代性

      D、對(duì)思想的束縛

      20.老一輩的人不懂瑪麗蘇、大叔控等詞的意思,體現(xiàn)了語言(D)。A、是交流工具

      B、是表達(dá)感情的媒介 C、具有大眾性 D、具有時(shí)代性

      21.(鏡像問題)藝術(shù)的真正魅力來源于(D)。A、覺悟 B、修養(yǎng) C、藝術(shù)技巧 D、語言技術(shù)

      藝術(shù)的真正魅力來自于語言。我的答案:√

      22.下面詞語被語言賦予時(shí)間流逝感受的是(B)。A、月亮 B、流水 C、桃花 D、梅花

      23.下面不能體現(xiàn)時(shí)間是空間的隱喻的是(C)。A、一頓飯的功夫 B、從前

      C、9點(diǎn)10分 D、自古以來

      24.毛澤東發(fā)表文章團(tuán)結(jié)廣大群眾抗日時(shí)使用的語言是(C)。A、嚴(yán)肅正規(guī)的語言 B、冷靜的語言 C、市井化的語言 D、有邏輯的語言

      25.下面不能體現(xiàn)語言中包含著權(quán)力的是(B)。A、青年特指男性

      B、俄羅斯的敘事詩很長(zhǎng) C、小老婆 D、叫花子

      26.紅色文學(xué)的主題是()。C A 愛情 B 青春 C 革命 D 農(nóng)村

      27.美國(guó)“垮掉的一代”反抗的是(C)。A、政府 B、體制 C、父輩 D、戰(zhàn)爭(zhēng)

      28.下面不能體現(xiàn)語言性別歧視的是(A)。A、女人不能罵人 B、默認(rèn)青年為男性

      C、女人沒有按照正常人類的形式被命名 D、生男孩是可好,生女孩是也好

      29.(鏡像問題)下面不屬于演講技術(shù)特點(diǎn)的是(D)。A、感染性 B、單向性 C、共謀性 D、冷靜性

      演講需要從幾個(gè)方面增強(qiáng)感染力,其中不包括(C)。A、感情 B、獨(dú)語論斷 C、道理

      D、自我打動(dòng)

      30.語言的權(quán)力效益體現(xiàn)在(D)。A、讓人覺得屈辱 B、讓人覺得憤怒 C、讓人覺得激動(dòng) D、以上都是

      31.下面不能作為公共象征的是(B)A、熱血 B、電腦 C、長(zhǎng)城 D、五星紅旗 我的答案:B(往年考過)32.《日喻說》是(A)的文章。A、蘇軾 B、韓愈 C、柳宗元 D、杜甫

      33.黑格爾提出,人和動(dòng)物最重要的區(qū)別是(D)。

      A、勞動(dòng) B、思維 C、情感 D、語言

      (往年考過)34..王蒙認(rèn)同語言工具論的提法。我的答案:×

      35.王蒙提出人與動(dòng)物最重要的區(qū)別是語言。()我的答案:×

      (往年考過)36.《語言的功能與陷阱》是王蒙的一篇演講詞。我的答案:√

      (往年考過)37.語言會(huì)激發(fā)思想,但也會(huì)扼殺人的創(chuàng)造力。我的答案:√

      38.語言的“狗屎化效應(yīng)”指語言說得多了,語言往往就失去意義了。我的答案:√

      (往年考過)39.語言規(guī)定了種種瞬間的體驗(yàn),甚至可以創(chuàng)造其本身不具有的意味。我的答案:√

      (往年考過)40.在文學(xué)創(chuàng)作中,永遠(yuǎn)是內(nèi)容決定形式,形式服務(wù)于內(nèi)容。我的答案:×

      (往年考過)41.語言不需要表達(dá)對(duì)象,本身就具有獨(dú)立的審美意義。我的答案:√

      (往年考過)42.演講是一種公共話語行為。語言組織總是暗含著鼓動(dòng)性和說服性。我的答案:√

      43.在演講中,理性的說服比感情的感染更有效。我的答案:×

      44.(鏡像問題)語言一定要符合現(xiàn)實(shí)生活,否則會(huì)產(chǎn)生消極的后果。我的答案:×

      45語言來源于生活,不能脫離現(xiàn)實(shí)。()我的答案:×

      (往年考過)46.語言本身隱藏著權(quán)力,本身可以影響我們的認(rèn)識(shí)和思考。我的答案:√

      (往年考過)47.一般來講,作家的理論思考能力都很好。我的答案:×

      48.語言有幫助思想、推動(dòng)思想的功能。我的答案:√

      (往年考過)49.語言一定要符合現(xiàn)實(shí)生活,否則會(huì)產(chǎn)生消極的后果。我的答案:×

      50.想象一種語言,就是想象一種社會(huì)生活。我的答案:√

      (往年考過)51.語言本身隱藏著權(quán)力,本身可以影響我們的認(rèn)識(shí)和思考。我的答案:√

      52.作家寫作都是事先在腦子里構(gòu)思好情節(jié)大綱才開始寫的。()我的答案:×

      53“不著一字,盡得風(fēng)流”體現(xiàn)了言不盡意的魅力。()我的答案:√

      54.任何時(shí)候言過其實(shí)都會(huì)讓人反感,要盡量避免言過其實(shí)。()我的答案:×

      55.不同的語言代表了不同的文化和社會(huì)地位。()我的答案:√

      56.語言可以催生感情,即使是哈哈大笑也可能傳遞悲涼的情緒。我的答案:√

      57.市場(chǎng)社會(huì)的代表語言就是官方語言。我的答案:×

      58演講中主體置換的目的是改變聽眾的利益需求。()我的答案:×

      59人們對(duì)客觀事物的感情很多時(shí)候會(huì)受到語言的影響。()我的答案:√

      60虛假提問能夠增強(qiáng)說話的氣勢(shì)。()我的答案:√

      61畫國(guó)畫的對(duì)墨色的分辨能力要強(qiáng)于畫油畫的 我的答案:√

      63語言的形式可以決定語言的內(nèi)容。()我的答案:√

      64蔣介石在大陸第一次作為正面人物出現(xiàn)的電視劇是《長(zhǎng)沙保衛(wèi)戰(zhàn)》

      我的答案:√

      (往年考過)65從語言的使用上來講,使用反問句去詢問他人,通常是不禮貌的。我的答案:√

      (往年考過)66在一個(gè)民主、自由的社會(huì)里,言論自由是社會(huì)得以正常運(yùn)作的基石,正是因?yàn)槌珜?dǎo)言論自由,因而也就排除了貴族或特權(quán)階層的存在可能,畢竟言論自由不等于言論特權(quán)。

      我的答案:√

      67王蒙曾在《語言的功能與陷阱中》舉了諸葛亮斬馬謖的故事,這個(gè)例子說明的是語言的哪個(gè)陷阱?言過其實(shí)

      68王蒙曾在《語言的功能與陷阱中》舉了阿Q和徐志摩分別向吳媽示愛的例子,作者意圖要說明語言的哪種功能?修辭

      69王蒙在《語言的功能與陷阱》中舉了“失空斬“的例子,用來說明語言的哪種問題?言過其實(shí)

      70王蒙在《語言的功能與陷阱》中舉了”輪扁斫輪"的例子,用來說明語言的哪種陷阱?言不能達(dá)意

      71王蒙在《語言的功能與陷阱》中曾提到李商隱的詩歌,是為了說明語言哪方面的問題?語言具有藝術(shù)和審美功能的問題 72列哪位作者曾經(jīng)擔(dān)任過中國(guó)的文化部長(zhǎng)?王蒙

      下載C語言缺陷與陷阱word格式文檔
      下載C語言缺陷與陷阱.doc
      將本文檔下載到自己電腦,方便修改和收藏,請(qǐng)勿使用迅雷等下載。
      點(diǎn)此處下載文檔

      文檔為doc格式


      聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn)自行上傳,本網(wǎng)站不擁有所有權(quán),未作人工編輯處理,也不承擔(dān)相關(guān)法律責(zé)任。如果您發(fā)現(xiàn)有涉嫌版權(quán)的內(nèi)容,歡迎發(fā)送郵件至:645879355@qq.com 進(jìn)行舉報(bào),并提供相關(guān)證據(jù),工作人員會(huì)在5個(gè)工作日內(nèi)聯(lián)系你,一經(jīng)查實(shí),本站將立刻刪除涉嫌侵權(quán)內(nèi)容。

      相關(guān)范文推薦

        王蒙《語言的功能和陷阱》(模版)

        王蒙《語言的功能與陷阱》 語言對(duì)人來說是太重要了,可以說是人與非人之間的一個(gè)非常重大的區(qū)別,當(dāng)然我們首先倡導(dǎo)的學(xué)說是勞動(dòng)創(chuàng)造了人,但是從一定意義上也可以說語言和勞動(dòng)一......

        面試中你必須要知道的語言陷阱5篇

        面試中你必須要知道的語言陷阱如:“你經(jīng)歷太單純,而我們需要的是社會(huì)經(jīng)驗(yàn)豐富的人”,“你性格過于內(nèi)向,這恐怕與我們的職業(yè)不合適”,“我們需要名牌院校的畢業(yè)生,你并非畢業(yè)于名牌......

        事業(yè)單位考試:面試技巧之如何避開語言陷阱

        文章來自赤峰人事考試信息網(wǎng):http://chifeng.offcn.com 事業(yè)單位考試:面試技巧之如何避開語言陷阱事業(yè)單位:語言,是事業(yè)單位面試重要的內(nèi)容之一。有些參加事業(yè)單位考試的考生可......

        薪酬陷阱

        薪酬陷阱:求職業(yè)要小心啊 找工作不是難事,找到一份合適自己又喜歡的工作就是難上加難了,而找到一份自己喜歡而工資又高的簡(jiǎn)直就比登天還難了,所以大家總是在不斷地尋找高工資的......

        合同陷阱

        新房首頁 > 資訊 > 買房知識(shí) > 全文 合同:合同陷阱摘要:實(shí)例1:《××居商品房認(rèn)購書》中規(guī)定:“若乙方支付定金之日起十天內(nèi)未能依時(shí)簽署《商品房買賣合同》及交付首期房?jī)r(jià)款,則......

        醫(yī)療缺陷標(biāo)準(zhǔn)與管理辦法

        洪江市第一中醫(yī)醫(yī)院 醫(yī)療缺陷標(biāo)準(zhǔn)與管理辦法 醫(yī)療糾紛大多因醫(yī)療缺陷引起。因此加強(qiáng)醫(yī)療缺陷的管理就顯得尤為重要。根據(jù)國(guó)家醫(yī)療質(zhì)量管理標(biāo)準(zhǔn)及有關(guān)文件精神,結(jié)合我院實(shí)際情......

        作文:缺陷與圓滿(教案)

        作文:缺陷與圓滿(教案) 【作文材料】 殘疾人舞蹈家邰麗華因領(lǐng)舞“千手觀音”而家喻戶曉,當(dāng)有人問她:“你會(huì)不會(huì)覺得老天不公平,沒有給你一個(gè)健全的身體,讓你聽不到這個(gè)世界?”邰麗華......

        公務(wù)員法的缺陷與完善

        《公務(wù)員法》的修改與完善K060841037李玉果摘要:公務(wù)員法是一個(gè)由憲法、國(guó)務(wù)院組織法、地方組織法、法官法、檢察官法、警察法、監(jiān)察法等國(guó)務(wù)院有關(guān)公務(wù)員管理的行政法規(guī),以......