博士TL;
While (!feof)是错误的,因为它测试了一些不相关的东西,而没有测试您需要知道的东西。结果是,您错误地执行了假定它正在访问已成功读取的数据的代码,而实际上这从未发生过。
我想提供一个抽象的、高层次的视角。所以,如果你对while(!feof)的实际功能感兴趣,请继续阅读。
并发性和同时性
I/O操作与环境交互。环境不是程序的一部分,也不在您的控制之下。环境真正地与您的程序“同时”存在。与所有并发的事情一样,关于“当前状态”的问题没有意义:并发事件之间没有“同时性”的概念。许多状态属性根本无法同时存在。
Let me make this more precise: Suppose you want to ask, "do you have more data". You could ask this of a concurrent container, or of your I/O system. But the answer is generally unactionable, and thus meaningless. So what if the container says "yes" – by the time you try reading, it may no longer have data. Similarly, if the answer is "no", by the time you try reading, data may have arrived. The conclusion is that there simply is no property like "I have data", since you cannot act meaningfully in response to any possible answer. (The situation is slightly better with buffered input, where you might conceivably get a "yes, I have data" that constitutes some kind of guarantee, but you would still have to be able to deal with the opposite case. And with output the situation is certainly just as bad as I described: you never know if that disk or that network buffer is full.)
So we conclude that it is impossible, and in fact unreasonable, to ask an I/O system whether it will be able to perform an I/O operation. The only possible way we can interact with it (just as with a concurrent container) is to attempt the operation and check whether it succeeded or failed. At that moment where you interact with the environment, then and only then can you know whether the interaction was actually possible, and at that point you must commit to performing the interaction. (This is a "synchronisation point", if you will.)
EOF
Now we get to EOF. EOF is the response you get from an attempted I/O operation. It means that you were trying to read or write something, but when doing so you failed to read or write any data, and instead the end of the input or output was encountered. This is true for essentially all the I/O APIs, whether it be the C standard library, C++ iostreams, or other libraries. As long as the I/O operations succeed, you simply cannot know whether further, future operations will succeed. You must always first try the operation and then respond to success or failure.
例子
在每个示例中,请仔细注意,我们首先尝试I/O操作,然后如果结果有效,则使用结果。进一步注意,我们总是必须使用I/O操作的结果,尽管结果在每个示例中具有不同的形状和形式。
C stdio, read from a file:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n == 0) { break; }
}
The result we must use is n, the number of elements that were read (which may be as little as zero).
C stdio, scanf:
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
consume(a, b, c);
}
The result we must use is the return value of scanf, the number of elements converted.
C++, iostreams formatted extraction:
for (int n; std::cin >> n; ) {
consume(n);
}
The result we must use is std::cin itself, which can be evaluated in a boolean context and tells us whether the stream is still in the good() state.
C++, iostreams getline:
for (std::string line; std::getline(std::cin, line); ) {
consume(line);
}
The result we must use is again std::cin, just as before.
POSIX, write(2) to flush a buffer:
char const * p = buf;
ssize_t n = bufsize;
for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; p += k, n -= k) {}
if (n != 0) { /* error, failed to write complete buffer */ }
The result we use here is k, the number of bytes written. The point here is that we can only know how many bytes were written after the write operation.
POSIX getline()
char *buffer = NULL;
size_t bufsiz = 0;
ssize_t nbytes;
while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
{
/* Use nbytes of data in buffer */
}
free(buffer);
The result we must use is nbytes, the number of bytes up to and including the newline (or EOF if the file did not end with a newline).
Note that the function explicitly returns -1 (and not EOF!) when an error occurs or it reaches EOF.
You may notice that we very rarely spell out the actual word "EOF". We usually detect the error condition in some other way that is more immediately interesting to us (e.g. failure to perform as much I/O as we had desired). In every example there is some API feature that could tell us explicitly that the EOF state has been encountered, but this is in fact not a terribly useful piece of information. It is much more of a detail than we often care about. What matters is whether the I/O succeeded, more-so than how it failed.
最后一个实际查询EOF状态的示例:假设您有一个字符串,并希望测试它是否代表一个完整的整数,除了空格之外,末尾没有额外的位。使用c++的iostreams,它是这样的:
Std::string input = " 123 ";/ /实例
std:: istringstream iss(输入);
int值;
如果(iss > >价值> > std:: ws & & iss.get () = = EOF) {
消费(价值);
}其他{
// error, "input"不能作为整数解析
}
我们在这里使用了两个结果。第一个是iss,即流对象本身,用于检查对value的格式化提取是否成功。但是,在使用空格之后,我们执行另一个I/O/操作is .get(),并期望它作为EOF失败,如果整个字符串已经被格式化的提取使用,就会出现这种情况。
在C标准库中,通过检查结束指针是否到达输入字符串的末尾,可以使用strto*l函数实现类似的功能。