Как избежать двойного нажатия клавиши ввода при использовании getchar() для очистки буфера ввода?

У меня есть эта программа:

#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(); 
 }
 }
 }

Вложенный цикл while с getchar() существует в случае, если введенная строка превышает максимальное количество символов, которое может содержать строка []. Если этого нет, ввод строки, скажем, с 20 символами, приведет к выводу:

Enter a new string.
12345123451234512345

Enter a new string.

Enter a new string.

Проблема в том, что для этого требуется дважды нажать клавишу ввода, чтобы ввести новую строку: один раз для 'fgets' и один для вложенного цикла while (это мое понимание того, что происходит).

Есть ли способ изменить это, поэтому мне нужно всего лишь нажать "Enter" один раз, или, возможно, способ изменить весь цикл while на что-то более элегантное?

3 ответа

Вы думаете правильно, вам просто нужно продумать, как и когда вам нужно немного очистить входной буфер.

Все линейно-ориентированные функции ввода (fgets и POSIX getline) прочтет и включит завершающий '\n' в буферах они заполняют (fgets только когда в буфере предусмотрено достаточно места).

Когда используешь fgets, у вас есть только два возможных возврата: (1) указатель на заполненный буфер или (2) "NULL в случае ошибки или в конце файла, когда символы не были прочитаны."

В случае fgets возвращает действительный указатель, а затем вам решать, была ли прочитана полная строка ввода или превышен ли размер буфера, оставив символы во входном буфере непрочитанными.

Чтобы сделать это определение, вы проверяете, является ли последний символ в буфере '\n' и если нет, то содержит ли буфер SIZE-1 символы, указывающие, что символы остаются во входном буфере. Вы можете сделать это несколькими способами, вы можете использовать strchr (чтобы получить указатель на '\n'), strcspn (чтобы получить индекс к нему) или старый добрый strlen (чтобы получить длину строки), а затем проверьте символ в len-1,

(примечание о предпочтении, вы можете использовать любой метод, который вам нравится, но в любом случае strcspn или же strlenсохраните индекс или длину, чтобы его можно было использовать для проверки того, превысил ли ввод размер буфера или пользователь завершил ввод, создав ручную EOF, Вы сохраняете индекс или длину, чтобы избежать необходимости повторять вызовы функций либо)

Также полезно создать простую вспомогательную функцию для очистки входного буфера, чтобы избежать размещения циклов везде, где вам нужна проверка. Простая функция сделает свое дело, например

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

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

если вы предпочитаете более компактную, но, возможно, менее читаемую версию, for цикл сделает, например

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

Оставшаяся часть вашего примера может быть структурирована для выполнения каждого из описанных выше тестов, чтобы обеспечить обработку ввода, как вы уже описали (хотя с использованием 1-го символа буфера 'A' контролировать цикл немного странно), например

#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;
}

Возможно, более полезный подход состоит в том, чтобы иметь выход из цикла, если ничего не вводится (например, когда только Enter вводится в пустой строке). Тогда тест будет while (*string != '\n'), Лучше лучше просто контролировать цикл ввода с помощью while (fgets (string, STRSIZE, stdin)), Там вы проверили чтение перед входом в цикл. Вы также можете обернуть все это в for (;;) зацикливание и управление выходом из зацикливания на основе любого выбранного вами условия ввода

Это все возможности для рассмотрения. Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

Если буфер, который получает от fgets содержит новую строку, вы знаете, что читать все, что было введено, так что вам больше ничего не нужно делать. Если нет, то вы используете дополнительный цикл для очистки буфера.

Использование strchr чтобы увидеть, если вход содержит новую строку. Если нет, позвоните fgets пока не будет найден символ новой строки, запросите новый ввод.

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

#define SIZE 19

int main ( void) {
    char input[SIZE] = "\001";

    do {
        if ( fgets ( input, sizeof input, stdin)) {
            if ( !strchr ( input, '\n')) {//no newline?
                while ( !strchr ( input, '\n')) {//call until newline is found to clear input
                    if ( !fgets ( input, sizeof input, stdin)) {
                        fprintf ( stderr, "\nfgets problem\n");
                        break;
                    }
                }
                input[0] = '\001';//unlikely to be a valid character
                printf ( "Enter a new string.\n");
            }
        }
        else {
            fprintf ( stderr, "\nfgets problem\n");
            return 0;
        }
    } while ( '\001' == input[0]);
    return 0;
}

fgets() действительно читает новую строку IF (и только если), буфер достаточно длинный, чтобы достигнуть и содержать его, вместе с завершающим нулевым терминатором.

Ваш пример ввода составляет 20 символов, за которым следует новая строка, а затем нулевой терминатор. Это не войдет в буфер из 19 символов.

Простой способ заключается в использовании fgets() в цикле, пока новая строка не будет включена в буфер.

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');

Этот цикл очищает ввод вплоть до первой новой строки, а также позволяет вам явно обрабатывать (отменять при необходимости) ВСЕ полученные данные. Поэтому нет необходимости использовать второй цикл с getchar(),

Вы не проверили, если fgets() возвращается NULL, так что я тоже не имею. Желательно проверить, так как это может указывать на ошибки на входе.

Другие вопросы по тегам