【问题标题】:Example of Why stream::good is Wrong?为什么 stream::good 是错误的示例?
【发布时间】:2015-02-03 13:09:37
【问题描述】:

我给出了一个答案,我想每次通过循环here检查流的有效性。

我的原始代码使用了good,看起来类似于:

ifstream foo("foo.txt");

while (foo.good()){
    string bar;
    getline(foo, bar);
    cout << bar << endl;
}

我立即被指向here,并被告知永远不要测试good。显然这是我不明白的事情,但我想正确地执行我的文件 I/O。

我用几个示例测试了我的代码,但无法使good-testing 代码失败。

首先(打印正确,以新行结尾):

屏蔽 1
出血 1 2
废话
换行结束

第二个(打印正确,以最后一行结尾):

阻止 1
出血 1 2
废话
这不会以新行结束

第三个是一个空文件(打印正确,一个换行符。)

Fourth 是一个丢失的文件(这正确地没有打印任何内容。)

谁能帮我举个例子来说明为什么 good-testing 不应该做?

【问题讨论】:

  • 你应该改用while (getline(foo, bar)) {..}
  • @NeilKirk 这真的取决于偏好。对循环条件使用getline 将不会为空文件输出任何内容,并截断最后一个尾随换行符。因此示例 2 和示例 3 会以不同的方式打印。如果 n 是文件中\ns 的数量,那么我个人的偏好是文件被表示为具有 n + 1 行。 Nathan Oliver 的答案中的 cmets 在一定程度上解决了这个问题。
  • 我假设您不想要额外的新行输出。如果你这样做,它看起来是正确的。

标签: c++ file-io stream filestream


【解决方案1】:

他们错了。口头禅是“永远不要测试.eof()”。

即使这个口头禅也太过分了,因为两者都有助于在提取失败后诊断流的状态。

所以咒语应该更像

在尝试进一步阅读之前,请勿使用 good()eof() 检测 eof

fail()bad() 相同

当然stream.good可以在使用流之前有效地使用(例如,如果流是尚未成功打开的文件流)

但是,两者都经常滥用来检测输入的结束,但这不是它的工作原理。


为什么不应该使用此方法的典型示例:

std::istringstream stream("a");
char ch;
if (stream >> ch) {
   std::cout << "At eof? " << std::boolalpha << stream.eof() << "\n";
   std::cout << "good? " << std::boolalpha << stream.good() << "\n";
}

打印

false
true

Live On Coliru

【讨论】:

  • @doc 微妙的极简主义。我同意,尽管人们倾向于认为“显然good() 在这里返回 true,这是全新的新鲜流”。所以,这就是我阅读的原因。
【解决方案2】:

这已在其他答案中介绍过,但为了完整起见,我将简要介绍一下。与

的唯一功能区别
while(foo.good()) { // effectively same as while(foo) {
    getline(foo, bar);
    consume(bar); // consume() represents any operation that uses bar
}

while(getline(foo, bar)){
    consume(bar);
}

当文件中没有行时,前者是否会执行额外的循环,从而使这种情况与一个空行的情况无法区分。我认为这不是通常想要的行为。但我认为这是见仁见智的问题。

正如 sehe 所说,口头禅是过分。这是一个简化。真正的重点是,在测试失败或至少 EOF 之前,您不能 consume() 读取流的结果(并且任何测试在读取之前都是无关紧要的)。这是人们在循环条件下测试good() 时容易做的事情。

然而,getline() 的问题在于它会在内部为您测试 EOF 并返回一个空字符串,即使只读取 EOF 也是如此。因此,以前的版本可能大致类似于以下伪c++:

while(foo.good()) {
    // inside getline
    bar = "";               // Reset bar to empty
    string sentry;
    if(read_until_newline(foo, sentry)) {
        // The streams state is tested implicitly inside getline
        // after the value is read. Good
        bar = sentry        // The read value is used only if it's valid.
    // ...                  // Otherwise, bar is empty.
    consume(bar);
}

我希望这能说明我想说的话。可以说存在一个“正确”版本的读取循环inside getline()。这就是为什么即使 outer 循环不符合规则,使用 readline 也至少部分满足了该规则。

但是,对于其他阅读方法,打破规则的伤害更大。考虑:

while(foo.good()) {
    int bar;
    foo >> bar;
    consume(bar);
}

您不仅总是获得了额外的迭代,而且该迭代中的 bar 未初始化!

因此,简而言之,while(foo.good()) 在您的情况下是可以的,因为getline() 与某些其他读取功能不同,在读取 EOF 位后使输出处于有效状态。并且因为当文件为空时您不在乎甚至不期望额外的迭代。

【讨论】:

  • "echo" 总是添加一个换行符,所以你不只是打印你输入的内容:stackoverflow.com/q/15728703/2642059 在我看来,我希望打印的内容能够反映我在文本编辑器中看到的内容。一个空文件确实显示一个空行。这就是我想要的。以换行符结尾的文件显示最后一个空行。这就是我想要的。当我使用getline 作为我的循环条件时,我没有得到这些。现在正如你所说,这是一个见仁见智的问题。我认为既然我们知道这一点,我们就不会犯错。
【解决方案3】:

good()eof() 都会在代码中多出一行。如果你有一个空白文件并运行这个:

std::ifstream foo1("foo1.txt");
std::string line;
int lineNum = 1;

std::cout << "foo1.txt Controlled With good():\n";
while (foo1.good())
{
    std::getline(foo1, line);
    std::cout << lineNum++ << line << std::endl;
}
foo1.close();
foo1.open("foo1.txt");
lineNum = 1;

std::cout << "\n\nfoo1.txt Controlled With getline():\n";
while (std::getline(foo1, line))
{
    std::cout << line << std::endl;
}

你会得到的输出是

foo1.txt Controlled With good():
1

foo1.txt Controlled With getline():

这证明它不能正常工作,因为不应读取空白文件。知道这一点的唯一方法是使用读取条件,因为流在​​第一次读取时总是好的。

【讨论】:

  • 这实际上是我所期待的行为吗?你在我的问题中看到了这一点:“第三个是一个空文件(这个打印正确,一个换行符。)”但这也许是我被警告的陷阱?
  • 但是你不想要一个换行符。如果您将数据添加到向量中,您将拥有一个大小为 1 的向量。尽管使用空文件,但您不应该拥有它。
  • 我想这更像是一个“你期待什么?”的事情。如果文件的最后一行没有任何内容,我希望使用good 控制的循环给我一个空行。我希望getline 方法不会给我一个空文件一个以换行符结尾的文件的空行。这使得示例 1 在由 getline 控制时打印不同。
  • 但是你总会得到一条线。如果您有一个包含 20 行的文件并将它们添加到向量中,则该向量实际上将包含 21 个元素,使用 good() 时最后一个元素为空白
  • 这不是真的。示例 2 显示了一个有 4 行的文件,循环运行了 4 次(这对于使用 goodgetline 控制的两个循环都是如此。)示例 1 显示了一个有 5 行的文件,使用 good 控制的循环将运行 5 次(使用getline 控制的循环只会运行 4.)
【解决方案4】:

使用foo.good() 只是告诉您前一个读取操作工作得很好,下一个也可以工作。 .good() 在给定点检查流的状态。它不检查是否到达文件末尾。假设在读取文件时发生了一些事情(网络错误,操作系统错误,...),好的会失败。这并不意味着已到达文件末尾。然而,当到达文件末尾时 .good() 失败,因为流无法再读取。

另一方面,.eof() 检查是否确实到达了文件末尾。

因此,.good() 可能会在未到达文件末尾时失败。

希望这可以帮助您了解为什么使用.good() 检查文件结尾是一个坏习惯。

【讨论】:

  • Ummm... 如果出现网络错误或其他问题,我不想跳出循环吗?似乎您的回答是说使用good 是正确的做法,但随后您说:“使用.good() 检查文件结尾是一个坏习惯。”你能澄清一下吗?此外,遇到 EOF 时,good 设置为 false:cplusplus.com/reference/ios/ios/good
【解决方案5】:

让我明确地说sehe's answer 是正确的。

但是Nathan OliverNeil Kirkuser2079303提出的选项是使用readline而不是good作为循环条件。为了后代,需要解决。

我们将问题中的循环与以下循环进行比较:

string bar;

while (getline(foo, bar)){
    cout << bar << endl;
}

因为getline 返回作为第一个参数传递的istream,并且因为当istream 被强制转换为bool it returns !(fail() || bad()),并且由于读取EOF 字符将设置both failbiteofbit 这使得 getline 成为有效的循环条件。

但是,当使用getline 作为条件时,行为确实会发生变化,因为如果读取了仅包含 EOF 字符的行,则循环将退出,从而阻止输出该行。这在示例 2 和示例 4 中没有出现。但是示例 1:

屏蔽 1
出血 1 2
废话
换行结束

使用good 循环条件打印:

屏蔽 1
出血 1 2
废话
换行结束

但是用getline 循环条件砍掉最后一行:

屏蔽 1
出血 1 2
废话
换行结束

示例 3 是一个空文件:

使用good 条件打印:

getline 条件下不打印任何内容。

这些行为都不是错误的。 但是最后一行可以在代码中产生影响。希望此答案对您在两者之间进行编码时有所帮助。

【讨论】:

    猜你喜欢
    • 2015-12-07
    • 1970-01-01
    • 1970-01-01
    • 2017-11-20
    • 1970-01-01
    • 2018-02-18
    • 2014-06-05
    • 2019-12-09
    • 1970-01-01
    相关资源
    最近更新 更多