0
votes

I've encountered a problem when validating a single-char scanf input in C and I cannot find an existing solution that works...

The scenario is: a method is taking a single letter 'char' type input and then validating this input, if the criteria is not met, then pops an error message and re-enter, otherwise return this character value.

my code is:

char GetStuff(void)
{
    char c;
    scanf("%c", &c);
    while(c != 'A' || c != 'P')
    {
          printf("invalid input, enter again (A for AM or P for PM): ");
          scanf ("%c", &dtChar);
    }
    return c;
}

however, i got the infinite loop of error message no matter what input I type in. I read some other posts and guess it's the problem that %c specifier does no automatically get rid of the newline when I hit enter, and so far I have tried:

  1. putting a white space before/after %c like:

    scanf(" %c", &c);
    
  2. write a separate method or include in this GetStuff method to clean the newline like:

    void cleanBuffer(){
      int n;
      while((n = getchar()) != EOF && n != '\n' );
    }
    

Can anyone help me with this problem please? Thank you in advance.

5
c != 'A' || c != 'P' is always true. - n. 1.8e9-where's-my-share m.
@n.m. you are right....but it still does work when I change || to && - XYZ1007
As well as fixing the loop condition, you should check the return value from scanf(); it might be telling you EOF. You might well want to add a space before the %c in the conversion specifier, so as to skip white space (such as newlines) — using if (scanf(" %c", &c) != 1) { …process error… } might be better. You say that including fixed code in your bigger program still left you in an infinite loop. You should think about how you will debug it — adding print statements or using a debugger. See How to debug small programs. - Jonathan Leffler
Is there any particular reason you are using scanf for a single-character? Isn't that what getchar() (or fgetc) is for? You should also save and check the return of scanf, e.g. int rtn = scanf ("%c", &c); so you can check if (rtn == EOF) { /* handle user canceled input */ } - David C. Rankin

5 Answers

2
votes

Please consider the following snippet:

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

char GetStuff(void)
{
    char c;
    do {
          printf("Please enter A for AM or P for PM: ");
          scanf ("%c", &c);
          // clean input buffer (till the end of line)
          while(getchar()!='\n');
    } while(toupper(c) != 'A' && toupper(c) != 'P');
    return c;
}

int main(void)
{
    printf("Your input is'%c'\n", GetStuff());
    return 0;
}

Note the points:

  • condition while(c != 'A' || c != 'P') will be always true (just because one character cannot be 'A' and 'P' at the same time), so use while(c != 'A' && c != 'P') instead
  • No need for two scanf if you use do..while loop
  • After entering a char with scanf it is recommended to clean all characters from buffer, e.g. with while(getchar()!='\n'); (this will clean all input including incorrect and redundant characters)
  • use toupper to avoid making 4 comparison (actually single c=toupper(c) inside loop can minimize your while as while(c != 'A' && c != 'P') )

UPDATE:

To add message "Invalid input" and adding some other useful improvement subjected befor... new code is as:

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

void CleanBuffer(){
    int n;
    while((n = getchar()) != EOF && n != '\n' );
}

char GetStuff(void)
{
    char c;
    do {
          printf("Please enter A for AM or P for PM: ");
          scanf (" %c", &c);
          c = toupper(c); // here letter become uppercase
          CleanBuffer();
    } while( (c != 'A' && c != 'P')?printf("Invalid input! "):0 );
    return c;
}

int main(void)
{
    printf("You have entered: %c\n", GetStuff());
    return 0;
}

Note: function will return 'A' or 'P' in uppercase, so if this is not needed change the code as in example before update (use two toupper and do not change c after scanf). Also you can use tolower as an option (of course with comparing to 'a' and 'p').

1
votes

Inside loop you are taking input in dtChar but your loop condition checks variable c which is not updated in the loop, that is causing infinite loop

Also you would change your condition

while(c != 'A' || c != 'P')

to

while(c != 'A' && c != 'P') 

If you want user to enter either 'A' or 'P'

1
votes
#include <stdio.h>

char GetStuff(void) {
    char c;
    scanf("%c", &c);
    getchar();
    while ((c != 'A') && (c != 'a') && (c != 'P') && (c != 'p')) {
        printf("invalid input, enter again (A for AM or P for PM): ");
        scanf ("%c", &c);
        getchar();
    }
    return c;
}

int main(void) {
    printf("Calling GetStuff()...\n");
    char x = GetStuff();
    printf("User entered %c\n", x);
    return 0;
}

You are using while (c != 'A' || c != 'P') as your loop conditional, but this will always return true. What you meant to use is the && "and" operator, instead of the || "or" operator.

Also, call getchar() after your scanf statements, to capture the newline. This should work the way you want it to.

0
votes

Another possible solution. As others mentioned the condition was to be done with &&. Anyway the big problem is how to remove what's left on the console input line. Since the console works by lines, we remove everything up to the next '\n'. If the user already left something on the input line before calling GetStuff(), it would be useful to add a call to SkipRestOfTheLine() before the while loop.

In general I suggest to start with a while(1) loop, before making it nicer (such as in the cleanBuffer() you posted).

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

void SkipRestOfTheLine(void)
{
    while (1) {
        int c = fgetc(stdin);
        if (c == EOF || c == '\n')
            break;
    }
}

char GetStuff(void)
{
    while (1) {
        int c = fgetc(stdin);
        if (c == EOF)
            exit(EXIT_FAILURE); // Deal with this case in an appropriate way
        if (c == 'A' || c == 'P')
            return c;
        printf("invalid input, enter again (A for AM or P for PM): ");
        SkipRestOfTheLine();
    }
}

int main(void)
{
    char c = GetStuff();
    return 0;
}
-1
votes

try this,

   char GetStuff(void)
    {
        char c;
        scanf("%c", &c);
        while (((c != 'A') || (c != 'a')) && ((c != 'P') || (c != 'p'))==1)
        {
              printf("invalid input, enter again (A for AM or P for PM): ");
              scanf ("%c", &dtChar);
        }
        return c;
    }

I hope this works, some time because of not given proper bracket it is stuck in the loop.