用法char ch getchar 正確性詳解

2022-05-22 20:21:11 字數 3299 閱讀 7427

《c陷阱與缺陷》 chap5.1

#include int main()

return 0;

}

getchar函式在一般情況下返回的是標準輸入檔案中的下乙個字元,當沒有輸入時返回eof(乙個在標頭檔案stdio.h中被定義的值,不同於任何乙個字元,一般c語言實現定義為-1)。這個程式乍一看似乎把標準輸入複製到標準輸出,實則不然。

原因在於程式中的變數c被宣告為char型別,而不是int型別。這意味著c無法容下所有可能的字元,特別是,可能無法容下eof。因此,最終結果存在以下幾種可能:

1)某些合法的輸入在被截斷後,使得c的取值和eof相同;這時程式將在檔案複製的中途終止;

2)c根本不可能取到eof這個值;這時程式陷入死迴圈;

3)程式表面上似乎能夠正常工作,但完全是因為巧合。儘管函式getchar的返回結果在賦值給char型別的變數c時會發生「截斷」操作,儘管while語句中比較運算的運算元不是函式getchar的返回值,然而令人驚訝地是許多編譯器對上述表示式的實現並不正確。這些編譯器確實對函式getchar的返回值做了截斷處理,並把低端直接部分賦給了變數c。但是,它們在比較表示式中並不是比較c與eof,而是比較getchar函式的返回值與eof,編譯器如果採取這種做法,上面的例子程式看上去就能夠「正常」執行了。

一篇網文的詳盡分析

)許多初學者都習慣用 char 型變數接收 getchar、getc,fgetc 等函式的返回值,其實這麼做是不對的,並且隱含著足以致命的錯誤。getchar 等函式的返回值型別都是 int 型,當這些函式讀取出錯或者讀完檔案後,會返回 eof.eof 是乙個巨集,標準規定它的值必須是乙個 int 型的負數常量。通常編譯器都會把 eof 定義為 -1.問題就出在這裡,使用 char 型變數接收 getchar 等函式的返回值會導致對 eof 的辨認出錯,或者錯把好的資料誤認為是 eof,或者把 eof 誤認為是好的資料。例如:

int c;  /* 正確。應該使用 int 型變數接收 fgetc 的返回值 */

while ( (c = fgetc(fp)) != eof )

如上例所示,我們很多時候都需要先用乙個變數接收 fgetc 等函式的返回值,然後再用這個變數和 eof 比較,判斷是否已經讀完檔案。上面這個例子是正確的,把 c 定義為 int 型保證了它能正確接收 fgetc 返回的 eof,從而保證了這個比較的正確性。但是,如果把 c 定義為 char 型,則會導致意想不到的後果。

首先,因為 fgetc 等函式的返回值是 int 型的,當賦值給 char 型變數時,會發生降級,從而導致資料截斷。例如:

---------------------------------

| 十進位制 |      int     |  char |

|--------|--------------|-------|

|   10   | 00 00 00 0a  |   0a  |

|   -1   | ff ff ff ff  |   ff  |

|   -2   | ff ff ff fe  |   fe  |

---------------------------------

在此,我們假設 int 和 char 分別是 32 位和 8 位的。由上表可得,從 int 型到 char 型,損失了 3 個位元組的資料。而當我們要拿 char 型和 int 型比較的時候,char 型會自動公升級為 int 型。char 型公升級為 int 型後的值會因為它到底是 signed char 還是 unsigned char 而有所不同。不幸的是,如果我們沒有使用 signed 或者 unsigned 來修飾 char,那麼我們無從知曉 char 到底是指 unsigned char 還是指 signed char,因為這是由編譯器決定的。不過,無論 char 是 signed 的也好,unsigned 的也罷,都不能改變使用 char 型變數接收 fgetc 等函式的返回值是錯誤的這個事實。唯一能改變的是該錯誤導致的後果。前面我們說了,char 型和 int 型比較時,char 會自動公升級為 int,下面我們來看看 signed char 和 unsigned char 在轉換成 int 後,它們的值有什麼不同:

---------------------------------------

|  char |   unsigned    |   signed    |

|-------|---------------|-------------|

|  10   |  00 00 00 0a  | 00 00 00 0a |

|  ff   |  00 00 00 ff  | ff ff ff ff |

|  fe   |  00 00 00 fe  | ff ff ff fe |

---------------------------------------

由上表可知,當 char 是 unsigned 的時候,其轉換為 int 後的值是正數。也就是說,假如我們把 c 定義為 char 型變數,而編譯器預設 char 為 unsigned char,那麼以下表示式將永遠成立:

(c = fgetc(fp)) != eof  /* c 的值永遠為正數,而標準規定 eof 為負數 */
也就是說以下迴圈是乙個死迴圈:

while ( (c = fgetc(fp)) != eof )

讀到這裡,可能有些讀者朋友會說:「那麼我明確把 c 定義為 signed char 型的就沒問題了吧!」很遺憾,就算把 c 定義為 signed char,仍然是錯誤的。假設 fgetc 等函式讀到乙個位元組的值為 ff,那麼返回值就是 00 00 00 ff。把這個值賦值給 c 後, c 的值變成 ff。然後 c 的值為了和 eof 比較,會自動公升級為 int 型的值,也就是 ff ff ff ff。從而導致以下表示式不成立:

(c = fgetc(fp)) != eof  /* 讀到值為 ff 的字元,誤認為 eof */
也就是說以下迴圈在沒有讀完檔案的情況下提前退出while迴圈。

綜上所述,使用 char 型變數接收 fgetc 等函式的返回值是錯誤的,我們必須使用 int 型變數接收這些函式的返回值,然後判斷接收到的值是否 eof.只有判斷發現該返回值並非 eof,我們才可以把該值賦值給 char 型變數。

同理,c++ 中,用 char 型變數接收 cin.get() 的返回值也是錯誤的。不過,把 char 型變數當作引數傳遞給 cin.get 則是正確的。例如:

char c = cin.get();         // 錯誤,理由同上

char c;

cin.get(c);          // 正確

stack用法,queue用法,

stack stack 模板類的定義在標頭檔案中。stack 模板類需要兩個模板引數,乙個是元素型別,乙個容器型別,但只有元素型別是必要 的,在不指定容器型別時,預設的容器型別為deque。定義stack 物件的示例 如下 stack s1 stack s2 stack 的基本操作有 入棧,如例 s...

stack用法,queue用法,

stack stack 模板類的定義在標頭檔案中。stack 模板類需要兩個模板引數,乙個是元素型別,乙個容器型別,但只有元素型別是必要 的,在不指定容器型別時,預設的容器型別為deque。定義stack 物件的示例 如下 stack s1 stack s2 stack 的基本操作有 入棧,如例 s...

object args用法 args的用法

object args用法 args和 kwargs主要用於函式定義中。args和 kwargs允許您將可變數量的引數傳遞給函式。變數在這裡的含義是,您事先不知道使用者可以將多少個引數傳遞給您的函式,因此在這種情況下,您將使用這兩個關鍵字。args用於將非關鍵字的可變長度引數列表傳送到函式。這是乙個...