Two things to remember about scanf and the %d conversion specifier:
%d tells scanf to skip over any leading whitespace, then read any decimal digit characters up to the first non-decimal digit character;
scanf returns the number of items successfully converted and assigned.
So, here's how scanf( "%d", &input ) will behave based on different input:
Input text Value saved to input Return value
---------- -------------------- ------------
" abc" n/a 0
" 12c" 12 1
" 1b3" 1 1
" 123" 123 1
In all cases, the %d conversion specifier tells scanf to skip over leading whitespace.
If the first non-whitespace character entered is a non-digit character, scanf will stop reading at that point, input will not be updated, and it will return 0.
If the first non-whitespace character is a decimal digit, scanf will read up to the first non-decimal digit character, then convert and assign the resulting digit string to input. Unfortunately, this won't help you catch cases like "12c" or "1b3" - as long as there's at least one decimal digit in the string, scanf( "%d", ... ) will treat it as a success and return 1.
So, if you want to validate that your user has input a valid decimal integer string, you have to do two things:
- You have to check the return value of
scanf;
- You have to check the first character following the end of the input to see whether or not it's whitespace.
There are a couple of ways to accomplish the second part. You can read the character immediately following what should be the decimal integer input - if it's anything other than whitespace, then the input isn't valid:
char dummy;
int itemsRead = scanf( "%d%c", &input, &dummy );
if ( itemsRead == 0 || itemsRead == 2 && !isspace( dummy ) )
{
fprintf( stderr, "Input is not valid, try again!\n" );
while ( getchar() != '\n' )
; // empty loop
}
If itemsRead is 0, then the first input character was not a decimal digit character, and the input is not good.
If itemsRead is 2 and dummy is not a whitespace character, then we have a situation like "12c" or "1b3" and the input is not good.
If the input is not good, then write an error message and scrub the input stream of any remaining characters up to the next newline character using getchar.
If itemsRead is 1, then we saw EOF after nothing but decimal digit characters, so the input is good and there's nothing else to do. If itemsRead is 2 and dummy is whitespace, then the input is good and there's nothing else to do (I'm assuming you don't need to push whitespace back onto the input stream).
Alternately, you can read your input as text and use strtol to convert it to an integer. strtol will also set a pointer to point to the first character not converted:
#define MAX_INPUT_SIZE 11 // A 32-bit integer takes up to 10 decimal digits,
// plus a possible sign character.
char buffer[MAX_INPUT_SIZE + 1]; // +1 for string terminator
if ( fgets( buffer, sizeof buffer, stdin ) )
{
char *chk;
int tmp = (int) strtol( buffer, &chk, 10 ); // convert decimal digit string
// to an integer value
if ( isspace( *chk ) || *chk == 0 )
{
input = tmp;
}
else
{
fprintf( stderr, "%s is not a valid input, try again\n", buffer );
}
}
else
{
// error on input
}
You'll notice I assigned the result of strtol to a temporary - it's a good idea to not update your target until after you've verified the input is good.