2012-01-21 8 views
7

Il mio codice legge le variabili int non firmate dal file di testo Input_File_Name.Come leggere le variabili int non firmate dal file correttamente, usando 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; 
} 

Funziona eccellente. Ma quando riempio file di testo con dati errati

33abcd4 567fg8 

Funziona in modo tale:

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

Perché non questa eccezione tiro linea input_stream>>Row_Count;? Come ho capito, input_stream considera qualsiasi simbolo non numerico come delimitatore e sul prossimo passo prova a leggere "abcd". È così? Come impostare un simbolo di spazio come delimitatore per generare un'eccezione ios::failure da questa riga di codice input_stream>>Row_Count; durante la lettura di "33abcd4"?

+1

È proprio come funziona 'operator >>'; estrarrà '33' e quindi si fermerà, 'abcd' rimarrà nello stream per la prossima chiamata a 'operator >>'. Si potrebbe invece leggere '33abcd4' in una stringa e quindi verificare la presenza di caratteri non numerici. Inoltre, se hai un compilatore recente che supporta C++ 11, controlla se la libreria standard fornisce 'std :: stoull'. – jrok

risposta

4

L'estrazione normale di un valore intero ha esito positivo se il flusso è in grado di leggere qualsiasi valore intero. Cioè, se c'è almeno una cifra seguita facoltativamente da qualcosa, la lettura di un intero ha esito positivo. Le normali operazioni di estrazione non provano a leggere, in particolare non cercano di trovare il successivo spazio bianco.

Dal suono di ciò, si vuole essere sicuri che ci sia uno spazio vuoto che segue il proprio numero e fallisce se non c'è. Posso pensare a due diversi approcci per fare questo:

  1. Creare un semplice manipolatore che controlli la presenza del flusso su un carattere di spazio bianco. Ciò, tuttavia, significa che si leggeranno i valori usando qualcosa come in >> value >> is_space.
  2. Creare un facet personalizzato std::num_get<char>, installarlo in un std::locale e imbue() questo std::locale nel tuo stream (s). È un po 'più coinvolto, ma non richiede modifiche al modo in cui vengono letti gli interi.

Creazione di un manipolatore come questo è abbastanza banale:

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

Ora, cambiando i numeri modo in cui vengono letti è più interessante e ho il sospetto che avevo appena nominato un certo numero di classi della libreria standard la maggior parte delle persone sono abbastanza inconsapevole di. Quindi, digitiamo rapidamente un esempio anche per questo. Cambierò la faccetta std::num_get<char> solo per gestire unsigned int: per farlo per altri tipi interi è necessario sovrascrivere più funzioni.Quindi, ecco un sostituto per il std::num_get<char> sfaccettatura:

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

Tutto ciò che fa è per derivare una classe da std::num_get<char> e ignorare una delle sue funzioni virtuali. L'implementazione di questa funzione è abbastanza semplice: inizia con la lettura del valore delegando alla classe base (mi sono appena reso conto che le funzioni virtuali in realtà vogliono essere protette piuttosto che private come ho fatto in passato ma questa è una discussione completamente diversa) . Indipendentemente dal fatto che questo abbia avuto successo (se non lo fosse, avrà impostato erroneamente uno stato di errore) i controlli di override se è disponibile un altro carattere e, in tal caso, controlla se si tratta di uno spazio e se non imposta uno std::ios_base::failbit in il risultato dell'errore err.

ciò che rimane è per impostare il flusso di utilizzare questo particolare aspetto in un std::locale e agganciare il nuovo std::locale in un flusso:

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

Le std::locale s ed i suoi aspetti sono internamente riferimento contati, cioè si shouldn 't tenere traccia del puntatore al facet e non è necessario mantenere il std::locale in entrambi. Se sembra essere complicato a il std::locale creato o si desidera utilizzare questa logica modificata ovunque, è possibile impostare il globale std::locale che viene utilizzato per inizializzare qualsiasi flusso appena creato per utilizzare il facet personalizzato std::num_get<char>.

2

Si può fare in questo modo:

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

Se si desidera che questo a lavorare per altri tipi di troppo, quindi implementare la seguente nello stesso modo:

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

dove T è uno dei bool, long, long long, unsigned short, unsigned long, unsigned long long, float, double, long double e void*.

Problemi correlati