Должен ли я использовать fgets или scanf с ограниченным вводом в c?

0

Вопрос

Должен ли я использовать fgets или отформатирован scanf Нравится scanf("%10s", foo).

За исключением того, что scanf не читает пустые символы, которые можно решить и сделать больше с помощью scanset, тогда почему я должен использовать fgets вместо scanf?

Любая помощь будет признательна.


Редактировать

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

c fgets input scanf
2021-11-23 13:53:00
4

Лучший ответ

5

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

Для пользовательского ввода на основе строк программа имеет смысл и интуитивно понятна для чтения одной строки ввода за раз. Это то, что функцияfgets делает (при условии, что буфер достаточно велик для хранения всей строки ввода).

Функцияscanf, с другой стороны, обычно не считывает по одной строке ввода за раз. Например, когда вы используете %s или %d спецификатор формата преобразования с scanf, он не будет потреблять всю строку ввода. Вместо этого он будет потреблять только столько входных данных, сколько соответствует спецификатору формата преобразования. Это означает, что символ новой строки в конце строки обычно не используется (что может легко привести к ошибкам программирования). Также, scanf позвонил с %d спецификатор формата преобразования будет учитывать такие входные данные, как 6sldf23dsfh2 в качестве допустимого ввода для числа 6, но любые дальнейшие призывы к scanf с тем же спецификатором произойдет сбой, если вы не удалите оставшуюся часть строки из входного потока.

Такое поведение scanf противоречит интуиции, в то время как поведение fgets интуитивно понятен при работе с пользовательским вводом на основе строк.

После использования fgets, вы можете использовать функциюsscanf в строке для анализа содержимого отдельной строки. Это позволит вам продолжать использовать наборы сканирования. Или вы можете проанализировать строку каким-либо другим способом. В любом случае, пока вы используете fgets вместо scanf для чтения входных данных вы будете обрабатывать по одной строке ввода за раз, что является естественным и интуитивно понятным способом работы с пользовательским вводом на основе строк.

Когда мы используем fgets что произойдет, если пользователь введет символов больше границы (я имею в виду много символов), приведет ли это к переполнению буфера? Тогда как с этим бороться?

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

2021-11-23 15:13:39

Большое вам спасибо, ваш ответ действительно полезен для меня.
Becker

Поведение функции fgets() интуитивно понятно только для входных данных, длина которых не превышает ожидаемого.
supercat
2

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

"...он скажет вам, удалось ли ему это или нет, но может сказать вам только приблизительно, где он потерпел неудачу, а вовсе не как или почему. У вас очень мало возможностей для исправления ошибок".
(джеймсдлин). Но в интересах достижения баланса, я начну с этой дискуссии.

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

Один из немногих случаев, когда можно использовать форму scanf(): fscanf (), может быть использован при преобразовании входных данных из очень контролируемого источника, т. Е. При чтении строго отформатированного файла с повторяющимися предсказуемыми полями.

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

Изменить: для решения дополнительного вопроса о переполнении:

"Еще одна вещь, которую я хочу спросить: даже когда мы используем fgets, что произойдет, если пользователь введет символов больше границы (я имею в виду много символов), приведет ли это к переполнению буфера? Тогда как с этим бороться?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm хорошо разработан для предотвращения переполнения буфера, просто правильно используя его параметры, например:

char buffer[100] = {0};
...
while fgets(buffer, sizeof buffer, stdin);  

Это предотвращает обработку ввода, превышающего размер буфера, что предотвращает переполнение.

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

char buffer[101] = {0};// includes space for 100 + 1 for NULL termination

scanf("%100s", buffer);
        ^^^  width specifier 

Однако с числами переполнение не так приятно использовать scanf(). Чтобы продемонстрировать, используйте этот простой код, вводя два значения, указанные в комментарии по одному за запуск:

int main(void)
{
    int val = 0;
    // test with 2147483647 & 2147483648
    scanf("%d", &val);
    printf("%d\n", val);
    
    return 0;
}

Для второго значения моя система выдает следующее:

NON-FATAL RUN-TIME ERROR: "test.c", line 11, col 5, thread id 22832: Function scanf: (errno == 34 [0x22]). Range error `

Здесь вам нужно прочитать строку, а затем выполнить преобразование строки в число с помощью одного из strto_() функции: strtol(), strtod(), ...). Оба включают возможность проверки на переполнение перед тем, как вызвать предупреждение или ошибку во время выполнения. Обратите внимание, что с помощью atoi(), atod() также не защитит от переполнения.

2021-11-23 14:10:20

Спасибо, я действительно ценю ваш ответ.
Becker

Вопрос "полон мнений"? Я должен с уважением не согласиться. Это не вопрос мнения, что scanf почти полностью бесполезен, в лучшем случае хорош для чтения отдельных простых входных данных в программах ввода-вывода на C, но непомерно сложен для выполнения чего-либо отдаленно сложного — это очевидные факты! :-)
Steve Summit
1

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

Правда в том, что часто вы на самом деле не знаете, как долго будет вводиться пользователем. Вы можете обойти это с помощью fgets, я надеюсь, что это будет достаточно большой буфер, но это не совсем эллегантно. Вместо этого вы часто хотите иметь динамический буфер, который станет достаточно большим для хранения любого пользовательского ввода. И вот когда getline в игру вступает функция. Он используется для считывания любого количества символов от пользователя, пока не встретится \n. По сути, он загружает всю строку в вашу память в виде строки.

size_t getline(char **lineptr, size_t *n, FILE *stream);

Эта функция принимает указатель на динамически выделяемую строку в качестве первого аргумента, указатель на размер выделенного буфера в качестве второго аргумента и поток в качестве третьего аргумента. (вы, по сути, разместите stdin там для ввода в командной строке). И возвращает количество прочитанных символов, включая \n в конце, но не завершающее значение null.

Здесь вы можете увидеть пример использования этой функции:

int main() {

printf("Input Something:\n");  // asking user for input

size_t length = 10;                   // creating "base" size of our buffer
char *input_string = malloc(length);  // allocating memory based on our initial buffer size
size_t length_read = getline(&input_string, &length, stdin);  // loading line from console to input_string
// now length_read contains how much characters we read
// and length contains new size of our buffer (if it changed during the getline execution)

printf("Characters read (including end of line but not null at the end)"
       ": %lu, current size of allocated buffer: %lu string: %s"
       , length_read, length, input_string);

free(input_string);    // like any other dynamically-allocated pointer, you must free it after usage
return 0;
}

Конечно, использование этой функции требует базовых знаний об указателях и динамической памяти в C, однако несколько более сложная природа getline это определенно того стоит, из-за обеспечиваемой безопасности и гибкости.

Вы можете прочитать больше об этой функции и других функциях ввода, доступных в C, на этом веб-сайте: https://www.studymite.com/blog/strings-in-c Я считаю, что в нем довольно хорошо обобщены тонкости ввода C.

2021-11-23 19:18:00

Спасибо за ваш совет и ссылку, это мне очень помогает.
Becker
1

Если у вас есть, например, массив символов, объявленный как

char s[100];

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

scanf( "%99[^\n]", s );

или fgets Нравится:

fgets( s, sizeof( s ), stdin );

Разница между этими двумя вызовами заключается в том, что вызов scanf не считывает символ новой строки '\n' из входного буфера. Пока fgets считывает символ новой строки '\n' если в массиве символов достаточно места.

Чтобы удалить новый символ строки '\n' это сохраняется в массиве символов после использования fgets вы можете написать, например:

s[ strcspn( s, "\n" ) ] = '\0';

Если входная строка содержит более 99 символов, то оба вызова считывают только 99 символов и добавляют последовательность символов с завершающим нулевым символом '\0'. Все оставшиеся символы по-прежнему будут находиться во входном буфере.

Существует проблема с fgets. Например, если раньше fgets там используется scanf как, например:

scanf( "%d", &x );
fgets( s, sizeof( s ), stdin );

и пользовательский ввод является:

10
Hello World

затем раздался зов fgets будет считываться только символ новой строки '\n' который сохраняется в буфере после нажатия клавиши Enter, когда целочисленное значение в вызове scanf был прочитан.

В этом случае вам нужно написать код, который удалит символ новой строки '\n' прежде чем позвонить fgets.

Вы можете сделать это, например, следующим образом:

scanf( "%d", &x );
scanf( " " );
fgets( s, sizeof( s ), stdin );

Если вы используете scanf тогда в такой ситуации вы можете написать:

scanf( "%d", &x );
scanf( " %99[^\n]", s );
       ^^ 
2021-11-23 14:05:15

На других языках

Эта страница на других языках

Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................