Kerry Huang
Kerry Huang

Reputation: 21

How to avoid pressing enter twice when using getchar() to clear input buffer?

I have this program:

#include <stdio.h>
#define SIZE 19

int main(){

char string[SIZE];

while (string[0] != 'A'){
 printf("\nEnter a new string.\n");
 fgets(string,SIZE,stdin);

 int storage = 0;
 while (storage != '\n') 
 {
   storage = getchar(); 
 }
 }
 }

The nested while loop with getchar() exists in case the inputted string exceeds the maximum number of characters string[] can hold. If that is not there, inputting a string with, say, 20 characters, would cause the output to be:

Enter a new string.
12345123451234512345

Enter a new string.

Enter a new string.

The problem is that this requires me to press enter twice in order to enter a new string: once for 'fgets' and one for the nested while loop (this is my understanding of what's going on).

Is there a way to change this so I only have to press 'Enter' once, or possibly a way to change the entire while loop into something more elegant?

Upvotes: 2

Views: 1690

Answers (3)

David C. Rankin
David C. Rankin

Reputation: 84551

You are thinking correctly, you just need to think through how and when you need to empty the input buffer a bit further.

All line-oriented input functions (fgets and POSIX getline) will read, and include, the trailing '\n' in the buffers they fill (fgets only when sufficient space is provided in the buffer).

When using fgets, you have only two possible returns, (1) a pointer to the buffer filled, or (2) "NULL on error or when end of file occurs while no characters have been read."

In case fgets returns a valid pointer, then it is up to you to determine whether a complete line of input was read, or whether the input exceeds the buffer size, leaving characters in the input buffer unread.

To make that determination, you check whether the last character in the buffer is '\n' and if not, whether the buffer contains SIZE-1 characters indicating that characters remain in the input buffer. You can do that a number of ways, you can use strchr (to get a pointer to the '\n'), strcspn (to get an index to it) or good old strlen (to get the length of the string) and then check the character at len-1.

(a note on preference, you can use whatever method you like, but in either case of strcspn or strlen, save the index or length so it can be used to validate whether the input exceeded the buffer size or whether the user ended input by generating a manual EOF. You save the index or length to prevent having to make duplicate function calls to either)

It is also helpful to create a simple helper-function to clear the input buffer to avoid placing loops everywhere you need the check. A simple function will do the trick, e.g.

/* simple function to empty stdin */
void empty_stdin (void)
{
    int c = getchar();

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

of if you prefer the more-compact, but arguably less readable version, a single for loop will do, e.g.

void empty_stdin (void)
{
    for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
}

The remainder of your example can be structured to complete each of the tests described above to provide input handling as you have described (although using the 1st character of the buffer being 'A' to control the loop is a bit strange), e.g.

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

#define STRSIZE 19

/* simple function to empty stdin */
void empty_stdin (void)
{
    int c = getchar();

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

int main (void) {

    char string[STRSIZE] = "";          /* initialize string all 0 */

    while (*string != 'A') {            /* up to you, but 'A' is a bit odd */
        size_t len = 0;                 /* variable for strlen return */
        printf ("enter a string: ");    /* prompt */
        if (!fgets (string, STRSIZE, stdin)) {  /* validate read */
            putchar ('\n');             /* tidy up with POSIX EOF on NULL */
            break;
        }
        len = strlen (string);              /* get length of string */
        if (len && string[len-1] == '\n')   /* test if last char is '\n' */
            string[--len] = 0;              /* overwrite with nul-character */
        else if (len == STRSIZE - 1)        /* test input too long */
            empty_stdin();                  /* empty input buffer */
    }

    return 0;
}

An arguably more useful approach is to have the loop exit if nothing is input (e.g. when Enter alone is pressed on an empty line). The test would then be while (*string != '\n'). A better approach rather is simply controlling your input loop with while (fgets (string, STRSIZE, stdin)). There, you have validated the read before entering the loop. You can also wrap the whole thing in a for (;;) loop and control the loop exit based on any input condition you choose.

Those are all possibilities to consider. Look things over and let me know if you have further questions.

Upvotes: 1

Peter
Peter

Reputation: 36597

fgets() does read the newline IF (and only if) the buffer is long enough to reach and contain it, along with a trailing nul terminator.

Your sample input is 20 characters, which will be followed by a newline, and then a nul terminator. That won't go into a buffer of 19 characters.

The simple way is to use fgets() in a loop, until the newline is included in the buffer.

printf("\nEnter a new string.\n");
do
{
     fgets(string,SIZE,stdin);
       /* 
            handle the input, noting it may or may not include a trailing '\n'
            before the terminating nul
       */
} while (strlen(string) > 0 && string[strlen(string) - 1] != '\n');

This loop will clear input up to and including the first newline, and also allow you to explicit handle (discard if needed) ALL the input received. It is therefore not necessary to use a second loop with getchar().

You haven't checked if fgets() returns NULL, so neither have I. It is advisable to check, as that can indicate errors on input.

Upvotes: 0

dbush
dbush

Reputation: 223739

If the buffer that receives from fgets contains a newline, you know it read everything that was inputted so you don’t need to do anything else. If not, then you use the extra loop to flush the buffer.

Upvotes: 1

Related Questions