简体   繁体   中英

C - Trying to scanf hex/dec/oct values to check if they're equal to user input

So, I'm new to C and this is my ever first project for class. I basically need a program that asks the user how many questions he wants, then retrieves a positive max 8 bit number which can be in oct/dec/hex (it's random) and then asks the user to convert it to to a random base. For example, If I get a decimal number It will randomly ask me to convert it to hex or octal. At the end of every question, it says if my convertion is right or wrong and in the end of the program it shows how many questions I got right.

All works well until I start typing random letters/characters when it asks me to convert to other than hex. If it asks me to, for example, convert octal to decimal, if I enter a single letter it will sometimes say It's right and it also skips questions and continues the cycle until it gets an hex.

I can't really figure out what can I do. Here's my code:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main()
{
    int rightanswers = 0;
    int answer;
    int nquestions;

    printf("Number of questions:");
    scanf("%d", &nquestions);

    srand((unsigned int) time(NULL));
    unsigned char questions[nquestions];

    for (int i=1; i<=nquestions; i++)
    {
        questions[i] = (rand()%255)+1;
        int randomnumb = (rand()%6)+1;
        switch(randomnumb)
        {
            case 1:
                printf("\nConvert 0%o to base 10:", questions[i]);
                scanf("%d", &answer);
                if (answer == questions[i])
                {
                    rightanswers++;
                    printf("Right!");                                       
                }
                else
                {
                    printf("Wrong!");                       
                }
                break;
            case 2:
                printf("\nConvert 0%o to base 16:", questions[i]);
                scanf("%x", &answer);
                if (answer == questions[i])
                {
                    rightanswers++;
                    printf("Right!");                                       
                }
                else
                {
                    printf("Wrong!");                       
                }
                break;
            case 3:
                printf("\nConvert %d to base 8:", questions[i]);
                scanf("%o", &answer);
                if (answer == questions[i])
                {
                    rightanswers++;
                    printf("Right!");                                       
                }
                else
                {
                    printf("Wrong!");                       
                }
                break;
            case 4:
                printf("\nConvert %d to base 16:", questions[i]);
                scanf("%x", &answer);
                if (answer == questions[i])
                {
                    rightanswers++;
                    printf("Right!");                                       
                }
                else
                {
                    printf("Wrong!");                       
                }
                break;
            case 5:
                printf("\nConvert 0x%x to base 8:", questions[i]);
                scanf("%o", &answer);
                if (answer == questions[i])
                {
                    rightanswers++;
                    printf("Right!");                                       
                }
                else
                {
                    printf("Wrong!");                       
                }
                break;
            case 6:
                printf("\nConvert 0x%x to base 10:", questions[i]);
                scanf("%d", &answer);
                if (answer == questions[i])
                {
                    rightanswers++;
                    printf("Right!");                                       
                }
                else
                {
                    printf("Wrong!");                       
                }
                break;  
        }
    }
    printf("\nYou got %d conversions right!", rightanswers);
    return 0;
}

Continuing from my comments. scanf (and family) give new C programmers no end of trouble with user input because of the numerous pitfalls associated with scanf . Largely because the conversion specifiers behave differently regarding handling of leading whitepace (eg space , tab , newline , etc..) and the programmers failure to validate the return . The return validation is critical because what is left in the input buffer (eg stdin ) depends on whether a successful conversion takes place.

scanf returns the number of successful conversion. (eg scanf ("%s %d", strvar, &intvar) ) contains 2 conversion specifiers ( %s & %d ). If a string and integer are successfully converted and stored in the variables provided, the return is 2 . Anything less indicates either a matching failure or input failure or the user canceled input by manually generating an EOF with Ctrl+D (or Ctrl+Z on windoze).

If a conversion fails, either due to a matching failure (mismatch between input type and conversion specifier) or an input failure (there wasn't enough input for each conversion specifier), reading from stdin stops, no further characters are read from the input buffer and all characters are left -- just waiting to torpedo your next call to scanf .

Further, you must account for the '\\n' (generated by the user pressing Enter ) that is left in the input buffer after each call to scanf . Some format specifiers will consume leading whitespace while others (such as character format specifiers will not), in fact %c will happily take the '\\n' left in stdin as your next input .

Specifically, the numeric format specifiers (eg %d, %x, %o, %lf, ... ) will all ignore leading whitespace, so you do not have to specifically remove whitespace before your next scanf call. For all others you do. This accounting for what remains (or could remain) in stdin is critical for the use of scanf for input. Otherwise you are just asking for input appearing to be skipped or... an infinite loop. (you can handle whitespace in the way you craft your format string )

All of which is why a line oriented input function like fgets is the recommended way to handle user input. It will read up to and including the trailing '\\n' which provides a simple check for whether all input characters provided by the user where properly read. (after the user input is validated, you then parse whatever you need from the buffer filled by fgets (or POSIX getline )

But since you will encounter scanf many times, it is well worth the time it takes to read (and understand) man scanf . Yes it's a bit dry reading, but it is the only thing that will explain exactly where the scanf pitfalls are.

With that in mind, the following provides two examples (modifications to your code) to show you how to approach handling input with scanf . The first reading of nquestions just shows a general approach to validating integer input with scanf , checking the return, handling the user cancellation (which generates an EOF ), and finally emptying any characters that remain with a helper function empty_stdin() that simply reads from stdin until a '\\n' (generated by the user pressing Enter ) or EOF is found.

The remainder of the user input is handled by the helper function getintvalue (since you don't want to duplicate the validation code over-and-over again in your code). getintvalue takes the prompt to display and the format string as arguments, but essentially just does the same thing that was done for nquestions in a function.

Other changes. You don't need an array for questions[] . A simple int value will do. You don't need to duplicate code in each case of the switch . (that has been moved to the end). The remaining changes and issues are addressed in the comments in-line, below:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define PRMTSZ 128  /* if you need a constant define one */

/* or use an enum to define several */
enum { NSWITCH = 6, QMAX = 256 };

/* function to get integer value from user.
 * prompt user with 'prompt', read value with format 'fmt'.
 * returns int value or EOF on user cancelation of input.
 */
int getintvalue (const char *prompt, const char *fmt);

/* simple function to empty remaining chars from stdin */
void empty_stdin();

int main (void) {

    int rightanswers = 0,
        nquestions = 0;

    srand (time(NULL));

    /* example input loop -- loop continually until valid input or EOF */
    for (;;) {
        int rtn = 0;

        printf ("Number of questions: ");
        if ((rtn = scanf ("%d", &nquestions)) == EOF) {
            fprintf (stderr, "warning: user canceled input.\n");
            return 1;
        }
        empty_stdin();  /* remove all remaining chars from stdin */

        if (rtn == 1)   /* good input */
            break;

        /* handle matching or input failure */
        fprintf (stderr, "error: invalid input.\n");
    }

    /* loops are ZERO based in C */
    for (int i = 0; i < nquestions; i++)
    {
        /* declarations 1st in each block, for C89 portability - Win7, etc. */
        char prompt[PRMTSZ] = "";               /* buffer for prompt */
        int randomnumb = rand() % NSWITCH,      /* values 0 - 5 */
            question = rand() % QMAX,           /* values 0 - 255 */
            answer = 0;

        switch (randomnumb)
        {
            case 0:
                sprintf (prompt, "\nConvert 0%o to base 10: ", question);
                /* let's use a getintvalue to validate user int input */
                if ((answer = getintvalue (prompt, "%d")) == EOF)
                    return 1;
                break;
            case 1:
                sprintf (prompt, "\nConvert 0%o to base 16: ", question);
                if ((answer = getintvalue (prompt, "%x")) == EOF)
                    return 1;
                break;
            case 2:
                sprintf (prompt, "\nConvert %d to base 8: ", question);
                if ((answer = getintvalue (prompt, "%o")) == EOF)
                    return 1;
                break;
            case 3:
                sprintf (prompt, "\nConvert %d to base 16: ", question);
                if ((answer = getintvalue (prompt, "%x")) == EOF)
                    return 1;
                break;
            case 4:
                sprintf (prompt, "\nConvert 0x%x to base 8: ", question);
                if ((answer = getintvalue (prompt, "%o")) == EOF)
                    return 1;
                break;
            case 5:
                sprintf (prompt, "\nConvert 0x%x to base 10: ", question);
                if ((answer = getintvalue (prompt, "%d")) == EOF)
                    return 1;
                break;
            default:
                fprintf (stderr, "error: something went wrong in switch.\n");
                goto badswitch;
                break;
        }
        if (answer == question) {
            rightanswers++;
            printf ("Right!\n");                                       
        }
        else
            printf ("Wrong!\n");

        badswitch:;
    }

    /* always end with '\n' for POSIX compiant EOF */
    printf("\nYou got %d conversions right!\n", rightanswers);

    return 0;
}

int getintvalue (const char *prompt, const char *fmt)
{
    int value = 0;

    /* input loop -- loop continually until valid input or EOF */
    for (;;) {
        int rtn = 0;

        printf ("%s: ", prompt);
        if ((rtn = scanf (fmt, &value)) == EOF) {
            fprintf (stderr, "warning: user canceled input.\n");
            return rtn;
        }
        empty_stdin();  /* remove all remaining chars from stdin */

        if (rtn == 1)   /* good input */
            break;

        /* handle matching or input failure */
        fprintf (stderr, "error: invalid input.\n");
    }

    return value;
}

void empty_stdin()
{
    int c;

    do
        c = getchar();
    while (c != '\n' && c != EOF);
}

The following are the various validation runs showing proper handling of bad input or user cancellation at various stages of the program.

Example When all Goes Right

$ ./bin/inttestscanf
Number of questions: 4

Convert 218 to base 16: : da
Right!

Convert 0325 to base 10: : 213
Right!

Convert 0xe to base 10: : 14
Right!

Convert 0x39 to base 8: : 71
Right!

You got 4 conversions right!

Example When User Cancels on Number of Questions

$ ./bin/inttestscanf
Number of questions: foo
error: invalid input.
Number of questions: bar
error: invalid input.
Number of questions: warning: user canceled input.

Example When User Enters Invalid Input

$ ./bin/inttestscanf
Number of questions: 4

Convert 023 to base 16: : no good
error: invalid input.

Convert 023 to base 16: : 13
Right!

Convert 0xc7 to base 8: : foo
error: invalid input.

Convert 0xc7 to base 8: : 307
Right!

Convert 0353 to base 16: : eb
Right!

Convert 0x76 to base 10: : f8
error: invalid input.

Convert 0x76 to base 10: : 118
Right!

You got 4 conversions right!

( note: think about what happens if scanf expects a hex value and the user enters an arbitrary string starting with af ?)

Look things over and let me know if you have further questions.

If scanf encounters something it cannot assign, it stops and returns. The variable you are trying to assign will not be changed - but you check its value as if it was read. It will simply contain whatever was in it from before, or random stuff.

To use scanf, you need to always check its return value , it tells you how many variables were assigned. Only if this count shows your variable was really assigned should you access its content.

Note that after a failed scanf the 'unreadable' data will still be in the input stream, and the next scanf will try to read the same data again.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM