Почему следующий код не позволяет мне получать пользовательский ввод с помощью fgets, но работает с scanf? - PullRequest
0 голосов
/ 22 октября 2018

Это короткий отрывок из более крупной программы, но остальная часть программы не имеет значения, так как, я думаю, я смогла выделить проблему.Я подозреваю, что это как-то связано с тем, как я использую fgets.Я читал, что предпочтительнее использовать fgets вместо scanf, но я не могу заставить его работать должным образом.Когда я использую следующий код, программа не дает мне возможности ввести номер (но просто пропускает цикл while, который проверяет, находится ли введенный номер в правильном диапазоне):

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

#define SIZE 10

int main(void)
{
    // ask user for how many items to store
    printf("how many words would you like to enter? (1-%i): ", SIZE);

    // save number of words user would like to store
    char *input = malloc(sizeof(char));
    fgets(input, 1, stdin);
    // scanf("%c", input);

    int words = atoi(input);

    printf("the number of words is: %i\n", words);
    while (words < 1 || words > SIZE)
    {
        printf("please enter a number between 1 and %i: ", SIZE);
        scanf("%i", &words);
    }
}

Вот вывод, который я получаю:

~/workspace/extra_stuff/hash_tables/ $ ./test2
how many words would you like to enter? (1-10): the number of words is: 0
please enter a number between 1 and 10: 

Как видите, он никогда не позволял мне вводить число, а просто переходил к следующему шагу, казалось бы, при условии, что я ничего не вводил.

Если я изменю код следующим образом, все будет работать так, как запланировано:

#include <stdlib.h>

#define SIZE 10

int main(void)
{
    // ask user for how many items to store
    printf("how many words would you like to enter? (1-%i): ", SIZE);

    // save number of words user would like to store
    char *input = malloc(sizeof(char));
    // fgets(input, 1, stdin);
    scanf("%c", input);

    int words = atoi(input);

    printf("the number of words is: %i\n", words);
    while (words < 1 || words > SIZE)
    {
        printf("please enter a number between 1 and %i: ", SIZE);
        scanf("%i", &words);
    }
}

PS: я понимаю, что, используя scanf, я мог бы сразу сохранить входные данные в переменную int без использования atoi для преобразованияchar to int;однако кажется, что fgets требует char *, поэтому я выбрал этот маршрут.Кроме того, я понимаю, что я должен free(input) позже.

Может кто-нибудь объяснить это поведение?Спасибо.

РЕДАКТИРОВАТЬ:

Спасибо всем, кто ответил до сих пор!Там есть несколько полезных советов, но похоже, что у меня возникла та же проблема в моей программе.Вот выдержка из кода:

// ask for strings
    for (int j = 0; j < words; j++)
    {
        char buffer[4096];
        // fgets(buffer, 40, stdin);

        // name=calloc(NAME_SIZE, sizeof(char));
        // fgets(name, NAME_SIZE, stdin);

        // printf("size of (array[j]->next)->text is: %lu\n", sizeof((array[j]->next)->text));

        printf("please enter string #%i: ", j);

        fgets(buffer, 4096, stdin);

        printf("you've entered: %s", buffer);

        int length = strlen(buffer);
        printf("word length: %i\n", length); 
}

Когда я запускаю программу, она снова не дает мне возможности ввести мой ввод, когда она должна:

please enter string #0: you've entered: 
word length: 1

РЕДАКТИРОВАТЬ # 2:

После проработки ответа Дэвида и ссылки на комментарии других людей и другие темы SO, я придумал следующую версию кода, которая сначала спрашивает пользователядля количества слов, которые они хотели бы ввести (и проверяет ввод), а затем просит пользователя ввести эти слова (снова, проверяя ввод).Похоже, что он компилируется без ошибок и предупреждений и функционирует должным образом, хотя я не уверен на 100%, что я протестировал все возможные вещи, которые могут пойти не так с пользовательским вводом, и есть некоторые фрагменты кода, которые я до сих пор пишуне совсем понимаю (я перечислю их ниже) - если у кого-то есть время / желание / терпение, чтобы просмотреть это и сказать мне, если я все еще могу что-то улучшить, пожалуйста, дайте мне знать.Моя цель - использовать этот код в другой программе, которая будет запрашивать ввод данных пользователем и сохранять записи в хеш-таблице.

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

#define BUF_SIZE_WORDS 4096
#define BUF_SIZE_NUMBERS 256
#define MAX_WORDS 10

int word_input(int num_words);

void empty_stdin();

int main(void)
{
    int num_words = 0,       /* number of words to enter */
        word_count_check = 0;          /* word count */

    char buffer[BUF_SIZE_NUMBERS] = "";    /* buffer of sufficient size for input */

    for (;;) /* loop continually until valid input of NUMBER OF WORDS USER WANTS TO ENTER or user cancels */
    {
        printf ("how many words would you like to enter? [1-%d]: ", MAX_WORDS);
        // check for cancellation of input
        if (!fgets (buffer, BUF_SIZE_NUMBERS, stdin))
        {
            fputs ("user canceled input\n", stderr);
            return 1;
        }

        // check if user simply hit enter w/o typing anything
        if(buffer[0] == '\n')
        {
            printf("please enter a value\n");
            continue;
        }


        size_t inlength = strlen(buffer);

        // validate length < BUF_SIZE_NUMBERS - 1
        if (inlength >= BUF_SIZE_NUMBERS - 1)
        {
            fputs ("input exceeds allocated buffer size\n", stderr);
            return 2;
        }

        if (inlength && buffer[inlength - 1] == '\n')
        {
            // printf("hurray!\n");
            buffer[--inlength] = 0;
        }
        else if (inlength == BUF_SIZE_NUMBERS - 1) /* the line was too long */
        {
            printf("you've entered too many characters... please stick to a maximum of %i\n", BUF_SIZE_NUMBERS);
            empty_stdin();
            continue;
        }

        // make sure user actually entered a proper int
        if (sscanf (buffer, "%d", &num_words) != 1) /* sscanf is used for conversion */
        {
            fputs ("invalid conversion to int; please provide valid input\n", stderr);
            continue;
        }

        // check if the number entered is out of range
        if (num_words < 1 || num_words > MAX_WORDS)
            fprintf (stderr, "%2d out of valid range.\n", num_words);
        else
            break; /*if the input has been validated, we can now break out of the for loop */
    }

    // call the word_input function and store its return value in word_count_check
    word_count_check = word_input(num_words);

    // check if the number of words processed equals to the number requested by the user
    if(word_count_check == num_words)
    {
        printf("success!\n");
    }
    else
    {
        printf("something went wrong, since word_count_check != num_words...\n");
    }

}

int word_input(int num_words)
{
    int word_count = 0;

    for(;;) /* loop until word_count == num_words is achieved */
    {
        // declare an array for storing input string
        char buffer[BUF_SIZE_WORDS];
        char valid_input[BUF_SIZE_WORDS];

        // prompt user for input
        printf("please enter a string: ");

        // get input and check for CTRL+D
        if (!fgets(buffer, BUF_SIZE_WORDS, stdin))
        {
            fputs ("user canceled input\n", stderr);
            exit(1);
        }

         // check if user simply hit enter w/o typing anything
        if(buffer[0] == '\n')
        {
            printf("please enter a word that's more than 0 characters\n");
            // empty_stdin();
            continue;
        }

        size_t inlength = strlen(buffer);

        // check if user input exceed buffer size
        if (inlength >= BUF_SIZE_WORDS - 1)
        {
            empty_stdin();
            fputs ("input exceeds allocated buffer size, please try again\n", stderr);
            continue;
        }

        // check if the user entered too many characters
        if (inlength == BUF_SIZE_WORDS - 1) /* the line was too long */
        {
            printf("you've entered too many characters... please stick to a maximum of %i\n", BUF_SIZE_WORDS);
            empty_stdin();
            continue;
        }

        if (inlength && buffer[inlength - 1] == '\n')
        {
            buffer[--inlength] = 0;

            // get rid of trailing spaces using sscanf
            sscanf(buffer, "%s", valid_input);

            // figure out the length of the word the user entered
            int word_length = ((int) strlen(valid_input));
            printf("string length: %i\n", word_length);

            // print out the word entered by the user one character at a time
            printf("you've entered: ");
            for (int i = 0; i < word_length; i++)
            {
                printf("%c", valid_input[i]);
            }
            printf("\n");

            // increment word count
            word_count++;
            printf("word_count = %i\n", word_count);

            if (word_count == num_words)
            {
                return word_count;
            }
        }
    }
}

/* helper function to remove any chars left in input buffer */
void empty_stdin()
{
    int c = getchar();
    while (c != '\n' && c != EOF)
        c = getchar();
}

вещи, которые я до сих пор не полностью понимаю:

1)

if (!fgets (buf, MAXC, stdin)) { /* validate ALL user input */ 
    fputs ("(user canceled input)\n", stderr); 
    return 1; 
}

--- это просто проверяет, вводил ли пользователь вручную EOF (с помощью ctrl + d), или также проверяет что-то еще?

2) вызов функции empty_stdin () ниже, казалось, вызывал какое-то странное зависание, когда казалось, что программа ожидает от меня дальнейшего ввода информации, а не просто перехода к следующему шагу,особенно когда я использовал его часто (я подумал, почему бы не очищать входной поток каждый раз, когда пользователь вводит что-то странное?) и / или когда я уменьшал буфер до чего-то очень маленького, а затем целенаправленно вводил слишком много символов ..

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

3) в конце концов я хочу использовать часть этого кода для загрузки словаря из текстового файла (вместо ввода пользователя) и сохранения его в хеш-таблице, а в другой версии - в виде файла.Помимо использования isalpha (), чтобы убедиться, что мы храним только те слова, в которых есть буквы, есть ли какие-либо другие проверки / проверки, которые должны произойти при обработке ввода, помимо указанных выше?Следует ли пропустить какие-либо из вышеуказанных проверок?

1 Ответ

0 голосов
/ 22 октября 2018

В работе со строками в Си нет никакой магии, но вам нужно надеть свою учетную шляпу ... Почему?При работе с вводом вы должны учитывать не только количество символов, которые вы помещаете в свой буфер (или где бы вы ни хранили свой ввод), но вы также должны учитывать символы, которые остаются в вашем потоке ввода!

Это особенно верно при использовании любой функции семейства scanf для ввода.Зачем?Поскольку при совпадении или сбое ввода обработка (чтение и удаление) символов из буфера ввода (stdin здесь) останавливается , дальнейшие символы не читаютсяи любой символ, вызывающий сбой , соответствующий , остается непрочитанным в вашем входном потоке, просто ожидая, что вас снова укусят при следующей попытке чтения.Программисты на Си - это тот факт, что некоторые спецификаторы преобразования потребляют начальные пробелы (например, space, tab, newline,...), а другие - нет.Ваши числовые преобразователи (вместе с "%s") занимают первые пробелы, в то время как "%c" и "%[...]" этого не делают.

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

Использование fgets с последующим sscanf обеспечивает дополнительное преимущество разрешения проверка из (1) чтения ввода;и (2) анализ и преобразование ввода в необходимые значения.

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

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

  1. пользователь нажимает ctrl + d в Linux, чтобы создать руководство EOF ( ctrl + z при windoze);
  2. вы обрабатываете соответствие или ошибки ввода случаев, включая удаление любых некорректных символов из буфера ввода до следующей попытки чтения;и, наконец,
  3. у вас хороший ввод (возврат указывает на все ожидаемые преобразования, произошедшие).

В этом нет ничего волшебного, но требуется понимание возможногоусловия ошибок и обработка каждого из них на каждом входе.

В вашем случае, давайте посмотрим на вашу задачу получения количества слов, вводимых пользователем.Здесь вы пытались читать с fgets (это хорошо!), Но вы не смогли предоставить достаточно памяти для хранения ввода.Когда вы читаете небольшое количество текста от пользователя, вам нужен простой массив с автоматическим типом хранения.Тем не менее, вам необходимо соответствующим образом изменить размер буфера (и НЕ экономить на размере буфера).

Золотого правила не существует, но если бы у меня были пользователи, вводящие текст для преобразования в одно число, то я бы чувствовал себя хорошо с буфером символов 256 (которого более чем достаточно для хранения ввода любогодействительное число, плюс еще 230 нечетных символов для обработки времени, которое кошка делает на клавиатуре и т. д.)

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

#include <stdio.h>

#define SIZE  10    /* good form defining a constant! */
#define MAXC 256    /* max characters for buffer */

int main (void) {

    int nwords = 0,         /* number of words to enter */
        words = 0,          /* each word */
        wc = 0;             /* word count */
    char buf[MAXC] = "";    /* buffer of sufficient size for input */

    for (;;) {  /* loop continually until valid input or user cancels */
        printf ("number of words to enter? [1-%d]: ", SIZE);
        if (!fgets (buf, MAXC, stdin)) {    /* validate ALL user input */
            fputs ("(user canceled input)\n", stderr);
            return 1;
        }
        /* validate length < MAXC - 1 and buf[length-1] == '\n' here */

        if (sscanf (buf, "%d", &nwords) != 1) { /* sscanf for conversion */
            fputs ("  error: invalid conversion to int.\n", stderr);
            continue;
        }
        if (nwords < 1 || SIZE < nwords)  /* validate nwords in range */
            fprintf (stderr, " %2d out of valid range.\n", nwords);
        else  /* good input received, break loop */
            break;
    }

( примечание: ваш цикл while был преобразован в цикл, который будет непрерывно повторять цикл до тех пор, пока не будет введен действительный ввод между 1 < value < SIZE. Условие просто заставляет break; управлять циклом вполучен хороший ввод)

Этот цикл представляет классическое fgets/sscanf чтение и анализ информации из строки ввода, введенной пользователем.Вы можете разобрать номер из строки любым удобным для вас способом (но не используйте atoi() - он обеспечивает абсолютно ноль проверку ошибок преобразования).Вы можете использовать strtol (с надлежащей проверкой), и вы можете просто использовать указатель для обхода буфера, выбирая цифры, преобразовывая их из их ASCII в числовое значение, а затем умножая на 10 и добавляя на ходу.Любой способ хорош, пока вы проверяете, проверяете, проверяете каждую часть операции.

Теперь перейдя к чтению каждого из слов, которые должен ввести пользователь, мы будем игнорировать обычныемудрость и использовать scanf для выполнения задачи, но мы будем обрабатывать все три возможных случая возврата каждый раз.Мы также добавим счетчик, чтобы отслеживать действительные входные данные, предоставленные пользователем, и выходить из цикла только тогда, когда у нас есть такое количество допустимых целых чисел (или пользователь отменяет, генерируя руководство EOF).

    printf ("\nnumber of words entered: %d\n", nwords);
    for (; wc < nwords;) {  /* loop continually  */
        int rtn = 0;    /* scanf return */
        printf ("please enter a number between 1 and %d: ", SIZE);
        rtn = scanf ("%d", &words);     /* valdate ALL user input */
        if (rtn == EOF) {           /* handle EOF (manual) */
            fputs ("(user canceled input)\n", stderr);
            break;
        }
        else if (rtn == 0) {    /* handle "matching failure" */
            int c = getchar();  /* remove offending chars from stdin */
            while (c != '\n' && c != EOF)
                c = getchar();
            fputs ("  error: invalid integer input\n", stderr);
            continue;
        }
        else {  /* valid integer received */
            int c = getchar();      /* remove any extra chars from stdin */
            while (c != '\n' && c != EOF)
                c = getchar();
            if (words < 1 || SIZE < words)  /* validate in-range */
                fprintf (stderr, " %2d - invalid! (1 < valid < %d)\n", 
                        words, SIZE);
            else    /* good input, increment word count */
                printf (" word[%2d]: %3d\n", ++wc, words);
        }
    }

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

/* helper function to remove any chars left in input buffer */
void empty_stdin()
{
    int c = getchar();
    while (c != '\n' && c != EOF)
        c = getchar();
}

, которая поможет сохранить ваш код в чистоте.Я позволю вам включить это выше.

В целом, вы можете сделать что-то вроде следующего:

#include <stdio.h>

#define SIZE  10    /* good form defining a constant! */
#define MAXC 256    /* max characters for buffer */

int main (void) {

    int nwords = 0,         /* number of words to enter */
        words = 0,          /* each word */
        wc = 0;             /* word count */
    char buf[MAXC] = "";    /* buffer of sufficient size for input */

    for (;;) {  /* loop continually until valid input or user cancels */
        printf ("number of words to enter? [1-%d]: ", SIZE);
        if (!fgets (buf, MAXC, stdin)) {    /* validate ALL user input */
            fputs ("(user canceled input)\n", stderr);
            return 1;
        }
        /* validate length < MAXC - 1 and buf[length-1] == '\n' here */

        if (sscanf (buf, "%d", &nwords) != 1) { /* sscanf for conversion */
            fputs ("  error: invalid conversion to int.\n", stderr);
            continue;
        }
        if (nwords < 1 || SIZE < nwords)
            fprintf (stderr, " %2d out of valid range.\n", nwords);
        else 
            break;
    }

    printf ("\nnumber of words entered: %d\n", nwords);
    for (; wc < nwords;) {  /* loop continually  */
        int rtn = 0;    /* scanf return */
        printf ("please enter a number between 1 and %d: ", SIZE);
        rtn = scanf ("%d", &words);     /* valdate ALL user input */
        if (rtn == EOF) {           /* handle EOF (manual) */
            fputs ("(user canceled input)\n", stderr);
            break;
        }
        else if (rtn == 0) {    /* handle "matching failure" */
            int c = getchar();  /* remove offending chars from stdin */
            while (c != '\n' && c != EOF)
                c = getchar();
            fputs ("  error: invalid integer input\n", stderr);
            continue;
        }
        else {  /* valid integer received */
            int c = getchar();      /* remove any extra chars from stdin */
            while (c != '\n' && c != EOF)
                c = getchar();
            if (words < 1 || SIZE < words)  /* validate in-range */
                fprintf (stderr, " %2d - invalid! (1 < valid < %d)\n", 
                        words, SIZE);
            else    /* good input, increment word count */
                printf (" word[%2d]: %3d\n", ++wc, words);
        }
    }
}

Пример использования / Вывод

$ ./bin/getintstdin
number of words to enter? [1-10]: five, maybe six?
  error: invalid conversion to int.
number of words to enter? [1-10]: -2
 -2 out of valid range.
number of words to enter? [1-10]: 3

number of words entered: 3
please enter a number between 1 and 10: two? three?
  error: invalid integer input
please enter a number between 1 and 10: 2
 word[ 1]:   2
please enter a number between 1 and 10: -2
 -2 - invalid! (1 < valid < 10)
please enter a number between 1 and 10: 11
 11 - invalid! (1 < valid < 10)
please enter a number between 1 and 10: 3
 word[ 2]:   3
please enter a number between 1 and 10: 4
 word[ 3]:   4

Обратите внимание на все недопустимые входные данные выше и то, как код обрабатывает каждый из них.До тех пор, пока ввод не превышает 255 символов с fgets, код будет корректно реагировать на входы, которые не являются допустимыми целыми числами (независимо от того, сколько их задано), и он будет реагировать на целочисленные входные данные, выходящие за пределы допустимого диапазона.

Код не намного длиннее, чем код, который вы опубликовали, но он рассматривает возможные условия возникновения ошибок, а затем обрабатывает ошибки.Когда вы все сводите, это и есть кодирование.Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

...