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