10

以前の質問に触発されて

新しい C++ プログラマーにとってよくある間違いは、次の行に沿ったものをファイルから読み取ることです。

std::ifstream file("foo.txt");
std::string line;
while (!file.eof()) {
  file >> line;
  // Do something with line
}

多くの場合、ファイルの最後の行が 2 回読み取られたと報告されます。この問題の一般的な説明 (以前に説明したもの) は次のようになります。

抽出がファイルの終わりで停止した場合ではなく、ファイルの終わりを抽出しようとした場合にのみ、ストリームに EOF ビットが設定されます。file.eof()前の読み取りがファイルの終わりに到達したかどうかのみを通知し、次の読み取りが終了したかどうかは通知しません。最後の行が抽出された後、EOF ビットはまだ設定されておらず、反復がもう一度行われます。ただし、この最後の繰り返しでは、抽出は失敗し、line以前と同じ内容のままです。つまり、最後の行が重複しています。

ただし、この説明の最初の文が間違っているため、コードが何を行っているかの説明も間違っています。

フォーマットされた入力関数の定義 (である) は、入力文字を使用する、または入力文字を取得するためoperator>>(std::string&)の抽出を定義します。これらの関数のいずれかが を返す場合、EOF ビットが設定されると述べています。rdbuf()->sbumpc()rdbuf()->sgetc()traits::eof()

rdbuf()->sbumpc()またはが をrdbuf()->sgetc()返す場合traits::eof()、入力関数は、特に明記されていない限り、そのアクションを完了し、戻る前に (27.5.5.4)setstate(eofbit)をスローする可能性がある を実行します。ios_base::failure

std::stringstreamこれは、ファイルではなくを使用する単純な例で確認できます (どちらも入力ストリームであり、抽出時に同じように動作します)。

int main(int argc, const char* argv[])
{
  std::stringstream ss("hello");
  std::string result;
  ss >> result;
  std::cout << ss.eof() << std::endl; // Outputs 1
  return 0;
}

ここで、単一の抽出がhello文字列から取得し、EOF ビットを 1 に設定することは明らかです。

では、説明の何が問題なのですか?!file.eof()最終行が重複する原因となるファイルの違いは何ですか? !file.eof()抽出条件として使用してはいけない本当の理由は何ですか?

4

2 に答える 2

19

はい、例に示されているように、抽出がファイルの終わりで停止した場合、入力ストリームから抽出するとEOFビットが設定されますstd::stringstream。これが単純な場合!file.eof()、条件としてのループは、次のようなファイルで問題なく機能します。

hello
world

2回目の抽出ではworld、ファイルの終わりで停止し、その結果EOFビットが設定されます。次の反復は発生しません。

ただし、多くのテキストエディタには汚い秘密があります。あなたがそれと同じくらい単純なテキストファイルを保存するとき、彼らはあなたに嘘をついています。彼らがあなたに言っていないの\nは、ファイルの終わりに隠されているということです。ファイル内のすべての行は\n、最後の行を含めて、で終わります。したがって、ファイルには実際には次のものが含まれています。

hello\nworld\n

これが!file.eof()、条件として使用するときに最後の行が重複する原因になります。これがわかったので、2番目の抽出でEOFビットを設定せずに停止することがわかりますworldまだそこに到達していないため)。ループは3回繰り返されますが、抽出する文字列が見つからず、空白のみが検出されるため、次の抽出は失敗します。文字列は前の値が残ったままなので、重複した行が表示されます。\n

std::stringstreamあなたがストリームに固執するものはまさにあなたが得るものであるため、あなたはこれを経験しません。ファイルとは異なり、\nの終わりにはありません。std::stringstream ss("hello")実行するstd::stringstream ss("hello\n")と、同じ重複行の問題が発生します。

もちろん、テキストファイルから抽出するときの条件として使用してはならないことがわかり!file.eof()ますが、ここでの本当の問題は何ですか?ファイルから抽出するかどうかに関係なく、それを条件として使用しないのはなぜですか?

本当の問題はeof()、次の読み取りが失敗するかどうかがわからないことです。上記の場合、eof()0であっても、抽出する文字列がないため、次の抽出が失敗することがわかりました。ファイルストリームをどのファイルにも関連付けなかった場合、またはストリームが空の場合にも、同じ状況が発生します。EOFビットは設定されませんが、読み取るものはありません。が設定されていないという理由だけで、やみくもに先に進んでファイルから抽出することeof()はできません。

while (std::getline(...))抽出が開始される直前に、フォーマットされた入力関数が不良ビット、失敗ビット、またはEOFビットのいずれかが設定されているかどうかをチェックするため、使用および関連する条件は完全に機能します。それらのいずれかが存在する場合、それはすぐに終了し、プロセスの失敗ビットを設定します。また、抽出したいものを見つける前にファイルの終わりを見つけた場合も失敗し、eofビットとfailビットの両方を設定します。


\n注:保存する前に、vimに余分なものを追加せず:set noeolにファイルを保存できます:set binary

于 2013-01-30T23:15:59.007 に答える
4

あなたの質問には、いくつかの偽の概念があります。あなたは説明をします:

「抽出がファイルの終わりで停止した場合ではなく、ファイルの終わりを抽出しようとした場合にのみ、抽出はストリームに EOF ビットを設定します。」

次に、「間違っているので、コードが何をしているかの説明も間違っている」と主張します。

実際、そうです。例を見てみましょう....

に読み込むときstd::string...

std::istringsteam iss('abc\n');
std::string my_string;
iss >> my_string;

...デフォルトでは、質問のように、operator>>空白またはEOFが見つかるまで文字を読み込んでいます。そう:

  • からの読み取り'abc\n'-> が'\n'検出されると、「ファイルの終わりを抽出しようとする」のではなく、「[EOF] で停止する」だけであり、eof()戻りませんtrue
  • 代わりに読み取ります ->コンテンツ'abc'の終わりを発見するファイルの終わりを抽出しようとするため、が返されます。stringeof()true

同様に、解析では別の数字があるかどうかがわからず、それらを読み続けようとするため'123'intセットに解析します。will not setへの解析。eof()eof()'123 'inteof()

重要なことに、「a」を a に解析してcharも設定されませんeof()。これは、解析が完了したことを知るために末尾の空白が必要ないためです。文字が読み取られると、別の文字を見つけようとする試みは行われず、文字にeof()遭遇しません。(もちろん、同じストリームからさらに解析するとヒットしますeof)。

[for stringstream "hello" >> std::string] 単一の抽出が文字列から hello を取得し、EOF ビットを 1 に設定することは明らかです。では、説明のどこが間違っているのでしょうか? !file.eof() によって最後の行が複製される原因となるファイルの違いは何ですか? 抽出条件として !file.eof() を使用してはいけない本当の理由は何ですか?

理由は上記のとおりです...ファイルは「\ n」文字で終了する傾向があり、それらが>> std::string「ファイルの終わりを抽出しようとする」必要なしに行を取得するか、最後の非空白トークンを返すことを意味するためです(あなたの言い回しを使うために)。

于 2013-04-23T03:25:02.787 に答える