¿Por qué llamaríamos a cin.clear () y cin.ignore () después de leer la entrada?

86

El tutorial de C ++ de Google Code University solía tener este código:

// Description: Illustrate the use of cin to get input
// and how to recover from errors.

#include <iostream>
using namespace std;

int main()
{
  int input_var = 0;
  // Enter the do while loop and stay there until either
  // a non-numeric is entered, or -1 is entered.  Note that
  // cin will accept any integer, 4, 40, 400, etc.
  do {
    cout << "Enter a number (-1 = quit): ";
    // The following line accepts input from the keyboard into
    // variable input_var.
    // cin returns false if an input operation fails, that is, if
    // something other than an int (the type of input_var) is entered.
    if (!(cin >> input_var)) {
      cout << "Please enter numbers only." << endl;
      cin.clear();
      cin.ignore(10000,'\n');
    }
    if (input_var != -1) {
      cout << "You entered " << input_var << endl;
    }
  }
  while (input_var != -1);
  cout << "All done." << endl;

  return 0;
}

¿Cuál es el significado de cin.clear()y cin.ignore()? ¿Por qué son necesarios los parámetros 10000y \n?

JacKeown
fuente
1
Esta es mi publicación más votada de todos los tiempos. Debo haber alcanzado su punto máximo en la escuela secundaria.
JacKeown

Respuestas:

102

El cin.clear()borra el indicador de error cin(para que las futuras operaciones de E / S funcionen correctamente), y luego cin.ignore(10000, '\n')salta a la siguiente nueva línea (para ignorar cualquier otra cosa en la misma línea que el no número para que no cause otro error de análisis) . Solo saltará hasta 10000 caracteres, por lo que el código asume que el usuario no ingresará una línea muy larga e inválida.

Jeremías Willcock
fuente
57
+1. Quiero agregar que en lugar de ignorar hasta 10000 caracteres, sería mejor usar cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');.
Dennis
30
Si desea utilizar std::numeric_limits, asegúrese de hacerlo #include <limits>.
gbmhunter
1
¿Alguien puede explicar la sintaxis std::numeric_limits<std::streamsize>::max()?
Minh Tran
4
@Minh Tran: std :: streamsize es integral con signo que da el número de caracteres de E / S transferidos o el tamaño del búfer de E / S. Aquí, al usar la clase de plantilla "numeric_limits", queríamos saber el límite máximo del búfer de E / S o del carácter transferido.
Pankaj
1
@Minh Tran: solo una pequeña corrección "streamsize" no es una clase, es solo un tipo integral. También podemos obtener el límite de otros, es decir. int, char, etc., compruébalo en [enlace] ( en.cppreference.com/w/cpp/types/numeric_limits )
Pankaj
46

Entras en el

if (!(cin >> input_var))

declaración si ocurre un error al tomar la entrada de cin. Si se produce un error, se activa un indicador de error y fallarán los intentos futuros de obtener información. Es por eso que necesitas

cin.clear();

para deshacerse de la bandera de error. Además, la entrada que falló estará ubicada en lo que supongo que es una especie de búfer. Cuando intente obtener una entrada nuevamente, leerá la misma entrada en el búfer y fallará nuevamente. Es por eso que necesitas

cin.ignore(10000,'\n');

Saca 10000 caracteres del búfer pero se detiene si encuentra una nueva línea (\ n). El 10000 es solo un gran valor genérico.

vuelo
fuente
11
Solo agregue que el valor predeterminado para ignorar es omitir un solo carácter, por lo que necesita un número mayor para omitir una línea completa.
Bo Persson
22

Por qué usamos:

1) cin. Ignorar

2) cin.clear

?

Simplemente:

1) Para ignorar (extraer y descartar) valores que no queremos en la secuencia

2) Para borrar el estado interno de la secuencia. Después de usar cin.clear, el estado interno se establece nuevamente en goodbit, lo que significa que no hay 'errores'.

Versión larga:

Si algo se pone en 'stream' (cin), entonces debe tomarse desde allí. Por "tomado" nos referimos a "usado", "eliminado", "extraído" de la secuencia. La corriente tiene un flujo. Los datos fluyen en cin como agua en una corriente. Simplemente no puede detener el flujo de agua;)

Mira el ejemplo:

string name; //line 1
cout << "Give me your name and surname:"<<endl;//line 2
cin >> name;//line 3
int age;//line 4
cout << "Give me your age:" <<endl;//line 5
cin >> age;//line 6

¿Qué sucede si el usuario responde: "Arkadiusz Wlodarczyk" a la primera pregunta?

Ejecute el programa para verlo usted mismo.

Verá en la consola "Arkadiusz" pero el programa no le preguntará por 'edad'. Simplemente terminará inmediatamente después de imprimir "Arkadiusz".

Y no se muestra "Wlodarczyk". Parece como si se hubiera ido (?) *

¿Que pasó? ;-)

Porque hay un espacio entre "Arkadiusz" y "Wlodarczyk".

El carácter de "espacio" entre el nombre y el apellido es un signo para la computadora de que hay dos variables esperando ser extraídas en el flujo de 'entrada'.

La computadora piensa que está intentando enviar a la entrada más de una variable. Ese signo de "espacio" es un signo para que él lo interprete de esa manera.

Entonces, la computadora asigna "Arkadiusz" a 'nombre' (2) y, debido a que pones más de una cadena en el flujo (entrada), la computadora intentará asignar el valor "Wlodarczyk" a la variable 'edad' (!). El usuario no tendrá la oportunidad de poner nada en el 'cin' en la línea 6 porque esa instrucción ya se ejecutó (!). ¿Por qué? Porque todavía quedaba algo en marcha. Y como dije anteriormente, la corriente está en un flujo, por lo que todo debe eliminarse lo antes posible. Y la posibilidad llegó cuando la computadora vio la instrucción en la edad;

La computadora no sabe que usted creó una variable que almacena la edad de alguien (línea 4). 'edad' es simplemente una etiqueta. En el caso de las computadoras, la 'era' también podría llamarse: 'afsfasgfsagasggas' y sería lo mismo. Para él, es solo una variable a la que intentará asignar "Wlodarczyk" porque usted ordenó / indicó a la computadora que lo hiciera en la línea (6).

Está mal hacerlo, pero ¡eres tú quien lo hizo! ¡Es tu culpa! Bueno, tal vez usuario, pero aún así ...


Bien, bien. ¡¿Pero cómo solucionarlo ?!

Intentemos jugar un poco con ese ejemplo antes de arreglarlo correctamente para aprender algunas cosas más interesantes :-)

Prefiero hacer un enfoque donde entendamos las cosas. Arreglar algo sin saber cómo lo hicimos no da satisfacción, ¿no crees? :)

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate(); //new line is here :-)

Después de invocar el código anterior, notará que el estado de su secuencia (cin) es igual a 4 (línea 7). Lo que significa que su estado interno ya no es igual a goodbit. Algo está mal. Es bastante obvio, ¿no? Intentó asignar un valor de tipo de cadena ("Wlodarczyk") a la variable de tipo int 'edad'. Los tipos no coinciden. Es hora de informar que algo anda mal. Y la computadora lo hace cambiando el estado interno del flujo. Es como: "Jodido hombre, arréglame por favor. Te informo 'amablemente' ;-)"

Simplemente ya no puede usar 'cin' (stream). Está atorado. Como si hubieras puesto grandes troncos de madera en una corriente de agua. Debes arreglarlo antes de poder usarlo. Los datos (agua) ya no se pueden obtener de esa corriente (cin) porque el tronco de madera (estado interno) no le permite hacerlo.

Entonces, si hay un obstáculo (troncos de madera), ¿podemos simplemente eliminarlo con herramientas diseñadas para hacerlo?

¡Si!

El estado interno de cin establecido en 4 es como una alarma que aúlla y hace ruido.

cin.clear borra el estado y vuelve a la normalidad (goodbit). Es como si hubieras venido y silenciado la alarma. Solo lo pospones. Sabes que algo pasó, entonces dices: "Está bien dejar de hacer ruido, ya sé que algo anda mal, cállate (claro)".

¡Muy bien, hagámoslo! Usemos cin.clear ().

Invoque el siguiente código usando "Arkadiusz Wlodarczyk" como primera entrada:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl; 
cin.clear(); //new line is here :-)
cout << cin.rdstate()<< endl;  //new line is here :-)

Seguramente podemos ver después de ejecutar el código anterior que el estado es igual a goodbit.

Genial, ¿el problema está resuelto?

Invoque el siguiente código usando "Arkadiusz Wlodarczyk" como primera entrada:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl;; 
cin.clear(); 
cout << cin.rdstate() << endl; 
cin >> age;//new line is here :-)

Incluso aunque el estado se establezca en goodbit después de la línea 9, no se le pregunta al usuario por "edad". El programa se detiene.

¡¿POR QUÉ?!

Oh hombre ... Acabas de apagar la alarma, ¿qué pasa con el tronco de madera dentro del agua? * Vuelve al texto donde hablamos sobre "Wlodarczyk" cómo supuestamente se fue.

Tienes que quitar "Wlodarczyk" ese trozo de madera del arroyo. Apagar las alarmas no resuelve el problema en absoluto. ¿Lo acaba de silenciar y cree que el problema se ha ido? ;)

Entonces es hora de otra herramienta:

cin.ignore se puede comparar con un camión especial con cuerdas que viene y quita los troncos de madera que atascaron el arroyo. Elimina el problema que creó el usuario de su programa.

Entonces, ¿podríamos usarlo incluso antes de que suene la alarma?

Si:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;

El "Wlodarczyk" se eliminará antes de hacer ruido en la línea 7.

¿Qué es 10000 y '\ n'?

Dice eliminar 10000 caracteres (por si acaso) hasta que se cumpla '\ n' (ENTRAR). Por cierto, se puede hacer mejor usando numeric_limits pero no es el tema de esta respuesta.


Entonces, la principal causa del problema desapareció antes de que se hiciera ruido ...

Entonces, ¿por qué necesitamos "claro"?

¿Qué pasaría si alguien hubiera preguntado "dame tu edad" en la línea 6, por ejemplo: "veinte años" en lugar de escribir 20?

Los tipos no vuelven a coincidir. La computadora intenta asignar una cadena a int. Y comienza la alarma. Ni siquiera tienes la oportunidad de reaccionar en una situación como esa. cin.ignore no te ayudará en un caso así.

Así que debemos usar clear en un caso así:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;
cin.clear();
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

Pero, ¿debería limpiar el estado 'por si acaso'?

Por supuesto no.

Si algo sale mal (cin >> age;), la instrucción le informará devolviendo false.

Entonces podemos usar una declaración condicional para verificar si el usuario ingresó el tipo incorrecto en la secuencia

int age;
if (cin >> age) //it's gonna return false if types doesn't match
    cout << "You put integer";
else
    cout << "You bad boy! it was supposed to be int";

De acuerdo, podemos solucionar nuestro problema inicial como, por ejemplo, que:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

int age;
cout << "Give me your age:" << endl;
if (cin >> age)
  cout << "Your age is equal to:" << endl;
else
{
 cin.clear();
 cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
 cout << "Give me your age name as string I dare you";
 cin >> age;
}

Por supuesto, esto se puede mejorar, por ejemplo, haciendo lo que hizo en cuestión usando loop while.

PRIMA:

Quizás se esté preguntando. ¿Qué pasa si quisiera obtener el nombre y el apellido en la misma línea del usuario? ¿Es posible usar cin si cin interpreta cada valor separado por "espacio" como una variable diferente?

Claro, puedes hacerlo de dos formas:

1)

string name, surname;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin >> surname;

cout << "Hello, " << name << " " << surname << endl;

2) o usando la función getline.

getline(cin, nameOfStringVariable);

y así es como se hace:

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

La segunda opción puede ser contraproducente en caso de que la use después de usar 'cin' antes de getline.

Vamos a ver:

una)

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endl;

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

Si pone "20" como edad, no se le pedirá nameAndSurname.

Pero si lo haces de esa manera:

segundo)

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endll

todo esta bien.

¡¿QUÉ?!

Cada vez que pones algo en la entrada (flujo), dejas al final el carácter blanco que es ENTER ('\ n'). Tienes que ingresar valores de alguna manera en la consola. Entonces debe suceder si los datos provienen del usuario.

b) las características de cin es que ignora los espacios en blanco, por lo que cuando está leyendo información de cin, el carácter de nueva línea '\ n' no importa. Se ignora.

a) la función getline obtiene la línea completa hasta el carácter de nueva línea ('\ n'), y cuando el carácter de nueva línea es lo primero, la función getline obtiene '\ n', y eso es todo. Extrae el carácter de nueva línea que dejó en la transmisión el usuario que puso "20" en la transmisión en la línea 3.

Entonces, para arreglarlo es siempre invocar cin.ignore (); cada vez que use cin para obtener algún valor si alguna vez va a usar getline () dentro de su programa.

Entonces, el código adecuado sería:

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cin.ignore(); // it ignores just enter without arguments being sent. it's same as cin.ignore(1, '\n') 
cout << "Your age is" << age << endl;


string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

Espero que las transmisiones sean más claras para ti.

¡Ja, callame por favor! :-)

Morfidon
fuente
1

use cin.ignore(1000,'\n')para borrar todos los caracteres del anterior cin.get()en el búfer y elegirá detenerse cuando se encuentre con '\ n' o 1000 charsprimero.

Phy Lieng
fuente