受我的previous question启发

对于新的C++程序员来说,一个常见的错误是从文件中读取内容类似于以下内容:

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

他们通常会报告文件的最后一行已被读取两次。这个问题的常见解释(我之前已经给出过)如下:



但是,这种解释的第一句话是错误的,因此对代码正在做什么的解释也是错误的。

格式化输入函数(即operator>>(std::string&))的定义将提取定义为使用rdbuf()->sbumpc()rdbuf()->sgetc()获取输入字符。它指出,如果这些函数中的任何一个返回traits::eof(),那么EOF位置1:



我们可以通过使用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()作为提取条件的真正原因是什么?

最佳答案

是的,如std::stringstream示例所示,如果提取在文件末尾停止,则从输入流中提取将设置EOF位。如果就这么简单,以!file.eof()作为条件的循环将在类似以下文件的文件上正常工作:

hello
world

第二次提取将吃world,在文件末尾停止,然后将EOF位置1。下一次迭代不会发生。

但是,许多文本编辑器都有一个肮脏的 secret 。当您保存一个文本文件时,它们只是在骗您。他们没有告诉您的是,文件末尾有一个隐藏的\n。文件中的每一行都以\n结尾,包括最后一行。因此,该文件实际上包含:
hello\nworld\n

这就是使用!file.eof()作为条件时导致最后一行重复的原因。现在我们知道了,我们可以看到第二次提取将吃到world停止在\n而不设置EOF位(因为我们还没有到达那里)。该循环将第三次迭代,但是下一次提取将失败,因为它找不到仅要提取的字符串,而没有找到要提取的字符串。该字符串保留其先前值,该字符串仍在周围徘徊,因此我们得到了重复的行。

您不会通过std::stringstream体验到这一点,因为您在流中坚持的正是您所得到的。与文件不同,\n的末尾没有std::stringstream ss("hello")。如果要执行std::stringstream ss("hello\n"),则会遇到相同的重复行问题。

因此,当然,我们可以看到,从文本文件中提取内容时,切勿使用!file.eof()作为条件-但是真正的问题是什么?无论我们是否从文件中提取内容,为什么我们真的不应该将其用作条件?

真正的问题是 eof()不让我们知道下一次读取是否会失败。在上述情况下,我们看到即使eof()为0,但由于没有要提取的字符串,所以下一次提取失败。如果我们不将文件流与任何文件相关联,或者如果该流为空,则会发生相同的情况。 EOF位不会被设置,但是没有什么可读取的。我们不能仅仅因为未设置eof()而盲目地从文件中提取内容。

使用while (std::getline(...))和相关条件非常有效,因为在提取开始之前,格式化的输入函数会检查是否设置了bad,fail或EOF位。如果其中任何一个失败,它将立即结束,并在过程中设置失败位。如果它在找到要提取的内容之前先找到文件末尾,同时设置eof和fail位,它也会失败。

注意:如果在保存前先执行\n:set noeol,则可以在vim中保存不含多余:set binary的文件。

关于c++ - 不使用EOF位作为流提取条件的真正原因是什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14615671/

10-11 22:35
查看更多