Home > Software design >  Can you use scanf to read either an int or a char from a single position?
Can you use scanf to read either an int or a char from a single position?

Time:01-21

do
{
    printf("Enter a square, 1-9: ");
    scanf("%d",&choice);
    if (choice == 'q')
    {
        exit(0);
    }
}
while((choice < 1 || choice > 9) || (board[choice] == 'C' || board[choice] == 'U'));

This is a small segment of a larger program, but I wanted to ask the user to enter a number 1-9 (and a number that wasn't already chosen, but that isn't very relevant to my question), but I also wanted the program to end if the user entered 'q' Is there a way to this? Also, how would I stop the user from entering characters other than q? Currently if the user enters a character "Enter a square, 1-9:" starts looping infinitely.

CodePudding user response:

Then you should be reading a character (%c) rather than an integer (%d). You don't lose the ability to test for a valid number with > and < comparisons since numeric characters are stored consecutively on all character sets supported by C:

char input;
do
{ 
    printf("Enter a square, 1-9, or press Q to quit: ");
    if(scanf(" %c", &input) == 1) // i.e. 1 item was successfully read
    {
        if('Q' == input || 'q' == input)
        {
            exit(0);
        }
    }
}
while(input < '1' || input > '9');

Note that I prepended the scanf format string with a space (reason) and added a check to its return value.

To convert a character containing a digit back into a numeric type such as int, use:

int val;
if(isdigit(c)) // Omit this if you *KNOW* already that `c` is a digit (i.e. if it was just read from a digit-validating loop such as the one above) 
    val = c -= '0';

CodePudding user response:

With scanf("%d"), if the user enters something besides a number, it will return 0 and not consume any input. So you can do things like:

while(1) {
    int val;
    char ch;
    printf("Enter a number or 'q' to quit: ");
    fflush(stdout);
    switch(scanf("%d", &val)) {
    case 1:
        // got a number in 'val', so do something with it
        break;
    case 0:
        scanf("%c", &ch);
        if (ch == 'q' || ch == 'Q') exit(0);
        fprintf(stderr, "You entered an unexpected character '%c'\n", ch);
        break;
    case EOF:
        fprintf(stderr, "Unexpected end-of-file\n");
        exit(1);
    }
}

This has the drawback that if you enter multiple numbers on one line separated by spaces, it will process them all and print out multiple copies of the prompt before processing the next line. To avoid that it is generally better to use getline or fgets for interactive input (to read whole lines) and then use sscanf to process the line.

CodePudding user response:

You cannot use scanf with the %d conversion format specifier to read a character.

Although it is possible to solve this problem with scanf, I instead recommend that you read an entire line of input as a string with the function fgets. If you determine that the user did not enter "q", then you can attempt to convert that string to an integer, and then check whether this integer is in the range 1 to 9.

Here is an example:

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

//forward declaration
void get_line_from_user( char *buffer, int buffer_size );

int main( void )
{
    char board[9] = {
        'C', ' ', 'U',
        'U', ' ', ' ',
        'C', 'U', ' '
    };

    long choice;

    //repeat until input is valid
    for (;;) //infinite loop, equivalent to while(1)
    {
        char line[200], *p;

        printf( "Enter a square (1-9) or \"q\" to quit: " );
        get_line_from_user( line, sizeof line );

        //determine whether user wants to quit
        if ( strcmp( line, "q" ) == 0 )
        {
            printf( "Quitting program!\n" );
            exit( EXIT_SUCCESS );
        }

        //user did not want to quit, so attempt to convert input to
        //an integer
        choice = strtol( line, &p, 10 );
        if ( p == line )
        {
            printf( "Unable to convert input to an integer!\n" );
            continue;
        }

        //verify that the remainder of the line does not contain any
        //non-whitespace characters, so that input such as "6abc"
        //gets rejected
        for ( ; *p != '\0'; p   )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected character encountered!\n" );

                //we cannot use "continue" here, because this would
                //continue the innermost loop, but we want to continue
                //the outer loop
                goto continue_outer_loop;
            }
        }

        //verify that input is in the desired range
        if ( choice < 1 || choice > 9 )
        {
            printf( "Please enter a number between 1 and 9!\n" );
            continue;
        }

        //verify that square is not already occupied
        if ( board[choice-1] == 'C' || board[choice-1] == 'U' )
        {
            printf( "Board square is already occupied!\n" );
            continue;
        }

        //input is ok, so we can break out of the infinite loop
        break;

    continue_outer_loop:
        continue;
    }

    printf( "Input is valid!\n" );
    printf( "You entered: %ld\n", choice );
}

//This function will read exactly one line of input from the
//user. If the line is too long to fit in the buffer, then the
//function will automatically reprompt the user for input. On
//failure, the function will never return, but will print an
//error message and call "exit" instead.
void get_line_from_user( char *buffer, int buffer_size )
{
    char *p;

    //attempt to read one line of input
    if ( fgets( buffer, buffer_size, stdin ) == NULL )
    {
        printf( "Error reading from input\n" );
        exit( EXIT_FAILURE );
    }

    //attempt to find newline character
    p = strchr( buffer, '\n' );

    //make sure that entire line was read in (i.e. that
    //the buffer was not too small to store the entire line)
    if ( p == NULL )
    {
        //attempt to read one more character
        int c = getchar();

        //a missing newline character is ok if the next
        //character is a newline character or if we have
        //reached end-of-file (for example if the input is
        //being piped from a file or if the user enters
        //end-of-file in the terminal itself)
        if ( c != '\n' && !feof(stdin) )
        {
            printf( "Input was too long to fit in buffer!\n" );

            //discard remainder of line
            while ( c != EOF && c != '\n' )
            {
                c = getchar();
            }
        }
    }
    else
    {
        //remove newline character by overwriting it with
        //null character
        *p = '\0';
    }
}

This program has the following behavior:

Enter a square (1-9) or "q" to quit: test
Unable to convert input to an integer!
Enter a square (1-9) or "q" to quit: 6abc
Unexpected character encountered!
Enter a square (1-9) or "q" to quit: -5
Please enter a number between 1 and 9!
Enter a square (1-9) or "q" to quit: 0
Please enter a number between 1 and 9!
Enter a square (1-9) or "q" to quit: 10
Please enter a number between 1 and 9!
Enter a square (1-9) or "q" to quit: 1
Board square is already occupied!
Enter a square (1-9) or "q" to quit: 4
Board square is already occupied!
Enter a square (1-9) or "q" to quit: 5
Input is valid!
You entered: 5
  •  Tags:  
  • c
  • Related