One way to determine whether the user entered a character or a number is to call scanf
with the %d
conversion format specifier, and check the return value of scanf
. If it returns 1
, then the conversion format specifier was successfully matched. Otherwise, you print an error message and prompt the user again to enter input. For example:
#include <stdio.h>
int main( void )
{
int i;
//infinite loop, equivalent to while(1)
//repeat the loop until the input is valid
for (;;)
{
//prompt user for input
printf( "Please enter a number: " );
//attempt to read and convert user input
if ( scanf( "%d", &i ) == 1 )
{
//input was valid
break;
}
//print error message
printf( "Input was invalid, please try again!\n" );
//discard remainder of line
for ( int c; c = getchar(), c != '\n' && c != EOF; )
;
}
printf( "Input was successfully converted to the number %d.", i );
}
This is the output of the program:
Please enter a number: sjdfk
Input was invalid, please try again!
Please enter a number: erlh89
Input was invalid, please try again!
Please enter a number: 34
Input was successfully converted to the number 34.
However, this code has one problem: It will accept input such as "6sdfj23jlj"
as valid input for the number 6
:
Please enter a number: 6sdfj23jlj
Input was successfully converted to the number 6.
You would probably want to reject the input instead, in this case.
The function scanf
will do this, because it is not line-based; it only processes as much input as it can to match the %d
conversion format specifier.
One thing you could do to detect such invalid input would be to look at the remainder of the line, and verify that it is empty, apart from the newline character:
#include <stdio.h>
#include <stdbool.h>
int main( void )
{
bool successfully_matched = false;
bool found_newline = false;
int i;
//infinite loop, equivalent to while(1)
//repeat the loop until the input is valid
for (;;)
{
//prompt user for input
printf( "Please enter a number: " );
//attempt to read and convert user input
if ( scanf( "%d", &i ) == 1 )
successfully_matched = true;
//verify that remainder of line is empty
if ( getchar() == '\n' )
found_newline = true;
//break loop if everything was ok
if ( successfully_matched && found_newline )
break;
//print error message
printf( "Input was invalid, please try again!\n" );
//discard remainder of line, if necessary
if ( !found_newline )
{
for ( int c; c = getchar(), c != '\n' && c != EOF; )
;
}
}
printf( "Input was successfully converted to the number %d.", i );
}
Now, the program correctly rejects 6sdfj23jlj
as invalid input:
Please enter a number: 6sdfj23jlj
Input was invalid, please try again!
Please enter a number:
However, this code will reject input such as 21
(note the space character after the number). I'm not sure if you want to reject input simply because of a trailing space. If you want to allow spaces and other whitespace characters, then this is possible too, but would require a bit more coding.
In your question, you stated that even if the input is a valid number, you want to additionally check whether the number is in a certain range. This can be accomplished too. However, since the code is starting to get complicated, it seems better to create a new function get_int_from_user
to actually get a number from the user. After calling that function, we can then perform the range check, and if the number is not in the desired range, we print an error message and then call the function again.
#include <stdio.h>
#include <stdbool.h>
int get_int_from_user( const char *prompt )
{
bool successfully_matched = false;
bool found_newline = false;
int i;
//infinite loop, equivalent to while(1)
//repeat the loop until the input is valid
for (;;)
{
//prompt user for input
printf( "%s", prompt );
//attempt to read and convert user input
if ( scanf( "%d", &i ) == 1 )
successfully_matched = true;
//verify that remainder of line is empty
if ( getchar() == '\n' )
found_newline = true;
//break loop if everything was ok
if ( successfully_matched && found_newline )
break;
//print error message
printf( "Input was invalid, please try again!\n" );
//discard remainder of line, if necessary
if ( !found_newline )
{
for ( int c; c = getchar(), c != '\n' && c != EOF; )
;
}
}
return i;
}
int main( void )
{
int i;
//repeat until input is in the desired range
for (;;)
{
//read number from user
i = get_int_from_user( "Please enter a number: " );
//perform range check on number
if ( 1 <= i && i <= 30 )
{
//input is in the desired range
break;
}
//print error message
printf( "Number is not in the desired range, please try again!\n" );
}
printf( "The number %d is in the desired range.", i );
}
This program has the following output:
Please enter a number: 342
Number is not in the desired range, please try again!
Please enter a number: 27
The number 27 is in the desired range.
However, I generally do not recommend using scanf
for line-based user input. As previously stated, the function scanf
does not read one line of input at a time. This means that is can leave leftovers of the line on the input stream, which can be confusing to the programmer, and can lead to bugs, such as this one. See this question for more information on the disadvantages of using scanf
.
For line-based user input, it is generally better to use the function fgets
. Here is a very robust implementation of the function get_int_from_user
which I copied from this previous answer of mine to another question. This function uses fgets
and strtol
instead of scanf
, and performs extensive input validation and error checking:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
int get_int_from_user( const char *prompt )
{
for (;;) //loop forever until user enters a valid number
{
char buffer[1024], *p;
long l;
fputs( prompt, stdout );
//get one line of input from input stream
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
fprintf( stderr, "unrecoverable error reading from input\n" );
exit( EXIT_FAILURE );
}
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
{
int c;
printf( "line input was too long!\n" );
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "unrecoverable error reading from input\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
//attempt to convert string to number
errno = 0;
l = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "error converting string to number\n" );
continue;
}
//make sure that number is representable as an "int"
if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
{
printf( "number out of range error\n" );
continue;
}
//make sure that remainder of line contains only whitespace,
//so that input such as "6sdfj23jlj" gets rejected
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "unexpected input encountered!\n" );
//cannot use `continue` here, because that would go to
//the next iteration of the innermost loop, but we
//want to go to the next iteration of the outer loop
goto continue_outer_loop;
}
}
return l;
continue_outer_loop:
continue;
}
}