0

I am trying to read a numeric only value that will then execute a specific function, depending on its value using the switch statement.
I know I could just use while(option < 0 || option >3 || option != 99), but is there a workaround?

Personal work:

do
{
  printf("Please choose an option :\n0. Create Database\n1.Add New Student \n2. Show All Students \n3.Delete Student\n99.Exit\n");
  scanf("%d", &option);
} while (!isdigit(option));

Which does not work for some reason.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
Alexandros Voliotis
  • 321
  • 1
  • 3
  • 14
  • 1
    "option < 0 || option >3 || option != 99" will always be true; Remove "option != 99" then it should work: "while(option < 0 || option >3)" – Abdurrahim May 14 '20 at 01:23
  • Does this answer your question? [How to restrict the user to only input whole number and refuse any other characters in scanf?](https://stackoverflow.com/questions/8194521/how-to-restrict-the-user-to-only-input-whole-number-and-refuse-any-other-charact) – phuclv May 14 '20 at 01:28
  • Alexander Voliotis, What should happen to input that does not meet the criteria? Error message, ignore, exit the code? – chux - Reinstate Monica May 14 '20 at 01:38
  • @Abdurrahim although you also have to flush invalid input – M.M May 14 '20 at 01:43
  • You are treating integers like characters. I clearly see why that wouldn't work. Perhaps you could check the return value of scanf? –  May 14 '20 at 02:13

3 Answers3

1

Your major problem there is that, if scanf fails to read any digits for some reason (such as if you enter x), it will return zero (the number of items successfully scanned), not populate option, and (this is the killer) leave the input pointer in the same place as before you started.

That unfortunately means, when you go back to get another number, it will just attempt reread the problematic data and probably end up in an infinite loop without allowing more input.

The scanf family is meant for formatted data and there's precious little that's more unformatted than user input :-)

Your best bet would be to use a rock-solid input function to get in a line, then just check that line to see if it's valid. Such a function can be found here. Incorporating that into your needs would give you something like:

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

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2

static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

#include <ctype.h>  // only needed for isdigit, not for above code.

int main(void) {
    int option;

    do {
        // Only allowed up to two characters (plus '\0').

        char buff[3];
        int result = getLine(
           "Menu\n"
           "   0. Create Database\n"
           "   1. Add New Student\n"
           "   2. Show All Students\n"
           "   3. Delete Student\n"
           "  99. Exit\n"
           "Please choose option: ",
           buff, sizeof(buff));

        // No point continuing on EOF, input stream is closed.

        if (result == NO_INPUT) {
            puts("\n*** End of input");
            return 1;
        }

        // All faults just restart operation: too long, too short,
        // non-digits.

        if (result == TOO_LONG) continue;
        if (! isdigit(buff[0])) continue;
        if (buff[1] != '\0' && ! isdigit(buff[1])) continue;

        // Now get the integer representation and continue unless valid.

        sscanf(buff, "%d", &option);
    } while (option != 99 && (option < 0 || option > 3));                                                                                                                                                        

    printf("You chose %d\n", option);
    return 0;
}

And, yes, I know I said you should check the return value from scanf but it's not necessary in the case of my posted code since you've already established at that point that the string you're scanning is a valid one- or two-digit number.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
0

Like Carl said: isdigit is just for check single character, and should NOT be used here to the int value obtained from scanf .

To archive your requirement, we can use scanf to obtain a string, then use strtol convert to int, check the endptr to confirm if there is any invalid character.

Code may like this:

    int option;
    char str[8];
    char *endp;
    do{
        printf("Please choose an option :\n0. Create Database\n1.Add New Student \n2. Show All Students \n3.Delete Student\n99.Exit\n");
        scanf("%7s", str);
        option = strtol(str,&endp,10);
    } while (*endp);
tgarm
  • 473
  • 3
  • 8
  • Why `7` when you only need two characters plus newline? – paxdiablo May 14 '20 at 03:20
  • It can be any number, just ensure less than the size of str (which is 8 chars) and bigger than 2. A bigger number could make it a little more robust, cuz user may input number a larger number. – tgarm May 14 '20 at 07:25
0

This is the most complete and safest solution I can come up with using scanf:

int option;
int status;
char tailchar;

status = 2;
tailchar = '\n';
do {
    if(tailchar != '\n' || status != 2){
        for (int c=getchar(); c!='\n' && c!=EOF; c=getchar());
    }
    printf("Please choose an option:\n"
        " 0. Create Database\n"
        " 1. Add New Student \n"
        " 2. Show All Students \n"
        " 3. Delete Student\n"
        "99. Exit\n");
    status = scanf(" %d%c", &option, &tailchar);
} while (status != 2 || option < 0 || (option >3 && option != 99));
if(tailchar != '\n'){
    for (int c=getchar(); c!='\n' && c!=EOF; c=getchar());
}

It only accepts valid options, and cleans any bad input by the user before asking to choose again.

isrnick
  • 621
  • 5
  • 12