While scanf EOF loop misbehaving
我是C语言的初学者,但遇到了无法解决的问题,也无法在此处找到其他线程的解决方案。
我正在尝试使用以下代码从键盘输入/ txt文件读取整数:
1 2 3 4 5 6 7 8 9 10 11 12
| int grades [MAX_GRADES_LENGTH ]={0}, histogram [HISTOGRAM_SIZE ]={0};
int maxGradesHistogramBucket =0, median =0, gradesLength =0;
double avg =0.0;
int grade =0;
printf("Enter grades:\
");
while (scanf("%d",&grade ) != EOF )
{
grades [gradesLength ]=grade ;
gradesLength =gradesLength +1;
} |
我应该在grades []数组中设置这些"等级",并计算循环中数组的长度。
某种程度上来说,循环的行为不正确,似乎循环中可以使用某些输入,但是对于某些输入(实际上是大多数输入),scanf()并不会获得EOF,无论它是文件的实际结尾还是^ D终端中的命令。
我听说过scanf()并不是读取数据的最可靠方法,但是不幸的是,由于我们在课堂上还没有学过任何其他方法,因此我无法使用其他任何东西,因此我们只能在作业中使用scanf()。
我试图用== 1更改!= EOF及其所有相同。
用于输入
100 0 90 10 80 20 70 30 60 40 50
例如,它工作正常。
但对于输入:
0 50 100
循环是无限的。
我正在使用macbook pro btw(如果有问题的话)。
有任何想法吗?
-
MAX_GRADES_LENGTH的值是什么?
-
请提供完整的示例。
-
将if ([gradesLength >= MAX_GRADES_LENGTH) break;之前的if ([gradesLength >= MAX_GRADES_LENGTH) break;添加到while循环中,以确保代码不会读取太多内容。
-
我的控制台上的EOF字符是Ctrl-Z而不是Ctrl-D,并且此发布的代码确实有效(尽管不完美),类似于@DirkKoopman的回答。
-
如果您在MacBook Pro上使用,则EOF字符为control-D,除非您有意将其重新映射。 Control-Z暂停正在运行的程序,但这意味着"发送SIGSTOP"并将其放入暂停的动画中。 Shell再次提示,但程序尚未终止。您应该可以使用jobs看到它-如果您非常使用control-Z,则可能会感到惊讶。您可以使用stty -a检查字符映射,并在末尾查看输出的cchars部分。在我的Mac上,输出包括:cchars: … eof = ^D; … erase = ^?; intr = ^C; … susp = ^Z; …。
-
在具有MS操作系统的PC上使用@JonathanLeffler。
-
如果OP提供更多数据,肯定会很好。
-
@WeatherVane:同意在MS Windows上,control-Z字符是EOF,但问题是"我正在使用macbook pro btw(如果有关系)"。因此,仅当您对stty感到满意或运行的虚拟机毕竟运行Windows时,Control-Z才是EOF。
-
@Jonathan Leffler我想去买一台Macbook,所以我告诉其他不会的人,它的ctrl-z就像您对答案中的新手感到困惑。
-
scanf返回读取的项目数。它何时会返回EOF?
如果键入字母而不是数字,则scanf()将返回0(例如,"零成功转换的数字"),而不是EOF(例如,"没有数据可读取")。正确的测试是确保获得期望的值数-在这种情况下,为1:
1
| while (scanf("%d", &grade ) == 1) |
如果您需要知道是到达EOF还是没有结果(但是读取其余行可能会解决问题),那么请从scanf()捕获返回值:
1 2 3 4 5 6
| int rc ;
while ((rc = scanf("%d", &grade )) == 1)
{
}
if (rc != EOF )
…read the rest of the line , or at least the next character , before resuming the loop… |
而且,如果您确实愿意,可以编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int rc ;
while ((rc = scanf("%d", &grade )) != EOF )
{
if (rc == 1)
grades [gradesLength ++] = grade ;
else
{
printf("Discarding junk:");
int c ;
while ((c = getchar()) != EOF && c != '\
')
putchar(c );
putchar('\
');
if (c == EOF )
break;
}
} |
else子句中的代码可以合理地放入函数中。它还可能会将消息报告为标准错误而不是标准输出。让用户知道您反对的内容是有礼貌的。您可以使用其他测试(&& !isdigit(c) && c != '+' && c != '-',使用中的isdigit())在换行符之前停止。但是,用户没有机会重新编辑他们放在字母后面的内容,因此您可能会误解他们的输入。最好丢掉其余的输入行,然后让它们重新开始。
正如chux所指出的,在读取了可能是整数一部分的字符之后,需要将该字符放回输入流中。因此,如果我要分析该行的其余部分并重新开始扫描实际上可能是整数一部分的第一个数据,我会考虑使用类似以下内容的方法:
1 2 3 4 5 6
| #include <ctype.h>
static inline int could_be_integer (int c )
{
return isdigit(c ) || c == '+' || c == '-');
} |
然后else子句可能是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| else
{
printf("Discarding junk:");
int c ;
while ((c = getchar()) != EOF && c != '\
' && !could_be_integer (c ))
putchar(c );
putchar('\
');
if (could_be_integer (c ))
ungetc(c , stdin );
else if (c == EOF )
break;
} |
如您所见,这变得凌乱。有时(如Weather Vane在评论中指出的那样),使用fgets()读取一行然后在循环中使用sscanf()更容易(请参见如何在循环中使用sscanf()?)。警惕有关使用fflush(stdin)的建议;它并非在所有地方都会自动出错,但在正常情况下它无法在MacBook Pro上使用。
总体而言,简单地忽略输入的其余部分通常是更好的接口决策。
-
或使用fflush(stdin)放弃输入
-
@WeatherVane:我不在可以正常工作的系统上工作,所以我不这样做。有关详细信息,请参见"使用fflush(stdin)"(注意问题的注释与接受的答案一样多-以我的愚见,当平台是Microsoft Windows并且运行时库是MS C运行时库时,这是错误的)。
-
详细信息:当isdigit(c) ...需要后面的unget()时停止。
-
@chux:ungetc(),但是可以。直到换行之前更多的原因是断断续续。
-
scanf()所需的所有这些旋转显示了为什么我什至不使用它。 fgets()最好用空白条目(换行符)表示没有更多输入。
-
@WeatherVane:足够公平;我通常只是停止不匹配,而不必理会错误报告,但这是一个有意识的决定。我试图教育发问者有哪些选择。
-
好的举动,他的问题要求为scanf()。
-
scanf("%s", discardstring);也可以报告并清除输入缓冲区。
-
@WeatherVane:scanf("%*c");也是如此-有很多处理此问题的方法。如果您希望在选项上写出自己的百科全书式答案,请不要让我停下来,但是在这里真的不欢迎类似的其他评论。这不是你的错。您只是使我的答案比我想要的复杂。
-
提到我之后,这并没有阻止您在答案中添加有关fgets()的评论。
-
@WeatherVane:否;但这是最后一次进行此类编辑。 Ive刚刚为您添加了功劳-忽略了这一点。
这个对我有用。
因此,我附上了您的摘录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <stdio.h>
#include <errno.h>
#define MAX_GRADES_LENGTH 20
#define HISTOGRAM_SIZE 20
main ()
{
int grades [MAX_GRADES_LENGTH ]={0}, histogram [HISTOGRAM_SIZE ]={0};
int maxGradesHistogramBucket =0, median =0, gradesLength =0;
double avg =0.0;
int grade =0;
int i ;
printf("Enter grades:\
");
while (scanf("%d",&grade ) != EOF )
{
grades [gradesLength ]=grade ;
gradesLength =gradesLength +1;
}
if (errno )
perror("grade");
for (i = 0; i < gradesLength ; ++i ) {
printf("%d\
", grades [i ]);
}
} |
并运行它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| $ a.out
Enter grades:
100 0 90 10 80 20 70 30 60 40 50
100
0
90
10
80
20
70
30
60
40
50
$ a.out
Enter grades:
0 50 100
0
50
100
$ |
也许您在寻找错误的位置。该错误可能在您的输出例程中?
就个人而言,如果必须这样做,并且对scanf何时返回的内容有一定的不确定性,并且无需重写它,那么这个小的更改可能更可靠:
1 2 3 4 5
| int i , r ;
printf("Enter grades:\
");
while ((r = scanf("%d",&grade )) > 0) |
-
尝试输入a作为成绩之一。然后,代码严重失败。
-
@Jonathan scanf是一个可怕的例程,但是,对于它的价值,我的手册页针对"返回代码"说了这一点-"如果在第一次成功转换或匹配失败发生之前输入结束,则返回EOF值如果发生读取错误,也会返回EOF,在这种情况下,将设置流的错误指示符(error(3)),并且将errno设置为错误。
-
是的,scanf将实现SEGV,但这就是为什么很少使用scanf的众多原因之一。但这不是具体的问题。如果根据问题输入了数据,则输出如图所示。
-
在哪个系统上的哪个手册页?让我们检查一下:POSIX(C)scanf()指定:_如果输入在第一次转换(如果有)完成之前结束,并且没有发生匹配失败,则应返回EOF。_但是匹配失败是什么?我按照规范工作,但是我可以肯定地说,对于期望使用数字(数字)的字母,scanf()返回0,而不是EOF。
-
是,如果输入的是文本而不是数字,则返回0,并且文本保留在输入缓冲区中。
-
首先,感谢您的帮助。这段代码是我作业的一部分,并向我保证输入将是一组整数,因此我不必担心其他任何事情。看起来像t一样的代码也可以正常工作,但不能像您提到的那样与其余代码一起使用,因此问题可能在其他地方出现。生病只是尝试从头开始找到它。再次感谢您的帮助。