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

У меня небольшая C-программа, которая просто считывает числа из stdin, по одному в каждом цикле цикла. Если пользователь вводит несколько 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;
}

Моя проблема заключается в том, что при вводе некоторого не числа (например, "a" ) это приводит к бесконечному циклу, пишущему "- > Err..." снова и снова. Я предполагаю, что это проблема scanf(), и я знаю, что эта функция может быть заменена более безопасной, но этот пример для новичков, зная только о printf/scanf, if-else и циклах.

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

Ответ 1

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" для каждого нечислового char больше.

Здесь - довольно хороший обзор scanf.

Ответ 2

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

int c;
while((c = getchar()) != '\n' && c != EOF);

Ответ 3

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

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

Ответ 4

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

Ответ 5

Вместо использования 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);
    }
/* ... */

Ответ 6

У меня была аналогичная проблема. Я решил только с помощью 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);
        }
    }
}

Ответ 7

На некоторых платформах (особенно 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;
}

Ответ 8

Решение: Вам нужно добавить 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;
}

Ответ 9

У меня была такая же проблема , и я нашел несколько хакерское решение. Я использую 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;
}

Ответ 10

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

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

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

Ответ 11

Когда вводится не номер, возникает ошибка, а номер не сохраняется во входном буфере. Вы должны пропустить это. Также даже эта комбинация символов, например, 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

Ответ 12

Чтобы частично решить вашу проблему, я просто добавляю следующую строку после 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);

Ответ 13

Сбросьте входной буфер перед сканированием:

while(getchar() != EOF) continue;
if (scanf("%d", &number) == 0) {
    ...

Я собирался предложить fflush(stdin), но, по-видимому, это приводит к undefined поведение.

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

while (1) {
    printf("-> ");
    fflush(stdout);
    while(getchar() != EOF) continue;
    if (scanf("%d", &number) == 0) {
    ...

Ответ 14

Привет, я знаю, что это старый поток, но я только что закончил школьное задание, где столкнулся с этой проблемой. Мое решение состоит в том, что я использовал gets(), чтобы забрать то, что 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 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;
    }

Ответ 15

Добрый вечер. У меня недавно была одна и та же проблема, и я нашел решение, которое может помочь многим парням. Ну, на самом деле функция "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);
    }
}