我刚在回答中发现一条评论,说在循环条件中使用iostream::eof几乎肯定是错误的。我通常使用类似于while(cin>>n)的东西——我猜这暗含地检查了EOF。
为什么明确使用while (!cin.eof())检查eof是错误的?
它与在C中使用scanf("...",...)!=EOF有什么不同(我经常在使用时没有问题)?
- scanf(...) != EOF在c中也不起作用,因为scanf返回成功解析和分配的字段数。正确的条件是scanf(...) < n,其中n是格式字符串中的字段数。
- @Ben Voigt,如果达到EOF,它将返回一个负数(EOF通常定义为负数)。
- @Sebastiangodelet:实际上,如果在第一个字段转换(成功与否)之前遇到文件结尾,它将返回EOF。如果在字段之间达到文件结尾,它将返回成功转换和存储的字段数。与EOF相比是错误的。
- @塞巴斯蒂安:不,不是真的。当他说"通过循环没有(简单)的方法来区分正确的输入和不正确的输入"时,他就犯错了。实际上,在循环退出后,检查.eof()是很容易的。
- @本是的,在这种情况下(读一个简单的int)。但是,我们可以很容易地想到这样一个场景:while(fail)循环以实际故障和EOF终止。考虑一下,如果每次迭代都需要3个int(比如说您正在读取一个x-y-z点或其他东西),但是流中错误地只有两个int。
- 这个问题与C问题类似,并且有着相同的答案:为什么while(!feof(file))总是错误的?。因为只有在点击EOF后才会设置标志。
- 这里是C++常见问题解答的同一个问题。
- @斯莱:这是不是"三点"方案没有被江户十一〔九〕正确处理?如果不是,那什么流内容不能很好地工作呢?
- 这个问题没有归属于quora:quora.com/unanswered/&hellip;(quora问题标题与这个问题昨天编辑之前的标题完全匹配)。
因为iostream::eof只在读取流的末尾之后返回true。它并不表示下一次读取将是流的结尾。
考虑这一点(然后假设下一次读取将在流的末尾):
1 2 3 4 5 6 7
| while(!inStream.eof()){
int data;
// yay, not end of stream yet, now read ...
inStream >> data;
// oh crap, now we read the end and *only* now the eof bit will be set (as well as the fail bit)
// do stuff with (now uninitialized) data
} |
反对这一点:
1 2 3 4 5 6 7
| int data;
while(inStream >> data){
// when we land here, we can be sure that the read was successful.
// if it wasn't, the returned stream from operator>> would be converted to false
// and the loop wouldn't even be entered
// do stuff with correctly initialized data (hopefully)
} |
关于第二个问题:因为
1
| if(scanf("...",...)!=EOF) |
是一样的
1
| if(!(inStream >> data).eof()) |
和不一样
1 2
| if(!inStream.eof())
inFile >> data |
- 值得一提的是,如果(!(instream>>data).eof())也没有任何有用的功能。谬误1:如果在最后一段数据之后没有空白(最后一个数据将不被处理),它将不会进入条件。谬误2:即使读取数据失败,只要没有达到EOF(无限循环,反复处理相同的旧数据),它也会进入状态。
- 稍微偏离主题,但让我来说说。如果使用懒惰的评估,这种方法是否会毫无问题地成功?
- 我认为值得指出的是,这个答案有些误导。当提取ints或std::strings或类似的数据时,当您在结束前提取一个数据并提取到结束时,设置EOF位。你不需要再读一遍。从文件中读取时没有设置它的原因是在末尾有一个额外的
。我在另一个答案中对此进行了讨论。读取chars是另一回事,因为它一次只提取一个,而且不会继续到达终点。
- 主要的问题是,仅仅因为我们还没有达到EOF,并不意味着下一次读取会成功。
- @一切都是真的,但不是很有用…即使没有尾随的',也可以合理地希望其他尾随的空白在整个文件中与其他空白一致地处理(即跳过)。此外,"之前提取一个"的一个微妙的结果是,当输入完全为空时,while (!eof())在int或std::string上不会"工作",因此即使知道没有尾随的
需要注意。
- @托尼德完全同意。我之所以这么说,是因为我认为大多数人在读到这个和类似的答案时都会认为,如果流中包含"Hello"(没有尾随空格或
),并且提取了std::string),它会将H到o的字母提取出来,停止提取,然后不设置eof位。实际上,它会设置EOF位,因为它是停止提取的EOF。只是希望为人们澄清这一点。
- 这个答案应该是wiki'd,而不是有人不发布这个反模式的时候。
- 也值得一提。如果输入上存在无效数据,则读取可能会导致流进入错误状态。这将阻止进一步读取(除非明确清除)。然后,测试while(!inStream.eof())将进入一个无限循环(因为没有读取数据,永远不会达到eof)。
- EDCOX1〔14〕:不再是C++的11,见StAccOfFult.COM/A/1337 9073/300 2139。
底线顶端:正确处理空白,可以使用eof(甚至比fail()更可靠)进行错误检查:
1 2 3 4 5 6
| while( !(in>>std::ws).eof() ) {
int data;
in >> data;
if ( in.fail() ) /* handle with break or throw */;
// now use data
} |
(感谢Tony D提出的强调答案的建议。请参阅下面他的评论,了解为什么这一点更为强大。)
反对使用eof()的主要论据似乎遗漏了一个关于白空间作用的重要微妙之处。我的观点是,明确地检查eof()不仅"总是错误的",在这个和类似的so线程中这似乎是一个压倒一切的观点,而且通过适当地处理空白,它提供了一个更干净、更可靠的错误处理,并且始终是正确的解决方案(尽管,不一定是最简单的)。
总结建议的"适当"终止和阅读顺序如下:
1 2 3 4 5
| int data;
while(in >> data) { /* ... */ }
// which is equivalent to
while( !(in >> data).fail() ) { /* ... */ } |
超出EOF的读取尝试失败被视为终止条件。这意味着,要区分一个成功的流和一个真正失败的流,除了EOF,没有一个简单的方法。采用以下流程:
while(in>>data)以一组failbit终止,用于所有三个输入。在第一和第三阶段,也设置了eofbit。因此,在循环之后,需要非常丑陋的额外逻辑来区分正确的输入(第一个)和不正确的输入(第二个和第三个)。
鉴于,采取以下措施:
1 2 3 4 5 6 7
| while( !in.eof() )
{
int data;
in >> data;
if ( in.fail() ) /* handle with break or throw */;
// now use data
} |
在这里,in.fail()确认只要有东西要读,它就是正确的。它的目的不仅仅是一个while循环终止符。
到目前为止还不错,但如果流中有尾随空间,会发生什么情况——这听起来像是对作为终结符的eof()的主要关注?
我们不需要放弃我们的错误处理;只需占用空白:
1 2 3 4 5 6 7
| while( !in.eof() )
{
int data;
in >> data >> ws; // eat whitespace with std::ws
if ( in.fail() ) /* handle with break or throw */;
// now use data
} |
当设置eofbit而不是failbit时,std::ws跳过流中的任何潜在(零或更多)尾随空间。因此,只要至少有一个数据需要读取,in.fail()就可以按预期工作。如果所有空白流也可以接受,那么正确的形式是:
1 2 3 4 5 6 7 8
| while( !(in>>ws).eof() )
{
int data;
in >> data;
if ( in.fail() ) /* handle with break or throw */;
/* this will never fire if the eof is reached cleanly */
// now use data
} |
摘要:正确构造的while(!eof)不仅可能而且不错误,而且允许数据在范围内本地化,并像往常一样提供了更清晰的错误检查与业务分离。也就是说,while(!fail)是一个更常见、更简洁的习惯用法,在简单(每个读取类型的单个数据)场景中可能更受欢迎。
- "因此,通过循环,没有(简单)的方法来区分正确的输入和不正确的输入。"除了在一种情况下设置了eofbit和failbit,在另一种情况下只设置了failbit。您只需要在循环结束后测试一次,而不是在每次迭代中;它只会离开循环一次,所以您只需要检查它为什么离开循环一次。while (in >> data)适用于所有空白流。
- 您所说的(以及前面提到的一点)是,一个格式不好的流可以被标识为经过循环的!eof & fail。有些情况下,人们不能依赖于这一点。见以上评论(goo.gl/9mxyx)。另外,我不建议用eof支票作为更好的选择。我只是说,这是一种可能的(在某些情况下更合适)方式,而不是"最肯定是错误的!"因为它往往在这里被宣称是这样的。
- "作为一个例子,考虑如何检查数据是一个带有重载运算符>>同时读取多个字段的结构时的错误"-支持您观点的更简单的情况是stream >> my_int,其中流包含例如"-":eofbit和failbit已设置。这比operator>>方案更糟糕,用户提供的过载至少可以在返回帮助支持while (s >> x)使用之前清除eofbit选项。更一般地说,这个答案可能需要清理——只有最终的while( !(in>>ws).eof() )通常是健壮的,并且埋在最后。
- …的确。谢谢。我更新了答案。
因为如果程序员不写while(stream >> n),他们可能会写:
1 2 3 4 5
| while(!stream.eof())
{
stream >> n;
//some work on n;
} |
这里的问题是,如果不首先检查流读取是否成功,您就不能执行some work on n,因为如果失败,您的some work on n将产生不希望的结果。
关键是,在试图从流中读取数据后,设置了eofbit、badbit或failbit。因此,如果stream >> n失败,那么eofbit、badbit或failbit将立即设置,因此,如果编写while (stream >> n)则更为惯用,因为如果从流中读取失败,返回的对象stream将转换为false而导致循环停止。如果读取成功并且循环继续,则转换为true。
- 除了对n的未定义值进行处理时提到的"不希望的结果",如果失败的流操作不消耗任何输入,程序也可能陷入无限循环。
其他的答案解释了为什么while (!stream.eof())中的逻辑是错误的,以及如何修复它。我想关注一些不同的事情:
why is checking for eof explicitly using iostream::eof wrong?
一般来说,检查eof是错误的,因为流提取(>>可能会失败,而不会触及文件的结尾。如果您有int n; cin >> n;,并且流包含hello,那么h不是有效数字,因此提取将失败,而不会到达输入的结尾。
这个问题与尝试读取流状态之前检查流状态的一般逻辑错误相结合,这意味着对于n个输入项,循环将运行n+1次,导致以下症状:
如果流为空,则循环将运行一次。>>将失败(没有要读取的输入),所有应该设置的变量(由stream >> x设置)实际上都未初始化。这将导致正在处理的垃圾数据,这些数据可能表现为无意义的结果(通常是巨大的数字)。
如果流不是空的,则循环将在最后一个有效输入之后再次运行。因为在上一次迭代中,所有>>操作都失败了,所以变量的值很可能与前一次迭代保持一致。这可以显示为"最后一行打印两次"或"最后一条输入记录处理两次"。
如果流包含格式错误的数据,但您只检查.eof,则最终会得到一个无限循环。>>将无法从流中提取任何数据,因此循环将在不到达末尾的情况下旋转到位。
综上所述:解决方案是测试>>操作本身的成功,而不是使用单独的.eof()方法:while (stream >> n >> m) { ... },就像在c中测试scanf调用本身:while (scanf("%d%d", &n, &m) == 2) { ... }的成功一样。