以下程序演示了 std::istream(特别是在我的测试代码中,std::istringstream)设置 eof() 的方式不一致。
#include <sstream>
#include <cassert>
int main(int argc, const char * argv[])
{
// EXHIBIT A:
{
// An empty stream doesn't recognize that it's empty...
std::istringstream stream( "" );
assert( !stream.eof() ); // (Not yet EOF. Maybe should be.)
// ...until I read from it:
const int c = stream.get();
assert( c < 0 ); // (We received garbage.)
assert( stream.eof() ); // (Now we're EOF.)
}
// THE MORAL: EOF only happens when actually attempting to read PAST the end of the stream.
// EXHIBIT B:
{
// A stream that still has data beyond the current read position...
std::istringstream stream( "c" );
assert( !stream.eof() ); // (Clearly not yet EOF.)
// ... clearly isn't eof(). But when I read the last character...
const int c = stream.get();
assert( c == 'c' ); // (We received something legit.)
assert( !stream.eof() ); // (But we're already EOF?! THIS ASSERT FAILS.)
}
// THE MORAL: EOF happens when reading the character BEFORE the end of the stream.
// Conclusion: MADNESS.
return 0;
}
因此,当您在实际文件结尾之前读取字符时, eof() “触发” 。但如果流为空,它只会在您实际尝试读取字符时触发。eof() 的意思是“你只是想读完结尾吗?” 或者“如果你再读一遍,你会读到最后吗?” 答案是不一致的。
此外,断言是否触发取决于编译器。例如,Apple Clang 4.1 触发断言(在读取前面的字符时引发 eof())。例如,GCC 4.7.2 没有。
这种不一致使得很难编写明智的循环来读取流,但可以很好地处理空流和非空流。
选项1:
while( stream && !stream.eof() )
{
const int c = stream.get(); // BUG: Wrong if stream was empty before the loop.
// ...
}
选项 2:
while( stream )
{
const int c = stream.get();
if( stream.eof() )
{
// BUG: Wrong when c in fact got the last character of the stream.
break;
}
// ...
}
所以,朋友们,我该如何编写一个循环来解析流,依次处理每个字符,处理每个字符,但是当我们到达 EOF 时,或者在流开始为空的情况下停止而不大惊小怪,永远不会开始?
好吧,更深层次的问题:我有直觉,使用 peek() 可能会以某种方式解决这个 eof() 不一致问题,但是......该死的废话!为什么不一致?