Common macro to read input data and check its vali

2019-01-15 17:08发布

问题:

I saw on Stack Overflow that many similar questions are repeated and they are related to the reading of one input data item from stdin and check its validity.

The data could be integer "%d", double "%f", string "%s", unsigned int "%u"...

And I want to develop a common macro which could be used for the majority of these questions.

Example 1 of questions

The OP could ask:

  • scan the input data
  • the data should be integer so 11a, aaa, aa44,... inputs are not allowed. Only integer followed with a white space (refer to isspace()) is allowed
  • other conditions could be present in the question like: input integer should be >3 and <15

Example 2 of questions

The OP could ask:

  • scan the input data
  • the data should be double so 11.2a, aaa, aa44.3,... inputs are not allowed. Only double followed with a white space (refer to isspace()) is allowed
  • other conditions could be present in the question like: input double should be >3.2 and <15.0

Is it possible to develop a common macro like

#define SCAN_ONEENTRY_WITHCHECK(FORM,X,COND)
// FORM: format of the input data like "%d", "%lf", "%s"
// X: address where to store the input data
// COND: condition to add in the check of the input data

....

// example of calling the macro in the main()

int a;
SCAN_ONEENTRY_WITHCHECK("%d", &a,(a>3 && a<15))

The macro should scan the data and IF one of the following criteria is not true then print a message to the user asking him to enter again. and repeat that until the user enters valid data?

Criteria:

  • input data type should be the same indicated by format
  • input data should be followed by white space indicated in the isspace()
  • input data should be valid the condition COND

回答1:

A quick answer for this question will be

#define SCAN_ONEENTRY_WITHCHECK(FORM,X,COND)\
   while(scanf(" "FORM, X)<1 || !(COND))
      printf("Invalid input, enter again: ");

calling the above macro in the code with the following way

int a; 
SCAN_ONEENTRY_WITHCHECK("%d", &a, (a>3 && a<15))

is equivalent to this

int a;
while(scanf(" %d", &a)<1 || !(a>3 && a<15))
    printf("Invalid input, enter again: ");

but the above answer is wrong because if the user enter for example aaa as input then the above code will result an infinite loop because the stdin is not cleaned because the aaa input is not catched by the scanf(" %d", &a). So we have to add something that clean the stdin if the user enter a such input. adding scanf("%*[^\n]") could be a solution for that. and the above code will be

int a;
while(scanf(" %d", &a)<1 || !(a>3 && a<15)) {
    scanf("%*[^\n]"); // clean stdin
    printf("Invalid input, enter again: ");
}

but the above code still contains a limitation. For example If the user enter a valid integer (20) and the integr does not respect the condition (a>3 && a<15). In this case we do not have to clean the stdin because it's already cleaned otherwise the user will be asked for input data for 2 times . tis problem coud solved with the following solution:

int a;
int c;
while((c=(scanf(" %d", &a)<1)) || !(a>3 && a<15)) {
    if (c) scanf("%*[^\n]"); // clean stdin
    printf("Invalid input, enter again: ");
}

The above code does not respect the criteria input data should be followed by white space for example if the user enter 10abc as input then the above code will catch 10 as input integer and it will clean the rest abc. this problem could be solved if we check that the next charachter is a white space (with isspace() function) nothing else

int a;
char tmp;
int c;
while((c=(scanf(" %d%c", &a, &tmp)!=2 || !isspace(tmp))) || !(a>3 && a<15)) {
    if (c) scanf("%*[^\n]"); // clean stdin
    printf("Invalid input, enter again: ");
}

And now if we want to go back to the macro. the macro code will be:

#define SCAN_ONEENTRY_WITHCHECK(FORM,X,COND) \
do {\
    char tmp;\
    int c;\
    while ((c=(scanf(" "FORM"%c", X, &tmp)!=2 || !isspace(tmp)))\
            || !(COND)) {\
        if (c) scanf("%*[^\n]");\
        printf("Invalid input, please enter again: ");\
    }\
} while(0)

Adding do {...} while(0) in the macro could be explained by this link

The above macro could be writen in another way

#define SCAN_ONEENTRY_WITHCHECK(FORM,X,COND) \
do {\
    char tmp;\
    while(((scanf(" "FORM"%c",X,&tmp)!=2 || !isspace(tmp)) && !scanf("%*[^\n]"))\
            || !(COND)) {\
        printf("Invalid input, please enter again: ");\
    }\
} while(0)

Examples of using the macro:

int main()

{
    int decision;
    double q;
    char buf[32];

    printf("Input data, valid choice 1 or 0: ");
    SCAN_ONEENTRY_WITHCHECK("%d",&decision,(decision==0 || decision==1));
    printf("You have entered good input : %d\n", decision);

    printf("Input unsigned double: ");
    SCAN_ONEENTRY_WITHCHECK("%lf",&q, (q == (unsigned int) q));
    printf("You have entered good input : %lf\n", q);

    printf("Input name: ");
    SCAN_ONEENTRY_WITHCHECK("%s",buf, (strcmp(buf,"kallel")==0));
    printf("You have entered good input : %s\n", buf);

    printf("Input data should be valid integer: ");
    SCAN_ONEENTRY_WITHCHECK("%d",&decision,1);
    // COND is 1 ==> we do not have any check in the input integer
    printf("You have entered good input : %d\n", decision);
}

Execution

$ ./test
Input data, valid choice 1 or 0: 4
Invalid input, please enter again: a4
Invalid input, please enter again: a1
Invalid input, please enter again: 1a
Invalid input, please enter again: 1
You have entered good input : 1
Input unsigned double: 2.3
Invalid input, please enter again: a.0a
Invalid input, please enter again: 2.0a
Invalid input, please enter again: a2.0
Invalid input, please enter again: 2.0
You have entered good input : 2.000000
Input name: an
Invalid input, please enter again: anyad
Invalid input, please enter again: adny
Invalid input, please enter again: any
You have entered good input : any
Input data should be valid integer: 2.5
Invalid input, please enter again: -454f
Invalid input, please enter again: -454
You have entered good input : -454


标签: c++ c macros