Jos*_*eld 10 c++ iostream eof language-lawyer
灵感来自我之前的问题
新C++程序员的一个常见错误是从文件中读取以下内容:
std::ifstream file("foo.txt");
std::string line;
while (!file.eof()) {
file >> line;
// Do something with line
}
Run Code Online (Sandbox Code Playgroud)
他们经常会报告文件的最后一行被读取两次.这个问题的常见解释(我之前给出的一个)类似于:
如果您尝试提取文件结尾,则提取将仅在流上设置EOF位,而不是如果您的提取仅在文件结尾处停止.
file.eof()只会告诉你上一次读取是否到达文件结尾,而不是下一次读取.在提取最后一行之后,EOF位仍未设置,并且再次发生迭代.但是,在最后一次迭代中,提取失败并且line仍然具有与之前相同的内容,即最后一行是重复的.
但是,这个解释的第一句是错误的,因此对代码所做的解释也是错误的.
格式化输入函数的定义(即operator>>(std::string&))将提取定义为使用rdbuf()->sbumpc()或rdbuf()->sgetc()获取输入字符.它声明如果这些函数中的任何一个返回traits::eof(),则设置EOF位:
如果
rdbuf()->sbumpc()或rdbuf()->sgetc()返回traits::eof(),那么输入函数,除非另有明确说明,否则完成其操作,并且在返回之前setstate(eofbit)可以抛出ios_base::failure(27.5.5.4).
我们可以通过使用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;
}
Run Code Online (Sandbox Code Playgroud)
很明显,这里的单次提取获得hello从字符串和设置EOF位为1.
那么解释有什么问题?!file.eof()导致最后一行重复的文件有什么不同?我们不应该使用什么!file.eof()作为提取条件的真正原因是什么?
Jos*_*eld 18
是的,如果提取在文件结尾处停止,则从输入流中提取将设置EOF位,如std::stringstream示例所示.如果这很简单,那么具有!file.eof()条件的循环在文件上可以正常工作,例如:
hello
world
Run Code Online (Sandbox Code Playgroud)
第二次提取会吃掉world,停在文件末尾,然后设置EOF位.下一次迭代不会发生.
但是,许多文本编辑都有一个肮脏的秘密.当你保存文本文件时,他们会骗你,就像那样简单.他们没有告诉你的是\n文件末尾有一个隐藏的内容.文件中的每一行都以a结尾\n,包括最后一行.所以文件实际上包含:
hello\nworld\n
Run Code Online (Sandbox Code Playgroud)
这是在使用!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(...))和相关条件完美地工作,因为在提取开始之前,格式化的输入函数检查是否设置了任何坏,失败或EOF位.如果它们中的任何一个,它会立即结束,在此过程中设置失败位.如果它在找到它想要提取的内容之前找到文件结尾,同时设置eof和fail位,它也将失败.
注意:\n如果您在保存之前:set noeol和:set binary保存之前,可以在vim中保存没有额外文件的文件.