Почему scanf() вызывает бесконечный цикл в этом коде?
У меня есть небольшая C-программа, которая просто читает числа из стандартного ввода, по одному в каждом цикле цикла. Если пользователь вводит некоторое значение NaN, на консоль должна быть выведена ошибка, и запрос ввода должен вернуться снова. При вводе "0" цикл должен завершиться, а количество заданных положительных / отрицательных значений должно быть выведено на консоль. Вот программа:
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
printf("Err...\n");
continue;
}
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
Моя проблема в том, что при вводе некоторого числа (например, "а") это приводит к бесконечной циклической записи "-> Err..." снова и снова. Я предполагаю, что это проблема scanf(), и я знаю, что эту функцию можно заменить на более безопасную, но этот пример предназначен для начинающих, знающих только о printf/scanf, if-else и loop.
Я уже прочитал ответы на этот вопрос и пролистал другие вопросы, но на самом деле ничего не решает эту конкретную проблему.
13 ответов
scanf
потребляет только тот ввод, который соответствует строке формата, и возвращает количество использованных символов. Любой символ, который не соответствует строке формата, вызывает его остановку сканирования и оставляет недопустимый символ в буфере. Как говорили другие, перед тем, как продолжить, вам все еще нужно удалить неверный символ из буфера. Это довольно грязное исправление, но оно удалит оскорбительные символы из вывода.
char c = '0';
if (scanf("%d", &number) == 0) {
printf("Err. . .\n");
do {
c = getchar();
}
while (!isdigit(c));
ungetc(c, stdin);
//consume non-numeric chars from buffer
}
edit: исправил код для удаления всех нечисловых символов за один раз. Больше не будет печатать несколько "Errs" для каждого нечислового символа.
Вот довольно хороший обзор scanf.
scanf()
покидаетa
"все еще во входном буфере для следующего раза. Вы должны вероятно использовать getline()
читать строку независимо от того, что и затем анализировать ее strtol()
или аналогичные
(Да, getline()
специфичен для GNU, а не для POSIX. И что? Вопрос помечен как "gcc" и "linux". getline()
это также единственный разумный вариант, чтобы прочитать строку текста, если вы не хотите делать все вручную.)
Я думаю, что вы просто должны очистить буфер, прежде чем продолжить цикл. Нечто подобное, вероятно, сделало бы эту работу, хотя я не могу проверить, что я пишу отсюда:
int c;
while((c = getchar()) != '\n' && c != EOF);
На некоторых платформах (особенно Windows и Linux) вы можете использовать fflush(stdin);
:
#include <stdio.h>
int main(void)
{
int number, p = 0, n = 0;
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
fflush(stdin);
printf("Err...\n");
continue;
}
fflush(stdin);
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
Вместо того, чтобы использовать scanf()
и иметь дело с буфером, имеющим недопустимый символ, используйте fgets()
а также sscanf()
,
/* ... */
printf("0 to quit -> ");
fflush(stdout);
while (fgets(buf, sizeof buf, stdin)) {
if (sscanf(buf, "%d", &number) != 1) {
fprintf(stderr, "Err...\n");
} else {
work(number);
}
printf("0 to quit -> ");
fflush(stdout);
}
/* ... */
Из-за проблем с scanf
на что указывают другие ответы, вам стоит подумать об использовании другого подхода. Я всегда находил scanf
слишком ограничен для любого серьезного ввода и чтения. Лучше просто прочитать целые строки с fgets
а затем работает над ними с функциями, такими как strtok
а также strtol
(Который, кстати, правильно проанализирует целые числа и скажет вам точно, где начинаются недопустимые символы).
Решение: вам нужно добавитьfflush(stdin);
когда 0
возвращается из scanf
.
Причина: кажется, что при обнаружении ошибки входной символ остается в буфере, поэтому каждый разscanf
называется, он просто пытается обработать недопустимый символ, но никогда не удаляет его из буфера. Когда ты звонишьfflush
, входной буфер (stdin) будет очищен, поэтому недопустимый символ больше не будет обрабатываться повторно.
Ваша программа изменена : Ниже представлена ваша программа, измененная с необходимыми изменениями.
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
fflush(stdin);
printf("Err...\n");
continue;
}
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
У меня была похожая проблема. Я решил только с помощью Scanf.
Input "abc123<Enter>"
чтобы увидеть, как это работает.
#include <stdio.h>
int n, num_ok;
char c;
main() {
while (1) {
printf("Input Number: ");
num_ok = scanf("%d", &n);
if (num_ok != 1) {
scanf("%c", &c);
printf("That wasn't a number: %c\n", c);
} else {
printf("The number is: %d\n", n);
}
}
}
// all you need is to clear the buffer!
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
char clearBuf[256]; //JG:
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
fgets(stdin, 256, clearBuf); //JG:
printf("Err...\n");
continue;
}
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
Чтобы частично решить вашу проблему, я просто добавляю эту строку после scanf:
fgetc(stdin); /* to delete '\n' character */
Ниже ваш код со строкой:
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
while (1) {
printf("-> ");
if (scanf("%d", &number) == 0) {
fgetc(stdin); /* to delete '\n' character */
printf("Err...\n");
continue;
}
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
Но если вы введете более одного символа, программа продолжится один за другим до символа "\n".
Итак, я нашел здесь решение: как ограничить длину ввода с помощью scanf
Вы можете использовать эту строку:
int c;
while ((c = fgetc(stdin)) != '\n' && c != EOF);
Попробуйте использовать это:
if (scanf("%d", &number) == 0) {
printf("Err...\n");
break;
}
это работало нормально для меня... попробуйте это... оператор continue не подходит, так как Err.. должен выполняться только один раз. Итак, попробуйте сломать, что я проверял... это работало нормально для вас.. я проверял....
Когда вводится не число, возникает ошибка, и это число все еще сохраняется во входном буфере. Вы должны пропустить это. Также даже эта комбинация символов, как, например, 1a
будет читаться сначала как номер 1, я думаю, вы также должны пропустить такой ввод.
Программа может выглядеть следующим образом.
#include <stdio.h>
#include <ctype.h>
int main(void)
{
int p = 0, n = 0;
while (1)
{
char c;
int number;
int success;
printf("-> ");
success = scanf("%d%c", &number, &c);
if ( success != EOF )
{
success = success == 2 && isspace( ( unsigned char )c );
}
if ( ( success == EOF ) || ( success && number == 0 ) ) break;
if ( !success )
{
scanf("%*[^ \t\n]");
clearerr(stdin);
}
else if ( number > 0 )
{
++p;
}
else if ( number < n )
{
++n;
}
}
printf( "\nRead %d positive and %d negative numbers\n", p, n );
return 0;
}
Вывод программы может выглядеть так
-> 1
-> -1
-> 2
-> -2
-> 0a
-> -0a
-> a0
-> -a0
-> 3
-> -3
-> 0
Read 3 positive and 3 negative numbers
У меня была такая же проблема, и я нашел несколько хакерское решение. я использую fgets()
читать входные данные, а затем скормить это sscanf()
, Это неплохое решение для проблемы бесконечного цикла, и с помощью простого цикла я говорю C искать любой не числовой символ. Код ниже не позволит вводить данные, как 123abc
,
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int main(int argc, const char * argv[]) {
char line[10];
int loop, arrayLength, number, nan;
arrayLength = sizeof(line) / sizeof(char);
do {
nan = 0;
printf("Please enter a number:\n");
fgets(line, arrayLength, stdin);
for(loop = 0; loop < arrayLength; loop++) { // search for any none numeric charcter inisde the line array
if(line[loop] == '\n') { // stop the search if there is a carrage return
break;
}
if((line[0] == '-' || line[0] == '+') && loop == 0) { // Exculude the sign charcters infront of numbers so the program can accept both negative and positive numbers
continue;
}
if(!isdigit(line[loop])) { // if there is a none numeric character then add one to nan and break the loop
nan++;
break;
}
}
} while(nan || strlen(line) == 1); // check if there is any NaN or the user has just hit enter
sscanf(line, "%d", &number);
printf("You enterd number %d\n", number);
return 0;
}
Привет, я знаю, что это старая ветка, но я только что закончил школьное задание, где столкнулся с той же проблемой. Мое решение состоит в том, что я использовал get (), чтобы узнать, что оставил scanf().
Здесь OP код слегка переписан; вероятно, бесполезно для него, но, возможно, это поможет кому-то еще там.
#include <stdio.h>
int main()
{
int number, p = 0, n = 0;
char unwantedCharacters[40]; //created array to catch unwanted input
unwantedCharacters[0] = 0; //initialzed first byte of array to zero
while (1)
{
printf("-> ");
scanf("%d", &number);
gets(unwantedCharacters); //collect what scanf() wouldn't from the input stream
if (unwantedCharacters[0] == 0) //if unwantedCharacters array is empty (the user's input is valid)
{
if (number > 0) p++;
else if (number < 0) n++;
else break; /* 0 given */
}
else
printf("Err...\n");
}
printf("Read %d positive and %d negative numbers\n", p, n);
return 0;
}
Добрый вечер. Я недавно столкнулся с той же проблемой, и я нашел решение, которое может помочь многим парням. Ну, на самом деле функция "scanf" оставляет буфер в памяти... и поэтому вызывается бесконечный цикл. Таким образом, вам действительно нужно "сохранить" этот буфер в другой переменной, ЕСЛИ ваш начальный scanf содержит значение "null". Вот что я имею в виду:
#include <stdio.h>
int n;
char c[5];
main() {
while (1) {
printf("Input Number: ");
if (scanf("%d", &n)==0) { //if you type char scanf gets null value
scanf("%s", &c); //the abovementioned char stored in 'c'
printf("That wasn't a number: %s\n", c);
}
else printf("The number is: %d\n", n);
}
}
Очистите входной буфер перед сканированием:
while(getchar() != EOF) continue;
if (scanf("%d", &number) == 0) {
...
Я собирался предложить fflush(stdin)
, но, по-видимому, это приводит к неопределенному поведению.
В ответ на ваш комментарий, если вы хотите, чтобы приглашение появилось, вы должны очистить буфер вывода. По умолчанию это происходит только при печати новой строки. Подобно:
while (1) {
printf("-> ");
fflush(stdout);
while(getchar() != EOF) continue;
if (scanf("%d", &number) == 0) {
...