C陷阱與缺陷.doc_第1頁
C陷阱與缺陷.doc_第2頁
C陷阱與缺陷.doc_第3頁
C陷阱與缺陷.doc_第4頁
C陷阱與缺陷.doc_第5頁
已閱讀5頁,還剩2頁未讀, 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、1.1 &,| :按位運算 &&,| :邏輯運算 = 不同于 = 來看這樣一個例子,while (c=' ' | c= 't' | c='n') c = getc(f); 由于程序員在比較字符 和變量c時,誤將 = 寫成 = ,那么while后的表達式恒為 1,因為' '不等于零,它的ASCII碼值為32。 為了避免這種情況,我們可以這樣寫 while(''=c | 't'= c | 'n'=c) ,這樣即使不小心將 = 寫成 = ,編譯器也會報錯。 在C中,

2、單引號括起來表示一個整數,雙引號括起來表示指針。例如,字符和字符串。1.3 詞法分析中的“貪心法” C編譯器讀入一個符號的規則:每一個符號應該包含盡可能多的字符,方向為自左向右。.大嘴法 編譯器在解析字符的時候會盡可能的解析成可能組成一個符號的最常的字符串。 例如,a-b <=> (a-) -b 而 y = x/*p; 中 /* 被編譯器理解為一段注釋的開始,如果本意是用x除以p所指向的值,應該重寫如下y = x/ *p; 或更加清楚一點,寫作 y = x/(*p); 在用雙引號括起來的字符串中,注釋符 /* 屬于字符串的一部分,而在注釋中出現的雙引號""屬于注

3、釋的一部分。整數常量的第一個字符是數字0,那么該常量將被視為八進制數。 單引號所括起的字符代表一個整數,雙引號括起的則代表一個指針(字符數組,自動加0) 1. 理解函數聲明 通過認識這個讓人“不寒而栗”的式子: (*(void(*)()0)() 1)把0強制轉換成了類型 void(*)(),這是一個指向函數的指針,所指向的這個函數返回值為void ps:a)float *g():這是一個函數,返回指針float*,因為()的優先級比*高。 b)如果fp是一個函數指針,怎么調用該指針所指向的函數呢? (*fp)(); c)為了使表述更加清晰: typedef void (*funptr)();

4、則那個不寒而栗的式子可以表達為: (*(funptr)0)() 2)由1)-b我們得知,這個式子的意思就是調用了上面所說的函數指針所指向的函數。 2.運算符優先級 1)任何一個邏輯運算符的優先級低于任何一個關系運算符 2)移位運算符的優先級低于算術運算符,但是高于關系運算符 算術>移位>關系>邏輯 3.不要忘了switch中的break,否則會把滿足的case后面的所有case都執行。 小心指針和除法的一起使用: y = x/*p; 這里的/*理解為注釋!解決方法是: (1) y = x / *p (2) y = x /(*p) 以第二個更為清晰.6./*看上去是注釋的開始如

5、: a=/*b; 老版本的編譯器會當作是:a =/ *b 7.char *slash = '/' /* 編譯錯誤,'/'并不是字符指針 */ 同理,而且有些編譯器不檢查函數參數類型,所以: printf('n'); 在程序運行時會產生難以預料的結果,而不給出編譯器警告或者錯誤. 8.整型數(一般為16或者32位)的存儲空間可以容納多個字符(一般為8位),因此有些C編譯器允許在一個字符常量(以及字符串常量)中包含多個字符.也就是說,用'yes'代替"yes"并不被該編譯器檢測到.后者的含義是依次包含'y&

6、#39;,'e','s'以及空字符'0'的4個連續內存單元的首地址.前者(即是'yes')的含義并沒有準確地進行定義:大多數C編譯器理解為:"一個整數值,由'y','e','s'所代表的整數值按照特定編譯器實現中定義的方式組合得到". (1)在Borland C+ v5.5和LCC v3.6中采取的做法是,忽略多余的字符,最后的整數值就是第一個字符的整數值; (2)在Visual C+ 6.0和GCC v2.95中采取的做法是,依次用后一個字符覆蓋前一個字符,最后

7、得到的整數值就是最后一個字符的整數值. 二、語法陷阱2.1 理解函數聲明 來看一個表達式, (*(void(*)()0)(); 你能看出來這個表達式的含義嗎? 看不出來不要緊,慢慢來. 首先,回顧一下C變量的聲明,它由兩部分組成:類型以及一組類似表達式的聲明符,最簡單的聲明符就是單個變量。如,float f,g; 這個聲明的含義:當對其求值,表達式f和g的類型為浮點型。 因為聲明符與表達式類似,我們可以在聲明符中任意使用括號: float (f); 含義:當對其求值,(f)的類型為浮點型,由此可知,f也是浮點型。 我們將這個邏輯推廣到函數和指針的聲明,如: float ff(); float

8、*pt; 這些形式組合使用,如 float *g(),(*h)(); 由于()的結合優先級高于 * ,所以g是一個函數,它的返回類型為指向浮點數的指針;h是一個函數指針,h所指向函數的返回值為浮點類型。 由聲明到類型轉換符:只需要把聲明中的變量名和聲明末尾的分號去掉,再將剩余部分用一個括號整個“封裝”。如,聲明 float (*h)(); 為一個指向返回值為float的函數的指針 而 (float (*)() 表示一個“指向返回值為float的函數的指針”的類型轉換符。 函數指針的調用: (*fp)();如果fp的聲明如下:void (*fp)();fp是一個指向返回值為void類型的函數的指

9、針,那么(*fp)()的值為void。、 因此 (void (*)()0 表示將常數 0 轉型為“指向返回值為 void 的函數的指針”類型。請認真理解這一點。 我們可以用 (void (*)()0 代替 fp ,從而得到如下調用(*(void(*)()0)(); 2.3 注意作為語句結束標志的分號 來看一個代碼段if (a>b); a=0;else b=0; 注意if表達式后的分號,上面的代碼相當于if (a>b) a=0;else b=0; 由于沒有if與 else 匹配,編譯器將產生警告。再來看一個例子,struct logrec int date; int time;mai

10、n(). 注意到第一個與main定義之間是沒有分號的。因此這段代碼的效果是聲明函數main的返回值是結構 logrec 類型。三、語義陷阱3.1 指針與數組C中的數組值得注意的兩點: 一是C中只有一維數組,而且數組的大小必須在編譯期就作為一個常數確定下來。然而,數組的元素可以使任何類型的對象,當然也可以是另外一個數組,這樣便可以“仿真”多維數組。 二是對于一個數組,我們只能夠做兩件事:確定該數組大小,一級獲得指向該數組下標為0的元素的指針(下標運算其實都是通過指針進行的)。 來看一個例子,若定義int s1231;int *p;int i;則 p = s4;是無誤的而 p = s; 則是不行的

11、。因為s會被轉換為指向二維數組的指針,與p類型不匹配。然而,像下面這樣做是可以的int (*ap)31;int s1231;那么 ap = s; 是可以通過的。 假如定義了 int a22; 則 *(a+i) 即數組a中下標為i的元素的引用,簡記為 ai。實際上, a+i 與 i+a 的含義一樣,因此 ai 與 ia 也具有同樣的含義。 3.2 非數組的指針 假如我們有兩個這樣的字符串 s 和 t,并且希望將他們連成單個字符串 r。如char *r;strcpy(r,s);strcat(r,t);不幸的是,這樣做事不行的。因為不能確定r 指向何處。不僅要讓r 指向一個地址,而且r所指向的地址還

12、應該有內存空間可供容納字符串。于是我們可以這樣做char *r;r = malloc(strlen(s) + strlen(t) + 1);現在可以 strcpy(r,s); strcat(r,t); 了,不過別忘了最后 free(r); 這里注意到給 r 分配內存的時候 +1 了,這是因為 strlen() 返回的值不包括 '0',所以要多申請存放 '0' 的空間! 3.6 邊界計算與不對稱邊界 先來看一個例子,我們常常對類似的代碼這樣處理int i,a10;for (i=0;i<10;i+)ai = 0;而不是像下面這樣:int i,a10;for (

13、i=1;i<=10;i+)ai-1 = 0;或者int i,a10;for (i=0;i<=9;i+)ai = 0; 原因是前一種方式更適合像C這樣的數組下標從0開始的語言。這里包含在取值范圍中的 0 為“入界點”,而不包含在取值范圍之中的 10 為“出界點”。由于是形如 >=0 且 <10 這樣不對稱的形式,不妨把這種方式稱為“不對稱邊界”。 在處理數組指針的時候,我們將“上界”指向緩沖區第一個未占用字符(假設指針為k),而不是最后一個已占用的字符,則數組的元素個數為該指針減去數組的首地址(假設為p): k - p。 這里細心的讀者可能會擔心越界的問題。請看: C標準

14、中:數組中 實際不存在的“溢界”元素 的地址位于數組所占內存之后,對該元素的引用是非法的,然而這個地址可以用于進行賦值和比較。也就是數組最后一個元素所在內存的下一個內存地址可以用于比較,但該內存中的內容卻是不可讀寫的。 這樣,結合特例外推法可以準確、直觀的處理邊界問題。這部分內容值得反復體會,請參閱原書P45。 3.7 求值順序 C語言中只有四個運算符(&& 、|、?:和 ,)存在規定的求值順序。其中:&&和 | 是先對左側操作數求值,只在需要時對右操作數求值。A?B:C 是先對A求值,根據A的值再求B或C的值。而逗號運算符是先對做操作數求值,然后該值被“丟棄”

15、,再求右值。 在函數參數中的逗號并非逗號運算符,其求值順序未定義。但在g(x,y)中卻是先 x后y求值,該函數只有一個參數,即求值結果。 來看個小技巧,if(y!=0 && x/y > 5) complain(); 這里利用 && 的求職順序先檢測y是否為 0 ,避免 0 作為除數。 3.10 為函數 main提供返回值 來看這個函數main()若無顯式聲明返回類型,則默認返回類型為 int 。一個返回值為整型的函數如果返回失敗,實際上是隱含地返回了某個“垃圾”整數。但是由這個返回值是無法判斷該函數是否執行成功的! 六 預處理器需求:需要將特定數量(例如,

16、某個數據表的大?。┰诔绦蚶锍霈F的所有實例統統加以修改。我們希望只改動一處數值,然后重新編譯即可實現。大多數C語言實現在函數調用時都會帶來重大的開銷。我們希望有這樣一種程序塊,它看上去像函數,但卻沒有函數調用的開銷。 6.1 宏中的空格對于 #define f (x) (x)-1)它展開后為 (x) (x)-1) 而 #define f(x) (x)-1) 展開后為 (x)-1) ,并且對這個宏定義,f(3) 和 f (3)這兩種調用方式求值后都為2。 6.2 宏并不是函數 從表面上看,宏玉函數的行為非常相似,但它們并不完全等同。假如:#define abs(x) x>0?x:-x則對 a

17、bs(a-b)展開后得到 a-b>0?a-b:-a-b ,其中-a-b即(-a)-b 與我們期望的 -(a-b) 不一致;又,對abs(a)+1 展開后得到 a>0?a:-a+1 (與上類似,與期望不符)。為此,應該將宏定義中每一個參數用括號括起來,并且將整個表達式也括起來,以預防引起優先級有關的問題。正確的定義應該是這樣的#define abs(x) (x)>0)?(x):(-x) 再來看一個例子,假如有宏定義#define max(a,b) (a)>(b)?(a):(b)程序中有這樣的代碼:biggest = x0;i=1;while(i<n) biggest

18、 = max(biggest,xi+);如果max為一個真正的函數,則上述代碼無誤,而如果max為一個宏,那就不能正常工作。原因是對宏擴展后biggest = (biggest)>(xi+)?(biggest):(xi+);其中這部分 xi+ 有可能被執行兩次,與我們的期望不符。解決辦法是確保宏max中的參數沒有副作用,像這樣:biggest = x0;for(i = 1;i<n;i+) biggest = max(biggest,xi); 由此,我們看到宏和函數的一些區別。 6.3 宏并不是語句 考慮這樣一個例子#define assert(e) if(!(e) assert_error(_FILE_,_LINE_)則對以下代碼if

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論