2012-06-07 68 views
5

Estoy confundido sobre el comportamiento de scanf en el siguiente programa. scanf parece ingresar una vez y luego no volver a ingresar, hasta que se imprima una secuencia de caracteres.¿Por qué aparece Scanf para omitir la entrada?

continuación en un programa de 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; 
} 

aquí, hasta que estoy introduciendo cualquier programa entero funciona perfectamente bien, pero cuando se introduce un personaje que va sobre la impresión del último valor inputed de i y nunca se detiene (hasta el j es 10 cuando el ciclo sale) para que scanf tome la siguiente entrada.

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 

same thing is happening in C++ also.

#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 

único cambio en C++ es que 0 se está imprimiendo en lugar del último valor.

Lo sé aquí, el programa espera valores enteros, pero quiero saber qué sucede cuando se ingresa el carácter en lugar de un número entero. ¿Cuál es el motivo de todo lo que sucede arriba?

+1

Esto podría ayudarlo: http://www.parashift.com/c++faq-lite/input-output.html#faq-15.3. La siguiente respuesta explica un poco más en el fondo también. – chris

+1

posible duplicado de [scanf() portándose mal] (http://stackoverflow.com/questions/5062646/scanf-misbehaving) –

Respuesta

5

Al entrar en a, a continuación, cin >> i no puede leer porque el tipo de i es int a la que un personaje no se puede leer. Eso significa que a permanece en la corriente para siempre.

Ahora por qué i imprime 0 es una historia diferente. En realidad, puede imprimir cualquier cosa. El contenido de i no se define una vez que falla el intento de lectura. Algo similar ocurre con scanf también.

La forma correcta de escribirlo esto:

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

O simplemente esto (si desea salir del bucle si se produce un error):

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

Si eso es correcto o no depende de cómo quiera manejar los errores de entrada. –

+0

@BenjaminLindley: cierto. Por lo tanto, edita mi publicación. – Nawaz

+0

'" El contenido de i no se define una vez que falla el intento de lectura "' Para 'cin >> i', se define en C++ 11 – Cubbi

5

Si scanf ve un carácter en el flujo de entrada que no coincide con el especificador de conversión, detiene la conversión y deja el carácter ofensivo en el flujo de entrada.

Hay un par de formas de lidiar con esto. Una es leer todo como texto (usando scanf con un especificador de conversión %s o %[ o fgets) y luego usar atoi o strtol para hacer la conversión (mi método preferido).

Como alternativa, puede verificar el valor de retorno de scanf; indicará la cantidad de conversiones exitosas. Por lo tanto, si scanf("%d", &i); es igual a 0, entonces sabrá que hay un carácter incorrecto en la secuencia de entrada. Puede consumirlo con getchar() y vuelva a intentarlo.

2

El problema es que cuando se introduce una entrada que no es del tipo esperado (especificado por %d para scanf, y el tipo int para cin>>i;, el flujo de entrada no es avanzada, lo que se traduce en ambas operaciones tratar de extraer la misma tipo de datos de exactamente la misma entrada incorrecta (y esta vez también falla), por lo tanto, nunca solicitará otra entrada.

Para garantizar que esto no ocurra, deberá verificar el valor de retorno de ambos operaciones (lea el manual de cómo cada uno informa errores). Si ocurre un error (como cuando ingresa un carácter), tendrá que borrar el error, consumir la entrada no válida y Inténtalo de nuevo. Me parece mejor en C++ leer una línea completa usando std::gtline() en lugar de int o incluso std::string al obtener información de su usuario interactivamente, para que pueda entrar en este ciclo "infinito" que experimentó.

1

Está ignorando el valor de retorno. Vea lo que dice el manual 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.

falla a juego un número entero.

1

Para scanf, debe verificar su valor de retorno para ver si la conversión en la entrada funcionó. scanf devolverá la cantidad de elementos escaneados con éxito. Si la conversión no funcionó, dejará la entrada solo, y puede intentar escanear de manera diferente, o simplemente informar un error. Por ejemplo:

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

En su programa, ya que la entrada es dejado solo, el bucle se repite tratando de leer la misma entrada.

En C++, comprueba el error utilizando el método fail, pero el estado de falla de la corriente de entrada es adhesivo. Por lo tanto, no le permitirá escanear más sin borrar el estado de error.

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

En su programa, ya que nunca se borra el estado de fallo, se repite el bucle utilizando cin en un estado de fallo, por lo que sólo informa de la falta sin hacer nada.

En ambos casos, le resultará más fácil o más confiable trabajar con la entrada si primero lee en la línea de entrada y luego intenta analizar la línea de entrada. En pseudocódigo:

while read_a_line succeeds 
    parse_a_line 

En C, la captura de la lectura de una línea es que si es más largo que el búfer, tendrá que comprobar eso y concatenar varias fgets resultados de llamadas entre sí para formar la línea. Y, para analizar una línea, puede usar sscanf, que es similar a scanf pero funciona en cadenas.

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

En C++, para un rápido análisis de bajo nivel de entrada con formato, también prefiero sscanf. Pero, si desea utilizar el enfoque de flujo, puede convertir el buffer string en un istringstream para escanear la entrada.

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

Usted puede comprobar el valor de retorno de scanf para determinar si un número entero se ha analizado correctamente (de vuelta deben = 1). En caso de falla, tiene opciones: notificar al usuario del error y finalizarlo, o recuperarlo leyendo el próximo token con un scanf("%s" ...), quizás con una advertencia.

Cuestiones relacionadas