MiguelD
MiguelD

Reputation: 449

How to handle less input than expected with scanf - C

I need to receive from the user input that can come in 2 forms, char int float or char int, but I don't know which of them will be given. I tried this:

int main(){
    char letter;
    int num;
    float value;
    while(getchar()!=EOF){
        scanf(" %c %d %f", &letter, &num, &value);
        printf("%c\n", letter);
        printf("%d\n", num);
        printf("%f\n", value);
    }
    return 0;
}

The problem with this is when I give an input like this:

? 12345
g 12345 45.6
? 12345

Output given:                      Expected Output:
1                                  ?
2345                               12345
0.000000                           0.000000
1                                  g
2345                               12345
45.599998                          45.599998
?                                  ?
12345                              12345
45.599998                          45.599998

Why is part of the number going in the place of the char and the char is ignored? Is there a way to fix this?

Upvotes: 2

Views: 1679

Answers (3)

There are several problems here:

  1. The missing char. When you do while(getchar()!=EOF) for the first time, you are consuming the first character in the stream, which happens to be '?'. The subsequent scanf() cannot retrieve that anymore, so it grabs the next non-whitespace character, which happens to be the '1' of the following number.

    After that '1' is read, the rest of the number is parsed, providing the result 2345.

    The following conversion tries to parse a float, but the next non-whitespace character is a g, so no float can be read, and the conversion fails.

  2. You fail to check, how many conversions are successful. scanf() dutifully returns the number of successful conversions, but you ignore that.

    If you had checked the return value, you would have found that the first scanf() call returns 2 because the character and the integer conversions finished successfully, but the float conversion failed.

    Likewise, the second scanf() call should have returned 3, signaling that all three conversions succeeded. The last call to scanf() should return 2 again.

  3. The gobbling up of a character in while(getchar()!=EOF) bites you again on the second line, this time it is the 'g' that's removed from the stream before the scanf() gets a chance to read it.

    After the second call to scanf() successfully terminates, the '\n' character is left in the stream. Thus, the following while(getchar()!=EOF) gobbles that up, and the third scanf() call can finally correctly fetch the first character on the line ('?').

  4. The float conversion fails again on the third input line, and the corresponding variable is left untouched, and you fail to detect this condition by ignoring the return value of scanf().

Long story short:

  • Each getchar() call consumes a character that cannot be read by scanf() anymore.

  • Any scanf() call that ignores the return value is a bug. Without checking that, you cannot know that you actually read anything.


Btw: man scanf and man getchar could have told you everything I just said. It pays off to know how to read manpages.

Upvotes: 1

tom
tom

Reputation: 1313

I wonder if it would be simpler to read in a block of data from the keyboard to a string from the keyboard and then analyse with sscanf as in the outline below

#define MAX_INPUT 100

char data_input[MAX_INPUT];
int finished=0, test;

char letter;
int num;
float value;

while (finished==0) {
  // scanf("%s",data_input); //taken out after useful comments below
  fgets(data_input,100,stdin);  //replacement from comments...

  // insert code using sscanf to read in data from user
  test=sscanf(data_input, "%c %d %f", &letter, &num, &value);

  if (test==3) 
  {
    //success - sscanf successfully assigned 3 values... 

    // (ony if you have everything you need then....)
      finished =1; 
  } else if (test==2){
     // insert something to try to test if data from two data entries is ok....

    // (ony if you have everything you need then....)
      finished =1; 
  } else {
    printf("problem with data entry - please type again\n");
  }
}

with apologies to @Tom's because the use of sscanf was first suggested by him...

Upvotes: 1

Tom's
Tom's

Reputation: 2506

In this case, it's best to first retrieve the user input as char* (fgets-like) and then parse it with sscanf (not scanf, sscanf). You will have to check the return value of sscanf in order to know wether the input user is correctly formatted.

If you need to check if there is not garbage value after the parsing (like "g 12345 45.6 garbagevalue"), you can add a %c after and check if the value have changed.

char   letter;
int    integerNumber;
double floatingNumber;
char   end;
char *userInput = ...;

end = '\0';
if (sscanf(userInput, "%c %d %f%c", &letter, &integerNumber, &floatingNumber, &end) == 3 && end == '\0') {
    // First form
} else {
    end = '\0';
    if (sscanf(userInput, "%c %d%c", &letter, &integerNumber, &end) == 2 && end == '\0') {
        // Second form
    } else {
        // Error : Input not strictly "%c %d %f" or "%c %d"
    }
}

Sound good to you ?

Upvotes: 1

Related Questions