Почему scanf () вызывает бесконечный цикл в этом коде? - PullRequest
44 голосов
/ 11 ноября 2009

У меня есть небольшая 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;
}

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

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

Ответы [ 14 ]

36 голосов
/ 11 ноября 2009

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.

7 голосов
/ 11 ноября 2009

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

int c;
while((c = getchar()) != '\n' && c != EOF);
6 голосов
/ 11 ноября 2009

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

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

3 голосов
/ 25 октября 2012

На некоторых платформах (особенно 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;
}
3 голосов
/ 14 сентября 2012

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

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

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

0 голосов
/ 12 мая 2019

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

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

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