この回答iostream::eof
で、ループ条件での使用は「ほぼ間違いなく間違っている」というコメントを見つけました。私は通常、次のようなものを使用しますwhile(cin>>n)
-暗黙的にEOFをチェックすると思います。
while (!cin.eof())
明示的に間違った ものを使用してeofをチェックするのはなぜですか?
scanf("...",...)!=EOF
C(私はよく問題なく使用します)で使用するのとどう違うのですか?
ストリームの終わりを読み取った後iostream::eof
にのみ戻るためです。次の読み取りがストリームの終わりになることを示すものではありません。true
これを考慮してください(そして、次の読み取りがストリームの最後になると仮定します):
while(!inStream.eof()){
int data;
// yay, not end of stream yet, now read ...
inStream >> data;
// oh crap, now we read the end and *only* now the eof bit will be set (as well as the fail bit)
// do stuff with (now uninitialized) data
}
これに対して:
int data;
while(inStream >> data){
// when we land here, we can be sure that the read was successful.
// if it wasn't, the returned stream from operator>> would be converted to false
// and the loop wouldn't even be entered
// do stuff with correctly initialized data (hopefully)
}
そしてあなたの2番目の質問について:なぜなら
if(scanf("...",...)!=EOF)
と同じです
if(!(inStream >> data).eof())
と同じではありません
if(!inStream.eof())
inFile >> data
ボトムライントップ: 空白を適切に処理すると、次のように使用できます(さらに、エラーチェックeof
よりも信頼性が高くなります)。fail()
while( !(in>>std::ws).eof() ) {
int data;
in >> data;
if ( in.fail() ) /* handle with break or throw */;
// now use data
}
(答えを強調するための提案をしてくれたTony Dに感謝します。これがより堅牢である理由の例については、以下の彼のコメントを参照してください。)
使用に反対する主な議論はeof()
、空白の役割に関する重要な微妙な点を見逃しているようです。私の提案は、明示的にチェックすることは「常に間違っているeof()
」だけでなく、これはこのスレッドや同様のSOスレッドで最も重要な意見であるように見えるだけでなく、空白を適切に処理することで、よりクリーンで信頼性の高いものを提供するというものです。エラー処理であり、常に正しい解決策です(ただし、必ずしも最悪ではありません)。
「適切な」終了および読み取り順序として提案されているものを要約すると、次のようになります。
int data;
while(in >> data) { /* ... */ }
// which is equivalent to
while( !(in >> data).fail() ) { /* ... */ }
eofを超えた読み取り試行による失敗は、終了条件と見なされます。これは、成功したストリームと、eof以外の理由で実際に失敗したストリームを区別する簡単な方法がないことを意味します。次のストリームを使用します。
1 2 3 4 5<eof>
1 2 a 3 4 5<eof>
a<eof>
while(in>>data)
3つの入力すべてのセットfailbit
で終了します。1回目と3回目も設定されています。したがって、ループを過ぎると、適切な入力(1番目)と不適切な入力(2番目と3番目)を区別するために、非常に醜い追加のロジックが必要になります。eofbit
一方、次のことを行ってください。
while( !in.eof() )
{
int data;
in >> data;
if ( in.fail() ) /* handle with break or throw */;
// now use data
}
ここで、in.fail()
読むものがある限り、それが正しいものであることを確認します。その目的は、単なるwhileループターミネータではありません。
これまでのところ良好ですが、ストリームに後続のスペースがある場合はどうなりeof()
ますか?ターミネーターとしての主な懸念事項のように聞こえますか?
エラー処理を放棄する必要はありません。空白を食い尽くすだけです:
while( !in.eof() )
{
int data;
in >> data >> ws; // eat whitespace with std::ws
if ( in.fail() ) /* handle with break or throw */;
// now use data
}
std::ws
を設定している間、ストリーム内の潜在的な(ゼロ以上の)後続スペースをスキップします。。はスキップしeofbit
ませんfailbit
。したがって、in.fail()
読み取るデータが少なくとも1つある限り、期待どおりに機能します。すべて空白のストリームも受け入れられる場合、正しい形式は次のとおりです。
while( !(in>>ws).eof() )
{
int data;
in >> data;
if ( in.fail() ) /* handle with break or throw */;
/* this will never fire if the eof is reached cleanly */
// now use data
}
概要:適切に構築されwhile(!eof)
ていることは可能であり、間違いではないだけでなく、データをスコープ内にローカライズすることを可能にし、通常どおりビジネスからエラーチェックをより明確に分離します。そうは言っても、while(!fail)
間違いなくより一般的で簡潔なイディオムであり、単純な(読み取りタイプごとに単一のデータ)シナリオで好まれる可能性があります。
プログラマーが書かない場合while(stream >> n)
、彼らはおそらくこれを書くからです:
while(!stream.eof())
{
stream >> n;
//some work on n;
}
some work on n
ここでの問題は、ストリームの読み取りが成功したかどうかを最初に確認せずに行うことはできません。失敗した場合、some work on n
望ましくない結果が生じるためです。
重要なのは、、、、、またははeofbit
、ストリームからの読み取りが試行された後にbadbit
設定failbit
されるということです。したがって、失敗した場合は、、、またはがすぐに設定されます。したがって、を書き込むと、より慣用的なものになります。これは、返されたオブジェクトが、ストリームからの読み取りに何らかの失敗があった場合に変換され、その結果、ループが停止するためです。そして、読み取りが成功したかどうかに変換され、ループが続行されます。stream >> n
eofbit
badbit
failbit
while (stream >> n)
stream
false
true
他の回答は、ロジックが間違っている理由while (!stream.eof())
とそれを修正する方法を説明しています。私は何か違うことに焦点を合わせたい:
なぜeofを明示的に
iostream::eof
間違って使用してチェックするのですか?
一般的に、ストリーム抽出()はファイルの終わりに到達せずに失敗する可能性があるため、チェックeof
のみが間違っています。>>
たとえばint n; cin >> n;
、ストリームにが含まれている場合hello
、h
は有効な数字ではないため、入力の最後に到達せずに抽出は失敗します。
この問題は、ストリームの状態を読み取ろうとする前にチェックするという一般的な論理エラーと相まって、N個の入力アイテムに対してループがN + 1回実行されることを意味し、次の症状を引き起こします。
ストリームが空の場合、ループは1回実行されます。>>
失敗し(読み取る入力がない)、(によって)設定されるはずだったすべての変数stream >> x
は実際には初期化されていません。これにより、ガベージデータが処理され、無意味な結果(多くの場合、膨大な数)として現れる可能性があります。
(標準ライブラリがC ++ 11に準拠している場合、状況は少し異なります。失敗すると、>>
数値変数が0
初期化されないままになるのではなく、に設定されるようになりました(sを除くchar
)。)
ストリームが空でない場合、最後の有効な入力の後にループが再度実行されます。最後の反復ではすべての>>
操作が失敗するため、変数は前の反復からの値を保持する可能性があります。これは、「最後の行が2回印刷される」または「最後の入力レコードが2回処理される」として現れる可能性があります。
(これは、C ++ 11(上記を参照)以降、少し異なって表示されるはずです。これで、最後の行が繰り返される代わりに、ゼロの「ファントムレコード」が得られます。)
ストリームに不正な形式のデータが含まれているが、チェックするだけの場合.eof
、無限ループになります。>>
ストリームからのデータの抽出に失敗するため、ループは最後に到達することなく所定の位置で回転します。
要約すると、解決策は、別のメソッド>>
を使用するのではなく、操作自体の成功をテストすることです。Cの場合と同様に、呼び出し自体の成功をテストします。.eof()
while (stream >> n >> m) { ... }
scanf
while (scanf("%d%d", &n, &m) == 2) { ... }
覚えておくべき重要なことは、ファイルの終わりに到達したため、読み取りの試行が失敗するまでinFile.eof()
はならないということです。したがって、この例では、エラーが発生します。True
while (!inFile.eof()){
inFile >> x;
process(x);
}
このループを正しくする方法は、読み取りとチェックを1つの操作に結合することです。
while (inFile >> x)
process(x);
慣例により、operator>>
読み取り元のストリームを返しFalse
、ストリームが失敗すると(ファイルの終わりに到達するなど)、ストリームのブールテストが返されます。
したがって、これにより正しいシーケンスが得られます。
ファイルから正しく読み取ることができない他の問題が発生した場合、そのような問題に到達することはできませんeof()
。たとえば、このようなものを見てみましょう
int x;
while (!inFile.eof()) {
inFile >> x;
process(x);
}
例を挙げて、上記のコードの動作をトレースしてみましょう
'1', '2', '3', 'a', 'b'
。a
ます。a
、失敗します。clear
なります。ストリームからの読み取りはすべて失敗します。False
と、ファイルの最後にいないため、まだa
読み取られるのを待っているため、が返されます。ただし、このようなループを使用すると、必要な出力が得られます。
while (inFile >> x)
process(x);
この場合、ストリームは、ファイルの終わりの場合だけでなく、整数として読み取ることができないFalse
などの変換に失敗した場合にも変換されます。a
ループ内のiostream::eofは、EOFに達していないため、間違っていると見なされます。したがって、次の読み取りが成功するという意味ではありません。
私のステートメントを2つのサンプルコードで説明します。これは、概念をよりよく理解するのに間違いなく役立ちます。たとえば、C++のファイルストリームを使用してファイルを読み取りたい場合です。また、ループを使用してファイルに書き込む場合、stream.eof()を使用してファイルの終わりをチェックすると、実際にはファイルが終わりに達したかどうかをチェックしています。
サンプルコード
#include<iostream>
#include<fstream>
using namespace std;
int main() {
ifstream myFile("myfile.txt");
string x;
while(!myFile.eof()) {
myFile >> x;
// Need to check again if x is valid or eof
if(x) {
// Do something with x
}
}
}
ストリームを直接ループで使用している場合、条件を再度チェックすることはありません。
サンプルコード
#include<iostream>
#include<fstream>
using namespace std;
int main() {
ifstream myFile("myfile.txt");
string x;
while(myFile >> x) {
// Do something with x
// No checks needed!
}
}