跳轉到內容

C 語言入門/C 語言常見程式設計問題

來自華夏公益教科書,開放的書籍,開放的世界

C 語言中存在一些常見的程式設計陷阱,即使是經驗豐富的程式設計師也會陷入其中

1: 將“=”(賦值運算子)與“==”(等號運算子)混淆。例如

   if ( x = 1 ){ /* wrong! */
   }
   ...
   if ( x == 1 ){ /* good. */
   }

以及

   for ( x == 1; ... /* wrong! */
   ...
   for ( x = 1; ... /* good. */

2: 混淆表示式中運算子的優先順序。如有疑問,請使用括號來強制優先順序。使用一些額外的括號絕不會有害。

3: 混淆結構成員運算子。如果“struct_val”是一個結構體,而“struct_ptr”是指向該結構體的指標,那麼

   struct_val->myname

是錯誤的,

   struct_ptr.myname

也是錯誤的。

4: 對“printf()”和“scanf()”使用不正確的格式程式碼。例如,使用“%f”列印“int”會導致奇怪的輸出。

   int data[20];
   ...
   for ( x = 1; x <= 20; ++x )
   {
     printf( "%d\n", data[x] );
   }

5: 記住陣列的實際基索引為 0,最後一個索引比宣告的大小小 1。例如

當“x”為 20 時,將給出無效的結果。由於 C 語言不執行邊界檢查,因此這可能很難發現。

6: 混淆多維陣列的語法。如果

   data[10][10]

是一個二維陣列,那麼

   data[2][7]

將選擇該陣列中的一個元素。但是

   data[ 2, 7 ]

將給出無效的結果,但不會被 C 語言標記為錯誤。

7: 混淆字串和字元常量。以下是字串

   "Y"

與字元常量

   'Y'

相反。這可能會在將字串與字元常量進行比較時造成麻煩。

8: 忘記字串以空字元 ('\0') 結尾。這意味著字串將始終比它儲存的文字大一個字元。如果字串是逐字元建立的,並且程式沒有在末尾新增空字元,也會導致問題。

9: 忘記為字串分配足夠的記憶體——或者,如果聲明瞭指標,則完全沒有為其分配任何記憶體。

10: 忘記檢查庫函式的返回值。大多數庫函式都返回一個錯誤程式碼;雖然可能不希望檢查每次“printf()”呼叫,但在關鍵操作中要小心不要忽略錯誤程式碼。

當然,忘記儲存函式返回的值(當這是獲取該值的唯一方法時)是一個愚蠢的行為,但人們偶爾會做這樣的事。

11: 擁有重複的庫函式名稱。編譯器不會始終捕獲此類錯誤。

12: 忘記為庫函式指定標頭檔案。

13: 將變數作為引數傳遞給函式,而應該傳遞指標,反之亦然。如果函式透過引數返回值,則意味著它必須指定為指標

   myfunc( &myvar );

以下將不起作用

   myfunc( myvar );

記住,即使函式不返回值,也可能需要指標作為引數,不過作為規則,這不是一個好的程式設計實踐。

14: 在使用巢狀的“if”和“else”語句時感到困惑。避免此問題的最佳方法是始終使用括號。避免複雜的“if”結構也是一個好主意;如果有選擇,請使用“switch”。即使對於簡單的“if”語句,使用“switch”也很有用,因為它更容易擴充套件結構,如果需要的話。

15: 忘記分號——儘管編譯器通常會捕獲這一點——或者在不應該出現的地方新增分號——它通常不會這樣做。例如

   for( x = 1; x < 10; ++x );
   {
      printf( "%d\n", x )
   }

永遠不會列印任何內容。

16: 忘記“switch”結構中的“break”語句。如前所述,這樣做只會導致執行從“switch”的一個子句流向另一個子句。

17: 粗心地混合和濫用有符號和無符號值,或不同資料型別。這可能會導致一些非常微妙的錯誤。要注意的一個特殊問題是將單個字元變數宣告為“unsigned char”。許多 I/O 函式將期望“unsigned int”型別的返回值,並且無法正確地標記 EOF。建議將函式引數強制轉換為適當的型別,即使看起來型別轉換會自行處理也是如此。

18: 變數名稱混淆。為了確保程式碼的可移植性,建議此類識別符號的前六個字元是唯一的。

19: 通常,過於複雜和巧妙的程式碼。程式是令人討厭的東西,即使它有效,也必須修改甚至移植到不同的語言。保持乾淨的結構,做簡單直接的事情,除非它帶來了不可接受的代價。

華夏公益教科書