根据我的经验,在使用
<stdio.h>
时,“eof”和“error”位的确切语义非常微妙,以至于通常不值得(甚至可能不可能)尝试精确理解它们的工作原理。(我在SO上
提出的第一个问题就是关于此事,尽管涉及的是C ++而不是C。)
我认为你知道这一点,但首先要理解的是,
feof()
的意图绝对不是预测下一次输入尝试是否会到达文件结尾。实际上,其意图甚至不能说输入流“已经”到达了文件结尾。正确的思考
feof()
(以及相关的
ferror()
)的方式是,它们用于错误恢复,以告诉您有关先前输入调用失败原因的更多信息。
这就是为什么使用while(!feof(fp))
编写循环总是错误的原因。
但你现在问的是fscanf
何时会遇到文件结尾并设置eof位,与getc
/fgetc
有何区别。对于getc
和fgetc
,很容易理解:它们尝试读取一个字符,要么成功读取一个字符,要么未读取任何字符(如果未读取任何字符,则可能是因为已经到达文件结尾或遇到了I/O错误)。
但对于fscanf
而言,情况就更加复杂了,因为它所解析的输入格式说明符不同,接受的字符也不同。例如,%s
说明符不仅在遇到文件结尾或出错时停止,还会在遇到空格字符时停止。(这就是为什么评论中的人们问你的输入文件是否以换行符结束的原因。)
我已经尝试过该程序。
#include <stdio.h>
int main()
{
char buffer[100];
FILE *stream = stdin;
while(!feof(stream)) {
fscanf(stream,"%s",buffer);
printf("%s\n",buffer);
}
}
这与您发布的内容非常接近。(我在printf
中添加了一个\n
,以便输出更易于查看,并且更符合输入。)然后我在输入上运行了该程序。
This
is
a
test.
具体而言,这四行代码的每一行都以换行符结尾。输出结果不出所料地是:
This
is
a
test.
test.
最后一行被重复是因为当你写
while(!feof(stream))
时,通常会发生这种情况。但是我尝试在输入上运行它。
This\n
is\n
a\n
test.
上一行没有换行符。这次,输出结果为:
This
is
a
test.
这一次,最后一行没有被重复输出。(输出仍然与输入不完全相同,因为输出包含四个换行符,而输入只有三个。)
我认为这两种情况之间的差异在于,在第一种情况下,当输入包含一个换行符时,
fscanf
会读取最后一行,读取到最后一个
\n
,注意到它是空格符,然后返回,但它并没有遇到 EOF,所以没有设置 EOF 位。在第二种情况下,没有尾随换行符,
fscanf
在读取最后一行时遇到了文件结束符,因此设置了 eof 位,因此
while()
中的
feof()
条件得到满足,代码不会再进行一次循环,最后一行也不会被重复。
如果我们查看
fscanf
的返回值,就可以更清楚地看到发生了什么。我将循环修改为以下形式:
while(!feof(stream)) {
int r = fscanf(stream,"%s",buffer);
printf("fscanf returned %2d: %5s (eof: %d)\n", r, buffer, feof(stream));
}
现在,当我在以换行符结尾的文件上运行它时,输出结果为:
fscanf returned 1: This (eof: 0)
fscanf returned 1: is (eof: 0)
fscanf returned 1: a (eof: 0)
fscanf returned 1: test. (eof: 0)
fscanf returned -1: test. (eof: 1)
我们可以清楚地看到,在第四次调用后,
feof(stream)
仍不为真,这意味着我们将在循环中进行最后一次额外且不必要的第五次旅行。但是我们可以看到,在第五次旅行期间,
fscanf
返回-1,表示(a)它没有按预期读取字符串,以及(b)它达到了EOF。
另一方面,如果我在不包含尾随换行符的输入上运行它,则输出如下:
fscanf returned 1: This (eof: 0)
fscanf returned 1: is (eof: 0)
fscanf returned 1: a (eof: 0)
fscanf returned 1: test. (eof: 1)
现在,在第四次调用fscanf后,feof立即变为true,并且不会再进行额外的操作。
底线是:道德是:
1. 不要写while(!feof(stream))。
2. 仅使用feof()和ferror()来测试为什么先前的输入调用失败。
3. 检查scanf和fscanf的返回值。
我们还需要注意:要小心不以换行符结尾的文件!它们的行为可能会有惊人的不同。
附录:这里有一种更好的编写循环的方法:
while((r = fscanf(stream,"%s",buffer)) == 1) {
printf("%s\n", buffer);
}
当您运行此代码时,它总是准确地打印出输入中看到的字符串。它不会重复任何内容;它不会根据最后一行是否以换行符结尾而有任何显著不同的操作。并且 - 重要的是 - 它根本不需要调用
feof()
!
注:在这一切中,我忽略了使用*scanf读取字符串而不是行的事实。此外,如果遇到大于要接收它的缓冲区的字符串,
%s
倾向于表现得非常糟糕。
fscanf
是否读到了EOF。fscanf(“%c”)
与fgetc
完全类似。 - Antti Haapala -- Слава УкраїніEOF
不是0xff
;EOF
被保证为负数,以便其与成功的fgetc
返回值不会被混淆。 - melpomenefscanf
与%s
,对我来说它会打印最后一行两次,就像逐个字符版本会打印最后一个字符两次一样。你能描述一下你在你(现在)的第一个示例中如何打印输出,以及你的输入是什么样子的吗? - Steve Summit%s
被指定为读取一系列连续的非空白字符,因此它必须继续读取直到遇到空白字符或 EOF。在后一种情况下,它可能会在流上设置 eof 指示器。 - melpomene