Вопрос по c, cout, scanf, c++, io – Почему scanf появляется, чтобы пропустить ввод?

5

Я запутался в поведении scanf в следующей программе. scanf вводится один раз, а затем не вводится снова, пока не будет напечатан поток символов.

Ниже в программе на Си

#include<stdio.h>
int main()
{
    int i, j=0;

    do
    {
        ++j;
        scanf("%d", &i);
        printf("\n\n%d %d\n\n", i, j);
    }
    while((i!=8) && (j<10));  

    printf("\nJ = %d\n", j);
    return 0;
}

здесь, пока я не введу любую целочисленную программу, она работает отлично, но когда вводится символ, он продолжает печатать последнее введенное значение i и никогда не останавливается (до тех пор, пока j не достигнет 10, когда цикл завершается) для scanf для следующего ввода.

output::  
1    <-----1st input
1 1
2    <---- 2nd input
2 2
a    <---- character input
2 3  
2 4
2 5
2 6
2 7
2 8
2 9
2 10

J = 10  

то же самое происходит и в с ++.

#include<iostream>
using namespace std;
int main()
{
    int i, j=0;

    do
    {
        ++j;
        cin>>i;
        cout<<i<<" "<<j<<"\n";
    }
    while((i!=8) && (j<10));

    cout<<"\nj = "<<j<<"\n";
}   


output of c++ program ::  
1     <-----1st input
1 1
2     <-----2nd input
2 2
a    <------ character input
0 3
0 4
0 5
0 6
0 7
0 8
0 9
0 10

j = 10  

единственное изменение в c ++ состоит в том, что вместо последнего значения печатается 0.

Я знаю, что целочисленные значения ожидаются программой, но я хочу знать, что происходит, когда символ вводится вместо целого числа? в чем причина всего происходящего выше?

возможный дубликатscanf() misbehaving Bo Persson
Это может помочь вам:parashift.com/c++-faq-lite/input-output.html#faq-15.3, Следующий ответ объясняет немного больше на фоне тоже. chris

Ваш Ответ

7   ответов
6

a, затемcin >> i не может прочитать его, потому что типi являетсяint к которому персонаж не может быть прочитан. Это означает,a остается в потоке навсегда.

Теперь почемуi печать0 это другая история. На самом деле он может напечатать что угодно. Содержаниеi не определяется, если попытка чтения не удалась. Похожая вещь случается сscanf также.

Правильный способ написать это так:

do
{
    ++j;
    if (!(cin>>i)) 
    {
       //handle error, maybe you want to break the loop here?
    }
    cout<<i<<" "<<j<<"\n";
}
while((i!=8) && (j<10));

Или просто это (если вы хотите выйти из цикла при возникновении ошибки):

int i = 0, j = 0;
while((i!=8) && (j<10) && ( cin >> i) )
{
    ++j;
    cout<<i<<" "<<j<<"\n";
}
"The content of i is not defined once the attempt to read fails"      Заcin >> i, это определено в C ++ 11
Является ли это правильным или нет, зависит от того, как вы хотите обрабатывать ошибки ввода.
@BenjaminLindley: правда. Отсюда и редактируйте мой пост.
5

scanf видит символ во входном потоке, который не соответствует спецификатору преобразования, он останавливает преобразование и оставляет нарушающий символ во входном потоке.

Есть несколько способов справиться с этим. Один состоит в том, чтобы прочитать все как текст (используяscanf с%s или же%[ спецификатор конверсии илиfgets), а затем использоватьatoi или жеstrtol сделать преобразование (мой предпочтительный метод).

Кроме того, вы можете проверить возвращаемое значениеscanf; это будет указывать количество успешных конверсий. Так что еслиscanf("%d", &i); равно 0, то вы знаете, что во входном потоке плохой символ. Вы можете потреблять это сgetchar() и попробуй еще раз.

1

что говорится в руководствеscanf(3):

RETURN VALUE
These functions return the number of input items successfully matched and assigned, which can be fewer than provided for, or even zero in the event of an early matching failure.

Не удается найти целое число.

1

scanf, вам нужно проверить его возвращаемое значение, чтобы увидеть, работает ли преобразование на входе.scanf вернет количество успешно отсканированных элементов. Если преобразование не сработало, оно оставит входные данные одними, и вы можете попробовать сканировать его по-другому или просто сообщить об ошибке. Например:

if (scanf("%d", &i) != 1) {
    char buf[512];
    fgets(buf, sizeof(buf), stdin);
    printf("error in line %d: got %s", j, buf);
    return 0;
}

В вашей программе, поскольку вход остается один, ваш цикл повторяется при попытке прочитать тот же вход.

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

    std::cin >> i;
    if (std::cin.fail()) {
        std::string buf;
        std::cin.clear();
        std::getline(cin, buf);
        std::cout
             << "error in line " << j
             << ": got " << buf
             << std::endl;
        return 0;
    }

В вашей программе, поскольку вы никогда не очищаете состояние ошибки, цикл повторяется с использованиемcin в состоянии сбоя, поэтому он просто сообщает о сбое, ничего не делая.

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

while read_a_line succeeds
    parse_a_line

В C выгода для чтения строки состоит в том, что если она длиннее вашего буфера, вам нужно будет проверить это и объединить несколькоfgets вызовите результаты вместе, чтобы сформировать линию. И, чтобы разобрать строку, вы можете использоватьsscanf, который похож наscanf но работает на струнах.

    if (sscanf(buf, "%d", &i) != 1) {
        printf("error in line %d: got %s", j, buf);
        return 0;
    }

В C ++ для быстрого анализа низкого уровня форматированного ввода я также предпочитаюsscanf, Но, если вы хотите использовать потоковый подход, вы можете преобразоватьstring буфер вistringstream сканировать вход.

    std::getline(cin, buf);
    if (std::cin.fail()) {
        break;
    }
    std::istringstream buf_in(buf);
    buf_in >> i;
    if (buf_in.fail()) {
        std::cout << "error in line " << j
             << ": got " << buf
             << std::endl;
        return 0;
    }
2

что при вводе ввод не относится к ожидаемому типу (указанному%d для сканирования, иint тип дляcin>>i;входной поток не является расширенным, что приводит к тому, что обе операции пытаются извлечь данные одного и того же типа из одного и того же неверного ввода (и на этот раз тоже не удалось), поэтому вам никогда не потребуется другой ввод.

Чтобы этого не произошло, вам необходимо проверить возвращаемое значение обеих операций (см. Руководство по тому, как каждый сообщает об ошибках). Если ошибкаdoes произойдет (например, когда вы вводите символ), вам нужно будет удалить ошибку, использовать неверный ввод и повторить попытку. Я считаю, что в C ++ лучше читать целую строку, используяstd::gtline() вместоint или дажеstd::string когда вы получаете информацию от пользователя в интерактивном режиме, то вы попадаете в это «бесконечное» цикл вы испытали.

1

scanf определить, правильно ли было проанализировано целое число (return должен = 1). В случае неудачи у вас есть выбор: либо уведомить пользователя об ошибке и прекратить его, либо восстановить, прочитав следующий токен сscanf("%s" ...) возможно с предупреждением.

2

что ваши пользователи будут вводить действительные вещи. Лучшая практика заключается впрочитать ввод в строку и попытаться преобразовать его в целое число. Если ввод не целое число, вы можете дать сообщение об ошибке пользователю.

Похожие вопросы