Почему scanf () вызывает бесконечный цикл в этом коде?

Мы имеем небольшую C-программу, которая просто читает числа из стандартного ввода, по одному в каждом цикле цикла. Если пользователь вводит некоторое значение NaN, на консоль должна быть выведена ошибка, и запрос ввода должен вернуться снова. На входе "0"цикл должен закончиться, а количество заданных положительных / отрицательных значений должно быть выведено на консоль. Вот'С программой:

#include 

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

Моя проблема в том, что при вводе некоторого номера (например, "а"), это приводит к записи бесконечного цикла "-> Эээ ...» вновь и вновь. Я думаю этоs проблема с scanf (), и я знаю, что эту функцию можно заменить на более безопасную, но этот пример предназначен для начинающих, знающих только о printf / scanf, if-else и loops.I '

мы уже прочитали ответы наэтот вопрос и пролистал другие вопросы, но на самом деле ничего не ответило на эту конкретную проблему.

 Jonathan Leffler15 сент. 2016 г., 07:32
Смотрите такжеС помощью .fflush(stdin)
 Jonathan Leffler11 нояб. 2009 г., 23:34
Многие тесно связанные вопросы SO, в том числе:stackoverflow.com/questions/1669821
 user20878513 нояб. 2009 г., 21:51
В ответ на все ответы и подсказки: добавляем while (getchar ()! = '\ П»); до "Продолжить" внутри if-оператора работает очень хорошо для меня и (надеюсь) решает все / большинство проблем. Далее этоРазумно объяснимо для начинающих :).

Ответы на вопрос(13)

попробуйте использовать это:

if (scanf("%d", &number) == 0) {
        printf("Err...\n");
        break;
    }

это работало нормально для меня ... попробуйте это ..Продолжить Заявление не подходит какErr .. должен выполняться только один раз. так что постарайтесьперерыв который я проверял ... это работало нормально для вас .. я проверял ....

Вместо того, чтобы использовать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);
    }
/* ... */
 Roman Nikitchenko11 нояб. 2009 г., 17:55
fgets () читает некоторый буфер и, если это не такt содержит формат с самого начала, вся строка отбрасывается. Это может быть неприемлемо (но может быть желательным, это зависит от требований).

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

if (scanf("%d", &number) == 0) {
  printf("Err. . .\n");
  do {
    c = getchar();
  }
  while (!isdigit(c));
  ungetc(c, stdin);
  //consume non-numeric chars from buffer
}

редактировать:исправлен код для удаления всех нечисловых символов за один раз. Победил'распечатать несколькозаблуждается» для каждого нечислового символа больше.

Вот довольно хороший обзор scanf.

 jergason11 нояб. 2009 г., 17:44
Да, это довольно гетто. Я немного подправлю.
 caf11 нояб. 2009 г., 22:21
Теперь, если вход "абы-10", он будет некорректно удалять знак минус с ввода и читать "10" как следующий номер.
 ilgaar11 сент. 2015 г., 17:16
Это по-прежнему приводит к нескольким строкам ввода, попробуйте4t а также ,t44t дам тебе-> Err. . . а такжеt4 даже не выдаст никаких ошибок, но все же несколько строк ввода:-> ->
 Michael Armbruster24 мая 2015 г., 17:39
Я знаю это'старый, но просто измените его наwhile (!isdigit(c) && c != '-');, это также должно помочь со знаками минус.
 Teddy11 нояб. 2009 г., 16:57
Если вход "а», этот код напечатаетErr. , «. три раза.
 ilgaar22 янв. 2019 г., 14:01
Ты лучше поставьgetchar() как раз перед printf ("Err. , . \ П»);

scanf() оставляет ""a еще в буфере ввода в следующий раз. Вы, вероятно, должны использоватьgetline() читать строку независимо от того, что и затем анализировать ееstrtol() или аналогичные

(Да,getline() специфичен для GNU, а не для POSIX. И что? Вопрос помеченНКУ» а также "Linux».getline() также является единственным разумным вариантом для чтения строки текста, если вы не хотите делать все вручную.)

 Tim Post11 нояб. 2009 г., 16:57
... POSIX кто-нибудь? -1.
 Rik Heywood11 нояб. 2009 г., 16:44
это мило ...
 Tim Post11 нояб. 2009 г., 16:58
Вы не можете полагаться на нестандартные расширения для чего-то столь же важного, как пользовательский ввод, не предоставляя их в своем собственном дереве в случае, если они не существуют. Если вы отредактируете свой ответ, чтобы отразить это, я отзову свой отрицательный голос.
 ilgaar02 дек. 2018 г., 06:40
предлагая использоватьgetline() нарушит переносимость кода.
 Tim Post11 нояб. 2009 г., 17:02
Также может помочь хотя бы подсказка, как включить такое расширение :)
 nyuszika7h06 сент. 2014 г., 18:14
@TimPostИ getline (), и getdelim () изначально были расширениями GNU. Они были стандартизированы в POSIX.1-2008.
 Tim Post11 нояб. 2009 г., 17:03
@ Andomar: Это была проблема с getline (), с которой я столкнулся;)
 Andomar11 нояб. 2009 г., 17:00
@tinkertim: вопрос указывает gcc на Linux, гарантируя, чтоstrtol доступен

scanf на что указывают другие ответы, вам стоит подумать об использовании другого подхода. Я'мы всегда находилиscanf слишком ограничен для любого серьезного ввода и чтения. Это'Лучше просто прочитать целые строки сfgets а затем работает над ними с функциями, такими какstrtok а такжеstrtol (Который BTW правильно проанализирует целые числа и скажет вам точно, где начинаются недопустимые символы).

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

int c;
while((c = getchar()) != '\n' && c != EOF);
 ilgaar02 дек. 2018 г., 06:38
Вышеуказанное осложнение вwhile заявление'условие не требуется.
 pmg11 нояб. 2009 г., 16:54
преждевременная оптимизация - корень зла ... но поменяйте константы:'\n' гораздо чаще появляется, чемEOF :)
 Andomar11 нояб. 2009 г., 17:05
Вы'буду надеятьсяEOF на 100% гарантированно появится; в противном случае у вас либо очень быстрая клавиатура, либо очень медленный процессор
 Lucas11 нояб. 2009 г., 17:02
@pmg: Конечно, почему бы и нет.
 Lucas11 нояб. 2009 г., 17:03
@pmg: у вас более 4000 повторений, вы можете свободно редактировать мои сообщения ...

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) {
    ...
 user20878511 нояб. 2009 г., 17:17
Добавление этого цикла while перед оператором if приводит к неправильному поведению программы. Чтобы быть точным, "->" подскажите нетt показано после первого ввода, может ли оно быть правильным или неправильным.
 pmg11 нояб. 2009 г., 17:29
Вашwhile цикл будет потреблять все,'\n' включен.
 user20878511 нояб. 2009 г., 17:31
Afaik fflush () не делаетодинаково работает в каждой системе. По крайней мере, на моем Linux-сервере fflush (stdout) непомогите показать "->" незамедлительный. Кроме того, вызов setvbuf () неЗдесь тоже не поможет.

Input "abc123" чтобы увидеть, как это работает.

#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);
        }
    }
}
</stdio.h>
 ilgaar11 сент. 2015 г., 16:51
Это все еще не• полностью решить проблему, поскольку при вводе комбинации буквенно-цифровых символов, например:6yInput Number: 6y приведет к:The number is: 6 Input Number: That wasn't a number: y программа считывает ввод за символом за символом, когда она находит числовой символ во вводе, она думает, что ввод является числом, и когда она находит не числовой, она думает, что это не число, но не может решить, что6y это не число в целом, и, конечно, в процессе из-за[Enter] ключ все еще присутствует в буфере, возникает та же проблема.

проблемаи я нашел несколько хакерское решение. я использую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;
}
</string.h></ctype.h></stdio.h>
 ilgaar22 янв. 2019 г., 12:52
@ M.Mscanf() не является нарушенной функцией, если операционная система нене знаю какscanf() работает и как его использовать, то оп наверное непрочитайте руководство поscans() а такжеscanf() не может быть обвинен в этом.
 M.M18 нояб. 2015 г., 05:09
scanf считается нарушенной функцией " - ну, это трудно использовать, ноsscanf также разделяет большинство тех же трудностей. В обоих случаях используйте очень осторожно.
 user220311715 янв. 2014 г., 15:15
Я вижу, что вы используете Goto, нет!»
 M.M22 янв. 2019 г., 13:12
мой текст в кавычках - это цитата из другого комментария, который с тех пор был удален

возникает ошибка, и это число все еще сохраняется во входном буфере. Вы должны пропустить это. Также даже эта комбинация символов, как, например,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;
}
</ctype.h></stdio.h>

Вывод программы может выглядеть так

-> 1
-> -1
-> 2
-> -2
-> 0a
-> -0a
-> a0
-> -a0
-> 3
-> -3
-> 0

Read 3 positive and 3 negative numbers

я знаю, что это старая ветка, но я только что закончил школьное задание, где столкнулся с той же проблемой. Мое решение состоит в том, что я использовал 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;
    }
</stdio.h>
 melpomene02 янв. 2019 г., 18:57
gets ужасно небезопасен и никогда не должен использоваться (по этой причине он был удален из стандарта C).
 midnightCoder04 янв. 2019 г., 11:45
Я согласен, что это опасный друг, и я использовал его здесь только для небольшого приложения (отсюда и субъективный массив из 40 символов). если бы рассматриваемая проблема была более объективной в своих требованиях, то да ^^.

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

#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);
    }
}
</stdio.h>
 melpomene02 янв. 2019 г., 18:56
scanf("%s", &c) это ошибка типа%s занимаетchar *неchar (*)[5], Кроме того, так как выне ограничивая количество прочитанных символов, это переполнение буфера, ожидающее выполнения. Простое отклонение ввода было бы намного лучшей идеей (%*s).

ать: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;
}
</stdio.h>
 ilgaar02 дек. 2018 г., 06:32
с помощьюfflush(stdin) приведет к неопределенному поведению и не будет работать переносимо.
 ramtoo04 июн. 2014 г., 05:30
Didn»у меня нет работы на Debian с GCC 4.8.2
 Dr Beco26 мая 2017 г., 18:18
Не работает для меня здесь с моей текущей версией.$ ldd --version даетldd (Debian GLIBC 2.19-18+deb8u9) 2.19, Это должно дать всю необходимую информацию. Кто-нибудь знает, почему?
 David C. Rankin07 окт. 2017 г., 01:12
fflush входной поток определяется только для входных потоков, связанных сдоступные для поиска файлы (например.,дисковые файлы,но не трубы или терминалы). POSIX.1-2001 не определяет поведение для очистки входных потоков, POSIX.1-2008 делает, но только описанным ограниченным способом.
 Jonathan Leffler04 янв. 2015 г., 08:31
Пожалуйста, прочитайтеС помощьюfflush(stdin) - особенно комментарии к вопросу - для информации об этом. Это работает на Windows, потому что Microsoft документирует это; это нея не работаю где-либо еще (насколько мне известно) на практике, несмотря на некоторую документацию, предполагающую обратное.
 Thomas Padron-McCarthy07 нояб. 2016 г., 09:35
Теперь он работает на Linux (или я должен сказать, glibc). Это нет, и я неНе знаю, когда они изменили это. Но в последний раз, когда я попробовал его на Mac, он разбился, и он не в стандарте, поэтому яМы добавили предупреждение о переносимости в этот ответ.

Ваш ответ на вопрос