1

I am implementing a super simple menu selection procedure with a basic input validity check mechanism. The legal inputs are {1,2,3} so the possible issues would be either a number that is out of this range, or a non integer. My code is shown below. This works fine for the former issue (i.e. when i input "4") but for the latter (when i try to input a char), it prints the invalidity message over and over again rather than waiting for a new input, it's like it skips the scanf line entirely on every iteration except the first.. what am I missing here ?

do{
      try = scanf("%d", &selection);
      if(try!=1 || selection < 1 || selection > 3){
           printf("\nInvalid input. Dear guest, please enter '1', '2', or '3'.\n\nInput:");
      }
 }while(try!=1 || selection < 1 || selection > 3);
Ozz
  • 79
  • 6
  • 2
    When the scanf fails (try != 1), you must read the pending input away before trying again. scanf tries to read a a number, fails with the first non-numeric char, *and puts that char back in the input stream,* where it will be encountered again by the next iteration. Upvote for checking scanf's return value though :-). – Peter - Reinstate Monica Aug 22 '20 at 06:50
  • Are you sure you can use `try` as the name of a variable? (It's a part of the `try ... catch` expression) I'd advise you to give it another name. – Dominique Aug 22 '20 at 06:50
  • @Dominique C, not C++. But probably a bad idea anyway because one may want to migrate to C++ later. – Peter - Reinstate Monica Aug 22 '20 at 06:51
  • @Peter-ReinstateMonica: it was with a migration to C++ in mind that I wrote my comment. – Dominique Aug 22 '20 at 06:52
  • QMan: about that migration: you are currently writing a program in C programming language. A common thing to do (maybe later for you) is to migrate C programs into C++ programs (the C syntax is more or less a subset of the C++ syntax, but C++ is object oriented). If you would try to do that migration (which you typically start doing by compiling your C code by a C++ compiler), the name of that variable might cause problems, hence my warning. – Dominique Aug 22 '20 at 06:55
  • Like what Peter said, you need to clear your input stream. `while ((getchar()) != '\n');` can be used to clear the input stream before the `scanf()` – Alaiko Aug 22 '20 at 07:24
  • Specifically, "read then parse", because your current code, once failing with `!=1` will continue failing. – Yunnosch Aug 22 '20 at 07:32
  • 1
    Move away from `scanf`. It is not really suitable for interactive input. Users type entire lines so you want to be geared for that. Call `fgets` and then parse the line (with `sscanf`, `strtol` or anything else). You may also want to try GNU readline or a similar library for `fgets` replacement. – n. m. could be an AI Aug 22 '20 at 08:06
  • @n.'pronouns'm. Well, there's nothing wrong with a user saying "1 2 3" for a sequence of selections if they know the menu and want to save enter key strokes. – Peter - Reinstate Monica Aug 22 '20 at 09:57
  • @Peter-ReinstateMonica last time I checked, `enter` and `space` had the same keystroke count of 1. – n. m. could be an AI Aug 22 '20 at 10:30
  • @n.'pronouns'm Sure, but space may be easier to hit or easier while holding a cup of coffee, or the enter key is broken or unknown to the user. My point was about the equally valid input formats. – Peter - Reinstate Monica Aug 22 '20 at 11:27
  • Hi all, I am indeed planning to move on to C++ so will be sure to avoid the use of try as a variable name. Thanks for the tip. – Ozz Aug 24 '20 at 04:17

1 Answers1

0

After entering a character which cannot be part of the text representation of a decimal number, say 'A', the input available to the program is the byte sequence 'A' '\n' (the latter being a newline), or perhaps 'A' '\r' '\n' on Windows (carriage return followed by a newline).

When scanf tries to parse a number from these characters it balks already at the 'A' and puts it back in the input stream. Input streams in C guarantee that you can perform at least one ungetc(), that is, put at least one character back into the stream so that it will be the first character read by the next input operation. This simple but ingenious facility makes it a lot easier to process variable-format input: Imagine you parse an expression in some C source code, and both integer literal or a variable name are syntactically allowed as the next token: You can try the number first, and if that fails, the input still contains all of the variable name to process. The work of keeping the first failing character "in mind" and making it available to other parts of the program is encapsulated in the FILE implementation.

This is what happens here. The first failing scanf() puts the 'A' back so that it will be encountered again by the next attempt, ad infinitum. The 'A' must be removed from the input. More specifically, the next "word" should be removed from the input altogether: The user may have entered "kkjkllkjlk", and you don't want 10 error messages for that. You can decide whether you would accept "lklkj2" (and read the 2) but it is simpler to discard the whole word.

You can also decide whether you would accept "1 2 3 2" as 4 valid successive inputs or whether you demand newlines between the numbers; for generality (for example, if the input does not come from a terminal) I would accept all number sequences separated by whitespace of any kind. In this case you simply want to read to the next whitespace:

#include <ctype.h>
// ...
while(!isspace(getchar())) { /* ignore */ }

This should do the trick. It is possible that more whitespace is following this one, including newlines etc., but that's OK: The symbolic input conversions of scanf (like %d) skip leading whitespace.

I think it is neat to let the user exit the program by ending the input (inserting end-of-file by pressing Ctrl-z in a Windows Console, or Ctrl-d on a Posix terminal), so I would test for the special case of scanf() returning EOF. If the input is from a pipe that may actually be essential. The fringe case that a bad input is immediately followed by EOF needs a check for EOF even in the code discarding wrong input (Posix: echo -n "a" | myprog would hang; -n suppresses the usual newline which echo usually appends). Putting it all together, my take on the input loop is this:

while(1) { // break on good input
      printf("Please enter your choice of 1, 2 or 3:\n");
      try = scanf("%d", &selection);
      if(try!=1 || selection < 1 || selection > 3){
          if(try == EOF)
          {
              return 0;
          }
           printf("\nThe input was not 1,2 or 3. Please try again.\n");
           int discard;
           {
               do{
                   discard = getchar();
                   if(discard == EOF) { return 0;} // catches EOF after bad char
               }while(!isspace(discard));
           }
      }
      else break;
}
Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62