討論區快速選單
知識庫快速選單
程式設計俱樂部Facebook粉絲團 虛擬社群公民行為研究 我的IT職涯該如何規劃 ?
[ 回上頁 ] [ 討論區發言規則 ]
〔教學〕C 及 C++ 常犯錯誤 - EOF 測試的錯誤用法
更改我的閱讀文章字型大小
作者 : sflam(Raymond)討論區板主 Visual C++ .NET卓越專家VC++曠世奇才新手入門優秀好手資訊類作業求救頂尖高手C++一代宗師貼文超過4000則
[ 貼文 4712 | 人氣 9172 | 評價 31020 | 評價/貼文 6.58 | 送出評價 138 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2009/11/2 下午 11:13:06
有許多 C 及 C++ 的初學者不了解標準函式 feof() (或 C++ 的 std::basic_ios::eof() 成員) 的行為, 在寫讀檔程式的時候, 把 feof() 當作 Pascal 的 eof() 函式來使用, 像這樣:

  while ( !feof(...) )
  {
    /* 讀檔 ... */
    /* 資料處理 ... */
  }

或 C++:
  while ( !infile.eof() )
  {
    // 讀檔 ...
    // 資料處理 ...
  }

但 C 的 end-of-file 測試跟 Pascal 的 eof() 是完全不同的. 在 Pascal 下, 當下一個讀取會導致end of file 錯誤的時候, eof() 就會傳回 true. 而在 C 的函式下, 只有當上一個讀取已經產生 end of file 錯誤的時候, feof() 才會傳回 true.

Pascal 的 eof() 是「預測」. C 的 feof() (或 C++ 的 eof() 成員) 是「已發生」.

用個例子來說明:

  目前狀況:
    <最後一筆資料><EOF>
    ↑
   File Pointer

  讀資料:
    <最後一筆資料><EOF>
            ↑
          File Pointer

在這時候測試 EOF, Pascal 的 eof() 會傳回 true, 因為下一個讀取 _將會_ 造成 end-of-file 的錯誤. 但在 C 函式庫下, feof() 還不會傳回 true, 因為上一次的資料讀取並沒有錯誤.

在 C 及 C++ 語言下, 只有當程式嘗試再讀多一次,

    <最後一筆資料><EOF>
            ↑–––→↑ (end of file 錯誤)
          File Pointer

並產生 end-of-file 讀取錯誤的時候, feof() 才會傳回 true.

所以, 如果套用 Pascal 的 while not eof() 的寫法, 在 C 或 C++ 下, 它必須再 loop 多一次, 才能測試出是否到達 end of file. 而這造成的結果是: 最後一筆資料有可能會被處理兩次.

解決這個問題的方法很簡單 大部份的輸入函式都會回傳包括 end-of-file 的錯誤訊息, 可以直接利用這個訊息來判斷, 而不需使用 feof(). 在大部份情況下, 直接把讀取資料的部份取代 eof 測試的部份:

  while ( /* 讀檔函式 */ )
  {
    /* 資料處理 */
  }

比方這個 C 語言寫的讀行程式, 原本的錯誤寫法是:
  #include <stdio.h>

  int main(void)
  {
    FILE *fp = fopen("test.txt", "r");
    char line[80];
    while ( !feof(fp) )
    {
      fgets(line, sizeof(line), fp);
      printf("%s", line);
    }
    fclose(fp);
  }

更改後是:
  #include <stdio.h>

  int main(void)
  {
    FILE *fp = fopen("test.txt", "r");
    char line[80];
    while ( fgets(line, sizeof(line), fp) )
    {
      printf("%s", line);
    }
    fclose(fp);
  }

不但正確, 而且更為簡潔.


C++ 的讀數值例子:
  #include <fstream>
  #include <iostream>

  int main(void)
  {
    std::fstream infile("test.txt");
    int i;
    while (!infile.eof())
    {
      infile >> i;
      std::cout << i << std::endl;
    }
  }

更改後:
  #include <fstream>
  #include <iostream>

  int main(void)
  {
    std::fstream infile("test.txt");
    int i;
    while (infile >> i)
    {
      std::cout << i << std::endl;
    }
  }

作者 : spainpollo(班班西) 貼文超過200則
[ 貼文 351 | 人氣 215 | 評價 1030 | 評價/貼文 2.93 | 送出評價 3 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2009/11/3 下午 05:20:41
>但 C 的 end-of-file 測試跟 Pascal 的 eof() 是完全不同的. 在 Pascal 下, 當下一個讀取會導致end of file 錯誤的時候, eof() 就會傳回 true. 而在 C 的函式下, 只有當上一個讀取已經產生 end of file 錯誤的時候, feof() 才會傳回 true.
>
>Pascal 的 eof() 是「預測」. C 的 feof() (或 C++ 的 eof() 成員) 是「已發生」.
>

這個說法我持保留態度
fgets並不算真的line input(readln)
如果一個txt檔
123456<cr><lf>
用fscanf(fp,"%6s\n",buf)去讀
feof(fp)是true
應該是EOL的狀況影響最後的結果
C有line input嗎?
這是我的看法,不知對不對?
===
檔案
123456<EOF>
fgets(buf,7,fp)
feof(fp) => false
fgets(buf,8,fp)
feof(fp) => true
..
===
當然C不用feof()去判結尾是正確的
在basic讀不同語言檔案
有時我也會用""判斷資料結束
line input #1,data
if trim(data)="" then
  print "end of data"

case
by case
作者 : sflam(Raymond)討論區板主 Visual C++ .NET卓越專家VC++曠世奇才新手入門優秀好手資訊類作業求救頂尖高手C++一代宗師貼文超過4000則
[ 貼文 4712 | 人氣 9172 | 評價 31020 | 評價/貼文 6.58 | 送出評價 138 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2009/11/3 下午 10:49:38
>這個說法我持保留態度
>fgets並不算真的line input(readln)

當然, fgets() 會讀到指定的長度減一, 或 end-of-line, 或 end-of-file.


>如果一個txt檔
>123456<cr><lf>
>用fscanf(fp,"%6s\n",buf)去讀
>feof(fp)是true

只有當 <cr><lf> 後面一直到 end-of-file 都是 whitespace, 才會如此.

>應該是EOL的狀況影響最後的結果

不是. 這是因為 format specification 堶悸 \n 的緣故.
'\n' 是個 whitespace character, 在 scanf() , 它有特定的行為:

C99 7.19.6.2 The fscanf function, 第 5 段:

  "A directive composed of white-space character(s) is executed by reading input up to the first non-white-space character (which remains unread), or until no more characters can be read."

以這個例子來說, 當它讀完 %6s 後, 由於後面的都是空白字元, 它會連續的跳過, 一直到檔案結束, fscanf() 無法再往下讀了, 才設定 end-of-file 的錯誤.

你用空格來取代 format specification 堛 "\n" 也能得到同樣的結果: fscanf(fp, "%6s ", buf);


>C有line input嗎?

最接近的標準函式是 fgets(), 只要在侷限內, 它可以視為 line input. 要完全做到像 std::getline() 那樣, 就要自己動手了.


>這是我的看法,不知對不對?
>===
>檔案
>123456<EOF>
>fgets(buf,7,fp)
>feof(fp) => false

fgets() 會嘗試讀入指定長度減一, 也就是 6 個字元. 而它也成功讀完 6 個字元, 過程中並沒有遇到任何錯誤, 所以 feof(fp) 會得到 false.

>fgets(buf,8,fp)
>feof(fp) => true

這裡, fgets() 會嘗試讀入 7 個字元, 它在意圖讀第 7 個字元的時候, 遇到了 end of file 錯誤. 它會結束讀取, 並設定 end-of-file 錯誤.

所以 end-of-file 的設定是由輸入函式在讀取資料的過程中設定的.

這裡要補充一點, 在有些系統 (特別是 *nix 系統)上, 每一行資料 (這裡只指 text 資料) 必須以end-of-line 來分割, 包括最後一行. 否則, 某些程式語言在, 或系統的 utilities 在處理最後一行時有可能會出問題.

〔資料〕<EOL>
...
〔資料〕<EOF> <-- 有可能會造成問題.

最好是這樣:
〔資料〕<EOL>
...
〔資料〕<EOL>
<EOF>

有些 *nix 的 text editor 會自動的確保最後一行也是以 EOF 來終結.

作者 : chiuinan2(青衫)討論區板主 Visual C++ .NET卓越專家VC++一代宗師Visual Basic優秀好手資訊類作業求救卓越專家一般曠世奇才程式設計甘苦談優秀好手C++ Builder優秀好手上班族的哈拉園地優秀好手C++頂尖高手Assembly優秀好手貼文超過3000則人氣指數超過150000點
[ 貼文 3722 | 人氣 170106 | 評價 34280 | 評價/貼文 9.21 | 送出評價 125 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2009/11/4 上午 06:47:19
Raymond的教學, 是因為太多人誤用了C的FILE結構的feof函數, 這點應該給予肯定.

但對於存取檔, 個人習慣用open系列, 自己做控制, 自己做buffering, 自己做斷行處理 (其實用f系列的函數也是做得到, 習慣問題). 既然fgets有太多的限制 (因為你無法預測輸入的字串或一行的字元有多少), 難道不能自己設法來解決嗎? 小檔案可以直接全部讀入做分行處理, 大檔案也可以利用buffering來判斷行的長度, 甚至邊判斷, 也邊寫入記憶體, 不必二次讀取.

如何進行最佳化處理, 都只存乎一心, 未必要拘泥於現有的函數. 別老是抱怨目前沒有函數或物件可處理, 所以沒辦法... 那該算是不用心吧. 因為客戶沒安裝email系統, 所以無法email通知, 難道不能寫個簡單的MIME編碼與SMTP傳送程式嗎? 大部份的軟體需求, 技術不可行的部份, 其實是微乎其微, 就看你怎麼做而已. 基礎與經驗愈弱, 遇到問題愈不知從何解決, 加油吧.
作者 : spainpollo(班班西) 貼文超過200則
[ 貼文 351 | 人氣 215 | 評價 1030 | 評價/貼文 2.93 | 送出評價 3 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2009/11/4 上午 10:00:41
>Raymond的教學, 是因為太多人誤用了C的FILE結構的feof函數, 這點應該給予肯定.
>
沒有否定的意思
因為不是本科出身
有的只有實作經驗
所以有些觀念需要正規者的澄清
有人教當然趕快問囉

>但對於存取檔, 個人習慣用open系列, 自己做控制, 自己做buffering, 自己做斷行處理 (其實用f系列的函數也是做得到, 習慣問題). 既然fgets有太多的限制 (因為你無法預測輸入的字串或一行的字元有多少), 難道不能自己設法來解決嗎? 小檔案可以直接全部讀入做分行處理, 大檔案也可以利用buffering來判斷行的長度, 甚至邊判斷, 也邊寫入記憶體, 不必二次讀取.
>
我的實作經驗中
fgetc跟fread用的多
fgets只有便宜行事時才會用

>如何進行最佳化處理, 都只存乎一心, 未必要拘泥於現有的函數. 別老是抱怨目前沒有函數或物件可處理, 所以沒辦法... 那該算是不用心吧. 因為客戶沒安裝email系統, 所以無法email通知, 難道不能寫個簡單的MIME編碼與SMTP傳送程式嗎? 大部份的軟體需求, 技術不可行的部份, 其實是微乎其微, 就看你怎麼做而已. 基礎與經驗愈弱, 遇到問題愈不知從何解決, 加油吧.
>
可能有點誤解我的意思
提出line input只是想強調2種輸入方式對eol的差異

======
系統整合整多了
看到各式各樣的"文字檔"
已經不知道"文字檔"長什麼樣子了..
每次都能得到正確結果就是對的
作者 : spainpollo(班班西) 貼文超過200則
[ 貼文 351 | 人氣 215 | 評價 1030 | 評價/貼文 2.93 | 送出評價 3 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2009/11/4 上午 10:59:23
解釋的好詳細,謝謝!

順便請教一下
<EOF>這個byte(0x1A)是不是正式(非正式?)不再使用
多這1個byte對讀檔有無影響(fgets,readln..)(算whitespace or ..)
一時找不到有加的程式 沒得測

我的PASCAL還停在APLLE II 跟PRIME 550上
以前都沒注意到..
作者 : sflam(Raymond)討論區板主 Visual C++ .NET卓越專家VC++曠世奇才新手入門優秀好手資訊類作業求救頂尖高手C++一代宗師貼文超過4000則
[ 貼文 4712 | 人氣 9172 | 評價 31020 | 評價/貼文 6.58 | 送出評價 138 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2009/11/4 下午 12:16:09
><EOF>這個byte(0x1A)是不是正式(非正式?)不再使用

以 ctrl-Z 做文字檔的 end-of-file marker 源自 DEC 早期的系統, 後來被 CP/M 借用, 再後來用在 MS-DOS 上.

使用 ctrl-Z 的原因是因為在早期的 file system, 檔案長度是以 128-byte 的 sector 為單位的, 當檔案的大小不是 sector 的整數倍數的話, ctrl-Z 用來標示檔案的真正結尾, 並以 ctrl-Z 把剩餘的 sector 填滿.

MS-DOS 自 2.0 起就可以正確的記綠檔案的正確長度, 已經不需要用 ctrl-Z 的機制了. 但這個機制到目前還在支援. 因為在某個情況下它非常有用.

當你在 console 上用鍵盤來輸入資料時, 可以用 ctrl-Z 來產生 end-of-file 訊息.

  #include <iostream>

  int main()
  {
    int x;
    int nCount = 0;
    while (std::cin >> x)
    {
      ++nCount;
    }
    std::cout << "you entered " << nCount << " numbers." << std::endl;
  }

〔執行〕
1 2 3 4
5 6 7 8^Z
you entered 8 numbers.

*nix 系統上用 ctrl-D 來 signal end-of-file.

以目前的系統來說, EOF 只是一個概念上的存在, 文字檔並不需要這個 marker, text editor 也不會自動的加入這個 marker.

>多這1個byte對讀檔有無影響(fgets,readln..)(算whitespace or ..)

在視窗系統上, ctrl-Z 不再做為檔案大小的依據, 也不會導致 file truncation. Ctrl-Z 可以存在文字檔內, 但它會影響輸入函式的行為: 如果檔案是以 text mode 來開啟的話, ctrl-Z 會終結輸入, 旣使 ctrl-Z 後面還有資料也是一樣.

作者 : spainpollo(班班西) 貼文超過200則
[ 貼文 351 | 人氣 215 | 評價 1030 | 評價/貼文 2.93 | 送出評價 3 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2009/11/4 下午 05:35:18
試了一下VB(好像又不同)

檔案
123456<cr><lf>

Private Sub Command1_Click()
Dim data as String
Dim fn as Integer
fn = FreeFile
Open "c:\temp\t.txt" for Input as #fn
Line Input #fn, data
Label1.Caption = EOF(fn) '==>true
Close #fn
End Sub
======

Private Sub Command1_Click()
Dim data as String
Dim fn as Integer
fn = FreeFile
Open "c:\temp\t.txt" for Binary as #fn
Line Input #fn, data
Label1.Caption = EOF(fn) '==>false
Close #fn
End Sub
======
答案在
http://msdn.microsoft.com/zh-tw/library/7ct2yy4s(VS.80).aspx
備註裡


EOF()真是個令人頭大的東西
作者 : sflam(Raymond)討論區板主 Visual C++ .NET卓越專家VC++曠世奇才新手入門優秀好手資訊類作業求救頂尖高手C++一代宗師貼文超過4000則
[ 貼文 4712 | 人氣 9172 | 評價 31020 | 評價/貼文 6.58 | 送出評價 138 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2009/11/4 下午 09:34:37
>試了一下VB(好像又不同)

離題了.
 板主 : simula
 > C++ - 討論區
 - 最近熱門問答精華集
 - 全部歷史問答精華集
 - C++ - 知識庫
  ■ 全站最新Post列表
  ■ 我的文章收藏
  ■ 我最愛的作者
  ■ 全站文章收藏排行榜
  ■ 全站最愛作者排行榜
  ■  月熱門主題
  ■  季熱門主題
  ■  熱門主題Top 20
  ■  本區Post排行榜
  ■  本區評價排行榜
  ■  全站專家名人榜
  ■  全站Post排行榜
  ■  全站評價排行榜
  ■  全站人氣排行榜
 請輸入關鍵字 
  開始搜尋
 
Top 10
評價排行
C++
1 Raymond 12510 
2 simula 4690 
3 青衫 4640 
4 coco 3850 
5 白老鼠(Gary) 3610 
6 Ben 2250 
7 Anderson 1960 
8 ozzy 1790 
9 windblown 1650 
10 Kenny 1540 
C++
  專家等級 評價  
  一代宗師 10000  
  曠世奇才 5000  
  頂尖高手 3000  
  卓越專家 1500  
  優秀好手 750  
Microsoft Internet Explorer 6.0. Screen 1024x768 pixel. High Color (16 bit).
2000-2014 程式設計俱樂部 http://www.programmer-club.com.tw/
0.171875