игнорирование scanf, бесконечный цикл

int flag = 0;
int price = 0;
while (flag==0)
{
    printf("\nEnter Product price: ");
    scanf("%d",&price);
    if (price==0) 
        printf("input not valid\n"); 
    else 
        flag=1;
}

Когда я ввожу правильный номер, цикл заканчивается, как и ожидалось. Но если я ввожу что-то, что не является числом, например hello, код переходит в бесконечный цикл. Он просто продолжает печатать Enter Product price: и input not valid. Но он не ждет, пока я введу новый номер. Почему это?


person Aviadjo    schedule 12.03.2011    source источник
comment
Компилятор превращает вашу программу в исполняемый файл; это все. Это не имеет ничего общего с поведением здесь.   -  person Jim Balter    schedule 13.03.2011


Ответы (6)


Когда вы вводите что-то, что не является числом, scanf завершится ошибкой и оставит эти символы на входе. Поэтому, если вы введете hello, scanf увидит h, отклонит его как недопустимый для десятичного числа и оставит его на входе. В следующий раз в цикле scanf снова увидит h, поэтому цикл будет продолжаться вечно.

Одним из решений этой проблемы является чтение всей строки ввода с помощью fgets и последующий анализ строки с помощью sscanf. Таким образом, если sscanf выйдет из строя, на входе ничего не останется. Пользователь должен будет ввести новую строку для fgets для чтения.

Что-то в этом роде:

char buffer[STRING_SIZE];
...
while(...) {
    ...
    fgets(buffer, STRING_SIZE, stdin);
    if ( sscanf(buffer, "%d", &price) == 1 )
        break;   // sscanf succeeded, end the loop
    ...
}

Если вы просто сделаете getchar, как предложено в другом ответе, вы можете пропустить символ \n, если пользователь вводит что-то после числа (например, пробел, за которым могут следовать другие символы).

Вы всегда должны проверять возвращаемое значение sscanf. Он возвращает количество назначенных конверсий, поэтому, если возвращаемое значение не совпадает с запрошенным количеством конверсий, это означает, что синтаксический анализ завершился неудачно. В этом примере запрошена 1 конверсия, поэтому sscanf возвращает 1 в случае успеха.

person ChrisJ    schedule 12.03.2011

Формат %d предназначен для десятичных дробей. Когда scanf терпит неудачу (вводится что-то другое, десятичное число), символ, вызвавший сбой, останется в качестве ввода.

Пример.

    int va;
    scanf("%d",&va);
    printf("Val %d 1 \n", val);

    scanf("%d",&va);
    printf("Val %d 2 \n", val);
    return 0;

Так что никакого преобразования не происходит.

Функция scanf возвращает значение макроса EOF, если сбой ввода происходит до любого преобразования. В противном случае функция scanf возвращает количество назначенных элементов ввода, которое может быть меньше, чем предусмотрено, или даже равно нулю, в случае ошибки раннего сопоставления.

7.19.6. Функция scanf — JTC1/SC22/WG14 — C

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

int scanf(char *format)

так что вы могли бы также сделать следующее

do {
        printf("Enter Product \n");
}
while (scanf("%d", &sale.m_price) == 1);

if(scanf("%d", &sale.m_price) == 0)
        PrintWrongInput();

Также держитесь в затылке, чтобы попытаться держаться подальше от scanf. scanf или отформатированное сканирование не следует использовать для интерактивного ввода данных пользователем. см. часто задаваемые вопросы по C 12.20

person phwd    schedule 12.03.2011
comment
В буфере есть \n И что? За исключением %c, scanf пропускает пробелы. - person Jim Balter; 13.03.2011
comment
@JimBalter Спасибо, что сообщили мне, глупое заявление, которое я сделал :( . - person phwd; 13.03.2011

После первого числа во входном буфере будет '\n' (возврат, который вы нажали для ввода числа), поэтому во второй итерации вызов scanf завершится ошибкой (поскольку \n не является числом), scanf будет не удалять этот \n из буфера, поэтому на следующей итерации он снова выйдет из строя и так далее.

Вы можете исправить это, прочитав '\n' с вызовом getchar() после scanf.

person fbafelipe    schedule 12.03.2011
comment
Что, если пользователь введет что-то после числа, прежде чем нажать клавишу возврата? Смотрите другой ответ ниже. - person ChrisJ; 13.03.2011
comment
-1: спецификация преобразования scanf "%d" пропускает пробелы в начале ввода. Если вы наберете "43\n37" в 1-м скане, он будет читать и преобразовывать "43", оставляя "\n37" в буфере. Второй scanf прочитает и преобразует это в 37. Наверняка где-то есть лишние символы, но это не '\n'. - person pmg; 13.03.2011
comment
Что за поток ответов от людей, которые понятия не имеют, о чем говорят, и не утруждают себя проверкой своих утверждений? И почему люди проголосовали за этот явно неправильный ответ? - person Jim Balter; 13.03.2011

«Ответы», в которых говорится, что это произойдет, потому что в буфере есть '\ n', ошибочны - scanf("%d", ...) пропускает пробелы, включая новые строки.

Он входит в бесконечный цикл, если x содержит 0, а scanf встречает не число (не только пробел) или EOF, потому что x останется 0, и у него нет возможности стать другим. Это должно быть ясно, если просто посмотреть на ваш код и подумать о том, что он будет делать в этом случае.

person Jim Balter    schedule 12.03.2011

Это переходит в бесконечный цикл, потому что scanf() не будет использовать входной токен, если совпадение не удается. scanf() будет пытаться сопоставить один и тот же ввод снова и снова. вам нужно очистить стандартный ввод.

if (!scanf("%d", &sale.m_price)) fflush(stdin);

person user396159    schedule 13.03.2011
comment
fflush(stdin) — это неопределенное поведение. - person ilgaar; 22.01.2019

Редактировать: Когда я впервые написал этот ответ, я был настолько глуп и не знал, как работает scanf().

  • Прежде всего позвольте мне кое-что прояснить, scanf() не является сломанной функцией, если я не знаю, как работает scanf(), и я не знаю, как ее использовать, то я, вероятно, не читал руководство для scans(), и это не может быть scanf() виноват.
  • Во-вторых, чтобы понять, что не так с вашим кодом, вам нужно знать, как работает scanf().

Когда вы используете scanf("%d", &price) в своем коде, scanf() пытается прочитать integer из ввода, но если вы вводите нечисловое значение, scanf() знает, что это неправильный тип данных, поэтому он помещает прочитанный ввод обратно в буфер. , однако в следующем цикле неверный ввод все еще находится в буфере, что снова приведет к сбою scanf(), поскольку буфер не был очищен, и этот цикл будет продолжаться вечно.

Чтобы решить эту проблему, вы можете использовать возвращаемое значение scanf(), которое будет числом успешных входных данных, прочитанных, однако вам нужно отбросить недопустимые входные данные, очистив буфер, чтобы избежать бесконечного цикла, входной буфер очищается. когда нажата клавиша enter, вы можете сделать это с помощью функции getchar(), чтобы сделать паузу для получения ввода, что потребует от вас нажатия клавиши enter, таким образом отбрасывая неверный ввод, обратите внимание, что это не заставит вас нажимать enter дважды, независимо от того, ввели ли вы правильный тип данных, потому что newline character все еще будет в буфере. После того, как scanf() успешно завершит чтение integer из ввода, он поместит \n обратно в буфер, поэтому getchar() прочитает его, но, поскольку он вам не нужен, его можно безопасно отбросить:

#include <stdio.h>

int main(void)
{
    int flag = 0;
    int price = 0;
    int status = 0;
    while (flag == 0 && status != 1)
    {
        printf("\nEnter Product price: ");
        status = scanf("%d", &price);
        getchar();
        if (price == 0) 
            printf("input not valid\n"); 
        else 
            flag = 1;
    }   

    return 0;
}
person ilgaar    schedule 31.12.2013