While scanf EOF loop misbehaving

2019-09-03 16:16发布

问题:

I'm a beginner in C, and I've got problem I can't figure out, and wasn't able to find a solution on other threads here.

I'm trying to read integers from a keyboard input/ txt file with the following code:

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;
}

I'm supposed to set these "grades" in the grades[] array and count the length of the array along the loop.

Somehow the loop is misbehaving, it seems that some inputs are ok with the loop, but for some inputs(most of them actually) the scanf() doesn't get the EOF, whether it's an actual end of file, or the ^D command in the terminal.

I've heard the scanf() is not the most reliable method to read data, but unfortunately I can't use anything else since we haven't learned any other method in class so we can only use scanf() on our homework.

I've tried to change the!= EOF with == 1 and its all the same.

for the input

100 0 90 10 80 20 70 30 60 40 50

for example it works fine. but for the input:

0 50 100

the loop is infinite.

I'm using a macbook pro btw (if it matters).

Any ideas?

回答1:

If you type a letter instead of a number, scanf() will return 0 (as in, "zero successfully converted numbers") and not EOF (as in, "there was no data left to read"). The correct test is to ensure that you got the expected number of values — in this case, 1:

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

If you need to know whether you got to EOF or got no result (but reading the rest of the line might clear the problem), then capture the return value from 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…

And, if you really want to, you could then write:

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;
    }
}

The code in the else clause could plausibly be put into a function. It might also report the messages to standard error rather than standard output. It is courteous to let the user know what it was that you objected to. You could stop before newline with a different test (&& !isdigit(c) && c != '+' && c != '-', using isdigit() from <ctypes.h>). However, the user doesn't have a chance to re-edit the stuff they put after the letters, so you may be going to misinterpret their input. It is probably better just to throw away the rest of the line of input and let them start over again.


As chux noted, after reading a character that could be part of an integer, that character needs to be put back into the input stream. Therefore, if I were going to analyze the rest of the line and restart scanning at the first data that could actually be part of an integer, I'd consider using something like:

#include <ctype.h>

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

and then the else clause might be:

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;
}

This gets messy, as you can see. Sometimes (as Weather Vane noted in a comment), it is easier to read a line with fgets() and then use sscanf() in a loop (see How to use sscanf() in a loop?). Be wary of suggestions about Using fflush(stdin); it isn't automatically wrong everywhere, but it won't work on a MacBook Pro under normal circumstances.

On the whole, simply ignoring the rest of the line of input is usually a better interface decision.



回答2:

It works for me.

I enclosed your snippet thus:

#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]);
    }
}

and ran it:

$ 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
$

Perhaps you are looking in the wrong place. Maybe the bug is in your output routine?

Personally, if had to do this, given some ambiquity over what scanf returns when, and without rewriting it, then this small change is probably more reliable:

int i, r;

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