Как использовать fgets() для безопасной обработки пользовательского ввода более одного раза

Извините, если дублирую, но я перепробовал ВСЕ и не могу понять, почему этот код продолжает ломаться. Наивысшей приоритетной целью было сделать так, чтобы этот код безопасно обрабатывал ввод или все, что пользователь может ввести в консоль, не ломая его. Однако мне также нужно, чтобы он мог запускаться более одного раза. fgets() не позволит мне сделать это, так как он продолжает где-то читать '\n' и не позволяет мне вводить ввод более одного раза, когда он достигает конца цикла do/while. Я пробовал fflushing stdin, пробовал scanf("%d *[^\n]"); и просто обычный scanf("%d *[^\n]");, но ни один из них не работает, и, по сути, они ломают код! Я использовал этот веб-сайт, чтобы попытаться получить код "Безопасная обработка ввода". работать, но я не совсем понимаю, что они делают. Я пытался накрутить (орфографию?) как мог, но не уверен, правильно ли я это сделал. Я что-то пропустил? Не думал, что такая, казалось бы, простая проблема может доставить столько головной боли! >_‹

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

using namespace std;

#define BUF_LEN 100
#define SPACE 32
#define SPCL_CHAR1F 33
#define SPCL_CHAR1L 47
#define SPCL_CHAR2F 58
#define SPCL_CHAR2L 64
#define SPCL_CHAR3F 91
#define SPCL_CHAR3L 96
#define NUMF  48
#define NUML 57
#define UC_CHARF 65
#define UC_CHARL 90
#define LC_CHARF 97
#define LC_CHARL 122

void main ()
{
    char* buffer;
    int SpcCounter=0, SpclCounter=0, NumCounter=0,LcCounter=0, UcCounter=0;
    char line[BUF_LEN],response[4];
    char*input="";
    bool repeat=false;

    do
    {
        for(int i=0;i<BUF_LEN;i++)
        {
            line[i]=NULL;
        }
        buffer=NULL;
        printf("Enter your mess of characters.\n");

        buffer=fgets(line,BUF_LEN,stdin);

        //To handle going over the buffer limit: BROKEN
        if(buffer!=NULL)
        {
            size_t last=strlen(line)-1;

            if(line[last]=='\n')

                line[last]='\0';
            else
            {
                fscanf(stdin,"%c *[^\n]");
            }
        }


        for(int i=0;i<BUF_LEN;i++)
        {
            char temp=buffer[i];
            if(temp==SPACE||temp==255)
                SpcCounter++;
            else if((temp >= SPCL_CHAR1F && temp <= SPCL_CHAR1L)||/*Special  characters*/
                (temp >= SPCL_CHAR2F && temp <= SPCL_CHAR2L)||
                (temp >= SPCL_CHAR3F && temp <= SPCL_CHAR3L))
                SpclCounter++;
            else if (temp >=NUMF && temp <= NUML)/*Numbers*/
                NumCounter++;
            else if (temp >= UC_CHARF && temp <= UC_CHARL)/*Uppercase letters*/
                UcCounter++;
            else if (temp >= LC_CHARF && temp <= LC_CHARL)/*Lowercase letters*/
                LcCounter++;
        }

        printf("There were %i space%s, %i special character%s, %i number%s, and %i letter%s,\n"
            "consisting of %i uppercase letter%s and %i lowercase.\n",
            SpcCounter,(SpcCounter==1?"":"s"),SpclCounter,(SpclCounter==1?"":"s"), NumCounter,(NumCounter==1?"":"s"),UcCounter+LcCounter,
            (UcCounter+LcCounter==1?"":"s"), UcCounter,(UcCounter==1?"":"s"), LcCounter);
        printf("Would you like to do this again? (yes/no)");

        input=fgets(response,4,stdin);
        /*
        ALL BROKEN
        if(input!=NULL)
        {
            size_t last=strlen(response)-1;

            if(response[last]=='\n')
                response[last]='\0';
            else
            {
                fscanf(stdin,"%*[^\n]");
                fscanf(stdin,"%c");
            }
        }
        */

        //To capitalize the letters
        for(int i=0;i<4;i++)
        {
            char* temp=&response[i];
            if (*temp >= LC_CHARF && *temp <= LC_CHARL)
                *temp=toupper(*temp);//Capitalize it
        }
        //To set repeat: WORKS, BUT WEIRD
        repeat=!strncmp(input,"YES",4);
    }
    while(repeat);
}

person Skello    schedule 06.01.2014    source источник
comment
stackoverflow.com/questions/4023895/   -  person paxdiablo    schedule 07.01.2014
comment
Этого было недостаточно. У меня была проблема с пониманием того, как работает каждая из разных частей, а не просто с получением ответа. Если вы помните из поста, у меня был ответ, но мне не хватало его понимания, поэтому я и пришел сюда. Кроме того, в вашей ссылке не рассматривается возможность обработки цикла while с использованием тех же fgets, что с вашим кодом, похоже, не работает.   -  person Skello    schedule 07.01.2014
comment
Чтобы быть более точным, мне нужно знать, почему мне нужно использовать fflush для чего-либо, как использование getchar() сбрасывает что-либо из буфера, что на самом деле делает fflush(stdin) (это не в моем выложенном коде, но он у меня был после каждого запроса на ввод ), и как мне избавиться от того, что по какой-то причине ОСТАЕТСЯ В БУФЕРЕ, даже после того, как я его сбросил, очистил буферы и сделал все scanf("%*[^\n]").   -  person Skello    schedule 07.01.2014
comment
Мудрец, очистка входных потоков - это поведение undefined. Кроме того, ваша жизнь станет намного проще, если вы не будете пытаться смешивать ввод строк и символов. Этот ответ, на который я ссылался, предоставляет все необходимое для линейного ввода, тщательно проверенного десятилетиями.   -  person paxdiablo    schedule 07.01.2014
comment
И это работает с непрерывными вызовами, если вы не возитесь с вводом символов между ними. Это было чем-то, что вы могли рассмотреть как возможность, пока ждали более полного ответа (вот почему это был комментарий).   -  person paxdiablo    schedule 07.01.2014
comment
Приношу свои извинения за такую ​​резкость. Целый день бьюсь с этой проблемой, так и не поняв, что делаю. Все эти вещи, которые я пробовал, кажутся либо бесполезными, либо противодействующими друг другу. Кроме того, я затрудняюсь сформулировать свой вопрос. Вы не возражаете, если я просто взорвусь на секунду: расскажу вам всю проблему или просто опубликую ее в отдельной теме?   -  person Skello    schedule 07.01.2014
comment
позвольте мне более подробно рассмотреть вопрос сегодня вечером (я еду домой). Я посмотрю, смогу ли я ответить подробно. И не бойтесь меня обидеть, моя кожа достаточно толстая, чтобы остановить нейтрино :-)   -  person paxdiablo    schedule 07.01.2014
comment
Мудрец, я добавил ответ, который (надеюсь) можно понять. Взгляните на него и не стесняйтесь задавать любые вопросы в комментариях, и я постараюсь расширить те области, которые, по вашему мнению, недостаточны.   -  person paxdiablo    schedule 07.01.2014


Ответы (1)


Для безопасного и защищенного пользовательского ввода в C (и в C++, если я использую строки в стиле C) я обычно возвращаюсь к моей старой любимой функции getLine:

// Use stdio.h and string.h for C.
#include <cstdio>
#include <cstring>

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Output prompt then get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

Эта функция:

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

Это довольно солидный фрагмент кода, который тестировался много лет и является хорошим решением проблемы пользовательского ввода.

Что касается того, как вы это называете для целей вашего вопроса, я бы добавил что-то очень похожее на то, что у вас есть, но с использованием функции getLine вместо прямого вызова fgets и возни с результатами. Сначала несколько заголовков и те же определения:

#include <iostream>
#include <cstdlib>
#include <cctype>

#define BUF_LEN 100
#define SPACE 32
#define SPCL_CHAR1F 33
#define SPCL_CHAR1L 47
#define SPCL_CHAR2F 58
#define SPCL_CHAR2L 64
#define SPCL_CHAR3F 91
#define SPCL_CHAR3L 96
#define NUMF 48
#define NUML 57
#define UC_CHARF 65
#define UC_CHARL 90
#define LC_CHARF 97
#define LC_CHARL 122

Затем первая часть main собирает допустимую строку (с использованием функции) для оценки:

int main () {
    int SpcCounter, SpclCounter, NumCounter, LcCounter, UcCounter;
    char line[BUF_LEN], response[4];
    bool repeat = false;

    do {
        SpcCounter = SpclCounter = NumCounter = LcCounter = UcCounter = 0;

        // Get a line until valid.

        int stat = getLine ("\nEnter a line: ", line, BUF_LEN);
        while (stat != OK) {
            // End of file means no more data possible.

            if (stat == NO_INPUT) {
                cout << "\nEnd of file reached.\n";
                return 1;
            }

            // Only other possibility is "Too much data on line", try again.

            stat = getLine ("Input too long.\nEnter a line: ", line, BUF_LEN);
        }

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

        for (int i = 0; i < strlen (line); i++) {
            char temp=line[i];
            if(temp==SPACE||temp==255)
                SpcCounter++;
            else if((temp >= SPCL_CHAR1F && temp <= SPCL_CHAR1L)||
                (temp >= SPCL_CHAR2F && temp <= SPCL_CHAR2L)||
                (temp >= SPCL_CHAR3F && temp <= SPCL_CHAR3L))
                SpclCounter++;
            else if (temp >=NUMF && temp <= NUML)
                NumCounter++;
            else if (temp >= UC_CHARF && temp <= UC_CHARL)
                UcCounter++;
            else if (temp >= LC_CHARF && temp <= LC_CHARL)
                LcCounter++;
        }

        printf("There were %i space%s, %i special character%s, "
               "%i number%s, and %i letter%s,\n"
               "consisting of %i uppercase letter%s and "
               "%i lowercase.\n",
            SpcCounter,  (SpcCounter==1?"":"s"),
            SpclCounter, (SpclCounter==1?"":"s"),
            NumCounter, (NumCounter==1?"":"s"),
            UcCounter+LcCounter, (UcCounter+LcCounter==1?"":"s"),
            UcCounter, (UcCounter==1?"":"s"),
            LcCounter);

Затем, наконец, аналогично тому, как указано выше, чтобы спросить, хочет ли пользователь продолжить.

        // Get a line until valid yes/no, force entry initially.

        *line = 'x';
        while ((*line != 'y') && (*line != 'n')) {
            stat = getLine ("Try another line (yes/no): ", line, BUF_LEN);

            // End of file means no more data possible.

            if (stat == NO_INPUT) {
                cout << "\nEnd of file reached, assuming no.\n";
                strcpy (line, "no");
            }

            // "Too much data on line" means try again.

            if (stat == TOO_LONG) {
                cout << "Line too long.\n";
                *line = 'x';
                continue;
            }

            // Must be okay: first char not 'y' or 'n', try again.

            *line = tolower (*line);
            if ((*line != 'y') && (*line != 'n'))
                cout << "Line doesn't start with y/n.\n";
        }
    } while (*line == 'y');
}   

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

Вы можете дополнительно улучшить код, удалив явные проверки диапазона и используя правильные классы символов с cctype(), например isalpha() или isspace(). Это сделало бы его более переносимым (для систем, отличных от ASCII), но я оставлю это упражнение на потом.

Пример запуска программы:

Enter a line: Hello, my name is Pax and I am 927 years old!
There were 10 spaces, 2 special characters, 3 numbers, and 30 letters,
consisting of 3 uppercase letters and 27 lowercase.
Try another line (yes/no): yes

Enter a line: Bye for now
There were 2 spaces, 0 special characters, 0 numbers, and 9 letters,
consisting of 1 uppercase letter and 8 lowercase.
Try another line (yes/no): no
person paxdiablo    schedule 07.01.2014
comment
Спасибо за подробный ответ. Это было очень полезно. Я также рассмотрю эти методы cctype(), isalpha() и isspace(), поскольку я не знал, что они существуют. У меня сейчас только один вопрос: что именно сделал scanf("%*[^\n])? Я не понимаю из того, что читал на cplusplus.com. - person Skello; 07.01.2014
comment
Я искал комплексную реализацию чтения строк более получаса, и это, безусловно, лучший вариант. - person sosmo; 28.04.2015
comment
C getline является расширением GCC. На Солярисе даже с -std=c99 -D_GNU_SOURCE не очень помогает. - person jww; 13.04.2019
comment
@jww, getLine, на который я ссылаюсь (обратите внимание на заглавную L), принадлежит мне и предоставлено в ответе. Он не имеет никакого отношения (кроме похожего имени) к функции getline, предоставляемой gcc (или glibc). Он должен работать на любом стандартном компиляторе. - person paxdiablo; 13.04.2019