2012-06-07 2 views
5

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

Ниже в программе C

#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; 
} 

здесь, пока я не вводя любое целое программа отлично работает, но когда персонаж вводится он идет на печать последнего занесены значения г и никогда не останавливается (недо 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 

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

#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.

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

+1

Это может помочь вам: http://www.parashift.com/c++-faq-lite/input-output.html#faq-15.3. Следующий ответ объясняет еще немного на фоне. – chris

+1

Возможный дубликат [scanf() misbehaving] (http://stackoverflow.com/questions/5062646/scanf-misbehaving) –

ответ

5

При входе 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"; 
} 
+0

Независимо от того, насколько это необходимо или нет, зависит от того, как вы хотите обрабатывать ошибки ввода. –

+0

@BenjaminLindley: Правда. Поэтому отредактируйте мой пост. – Nawaz

+0

'' Содержимое i не определено, как только попытка чтения завершилась неудачей. '' Для 'cin >> i', он определен в C++ 11 – Cubbi

5

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

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

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

2

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

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

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; 
    } 
1

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