【问题标题】:While scanf EOF loop misbehaving虽然 scanf EOF 循环行为不端
【发布时间】:2015-02-27 02:54:25
【问题描述】:

我是 C 的初学者,我遇到了我无法解决的问题,并且无法在此处的其他线程上找到解决方案。

我正在尝试使用以下代码从键盘输入/txt 文件中读取整数:

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:\n");
while (scanf("%d",&grade) != EOF)
{
    grades[gradesLength]=grade;
   gradesLength=gradesLength+1;
}

我应该在 Grades[] 数组中设置这些“等级”,并沿循环计算数组的长度。

不知何故循环行为不端,似乎某些输入与循环无关,但对于某些输入(实际上大多数输入),无论是文件的实际结尾,还是终端中的 ^D 命令。

我听说 scanf() 不是最可靠的读取数据的方法,但不幸的是我不能使用其他任何方法,因为我们还没有在课堂上学习任何其他方法,所以我们只能使用 scanf()我们的作业。

我已尝试将!= EOF 更改为== 1,但都一样。

对于输入

100 0 90 10 80 20 70 30 60 40 50

例如,它工作正常。 但对于输入:

0 50 100

循环是无限的。

顺便说一句,我正在使用 macbook pro(如果重要的话)。

有什么想法吗?

【问题讨论】:

  • MAX_GRADES_LENGTH的值是多少?
  • 请提供完整的例子。
  • grades[gradesLength]=grade; 之前添加 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; ….

标签: c while-loop scanf infinite-loop eof


【解决方案1】:

如果您键入字母而不是数字,scanf() 将返回 0(如“零成功转换的数字”)而不是 EOF(如“没有数据可供读取”)。正确的测试是确保您获得预期数量的值——在本例中为 1:

while (scanf("%d", &grade) == 1)

如果您需要知道是否到达 EOF 或没有结果(但阅读该行的其余部分可能会解决问题),然后从 scanf() 捕获返回值:

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…

而且,如果你真的想,你可以写:

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 != '\n')
            putchar(c);
        putchar('\n');
        if (c == EOF)
            break;
    }
}

else 子句中的代码可以合理地放入函数中。它还可能将消息报告给标准错误而不是标准输出。让用户知道您反对的是什么是礼貌的。您可以在换行符之前停止使用不同的测试(&& !isdigit(c) && c != '+' && c != '-',使用来自<ctypes.h>isdigit())。但是,用户没有机会重新编辑他们放在字母后面的内容,因此您可能会误解他们的输入。最好把剩下的输入行扔掉,让它们重新开始。


作为chuxnoted,在读取可能是整数一部分的字符后,需要将该字符放回输入流中。因此,如果我要分析该行的其余部分并重新开始扫描第一个实际上可能是整数一部分的数据,我会考虑使用类似的东西:

#include <ctype.h>

static inline int could_be_integer(int c)
{
    return isdigit(c) || c == '+' || c == '-');
}

然后 else 子句可能是:

else
{
    printf("Discarding junk: ");
    int c;
    while ((c = getchar()) != EOF && c != '\n' && !could_be_integer(c))
        putchar(c);
    putchar('\n');
    if (could_be_integer(c))
        ungetc(c, stdin);
    else if (c == EOF)
        break;
}

如您所见,这变得一团糟。有时(如comment 中的Weather Vane 所述),使用fgets() 读取一行然后在循环中使用sscanf() 会更容易(请参阅How to use sscanf() in a loop?)。警惕关于Using fflush(stdin)的建议;它不会在任何地方自动出错,但在正常情况下它不会在 MacBook Pro 上运行。

总体而言,简单地忽略输入行的其余部分通常是更好的界面决策。

【讨论】:

  • 或使用fflush(stdin)丢弃输入
  • @WeatherVane:我不在可以工作的系统上工作,所以我不这样做。有关详细信息,请参阅Using fflush(stdin)(注意问题的 cmets 与公认的答案一样多——在我看来,当平台是 Microsoft Windows 并且运行时库是 MS C 运行时库时,这是错误的)。跨度>
  • 详细信息:当isdigit(c) ... 需要关注unget() 时停止。
  • @chux: ungetc(),但是是的。更有理由大嚼直到换行。
  • scanf() 所需的所有这些玩弄说明了我什至不使用它的原因。更好的是 fgets() 带有一个空白条目(换行符),表示不再输入。
【解决方案2】:

它对我有用。

我因此附上了你的 sn-p:

#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:\n");
    while (scanf("%d",&grade) != EOF)
    {
        grades[gradesLength]=grade;
        gradesLength=gradesLength+1;
    }

    if (errno)
        perror("grade");

    for (i = 0; i < gradesLength; ++i) {
        printf("%d\n", grades[i]);
    }
}

然后运行它:

$ 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 何时返回的内容有些模糊,并且不重写它,那么这个小的更改可能更可靠:

int i, r;

printf("Enter grades:\n");
while ((r = scanf("%d",&grade)) > 0)

【讨论】:

  • 尝试输入“a”作为成绩之一。代码会严重失败。
  • @Jonathan scanf 是一个可怕的例程,但是,对于它的价值,我的手册页说“返回代码”-“如果在输入结束之前到达,则返回值 EOF第一次成功转换或匹配失败。如果发生读取错误,也会返回 EOF,在这种情况下,设置流的错误指示符 (error(3)),并设置 errno 指示错误。"
  • 是的,scanf SEGV,但这就是为什么一个very很少使用scanf的众多原因之一。但这不是具体的问题。如果按问题输入数据,则输出如图所示。
  • 哪个系统上的哪个手册页?让我们检查一下: POSIX (C) scanf() 指定: _ 如果输入在第一次转换(如果有)完成之前结束,并且没有发生匹配失败,则应返回 EOF。_ 但什么是“匹配失败”?我正在研究规范——但我可以肯定的是,对于需要数字(数字)的字母,scanf() 返回 0,而不是 EOF。
  • 首先,感谢您的帮助。这段代码是我作业的一部分,我被保证输入将是一组整数,所以我不必担心其他任何事情。似乎 t 的代码工作正常,但与您提到的其余代码不同,因此问题出在其他地方。我会试着从头开始找到它。再次感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-03
  • 1970-01-01
  • 1970-01-01
  • 2016-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多