2012-03-22 14 views
20

Cuando necesito escanear valores de varias cadenas, a menudo me encuentro recurriendo a C sscanf() estrictamente debido a su simplicidad y facilidad de uso. Por ejemplo, de manera muy sucinta puedo tirar un par de valores dobles de una cadena con:Más segura, fácil de usar y flexible alternativa de C++ a sscanf()

string str; 
double val1, val2; 
if (sscanf(str.c_str(), "(%lf,%lf)", &val1, &val2) == 2) 
{ 
    // got them! 
} 

Esto, obviamente, no es muy C++. No necesariamente lo considero una abominación, pero siempre estoy buscando una mejor manera de hacer una tarea común. Entiendo que el "modo C++" para leer cadenas es istringstream, pero el tipeo extra requerido para manejar los paréntesis y la coma en la cadena de formato de arriba simplemente lo hace demasiado engorroso para hacer que quiera usarlo.

¿Hay una buena manera de doblar las instalaciones integradas a mi voluntad de una manera similar a la anterior, o hay una buena biblioteca de C++ que hace lo anterior de una manera más segura? Parece que Boost.Format realmente ha resuelto el problema de salida de una buena manera, pero no he encontrado nada similar para la entrada.

+0

Eh, realmente hubiera esperado que Boost tuviera algo aquí. Ahora mis dedos están ansiosos por hacer una biblioteca propia para ello ... –

+1

FWIW, considero que sscanf es tan "C++" como cualquier otra cosa, solo tiene capacidad limitada (pero no tan sintácticamente horrible como iostreams). He visto propuestas para implementar las funciones de formato C en términos de plantillas variadic (por lo tanto C++ 11 solamente). Esto sería una gran mejora si se pudiera realizar. Un buen proyecto pequeño: avíseme cuando lo haya terminado. ; ^) – mcmcc

+0

@mcmcc: en realidad, implementar 'printf' con plantillas variadic es bastante fácil, aparte de los argumentos posicionales. Esperaría el mismo problema con 'sscanf'. Aparte de eso, no veo un problema en el rendimiento. En todo caso, la inclusión parcial podría ser realmente beneficiosa aquí. –

Respuesta

13

me escribió un poco de código que puede leer en la cadena de caracteres y literales. Al igual que las lecturas de transmisión normales, si obtiene datos no válidos, establece el bit de la secuencia. Esto debería funcionar para todo tipo de transmisiones, incluidas las extensas. Se adhieren este bit en una nueva cabecera:

#include <iostream> 
#include <string> 
#include <array> 
#include <cstring> 

template<class e, class t, int N> 
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, const e(&sliteral)[N]) { 
     std::array<e, N-1> buffer; //get buffer 
     in >> buffer[0]; //skips whitespace 
     if (N>2) 
       in.read(&buffer[1], N-2); //read the rest 
     if (strncmp(&buffer[0], sliteral, N-1)) //if it failed 
       in.setstate(in.rdstate() | std::ios::failbit); //set the state 
     return in; 
} 
template<class e, class t> 
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, const e& cliteral) { 
     e buffer; //get buffer 
     in >> buffer; //read data 
     if (buffer != cliteral) //if it failed 
       in.setstate(in.rdstate() | std::ios::failbit); //set the state 
     return in; 
} 
//redirect mutable char arrays to their normal function 
template<class e, class t, int N> 
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, e(&carray)[N]) { 
     return std::operator>>(in, carray); 
} 

Y hará que los caracteres de entrada muy fácil:

std::istringstream input; 
double val1, val2; 
if (input >>'('>>val1>>','>>val2>>')') //less chars than scanf I think 
{ 
    // got them! 
} 

PROOF OF CONCEPT. Ahora puede cin literales de cadena y caracteres, y si la entrada no es una coincidencia exacta, actúa igual que cualquier otro tipo que no haya podido ingresar correctamente. Tenga en cuenta que esto solo coincide con el espacio en blanco en los literales de cadena que no son el primer carácter. Son solo cuatro funciones, todas las cuales son cerebrales simples.

EDITAR

Analizar con las corrientes es una mala idea. Usa una expresión regular

+0

@JasonR Allí, tengo la resolución de sobrecarga para hacer ejercicio, por lo que ahora usa '>>' como todas las demás entradas –

+0

Me gusta. Aunque preferiría algo que me permita especificar la cadena de formato y los argumentos por separado (como sscanf o Boost.Format), esta es definitivamente la mejor solución disponible que he visto. Buen trabajo. –

+0

@JasonR: En realidad, eso es lo que me propuse hacer, pero me preguntaba cómo omitir el análisis sintáctico de cadenas, y me di cuenta de que podía dividir las cadenas ... lo que finalmente condujo a esta solución muy simple. Como dices, formatear las cosas de una manera específica sigue siendo complicado. Me pregunto si puedo abordar eso. Si pienso en algo, volveré a comentar. –

3

Creo que con la expresión regular podría hacerse fácilmente. Así que boost :: regex o std :: regex en un nuevo estándar. Después de eso simplemente convierta sus tokens para flotar usando lexical_cast o streams directamente.

+0

Ouch ... funciona, pero ciertamente no es elegante. –

6

Lo mejor que he usado para el análisis de cadenas es boost.spirit. Es rápido, seguro y muy flexible. La gran ventaja es que se puede escribir reglas de análisis en forma estrecha a la gramática EBNF

using namespace boost::spirit; 

boost::fusion::vector < double, double > value_; 

std::string string_ = "10.5,10.6 "; 

bool result_ = qi::parse(
    string_.begin(), 
    string_.end(), 
    qi::double_ >> ',' >> qi::double_, // Parsing rule 
    value_); // value 
+1

y paréntesis –

+1

Gracias por la entrada. No diría que está a la par con 'sscanf()' para mis necesidades. Estoy seguro de que es muy poderoso (no estoy familiarizado con el espíritu o lo que es "gramática EVNF"), pero para mis propósitos no es lo suficientemente simple como para querer cambiar. –

+0

@JasonR: Formulario extendido de Backus-Naur (ISO/IEC 14977). – MSalters

Cuestiones relacionadas