// 錯誤版本
FILE *in = fopen("in.txt", "r");
FILE *out = fopen("out.txt", "w");
char ch;
while (!feof(in)) { // ❌ 邏輯缺陷
ch = fgetc(in);
putchar(ch);
fputc(ch, out);
}
fclose(in); fclose(out);
in.txt 內容:`123` (3 位元組,無結尾換行)
out.txt 結果:`123` (4 位元組,多出一個 `0xFF` / `EOF` 字元)
int feof(FILE *stream);
// 返回: 非 0 = 文件結束標誌**已被設定** (上一次讀取操作嘗試讀取超過檔案末尾)
// 0 = 標誌未設定
| 迴圈 | feof(in) 調用前 | fgetc(in) 返回 | feof 標誌狀態 | 迴圈條件 !feof | 動作 |
|---|---|---|---|---|---|
| 1 | 未設定 (0) | `'1'` (49) | 未設定 (0) | 真 | 寫入 '1' |
| 2 | 未設定 (0) | `'2'` (50) | 未設定 (0) | 真 | 寫入 '2' |
| 3 | 未設定 (0) | `'3'` (51) | 未設定 (0) | 真 | 寫入 '3' |
| 4 | **未設定 (0)** ← 關鍵! | `EOF` (-1) | **設定 (1)** | **真** ← 邏輯錯誤 | **寫入 EOF (0xFF)** ❌ |
| 5 | 已設定 (1) | `EOF` (-1) | 已設定 (1) | 假 | 退出迴圈 |
核心矛盾:第 4 次迴圈進入時,`feof` **尚未設定** (因為上一次讀取成功),條件為真,**進入迴圈體**,才呼叫 `fgetc` 讀到 `EOF` 並設定標誌。但此時迴圈體**已經執行**,將 `EOF` 當作有效字元寫入。
int ch; // 必須用 int 接收,char 無法區分 EOF (-1) 和 0xFF
while ((ch = fgetc(in)) != EOF) {
putchar(ch);
fputc(ch, out);
}
char buf[1024];
while (fgets(buf, sizeof(buf), in) != NULL) {
fputs(buf, out);
}
char buf[4096];
size_t n;
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
fwrite(buf, 1, n, out);
}
int a, b;
while (fscanf(in, "%d%d", &a, &b) == 2) { // 返回成功賦值項目數
// 處理 a, b
}
當讀取迴圈因 `fgetc`/`fread`/`fgets` 返回失敗而退出時,**無法直接知道是 EOF 還是錯誤** (如磁碟壞塊、網路中斷、權限變更)。此時需檢查標誌:
char buf[1024];
while (fgets(buf, sizeof(buf), fp) != NULL) {
// 處理
}
if (ferror(fp)) {
perror("讀取錯誤");
} else if (feof(fp)) {
printf("正常讀取結束\n");
} else {
// 理論上不會到達
}
// C++: 運算子 bool() 重載,直接檢查流狀態 (goodbit)
while (in >> ch) { ... } // 等價於 while (in.good()) 後再嘗試提取
// 或
while (std::getline(in, line)) { ... }
// 失敗時自動轉為 false,包含 EOF 與錯誤,語義更直觀
這不僅是 C 語言習慣用法,更是**所有基於流的 I/O 系統 (C++ iostream, Java InputStream, Go io.Reader, Python file object) 的通用設計模式**:**「嘗試讀取,檢查返回值,處理數據」** —— 才是正確的流處理邏輯。