Je souhaite discuter des méthodes d'utilisation de stringstream
pour analyser une ligne avec plusieurs types. Je commencerais par regarder la ligne suivante:
"2.832 1.3067 nana 1.678"
Supposons maintenant que j'ai une longue ligne qui a plusieurs strings
et doubles
. La façon évidente de résoudre ce problème est de tokeniser la chaîne, puis de vérifier la conversion de chacune. Je suis intéressé à sauter cette deuxième étape et à utiliser stringstream
directement pour ne trouver que les chiffres.
J'ai pensé qu'un bon moyen d'approcher cela serait de lire la chaîne et de vérifier si le failbit
a été défini, ce qui sera le cas si j'essaie d'analyser une chaîne en double.
Disons que j'ai le code suivant:
string a("2.832 1.3067 nana 1.678");
stringstream parser;
parser.str(a);
for (int i = 0; i < 4; ++i)
{
double b;
parser >> b;
if (parser.fail())
{
std::cout << "Failed!" << std::endl;
parser.clear();
}
std::cout << b << std::endl;
}
Il imprimera les éléments suivants:
2.832
1.3067
Failed!
0
Failed!
0
Je ne suis pas surpris qu'il ne parvienne pas à analyser une chaîne, mais que se passe-t-il en interne de sorte qu'il ne parvienne pas à effacer son failbit
et à analyser le numéro suivant?
Le code suivant fonctionne bien pour ignorer le mauvais mot et collecter les valeurs valides double
istringstream iss("2.832 1.3067 nana 1.678");
double num = 0;
while(iss >> num || !iss.eof()) {
if(iss.fail()) {
iss.clear();
string dummy;
iss >> dummy;
continue;
}
cout << num << endl;
}
Voici un échantillon pleinement fonctionnel .
Votre échantillon a presque réussi, il manquait juste de consommer le champ de saisie non valide du flux après avoir détecté un format incorrect
if (parser.fail()) {
std::cout << "Failed!" << std::endl;
parser.clear();
string dummy;
parser >> dummy;
}
Dans votre cas, l'extraction essaiera de relire à partir de "nana"
Pour la dernière itération, d'où les deux dernières lignes dans la sortie.
Notez également la ruse à propos de iostream::fail()
et comment tester réellement iostream::eof()
dans mon 1er échantillon. Il y a un Q&A bien conn , pourquoi un simple test de EOF comme condition de boucle est considéré comme incorrect. Et cela répond bien, comment rompre la boucle d'entrée en cas d'inattendu/invalide mais comment ignorer/ignorer les champs de saisie non valides n'est pas expliqué ici (et n'a pas été demandé).
Peu de différences mineures par rapport à la réponse de πάντα ῥεῖ - le fait également gérer par exemple représentations de nombres négatifs, etc., en plus d'être - à mon humble avis - un peu plus simple à lire.
#include <iostream>
#include <sstream>
#include <string>
int main()
{
std::istringstream iss("2.832 1.3067 nana1.678 x-1E2 xxx.05 meh.ugh");
double num = 0;
for (; iss; )
if (iss >> num)
std::cout << num << '\n';
else if (!iss.eof())
{
iss.clear();
iss.ignore(1);
}
}
Production:
2.832
1.3067
1.678
-100
0.05
(voir le fonctionnement ici )
Si vous aimez la concision - voici une autre option qui (ab?) Utilise &&
Pour que cout
ne soit fait que lorsqu'un nombre a été analysé avec succès, et quand un nombre n'est pas analysé, il utilise l'opérateur virgule pour pouvoir clear()
streamer l'état d'erreur à l'intérieur du conditionnel avant de lire un caractère à ignorer ...
#include <iostream>
#include <sstream>
#include <string>
int main()
{
std::istringstream iss("2.832 1.3067 nana1.678 x-1E2 xxx.05 meh.ugh");
double num = 0;
char ignored;
while (iss >> num && std::cout << num << '\n' ||
(iss.clear(), iss) >> ignored)
;
}
J'ai créé une version plus fine pour cela, qui est capable d'ignorer les caractères d'entrée invalides (sans avoir besoin de séparer les nombres double
avec des espaces):
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
istringstream iss("2.832 1.3067 nana1.678 xxx.05 meh.ugh");
double num = 0;
while(iss >> num || !iss.eof()) {
if(iss.fail()) {
iss.clear();
while(iss) {
char dummy = iss.peek();
if(std::isdigit(dummy) || dummy == '.') {
// Stop consuming invalid double characters
break;
}
else {
iss >> dummy; // Consume invalid double characters
}
}
continue;
}
cout << num << endl;
}
return 0;
}
Production
2.832
1.3067
1.678
0.05
Vous pouvez utiliser std::istringstream::eof()
pour valider entrée comme ceci:
#include <string>
#include <sstream>
#include <iostream>
// remove white-space from each end of a std::string
inline std::string& trim(std::string& s, const char* t = " \t")
{
s.erase(s.find_last_not_of(t) + 1);
s.erase(0, s.find_first_not_of(t));
return s;
}
// serial input
std::istringstream in1(R"~(
2.34 3 3.f 3.d .75 0 wibble
)~");
// line input
std::istringstream in2(R"~(
2.34
3
3.f
3.d
.75
0
wibble
)~");
int main()
{
std::string input;
// NOTE: This technique will not work if input is empty
// or contains only white-space characters. Therefore
// it is safe to use after a conditional extraction
// operation >> but it is not reliable after std::getline()
// without further checks.
while(in1 >> input)
{
// input will not be empty and will not contain white-space.
double d;
if((std::istringstream(input) >> d >> std::ws).eof())
{
// d is a valid double
std::cout << "d1: " << d << '\n';
}
}
std::cout << '\n';
while(std::getline(in2, input))
{
// eliminate blank lines and lines
// containing only white-space (trim())
if(trim(input).empty())
continue;
// NOW this is safe to use
double d;
if((std::istringstream(input) >> d >> std::ws).eof())
{
// d is a valid double
std::cout << "d2: " << d << '\n';
}
}
}
Cela fonctionne parce que la vérification eof()
garantit que seulement le double a été entré et non pas comme des ordures comme 12d4
.