2009-02-13 12 views
7

Mi hermano recientemente comenzó a aprender C++. Él me contó un problema que encontró al intentar validar la entrada en un programa simple. Tenía un menú de texto donde el usuario ingresaba un número entero choice, si ingresaban una opción no válida, se les pedía que la ingresaran nuevamente (hacer while loop). Sin embargo, si el usuario ingresara una cadena en lugar de una int, el código se rompería. leí varias preguntas sobre stackoverflow y le dijo que reescribir su código a lo largo de las líneas de:¿Cuál es la mejor manera de hacer la validación de entrada en C++ con cin?

#include<iostream> 
using namespace std; 

int main() 
{ 
    int a; 
    do 
    { 
    cout<<"\nEnter a number:" 
    cin>>a; 
     if(cin.fail()) 
     { 
      //Clear the fail state. 
      cin.clear(); 
      //Ignore the rest of the wrong user input, till the end of the line. 
      cin.ignore(std::numeric_limits<std::streamsize>::max(),\ 
                '\n'); 
     } 
    }while(true); 
    return 0; 
} 

Si bien esto funcionó bien, también probé algunas otras ideas:
1. El uso de un bloque de intento de captura. No funcionó. Creo que esto se debe a que una excepción no se plantea debido a una mala entrada. 2. Intenté if(! cin){//Do Something} que tampoco funcionó. Todavía no he descubierto esto.
3. En tercer lugar, traté de ingresar una cadena de longitud fija y luego analizarla. Yo usaría atoi(). ¿Es compatible y compatible con los estándares? ¿Debo escribir mi propia función de análisis?
4. Si escribe una clase que usa cin, pero realiza este tipo de detección de error dinámicamente, tal vez determinando el tipo de la variable de entrada en tiempo de ejecución, ¿tendría demasiada sobrecarga? ¿Es posible?

Me gustaría saber cuál es la mejor manera de hacer este tipo de comprobación, ¿cuáles son las mejores prácticas?

Me gustaría agregar que, aunque no soy nuevo en la escritura de código C++, soy nuevo en la redacción de código compatible con los estándares. Estoy tratando de desaprender las malas prácticas y aprender las correctas. Me agradaría mucho que los contestadores brinden una explicación detallada.

EDIT: Veo que litb ha respondido a una de mis ediciones anteriores. Voy a publicar ese código aquí para referencia.

#include<iostream> 
using namespace std; 

int main() 
{ 
    int a; 
    bool inputCompletionFlag = true; 
    do 
    { 
    cout<<"\nEnter a number:" 
    cin>>a; 
     if(cin.fail()) 
     { 
      //Clear the fail state. 
      cin.clear(); 
      //Ignore the rest of the wrong user input, till the end of the line. 
      cin.ignore(std::numeric_limits<std::streamsize>::max(),\ 
                '\n'); 
     } 
     else 
     { 
      inputCompletionFlag = false; 
     } 
    }while(!inputCompletionFlag); 
    return 0; 
} 

Este código falla en la entrada como "1asdsdf". No sabía cómo solucionarlo, pero litb ha publicado una gran respuesta. :)

Respuesta

14

Aquí es el código que puede utilizar para asegurarse de que también rechazan cosas como

42crap 

Dónde caracteres no numéricos siguen la serie. Si usted lee toda la línea y luego analizarlo y ejecutar acciones de manera apropiada que posiblemente será necesario que para cambiar la forma en que funciona el programa. Si su programa de leer su número de diferentes lugares hasta ahora, entonces tiene que poner un lugar central que analiza una línea de entrada, y decide sobre la acción. Pero tal vez eso es una buena cosa también - por lo que podría aumentar la legibilidad del código de esa manera por tener cosas separadas: I Nput - P rocessing - O utput

De todos modos, aquí es cómo se puede rechazar el número-no-número de arriba. Leer una línea en una cadena, a continuación, analizarlo con un stringstream:

std::string getline() { 
    std::string str; 
    std::getline(std::cin, str); 
    return str; 
} 

int choice; 
std::istringstream iss(getline()); 
iss >> choice >> std::ws; 
if(iss.fail() || !iss.eof()) { 
    // handle failure 
} 

Se alimenta todo con espacios en blanco. Cuando se golpea el archivo final de la stringstream mientras lee el número entero o espacios en blanco, entonces se establece el EF-bit, y comprobar que. Si no ha podido leer cualquier número entero en el primer lugar, entonces la falla o se hayan establecido mala bits.

versiones anteriores de esta respuesta std::cin utilizan directamente - pero std::ws no funcionarán bien juntos con std::cin conectado a un terminal (que bloqueará en su lugar esperando a que el usuario introduzca algo), por lo que utilizan un stringstream para leer el número entero .


responder algunas de sus preguntas:

Pregunta: 1. El uso de un bloque de intento de captura. No funcionó. Creo que esto se debe a que una excepción no se plantea debido a una mala entrada.

Respuesta: Bueno, se puede decir la corriente a lanzar excepciones cuando se lee algo. Se utiliza la función istream::exceptions, que le dice a qué tipo de error que quieren tener una excepción lanzada:

iss.exceptions(ios_base::failbit); 

Yo no lo uso. Si lo hace en std::cin, tendrá que recordar restaurar las banderas para otros lectores que confían en que no lanzará. Es mucho más fácil simplemente usar las funciones fallar, malo para solicitar el estado de la transmisión.

Pregunta: 2. Intenté if(!cin){ //Do Something } que tampoco funcionó. Todavía no he descubierto esto.

Respuesta: que podría venir del hecho de que usted le dio algo así como "42crap". Para la transmisión, es una entrada completamente válida cuando se realiza una extracción en un número entero.

Pregunta: 3. En tercer lugar, traté de ingresar una cadena de longitud fija y luego analizarla. Yo usaría atoi(). ¿Es compatible y compatible con los estándares? ¿Debo escribir mi propia función de análisis?

Respuesta: atoi es conforme al estándar. Pero no es bueno cuando quieres verificar si hay errores. No hay verificación de errores, hecho por él en comparación con otras funciones. Si tiene una cadena y desea verificar si contiene un número, hágalo como en el código inicial anterior.

Hay funciones tipo C que pueden leer directamente desde una cuerda en forma de C. Existen para permitir la interacción con el código heredado antiguo y escribir código de ejecución rápida. Uno debe evitarlos en los programas porque funcionan bastante bajo nivel y requieren el uso de punteros desnudos. Por su propia naturaleza, tampoco se pueden mejorar para que funcionen con tipos definidos por el usuario. Específicamente, esto se refiere a la función "strtol" (de cadena a longitud) que básicamente es atoi con comprobación de errores y capacidad para trabajar con otras bases (por ejemplo, hexadecimal).

Pregunta: 4. Si escribo una clase que usa cin, pero dinámicamente hago este tipo de detección de errores, tal vez determinando el tipo de la variable de entrada en tiempo de ejecución, ¿tendrá demasiada sobrecarga? ¿Es posible?

Respuesta: En general, no es necesario preocuparse demasiado por encima de la cabeza aquí (si se refiere a tiempo de ejecución de gastos generales). Pero depende específicamente de dónde use esa clase. Esa pregunta será muy importante si está escribiendo un sistema de alto rendimiento que procesa las aportaciones y necesita tener alto durante todo el proceso. Pero si necesita leer la entrada desde un terminal o un archivo, ya verá a qué se reduce esto: esperar que el usuario ingrese algo tarda tanto, no es necesario que mire los costos de tiempo de ejecución en este punto en este momento. escala.

Si se refiere a la sobrecarga del código, depende de cómo se implemente el código. Debería escanear la cadena que ha leído, ya sea que contenga un número o no, si se trata de una cadena arbitraria. Dependiendo de lo que desea escanear (tal vez tiene una entrada de "fecha", o un formato de entrada de "tiempo" también. Mire en boost.date_time para eso), su código puede volverse arbitrariamente complejo. Para cosas simples como clasificar entre números o no, creo que se puede salir con poca cantidad de código.

+0

Gracias por la explicación detallada, pero miré hacia arriba y encontré que Boost no es una biblioteca estándar. A la luz de eso, ¿sería mejor rodar mi propio código si puedo o debo seguir con el impulso? – batbrat

+0

Acabo de notar que se va a estandarizar en gran medida en C++ 0x. Entonces usaré Boost. Me alegrará si confirmas. Gracias. – batbrat

+0

Sí, el próximo C++ incluirá algunas bibliotecas diseñadas después de las de refuerzo (shared_ptr, thread, system (error_code, ...), array, bind, function). pero no date_time Sin embargo, es muy recomendable impulsarlo. es mejor que rodar tu propia realidad –

3

Lo que haría sería doble: Primero, intente validar la entrada, y extraiga los datos, usando una expresión regular, si la entrada no es algo trivial. Puede ser muy útil incluso si la entrada es solo una serie de números.

Entonces, me gusta usar boost::lexical_ cast, que puede elevar una excepción de difusión bad_ lexical_ si la entrada no se puede convertir.

En su ejemplo:

std::string in_str; 
cin >> in_str; 

// optionally, test if it conforms to a regular expression, in case the input is complex 

// Convert to int? this will throw bad_lexical_cast if cannot be converted. 
int my_int = boost::lexical_cast<int>(in_str); 
12

Esto es lo que hago con C, pero es probable que sea aplicable para C++ también.

Ingrese todo como una cadena.

Luego, y solo entonces, analice la cadena en lo que necesite. A veces es mejor codificar el tuyo que intentar doblegar el de alguien más a tu voluntad.

+0

Gracias por responder Pax. Pensé en hacer lo que me dijiste, pero me preguntaba si era el enfoque correcto. Gracias por la respuesta. – batbrat

2

Olvídese de usar entrada formateada (el operador >>) directamente en código real. Siempre necesitará leer texto sin formato con std :: getline o similar y luego usar sus propias rutinas de análisis de entrada (que pueden usar el operador >>) para analizar la entrada.

4
  • Para obtener el exceptions with iostreams, debe establecer el indicador de excepción adecuado para la transmisión.
  • Y me gustaría utilizar get_line para obtener toda la línea de entrada y luego manejarlo en consecuencia - utilizar lexical_cast, las expresiones regulares (por ejemplo Boost Regex o Boost Xpressive, analizarlo con Boost Spirit, o simplemente utilizar algún tipo de lógica apropiada
+0

Gracias por la respuesta. Me gustaría saber si hay una biblioteca estándar equivalente a las alternativas de impulso mencionadas aquí. – batbrat

2

¿Qué tal una combinación de los diversos enfoques:

  1. Capturar la entrada de std::cin usando std::getline(std::cin, strObj) donde strObj es un std::string objeto.

  2. Uso boost::lexical_cast para realizar una traducción léxica de strObj ya sea a un número entero con o sin signo de anchura más grande (por ejemplo, unsigned long long o algo similar)

  3. Uso boost::numeric_cast a emitir el número entero hasta el rango esperado.

Se podía ir a buscar la entrada con std::getline y luego llamar a la boost::lexical_cast apropiadamente estrecha tipo entero, así dependiendo de donde se desea capturar el error. El enfoque de tres pasos tiene el beneficio de aceptar cualquier dato entero y luego captura los errores de estrechamiento por separado.

1

Estoy de acuerdo con Pax, la forma más sencilla de hacer esto es leer todo como cuerdas, a continuación, utilizar TryParse para verificar la entrada. Si está en el formato correcto, a continuación, proceder, otherwhise simplemente notificar al usuario y el uso de continuar en el bucle.

+0

TryParse es un modismo específico de .NET. Portátil, basado en estándares C++ no puede usarlo. –

+0

Me preguntaba sobre ese arpista. gracias por aclararlo. Gracias por responder Rekreativc – batbrat

1

Una cosa que no se ha mencionado aún es que generalmente es importante que pruebe para ver si la operación cin >> funcionó antes de usar la variable que supuestamente obtuvo algo de la transmisión.

Este ejemplo es similar al suyo, pero hace esa prueba.

#include <iostream> 
#include <limits> 
using namespace std; 
int main() 
{ 
    while (true) 
    { 
     cout << "Enter a number: " << flush; 
     int n; 
     if (cin >> n) 
     { 
     // do something with n 
     cout << "Got " << n << endl; 
     } 
     else 
     { 
     cout << "Error! Ignoring..." << endl; 
     cin.clear(); 
     cin.ignore(numeric_limits<streamsize>::max(), '\n'); 
     } 
    } 
    return 0; 
} 

Esto utilizará el operador >> semántica habitual; saltará primero el espacio en blanco, luego intentará leer tantos dígitos como sea posible y luego se detendrá. Así que "42crap" te dará los 42 y saltaras la "mierda". Si eso no es lo que quieres, entonces estoy de acuerdo con las respuestas anteriores, debes leerlo en una cadena y luego validarlo (quizás usando una expresión regular, pero puede ser exagerado para una secuencia numérica simple).

+0

Es lo que quiero. Gracias. sin embargo, las respuestas anteriores me hicieron reconsiderar la forma en que estaba haciendo las cosas y tengo una mejor idea sobre cómo escribir un código que cumpla con los estándares. Usaré tus sugerencias y las de los demás en el futuro. – batbrat

Cuestiones relacionadas