while(scanf("%d", &a) != 1) приводит к зацикливанию?

Модератор: Модераторы разделов

Ответить
plustilino
Сообщения: 106
ОС: Xubuntu, Windows
Контактная информация:

while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение plustilino »

Код: Выделить всё

main() {
  int a, b;

  while(scanf("%d %d",&a, &b) != 2)
    printf("Try again: ");

}

По идее пока scanf() не вернет 2 (т.е. до тех пор пока переменным a и b не будут удачно присвоены числа), scanf() должна продолжать вызываться. А на деле при первом некорректном вводе (например, символа) происходит зацикливание (постоянно выводится "try again"). Почему?

В книге K&R есть такой пример:

Код: Выделить всё

  double sum, v;

  sum = 0;
  while (scanf("%lf", &v) == 1)
    printf("\t%.2f\n", sum += v);
  return v;

И он работает корректно. В примере выше если исправить != на ==, то тоже все работает. Однако это уже не то, что надо.
Спасибо сказали:
entada
Сообщения: 223
ОС: Debian stable, IceWM

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение entada »

plustilino писал(а):
28.01.2012 23:31

Код: Выделить всё

main() {
  int a, b;

  while(scanf("%d %d",&a, &b) != 2)
    printf("Try again: ");

}

По идее пока scanf() не вернет 2 (т.е. до тех пор пока переменным a и b не будут удачно присвоены числа), scanf() должна продолжать вызываться. А на деле при первом некорректном вводе (например, символа) происходит зацикливание (постоянно выводится "try again"). Почему?

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

Вот тут-то и получается загвоздка — мне неизвестен простой и корректный способ очистить буфер.
Можно прочитать его данные как строку и никуда её не записывать:

Код: Выделить всё

    while(scanf("%d %d",&a, &b) != 2)
    {
        printf("Try again: ");
        scanf("%*[^\r^\n]");
        fflush(stdout);
    }
но это, на мой взгляд, некрасиво.

В Интернете предлагают использовать для очистки буфера fflush(stdin) и fpurge(stdin). У меня [Debian Squeeze] первое ни на что не влияет, а второе не компилируется — отсутствует функция fpurge.
Спасибо сказали:
Аватара пользователя
Фантом
Сообщения: 452
ОС: openSUSE

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение Фантом »

scanf() при неудачном чтении не чистит буфер, поэтому при каждом обращении считывает одно и то же (с одним и тем же результатом). Для устранения этого надо чистить буфер (с помощью __fpurge(), если дело происходит под Linux). Но, в принципе, scanf на данных с ошибками формата работает так, что есть грустная шутка - "лучший способ использования scanf() - не пользоваться scanf()".
Спасибо сказали:
entada
Сообщения: 223
ОС: Debian stable, IceWM

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение entada »

Фантом, спасибо!
Действительно,

Код: Выделить всё

#include <stdio.h>
#include <stdio_ext.h>
...
__fpurge(stdin);
работает.

P.S.
А как же им не пользоваться-то? Есть ли более удобный и столь же компактный способ форматированного ввода?
Спасибо сказали:
Аватара пользователя
Фантом
Сообщения: 452
ОС: openSUSE

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение Фантом »

entada писал(а):
29.01.2012 00:27
А как же им не пользоваться-то? Есть ли более удобный и столь же компактный способ форматированного ввода?

Пользоваться, если соблюдение формата гарантировано (хотя это скорее для fscanf() реально). Иначе - читать посимвольно и парсить. Ну или пользоваться другими языками.
Спасибо сказали:
entada
Сообщения: 223
ОС: Debian stable, IceWM

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение entada »

Печально...

А не подскажете ли кроссплатформенный способ очистки буфера stdin в C?
Спасибо сказали:
Аватара пользователя
Фантом
Сообщения: 452
ОС: openSUSE

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение Фантом »

entada писал(а):
29.01.2012 00:55
А не подскажете ли кроссплатформенный способ очистки буфера stdin в C?

Не буду утверждать наверняка, но, насколько мне известно, его не существует.
Спасибо сказали:
entada
Сообщения: 223
ОС: Debian stable, IceWM

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение entada »

Как жаль!
Но всё равно, спасибо Вам за ответ!
Спасибо сказали:
watashiwa_daredeska
Бывший модератор
Сообщения: 4038
Статус: Искусственный интеллект (pre-alpha)
ОС: Debian GNU/Linux

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение watashiwa_daredeska »

entada писал(а):
29.01.2012 00:55
А не подскажете ли кроссплатформенный способ очистки буфера stdin в C?
Вычитать всё, что есть, и выкинуть.
Спасибо сказали:
entada
Сообщения: 223
ОС: Debian stable, IceWM

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение entada »

watashiwa_daredeska писал(а):
29.01.2012 01:39
Вычитать всё, что есть, и выкинуть.

То есть scanf("%*[^\r^\n]"); и подобные ей конструкции имеют право на существование?
Спасибо сказали:
watashiwa_daredeska
Бывший модератор
Сообщения: 4038
Статус: Искусственный интеллект (pre-alpha)
ОС: Debian GNU/Linux

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение watashiwa_daredeska »

entada писал(а):
29.01.2012 09:59
То есть scanf("%*[^\r^\n]"); и подобные ей конструкции имеют право на существование?
Нет. Для чтения существует не только scanf. Тут нужна комбинация select()+read(), как самый кроссплатформенный вариант.
Спасибо сказали:
entada
Сообщения: 223
ОС: Debian stable, IceWM

Re: while(scanf("%d", &a) != 1) приводит к зацикливанию?

Сообщение entada »

watashiwa_daredeska писал(а):
29.01.2012 10:23
Тут нужна комбинация select()+read(), как самый кроссплатформенный вариант.

А можно подробнее?

Update
В процессе поиска способов очистки буфера в Интернете нашёлся такой способ:

Код: Выделить всё

        if ( 0 == t ) {
            scanf( "%*[^\n]" );
            t = scanf( "%*c" );    /* уберем оставшиеся '\n'  */
        }
Работает на доступной мне сейчас системе, но, может быть, у этого способа есть какой-то незаметный мне пока дефект.

Update2
Из буфера удаляется только одна строка.
Спасибо сказали:
Ответить