Search code examples
cconditional-operator

Printing result after evaluation using conditional operator


While executing the small piece of code below, every time I enter a character, the output is repeated and I don't understand why. Can someone explain to me why is it behaving like this? ps: Just started my programming journey in c.

If I print a character such as 'a' I'm supposed to have 1 as output then another prompt asking me to enter a character again. But I instead get the 1, a prompt, and a 1 again then another prompt asking me to enter a character.

#include <stdio.h>

int main()
{
  int usr_ch;
  for ( usr_ch = 0; (usr_ch != 'q');){
       printf("Enter a single character: (enter 'q' to stop)\n");
       usr_ch = getc(stdin);
       printf("%d\n", (usr_ch != 'q') ? 1 : 0));
  }
  return 0;
}

input: u
output:
Enter a single character: (enter 'q' to stop)
1
Enter a single character: (enter 'q' to stop)
1
Enter a single character: (enter 'q' to stop)

Solution

  • You already have a great answer explaining the additional '\n' character generated when the user presses ENTER. Continuing from the comments below the question and comment by @AllanWard about the use of fgets(), it can provide the ability to take all single characters as input and end the input when ENTER alone is pressed. There are a number of other benefits as well.

    When reading a line with fgets() you read the input into a buffer (character array or allocated block of memory). Don't skimp on buffer size... fgets() reads and includes the trailing '\n' in the buffer it fills. This means an entire line of input is consumed, including the trailing '\n' given a sufficiently sized buffer. The '\n' is not left in the input buffer (stdin) unread. This will avoid the problem you are experiencing.

    To access the first character in the array, all you need do is derefernce the pointer. (an array is converted to a pointer to its first element on access, C18 Standard - 6.3.2.1(p3)). So if you declare char line[1024]; to hold the input, simply referencing *line provides access to the first character.

    Using fgets() avoids all of the pitfalls new C programmers fall into using scanf() and eliminates the '\n' being left unread. These are the primary reasons new C programmers (as well as not so new C programmers) are encouraged to take all user input using fgets() (or POSIX getline() which behaves in the same manner, but can also provide auto-allocation to handle a string of any length)

    In addition to taking the input, without much more effort you can ensure the user has only entered one-printable character with a few simple tests. This allows you to handle individual error cases as needed. A short example of the use of fgets() and handling several of the foreseeable error cases can be written as:

    #include <stdio.h>
    #include <ctype.h>
    
    #define MAXC 1024     /* if you need a constant, #define one (or more) */
    
    int main (void)
    {
      char line[MAXC];    /* buffer to hold line */
      
      /* prompt and then read input into line */
      while (fputs ("Enter a single character: (enter alone to stop): ", stdout) &&
             fgets (line, MAXC, stdin)) {
        /* if ENTER alone, break */
        if (*line == '\n') {
          puts ("exiting");
          break;
        }
        /* if not a single-character, handle error */
        else if (line[1] != '\n') {
          fputs ("  error: more than 1 char entered.\n", stderr);
        }
        /* if printable character, output */
        else if (isprint ((unsigned char)*line)) {
          printf ("  you entered '%c'\n", *line);
        }
        else {  /* otherwise, handle error */
          fputs (" error: non-printable character generated.\n", stderr);
        }
      }
    }
    

    (note: these are only a few examples of the classification test you can use. You are free to add or remove as many as you like. You can even provide a lookup-table for non-printable character and output a representation, e.g. '\t', when one is pressed, it's entirely up to you.)

    Example Use/Output

    The following exercises each of the covered error cases (the '\t' character is used for the non-printable character), e.g.

    $ ./bin/fgets-char
    Enter a single character: (enter alone to stop): a
      you entered 'a'
    Enter a single character: (enter alone to stop): b
      you entered 'b'
    Enter a single character: (enter alone to stop): q
      you entered 'q'
    Enter a single character: (enter alone to stop): Q
      you entered 'Q'
    Enter a single character: (enter alone to stop):
     error: non-printable character generated.
    Enter a single character: (enter alone to stop): foo
      error: more than 1 char entered.
    Enter a single character: (enter alone to stop):
    exiting
    

    There is absolutely nothing wrong with using getc() or fgetc() or getchar() for taking a single-character as input, but you must handle any additional characters that remain unread (including the trailing '\n'). Then what if the user presses ENTER twice in a row, or a cat steps on the keyboard generating 500 keypresses? That's where fgets() can help.

    Another approach, unfortunately non-portable between different OS's, is to place the terminal in raw unbuffered (non-cannonical) mode where the input is processed immediately. For Linux you can use tcsetattr(). (you can also use setvbuf, see man 3 setbuf to switch between unbuffered, line-buffered or fully-buffered input) For Windows getch() can be used.

    Worth exploring each as you continue your learning in C. Let me know if you have further questions.