我們來詳細解釋一下 C 語言中的 ferror 和 feof 這兩個庫函數。
1. ferror 函數
int ferror(FILE *stream);
功能:檢查指定文件流(stream)上是否發生了錯誤。
返回值:
如果文件流上有錯誤發生,返回一個非零值(true)。
如果沒有錯誤發生,返回 0(false)。
説明:
當對文件進行讀寫操作時,如果發生錯誤(例如:磁盤已滿、權限問題、硬件故障等),文件流會記錄一個錯誤標誌。
ferror 函數就是用來檢查這個錯誤標誌的。
一旦錯誤標誌被設置,它將一直保持,直到調用 clearerr() 函數清除該標誌,或者關閉文件。
我們下面舉一個例子,看看會不會報錯,假設有該文件,然後我們以只讀打開,再寫入內容
#include <stdio.h>
int main()
{
FILE* file = fopen("nonexistent.txt", "r");
if (file == NULL)
{
perror("Error opening file");
return 1;
}
char buffer[7] = {"abcdef"};
if (fputs(buffer,file) == EOF)
{
if (ferror(file))
{
printf("Error reading to file.\n");
}
}
fclose(file);
file = NULL;
return 0;
}

我們看到果然報錯了,只讀情況下不可以寫入(vs編譯器中),所以在運行fputs時,fputs返回了一個EOF並且設置了一個錯誤的指示,當然這個指示在文本中是看不到的,下面是fputs返回值的原文。
On error, the function returns EOF and sets the error indicator (ferror).
2. feof 函數
int feof(FILE *stream);
功能:檢查指定文件流(stream)是否已經到達文件末尾(End-of-File)。
返回值:
如果文件流已經到達文件末尾,返回一個非零值(true)(這個説法其實並不準確,我下面會做進一步解釋)。
如果還沒有到達文件末尾,返回 0(false)。
A non-zero value is returned in the case that the end-of-file indicator associated with the stream is set.
Otherwise, zero is returned.
説明:
當文件指針移動到文件末尾時,文件流會記錄一個 EOF 標誌。
feof 函數就是用來檢查這個 EOF 標誌的。
注意:feof 只有在文件讀取操作嘗試越過文件末尾時才會返回 true。也就是説,在文件末尾的前一個字符被讀取後,此時使用feof 仍然返回 false,只有當再次嘗試讀取時(此時會失敗),feof 才會返回 true。
這是最核心的一點。其實文件本身的末尾並沒有一個叫做 "EOF" 的特殊字符。
每個打開的文件流(FILE *)內部都維護着一個文件位置指示器。
你可以把它想象成一個指向文件內容的光標。
當你打開一個文件時,這個指示器通常指向文件的開頭。
當你調用 fgetc()、fgets() 等讀取函數時,它們會從指示器當前的位置讀取數據,然後將指示器向後移動。
而feof 函數的作用是檢查一個內部標誌位,我們稱之為 "end-of-file indicator"。這個標誌位只有在特定情況下才會被設置。
讓我們通過一個具體的例子來一步步説明:
假設我們有一個文件 test.txt,內容非常簡單:
ABC\n
這個文件包含 3 個字符 'A', 'B', 'C',以及一個隱含的換行符 \n,總共 4 個字符。
然後我們設定一個流指針stream指向該流。
文件位置指示器的移動過程如下:
初始狀態:
文件位置指示器:指向 A 的前面。
feof(stream): false
第一次調用 fgetc(stream):
函數從指示器位置讀取字符 'A'。
文件位置指示器向後移動一位,現在指向 B 的前面。
函數返回 'A'
feof(stream): 仍然是 false,因為我們只是讀取到了文件中的一個有效字符,並沒有嘗試越過文件末尾。
第二次調用 fgetc(stream):
讀取字符 'B'。
指示器指向 C 的前面。
返回 'B'。
feof(stream): false。
第三次調用 fgetc(stream):
讀取字符 'C'。
指示器指向換行符 \n 的前面。
返回 'C'。
feof(stream): false。
第四次調用 fgetc(stream):
讀取字符 '\n'。
指示器向後移動,現在它指向了文件的物理末尾之後,也就是再往後沒東西了。
返回 '\n'。
feof(stream): 仍然是 false!
因為雖然指示器已經越過了最後一個字符,但我們這次讀取操作本身是成功的,我們成功地讀到了 \n。
而feof 只有在讀取失敗並且失敗的原因是:已經到達文件末尾時,才會為 true。
第五次調用 fgetc(stream):
函數嘗試從當前指示器位置(文件末尾之後)讀取一個字符。
這次嘗試失敗了,因為已經沒有更多的數據可以讀取了。
作為失敗的標誌,函數返回 EOF (即 -1)。
同時,在函數內部,它會設置文件流的 "end-of-file indicator"。
feof(stream): 現在變成了 true,因為它檢測到這個標誌位已經被設置。
總的來説feof是當流的讀取發生錯誤時,判斷錯誤是否為已經讀取到文件末尾,因為在文件讀取到末尾時使用feof依舊返回值為0,只有當讀取失敗,並且失敗原因為達到文件末尾,才會返回大於0的數
説白了就是看流的結構體中end-of-file indicator有沒有被設置
這其實根一些讀取字符的函數的返回邏輯有關,我們拿fgetc舉例
On success, the character read is returned (promoted to an int value).
The return type is int to accommodate for the special value EOF, which indicates failure:
If the position indicator was at the end-of-file, the function returns EOF and sets the eof indicator (feof) of stream.
If some other reading error happens, the function also returns EOF, but sets its error indicator (ferror) instead.
這是該函數的返回值的原文
成功時,返回讀取的字符(提升為 int 值)。
返回類型為 int,以容納表示失敗的特殊值 EOF:(因為EOF其實是一個宏,數值是-1)
如果位置指示器已到達文件末尾,函數返回 EOF 並設置流的 eof 標誌(feof)。
如果發生其他讀取錯誤,函數同樣返回 EOF,但會設置其錯誤標誌(ferror)。
函數讀取到文件末尾會設置eof指示器,但函數怎麼知道它讀的字符是不是最後一個呢,他只能通過再讀一次,看看能不能讀到值了,如果到文件末尾,操作系統就會告訴他End-of-File,他才會在流結構體中防止eof指示器
而feof只是看結構體裏有沒有這個指示器(具體情況可能更復雜,我上述説的只是一個個比喻,大佬勿噴,不對請指出)