我刚刚在这个答案中找到了一条评论,说iostream::eof
在循环条件中使用“几乎肯定是错误的”。我通常使用类似的东西while(cin>>n)
- 我猜它会隐式检查 EOF。
为什么检查 eof 显式使用while (!cin.eof())
错误?
它与scanf("...",...)!=EOF
在 C 中使用(我经常毫无问题地使用)有什么不同?
因为iostream::eof
只会在读取流的末尾true
后返回。它并不表示下一次读取将是流的结尾。
考虑一下(并假设下一次读取将在流的末尾):
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
}
反对这一点:
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)
}
关于你的第二个问题:因为
if(scanf("...",...)!=EOF)
是相同的
if(!(inStream >> data).eof())
和不一样
if(!inStream.eof())
inFile >> data
底线顶部: 通过正确处理空白,以下是如何eof
使用(甚至比fail()
错误检查更可靠):
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 线程中最重要的观点——而且通过正确处理空白,它提供了一个更清洁、更可靠错误处理,并且始终是正确的解决方案(尽管不一定是最简单的)。
总结建议的“正确”终止和阅读顺序如下:
int data;
while(in >> data) { /* ... */ }
// which is equivalent to
while( !(in >> data).fail() ) { /* ... */ }
超出 eof 的读取尝试失败作为终止条件。这意味着没有简单的方法可以区分成功的流和由于 eof 以外的原因而真正失败的流。采取以下流:
1 2 3 4 5<eof>
1 2 a 3 4 5<eof>
a<eof>
while(in>>data)
failbit
以所有三个输入的集合结束。在第一和第三,eofbit
也是设置。因此,在循环之后,需要非常难看的额外逻辑来区分正确的输入(第一个)和不正确的输入(第二个和第三个)。
鉴于,采取以下措施:
while( !in.eof() )
{
int data;
in >> data;
if ( in.fail() ) /* handle with break or throw */;
// now use data
}
在这里,in.fail()
验证只要有要阅读的内容,就是正确的。它的目的不仅仅是一个while循环终止符。
到目前为止一切都很好,但是如果流中有尾随空格会发生什么 - 听起来像eof()
终结者的主要问题是什么?
我们不需要放弃我们的错误处理;只是吃掉空白:
while( !in.eof() )
{
int data;
in >> data >> ws; // eat whitespace with std::ws
if ( in.fail() ) /* handle with break or throw */;
// now use data
}
std::ws
在设置 时跳过流中的任何潜在(零个或多个)尾随空格eofbit
,而不是failbit
. 因此,in.fail()
只要至少有一个数据要读取,就可以按预期工作。如果全空流也可以接受,那么正确的形式是:
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)
无疑是一种更常见和更简洁的习惯用法,并且在简单(每种读取类型的单个数据)场景中可能是首选。
因为如果程序员不写while(stream >> n)
,他们可能会这样写:
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
立即设置,因此如果您 write 则更惯用,因为如果从流中读取失败并因此循环停止while (stream >> n)
,则返回的对象stream
将转换为。如果读取成功并且循环继续false
,它会转换为。true
其他答案已经解释了为什么逻辑错误while (!stream.eof())
以及如何解决它。我想专注于不同的事情:
为什么检查 eof 显式使用
iostream::eof
错误?
一般而言,eof
仅检查是错误的,因为流提取 ( >>
) 可能会失败而不会到达文件末尾。如果您有 egint n; cin >> n;
并且流包含hello
, thenh
不是有效数字,因此提取将失败而不会到达输入的末尾。
此问题与在尝试读取流状态之前检查流状态的一般逻辑错误相结合,这意味着对于 N 个输入项,循环将运行 N+1 次,导致以下症状:
如果流为空,则循环将运行一次。>>
将失败(没有要读取的输入)并且应该设置(由stream >> x
)的所有变量实际上都未初始化。这会导致垃圾数据被处理,这可能表现为无意义的结果(通常是巨大的数字)。
(如果您的标准库符合 C++11,现在情况有些不同:A failed>>
现在将数值变量设置为,0
而不是让它们未初始化(char
s 除外)。)
如果流不为空,则循环将在最后一个有效输入之后再次运行。由于在最后一次迭代中所有>>
操作都失败了,因此变量很可能会保留上一次迭代的值。这可以表现为“最后一行被打印两次”或“最后输入记录被处理两次”。
(这应该与 C++11 (见上文)有所不同:现在你得到一个零的“幻像记录”而不是重复的最后一行。)
如果流包含格式错误的数据,但您只检查.eof
,则最终会出现无限循环。>>
将无法从流中提取任何数据,因此循环旋转到位而从未到达终点。
回顾一下:解决方案是测试>>
操作本身的成功,而不是使用单独的.eof()
方法:while (stream >> n >> m) { ... }
,就像在 C 中测试scanf
调用本身的成功一样:while (scanf("%d%d", &n, &m) == 2) { ... }
。
要记住的重要一点是,直到尝试读取失败后inFile.eof()
才会变为,因为您已经到达文件的末尾。所以,在这个例子中,你会得到一个错误。True
while (!inFile.eof()){
inFile >> x;
process(x);
}
使这个循环正确的方法是将读取和检查结合到一个操作中,就像这样
while (inFile >> x)
process(x);
按照惯例,operator>>
返回我们从中读取的流,False
当流失败(例如到达文件末尾)时,返回对流的布尔测试。
所以这给了我们正确的顺序:
如果您碰巧遇到一些其他问题阻止您正确读取文件,您将无法访问eof()
。例如,让我们看一下这样的东西
int x;
while (!inFile.eof()) {
inFile >> x;
process(x);
}
让我们通过一个例子来追溯上述代码的工作原理
'1', '2', '3', 'a', 'b'
.a
。a
为 int 时,它会失败。clear
使用该流,否则所有读取它的尝试都将失败。False
,因为我们不在文件末尾,因为还有a
等待读取。但是,如果我们使用这样的循环,我们将获得所需的输出。
while (inFile >> x)
process(x);
在这种情况下,流False
不仅会在文件结束的情况下转换为,而且还会在转换失败的情况下转换,例如a
我们无法读取为整数的情况。
循环中的 iostream::eof 被认为是错误的,因为我们还没有达到 EOF。所以这并不意味着下一次读取会成功。
我将通过两个示例代码来解释我的陈述,这肯定会帮助您以更好的方式理解这个概念。比方说,当我们想在 C++ 中使用文件流读取文件时。当我们使用循环写入文件时,如果我们使用stream.eof()检查文件的结尾,我们实际上是在检查文件是否已经到达结尾。
示例代码
#include<iostream>
#include<fstream>
using namespace std;
int main() {
ifstream myFile("myfile.txt");
string x;
while(!myFile.eof()) {
myFile >> x;
// Need to check again if x is valid or eof
if(x) {
// Do something with x
}
}
}
当我们在循环中直接使用流时,我们不会再次检查条件。
示例代码
#include<iostream>
#include<fstream>
using namespace std;
int main() {
ifstream myFile("myfile.txt");
string x;
while(myFile >> x) {
// Do something with x
// No checks needed!
}
}