2

C beginner here. For the program below, whenever the user inputs a character or a string it enters an infinite loop. How would you fix this while still using scanf? And what would be the better methods of writing this program rather than using scanf? Thanks to those who will answer.

#include <stdio.h>
#include <ctype.h>

int main() {

int rounds = 5;

do {
printf("Preferred number of rounds per game. ENTER NUMBERS ONLY: ");
scanf("%d", &rounds);   
} while(isdigit(rounds) == 0);

return 0;   
}
jason96
  • 57
  • 4
  • 1
    Always check the return value for `scanf()` to see if there were errors, for starters. – Shawn Oct 17 '19 at 14:00
  • Also, `isdigit()` is not appropriate here unless you're expecting people to enter the ascii values of digit characters. – Shawn Oct 17 '19 at 14:06
  • http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html – Eugene Sh. Oct 17 '19 at 14:12
  • Don't use `scanf()`. The `f` stands for "formatted". User input is not formatted. – Andrew Henle Oct 17 '19 at 14:13
  • 1
    @AndrewHenle Agree that user input is not formatted, but can't agree that `scanf` is. If `scanf` were truly designed for formatted input, a format string like `%d %d\n%d %d` would *fail* to read `1 2 3 4`, or `1\n2\n3\n4`. – Steve Summit Oct 17 '19 at 15:05
  • @SteveSummit That's more of a comment on the original `scanf()` implementation *quality* (that wound up standardized anyway...), not its [historical purpose](https://www.cs.dartmouth.edu/~doug/reader.pdf). ;-) Heck, you could say the fact that `scanf()` is so perverse in its parsing is just another reason to never use it. – Andrew Henle Oct 17 '19 at 15:20
  • @AndrewHenle Thanks for that link! I can't find anything in that paper to shed any light on `scanf`'s original "purpose", but it's a lovely trip down memory lane anyway, that I hadn't ever encountered. (And I certainly *do* say that `scanf`'s perverse parsing is another reason to ignore it! :-) ) – Steve Summit Oct 17 '19 at 15:43

5 Answers5

1

Using 'scanf' require the input to be formatted. Scanf has very limited ability to handle bad input. The common solution will be to use fgets/sscanf, following the structure below:

   char buff[256] ;
   int rounds = 0 ;
   while ( fgets(buff, sizeof(buff), stdin) ) {
      if ( sscanf(buff, "%d", &rounds) == 1 ) {
          // additional verification here
          break ;
      } ;
   } ;
   // Use rounds here ...

The fgets/sscanf will allow recovery from parsing error - the bad input line will be ignored. Depending on requirement, this might be accepted solution.

dash-o
  • 11,968
  • 1
  • 4
  • 29
0

Change

scanf("%d", &rounds);

To

int ret;

if ((ret = scanf(" %d", &rounds)) != 1) { // Skip over white space as well
   if (ret == EOF) break;
   scanf("%*[^\n\r]"); // Consume the rest of the line
}
Ed Heal
  • 55,822
  • 16
  • 77
  • 115
0

If you really like scanf, you can use getch to discard non-numeric input:

int rounds =  MIN_INT;
while (scanf("%d", &rounds)) != 1)
   if (getc() == EOF) /* discard a rubbish character */
      break; // or other error-handling return

// rounds is only valid if we did not break, when its value should be MIN_INT.
// but you might need another indicator
Gem Taylor
  • 4,767
  • 1
  • 5
  • 25
0

I'd say there are just two "fixes".

  1. Retain the scanf call(s), warts and all. Carefully refrain from typing non-digits when scanf is expecting digits.

  2. Abandon scanf and use something else. We've just been discussing this tactic over at this new question.

Once you're using scanf, it's always tempting to try to "fix" it, so there's potentially a third answer lurking here, explaining how to do better, more user-friendly, more error-tolerant input while still using scanf. In my opinion, however, this is a fool's errand, a waste of time. The easy, obvious fixes for scanf's many deficiencies are themselves imperfect and have further deficiencies. You will probably spend more time trying to fix a scanf-using program than you would have spent rewriting it to not use scanf -- and you'll get overall better results (not to mention a cleaner program) with the non-scanf-using rewrite anyway.

Steve Summit
  • 29,350
  • 5
  • 43
  • 68
0

C beginner here as well. Like you, I use scanf, and it can be problematic sometimes.

I've had your same problem and tried to solve it with scanf and basic stuff before finding a better solution. I've tried different solution from here but I continue to have the same problems again and again, like if I type:

  • a number followed by a character (e.g. 123a), the result is a valid number (which i don't want); the result is '123'.
  • a string of numbers and chars that begin with a number (e.g. 1a2b3), the result is still a valid number which is '1'.
  • a char at the beginning (e.g. a123) can generate infinite loop.

... and so on... I've tried do...while, only while, for... nothing.

The only solution I have found to prompt the user until he/she writes only numbers is the following, but...

NOTE: if the user type a space, the program considers only the part before it, e.g. '12 3', only 12 is considered, 3 doesn't exist... unless you want use an infinite loop like I did so, in this case, you can enter multiple numbers, check them and run your program on them all at once. e.g.: '12 23 34 45' ...

NOTE 2: this is a very basic beginner solution, I am learning, and this is just what I found with what I know. Can't do any better right now and, as I said, I didn't find any other solution that I liked the output.

NOTE 3: I use the counter to sum up all the inputs that are not numbers and store the value if it finds one. If I don't use this solution I'll end up in the case where if the first character is a number but the rest aren't, it's still valid (e.g.: '12w3' is 12, which I don't want)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int main (void)
{
  while (1) // try also multiple inputs separated by space
  {
    char str[10]; // should be enough for short strings/numbers (?!)
    int strlength, num, counter;

    do
    {
      printf("Enter a number: ");
      scanf("%s", str);

      strlength = strlen(str);
      counter = 0;
      for (int i = 0; i < strlength; i++)
      {
        if (!isdigit(str[i]))
          counter++; 
      }
      if (counter != 0)
        printf("%s is not a number.\n", str);
    } while (counter != 0);

    num = atoi(str);

    printf("%d is a number. Well done!\n\n", num);
  }
}

You can also put it in a function and away from the main().