2010-12-28 7 views
13

Me gustaría utilizar las facilidades proporcionadas por stringstream para extraer valores de un formato fijo string como una alternativa de tipo seguro a sscanf. ¿Cómo puedo hacer esto?Usar stringstream en lugar de `sscanf` para analizar una cadena de formato fijo

Considere el siguiente caso de uso específico. Tengo un std::string en el siguiente formato fijo:

YYYYMMDDHHMMSSmmm

Dónde:

YYYY = 4 digits representing the year 
MM = 2 digits representing the month ('0' padded to 2 characters) 
DD = 2 digits representing the day ('0' padded to 2 characters) 
HH = 2 digits representing the hour ('0' padded to 2 characters) 
MM = 2 digits representing the minute ('0' padded to 2 characters) 
SS = 2 digits representing the second ('0' padded to 2 characters) 
mmm = 3 digits representing the milliseconds ('0' padded to 3 characters) 

Anteriormente yo estaba haciendo algo en este sentido:

string s = "20101220110651184"; 
unsigned year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, milli = 0;  
sscanf(s.c_str(), "%4u%2u%2u%2u%2u%2u%3u", &year, &month, &day, &hour, &minute, &second, &milli); 

Los valores de anchura son números mágicos y está bien Me gustaría utilizar transmisiones para extraer estos valores y convertirlos a unsigned s en aras de la seguridad del tipo. Pero cuando intento esto:

stringstream ss; 
ss << "20101220110651184"; 
ss >> setw(4) >> year; 

year conserva el valor 0. Debe ser 2010.

¿Cómo hago lo que estoy tratando de hacer? No puedo usar Boost ni ninguna otra biblioteca de terceros, ni puedo usar C++ 0x.

+0

* Bill no puede esperar a que su copia de "estándar de C++ iostreams y Locales" ... 1 –

+0

'setw() 'es para escribir salida. No funciona para leer en. – marcog

+1

Tal vez debería volver al inicio de la secuencia antes de extraer el primer campo. –

Respuesta

6

Una opción no es particularmente eficiente sería construir algunas cadenas temporales y el uso de un yeso léxica:

std::string s("20101220110651184"); 
int year = lexical_cast<int>(s.substr(0, 4)); 
// etc. 

lexical_cast se pueden implementar en tan sólo unas pocas líneas de código; Herb Sutter presentó el mínimo indispensable en su artículo, "The String Formatters of Manor Farm."

No es exactamente lo que está buscando, pero es una forma segura para extraer campos de ancho fijo de una cadena.

+0

No puedo usar 'lexical_cast', ya que es parte de Boost. –

+0

Aunque podría usar streams nuevamente o algunas cosas de tipo 'atoi'. Sin embargo, esperaba poder lograr esto de una manera más natural. –

+0

@John: Puedes escribir el tuyo con bastante facilidad. Me he relacionado con uno de los artículos de Herb Sutter en el que se presenta una implementación muy básica (siete líneas de código con un bonito formato). O bien, publiqué una versión muy simple en [mi primera publicación de Stack Overflow] (http://stackoverflow.com/questions/1528374/how-can-i-extend-a-lexical-cast-to-support-enumerated-types); esa es dos líneas de código –

4

uso el siguiente, que podría ser útil para Usted:

template<typename T> T stringTo(const std::string& s) 
    { 
     std::istringstream iss(s); 
     T x; 
     iss >> x; 
     return x; 
    }; 

template<typename T> inline std::string toString(const T& x) 
    { 
     std::ostringstream o; 
     o << x; 
     return o.str(); 
    } 

Estas plantillas requieren:

#include <sstream> 

Uso

long date; 
date = stringTo<long>(std::cin); 

YMMV

+0

En la función 'stringTo', es muy importante verificar el estado de' iss' después de la extracción para asegurar que tuvo éxito y manejar los errores apropiadamente (lanzar una excepción, devolver un código de error, abortar la aplicación, lo que sea). –

+0

+1 esto es, en esencia, básicamente lo que @James sugiere más arriba. Esperaba usar algo que ya proporciona StdLib, pero es posible que deba escribirlo yo mismo –

1

De here, es posible encontrar esto útil:

template<typename T, typename charT, typename traits> 
std::basic_istream<charT, traits>& 
    fixedread(std::basic_istream<charT, traits>& in, T& x) 
{ 
    if (in.width() == 0) 
    // Not fixed size, so read normally. 
    in >> x; 
    else { 
    std::string field; 
    in >> field; 
    std::basic_istringstream<charT, traits> stream(field); 
    if (! (stream >> x)) 
     in.setstate(std::ios_base::failbit); 
    } 
    return in; 
} 

setw() sólo se aplica a la lectura en CStrings de cadenas. La función anterior usa este hecho, leyendo en una cadena y luego fundiéndolo al tipo requerido. Puede usarlo en combinación con setw() o ss.width(w) para leer en un campo de ancho fijo de cualquier tipo.

+0

+1 Esto, nuevamente, es esencialmente lo que también sugirió @James. Estoy sintiendo una tendencia aquí ... :) –

4

Erm, si es un formato fijo, ¿por qué no haces esto?

std::string sd("20101220110651184"); 
    // insert spaces from the back 
    sd.insert(14, 1, ' '); 
    sd.insert(12, 1, ' '); 
    sd.insert(10, 1, ' '); 
    sd.insert(8, 1, ' '); 
    sd.insert(6, 1, ' '); 
    sd.insert(4, 1, ' '); 
    int year, month, day, hour, min, sec, ms; 
    std::istringstream str(sd); 
    str >> year >> month >> day >> hour >> min >> sec >> ms; 
+0

+1 por jove, ¡esto podría funcionar! –

+0

Básicamente está creando una nueva cadena delimitada por espacios que el operador >> puede analizar porque contiene espacios ... No es muy eficiente. – BHS

0
template<typename T> 
struct FixedRead { 
    T& content; 
    int size; 
    FixedRead(T& content, int size) : 
      content(content), size(size) { 
     assert(size != 0); 
    } 
    template<typename charT, typename traits> 
    friend std::basic_istream<charT, traits>& 
    operator >>(std::basic_istream<charT, traits>& in, FixedRead<T> x) { 
     int orig_w = in.width(); 
     std::basic_string<charT, traits> o; 
     in >> setw(x.size) >> o; 
     std::basic_stringstream<charT, traits> os(o); 
     if (!(os >> x.content)) 
      in.setstate(std::ios_base::failbit); 
     in.width(orig_w); 
     return in; 
    } 
}; 

template<typename T> 
FixedRead<T> fixed_read(T& content, int size) { 
    return FixedRead<T>(content, size); 
} 

void test4() { 
    stringstream ss("20101220110651184"); 
    int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, ms = 0; 
    ss >> fixed_read(year, 4) >> fixed_read(month, 2) >> fixed_read(day, 2) 
      >> fixed_read(hour, 2) >> fixed_read(min, 2) >> fixed_read(sec, 2) 
      >> fixed_read(ms, 4); 
    cout << "year:" << year << "," << "month:" << month << "," << "day:" << day 
      << "," << "hour:" << hour << "," << "min:" << min << "," << "sec:" 
      << sec << "," << "ms:" << ms << endl; 
} 
0

La solución de ps5mh es muy agradable, pero no funciona para el análisis de tamaño fijo de cadenas que incluyen espacios en blanco. Las revisiones siguientes: Solución Este

template<typename T, typename T2> 
struct FixedRead 
{ 
    T& content; 
    T2& number; 
    int size; 
    FixedRead(T& content, int size, T2 & number) : 
     content(content), number(number), size(size) 
    { 
     assert (size != 0); 
    } 
    template<typename charT, typename traits> 
    friend std::basic_istream<charT, traits>& 
    operator >>(std::basic_istream<charT, traits>& in, FixedRead<T,T2> x) 
    { 
     if (!in.eof() && in.good()) 
     { 
      std::vector<char> buffer(x.size+1); 
      in.read(buffer.data(), x.size); 
      int num_read = in.gcount(); 
      buffer[num_read] = 0; // set null-termination of string 
      std::basic_stringstream<charT, traits> os(buffer.data()); 
      if (!(os >> x.content)) 
       in.setstate(std::ios_base::failbit); 
      else 
       ++x.number; 
     } 
     return in; 
    } 
}; 
template<typename T, typename T2> 
FixedRead<T,T2> fixedread(T& content, int size, T2 & number) { 
    return FixedRead<T,T2>(content, size, number); 
} 

Esto puede ser utilizado como:

std::string s = "90007127  19000715790007397"; 
std::vector<int> ints(5); 
int num_read = 0; 
std::istringstream in(s); 
in >> fixedread(ints[0], 8, num_read) 
    >> fixedread(ints[1], 8, num_read) 
    >> fixedread(ints[2], 8, num_read) 
    >> fixedread(ints[3], 8, num_read) 
    >> fixedread(ints[4], 8, num_read); 
// output: 
// num_read = 4 (like return value of sscanf) 
// ints = 90007127, 1, 90007157, 90007397 
// ints[4] is uninitialized 
Cuestiones relacionadas