9

以下程序演示了 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() 可能会以某种方式解决这个 eo​​f() 不一致问题,但是......该死的废话!为什么不一致?

4

5 回答 5

9

该标志仅用于确定您是否在某些操作eof()到达文件末尾。主要用途是在读取合理失败时避免出现错误消息,因为没有更多内容要读取。试图控制一个循环或使用的东西肯定会失败。在所有情况下,您都需要在尝试读取检查读取是否成功。在尝试之前,流无法知道您要阅读的内容。eof()

的语义eof()被彻底定义为“读取流时设置此标志导致流缓冲区返回失败”。如果我没记错的话,找到这个陈述并不容易,但这就是结果。在某些时候,该标准还说,在某些情况下,允许流读取比它必须读取的更多,这可能会导致eof()在您不一定期望它时设置。一个这样的例子是读取一个字符:流可能最终检测到该字符和 set 后面没有任何内容eof()

如果你想处理一个空流,这很简单:从流中查看一些东西,只有当你知道它不为空时才继续:

if (stream.peek() != std::char_traits<char>::eof()) {
    do_what_needs_to_be_done_for_a_non_empty_stream();
}
else {
    do_something_else();
}
于 2012-11-02T23:29:45.447 回答
5

永远不要eof单独检查。

在提取操作期间到达文件结尾时设置该标志(与由 返回的值中的位标志eof相同)。如果没有提取操作,则永远不会设置,这就是您的第一个检查返回的原因。eofbitrdstate()eofbitfalse

但是eofbit,没有迹象表明操作是否成功。为此,请签failbit|badbitrdstate()failbit表示“出现逻辑错误”,并badbit表示“出现 I/O 错误”。方便的是,有一个fail()函数可以精确返回rdstate() & (failbit|badbit). 更方便的是,有一个operator bool()返回的函数!fail()。所以你可以做类似的事情while(stream.read(buffer)){ ...

如果操作失败,您可以检查eofbit,badbitfailbit单独找出失败的原因

于 2012-11-03T00:02:53.077 回答
1

您使用的是什么编译器/标准 C++ 库?我用 gcc 4.6.3/4.7.2 和 clang 3.1 尝试过,它们都工作得很好(即断言没有触发)。

我认为您应该将此报告为工具链中的错误,因为我对标准的阅读符合您的直觉,即只要 get() 能够返回字符,就不应设置 eof()。

于 2012-11-02T23:37:56.223 回答
1

从某种意义上说,它不是一个错误,它是预期的行为。直到 输入失败后,您使用 test 的意图。eof()它的主要目的是在提取函数内部使用,在早期的实现中,std::streambuf::sgetc() 返回的事实EOF并不意味着它会在下一次被调用:意图是随时sgetc()返回EOF(现在 std::char_traits<>::eof(),这将被记住,并且流不会再调用streambuf。

实际上,我们确实需要两个eof():一个供内部使用,如上所述,另一个可以可靠地说明失败是由于已到达文件末尾。事实上,给定以下内容:

std::istringstream s( "1.23e+" );
s >> aDouble;

无法检测到错误是由于格式错误,而不是流没有更多数据。在这种情况下,内部的 eof 应该返回 true(因为我们已经看到文件结尾,当我们往前看时,我们想要抑制对 streambuf提取器函数的所有进一步调用),但外部的应该是 false,因为存在数据(即使在跳过初始空格之后)。

当然,如果您没有实现提取器功能,则ios_base::eof()在实际输入失败之前永远不要进行测试。这从来都不是提供任何有用信息的意图(这让人想知道他们为什么定义- 它返回的ios_base::good()事实意味着它可以提供也不可靠的信息 untin返回,在这一点上,我们知道它会返回,所以调用它没有意义)。falseeof()fail()truefalse

而且我不确定你的问题是什么。因为流不能提前知道你的下一个输入是什么(例如它是否会跳过空格),它不能提前知道你的下一个输入是否会因为文件结束而失败。采用的成语很明确:尝试输入,然后测试是否成功。没有其他方法,因为无法实施其他替代方案。Pascal 的做法不同,但是 Pascal 中的文件是类型化的——你只能从中读取一种类型,因此它总是可以在后台预读一个元素,如果预读失败,则返回文件结尾。没有预先的文件结尾是我们为能够从文件中读取多个类型而付出的代价。

于 2012-11-02T23:38:32.763 回答
0

行为有些微妙。 eofbit在尝试读取文件末尾之后设置,但这可能会也可能不会导致当前提取操作失败。

例如:

ifstream blah;
// assume the file got opened
int i, j;
blah >> i;
if (!blah.eof())
    blah >> j;

如果文件包含142<EOF>,则数字序列以文件结尾终止,因此eofbit设置为并且提取成功。不会尝试提取j,因为已经遇到文件结尾。

如果文件包含142 <EOF>,则数字序列以空格终止(提取i成功)。 eofbit尚未设置,因此blah >> j将被执行,它将到达文件末尾而没有找到任何数字,因此它将失败。

请注意文件末尾看起来无害的空白是如何改变行为的。

于 2012-11-02T23:39:37.750 回答