2012-01-21 5 views
7

Mi código lee variables int sin firmar del archivo de texto Input_File_Name.Cómo leer las variables int sin signo del archivo correctamente, utilizando ifstream?

unsigned int Column_Count; //Cols 
unsigned int Row_Count;//Rows 

try { 
    ifstream input_stream; 
    input_stream.open(Input_File_Name,ios_base::in); 
    if (input_stream) { 
     //if file is opened 
     input_stream.exceptions(ios::badbit | ios::failbit); 
     input_stream>>Row_Count; 
     input_stream>>Column_Count; 


    } else { 
     throw std::ios::failure("Can't open input file"); 
     //cout << "Error: Can't open input file" << endl; 
    } 

} catch (const ios::failure& error) { 
    cout << "Oh No!!" << error.what() << endl;   
} catch (const exception& error) { 
    cout << error.what() <<"Oh No!!" << endl; 
} catch (...) { 
    cout << "Unknown exception" << endl; 
} 

Funciona excelente. Pero cuando lleno archivo de texto con datos erróneos

33abcd4 567fg8 

Funciona de tal manera:

input_stream>>Row_Count; //Row_Count = 33; 
input_stream>>Column_Count; // throws an ios::failure exception 

¿Por qué no esta línea input_stream>>Row_Count; tiro excepción? Según entiendo, input_stream considera cualquier símbolo no numérico como delimitador, y en el siguiente paso intenta leer "abcd". ¿Es tan? Cómo establecer un símbolo de espacio como delimitador para lanzar una excepción ios::failure desde esta línea de código input_stream>>Row_Count; mientras lee "33abcd4"?

+1

Así es como funciona 'operator >>'; extraerá '33' y luego se detendrá, 'abcd' permanecerá en la secuencia para la siguiente llamada a 'operator >>'. En su lugar, podría leer '33abcd4' en una cadena y luego buscar caracteres no numéricos en ella. Además, si tiene un compilador reciente que admita C++ 11, verifique si la biblioteca estándar proporciona 'std :: stoull'. – jrok

Respuesta

4

La extracción normal de un valor entero tiene éxito si la transmisión puede leer cualquier valor entero. Es decir, si hay al menos un dígito opcionalmente seguido de cualquier cosa, la lectura de un entero tiene éxito. Las operaciones de extracción normal no intentan leer más, en particular, no intentan encontrar el siguiente espacio en blanco.

Por lo que suena, quiere asegurarse de que hay un espacio en blanco después de su número y si no lo hace, puede fallar. Puedo pensar en dos enfoques diferentes para hacer esto:

  1. Crea un manipulador simple que comprueba si la transmisión está en un carácter de espacio en blanco. Esto, sin embargo, significa que leería sus valores usando algo como in >> value >> is_space.
  2. Cree una faceta std::num_get<char> personalizada, instálela en std::locale y imbue() en la (s) fuente (es) std::locale. Es un poco más complicado, pero no requiere ningún cambio en la forma en que se leen los enteros.

Creación de un manipulador de este tipo es bastante trivial:

std::istream& is_space(std::istream& in) 
{ 
    if (!std::isspace(in.peek())) 
    { 
     in.setstate(std::ios_base::failbit); 
    } 
    return in; 
} 

Ahora, cambiando la forma en que los números se leen es más interesante y sospecho que acababa de llamada de un número de clases de la librería estándar de la mayoría de las personas son bastante inconsciente de. Entonces, escribamos rápidamente un ejemplo para esto también. Cambiaré la faceta std::num_get<char> solo para tratar con unsigned int: para otros tipos integrales es necesario anular más funciones.Así pues, aquí es un reemplazo para el std::num_get<char> faceta:

class num_get: 
    public std::num_get<char> 
{ 
    iter_type do_get(iter_type it, iter_type end, 
        std::ios_base& ios, std::ios_base::iostate& err, 
        unsigned int& value) const 
    { 
     it = std::num_get<char>::do_get(it, end, ios, err, value); 
     if (it != end && !isspace(static_cast<unsigned char>(*it))) 
     { 
      err |= std::ios_base::failbit; 
     } 
     return it; 
    } 
}; 

Todo esto hace es derivar una clase de std::num_get<char> y anular una de sus funciones virtuales. La implementación de esta función es bastante directa: empiezo por leer el valor delegando en la clase base (me acabo de dar cuenta de que las funciones virtuales realmente quieren proteger en lugar de ser privadas como lo he hecho en el pasado, pero esta es una discusión completamente diferente) . Independientemente de si esto fue exitoso (si no lo fue, habrá configurado un estado de error en err) la anulación verifica si hay otro carácter disponible y, de ser así, verifica si es un espacio y si no establece un std::ios_base::failbit en el resultado del error err.

Lo que queda es la creación de la corriente de utilizar esta faceta particular en un std::locale y enganche la nueva std::locale en una corriente:

std::locale loc(std::locale(), new num_get); 
in.imbue(loc); 

Los std::locale s y sus facetas están internamente contados referencia, es decir, usted no debe No pierda de vista el puntero a la faceta y no necesita mantener el std::locale alrededor tampoco. Si parece engorroso para imbue() el std::locale creado o si desea utilizar esta lógica modificada en cualquier lugar, puede establecer el std::locale global que se utiliza para inicializar cualquier secuencia recién creada para usar la faceta personalizada std::num_get<char>.

2

Puede hacerlo de esta manera:

#include <iostream> 
#include <locale> 

class my_num_get : public std::num_get<char> { 
protected: 
    iter_type do_get(iter_type in, iter_type end, std::ios_base& str, std::ios_base::iostate& err, unsigned int& v) const 
    { 
     in = std::num_get<char>::do_get(in, end, str, err, v); 
     if(in != end && !std::isspace(*in, str.getloc())) 
      err |= std::ios_base::failbit; 
     return in; 
    } 
}; 

int main() { 
    using namespace std; 
    cin.imbue(std::locale(cin.getloc(), new my_num_get)); 
    cin.exceptions(ios_base::badbit | ios_base::failbit); 
    try { 
     unsigned int x; 
     cin >> x; 
    } catch(const std::exception &e) { 
     cerr << e.what() << "\n"; 
    } 
} 

Si quieres que esto funcione para otros tipos, lo implemento el siguiente de la misma manera:

iter_type do_get(iter_type, iter_type, ios_base&, ios_base::iostate&, T& v) const 

donde T es uno de bool, long, long long, unsigned short, unsigned long, unsigned long long, float, double, long double y void*.

Cuestiones relacionadas