Como testar se o operador stringstream >> analisou um tipo incorreto e ignorá-lo

Estou interessado em discutir methods para usar stringstream para analisar uma linha com vários tipos. Eu começaria olhando a seguinte linha:

 "2.832 1.3067 nana 1.678" 

Agora vamos supor que eu tenha uma linha longa que tenha várias strings e doubles . A maneira óbvia de resolver isso é tokenizar a string e depois verificar a conversão de cada uma. Estou interessado em pular este segundo passo e usar stringstream diretamente para encontrar apenas os números.

Imaginei que uma boa maneira de abordar isso seria ler a seqüência e verificar se o failbit foi definido, o que será feito se eu tentar analisar uma cadeia em um duplo.

Digamos que eu tenha o seguinte código:

 string a("2.832 1.3067 nana 1.678"); stringstream parser; parser.str(a); for (int i = 0; i > b; if (parser.fail()) { std::cout << "Failed!" << std::endl; parser.clear(); } std::cout << b << std::endl; } 

Ele imprimirá o seguinte:

 2.832 1.3067 Failed! 0 Failed! 0 

Não me surpreende que não consiga analisar uma cadeia de caracteres, mas o que está acontecendo internamente de tal forma que ela não consegue limpar seu failbit e analisar o próximo número?

O código a seguir funciona bem para ignorar a má palavra e coletar os valores double válidos

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

Aqui está uma amostra totalmente funcional .


Sua amostra quase acertou, faltava apenas consumir o campo de input inválido do stream depois de detectar o formato errado

  if (parser.fail()) { std::cout << "Failed!" << std::endl; parser.clear(); string dummy; parser >> dummy; } 

No seu caso, a extração tentará ler novamente de "nana" para a última iteração, daí as duas últimas linhas na saída.

Observe também os truques sobre iostream::fail() e como testar realmente para iostream::eof() na minha primeira amostra. Há um Q & A bem conhecido , porque o teste simples para EOF como uma condição de loop é considerado errado. E responde bem, como quebrar o loop de input quando valores inesperados / inválidos foram encontrados. Mas apenas como ignorar / ignorar campos de input inválidos não é explicado lá (e não foi solicitado).

Poucas diferenças menores para a resposta do πάντα ῥεῖ – faz com que ele também lide, por exemplo, representações de números negativos, etc., além de ser – IMHO – um pouco mais simples de ler.

 #include  #include  #include  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); } } 

Saída:

 2.832 1.3067 1.678 -100 0.05 

(veja aqui correndo)

Eu construí uma versão mais bem ajustada para isso, que é capaz de pular caracteres de input inválidos (sem precisar separar números double com caracteres em branco):

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

Saída

  2.832 1.3067 1.678 0.05 

Demonstração ao vivo

Se você gosta de concisão – aqui está outra opção que (ab?) Usa && para obter cout somente quando um número foi analisado com sucesso, e quando um número não é analisado ele usa o operador vírgula para poder clear() estado de erro do stream dentro do condicional antes de ler um personagem para ser ignorado …

 #include  #include  #include  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) ; } 

http://ideone.com/WvtvfU

Você pode usar std::istringstream::eof() para validar a input como esta:

 #include  #include  #include  // 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'; } } } 

Isso funciona porque a verificação eof() garante que apenas o dobro foi inserido e não o lixo como 12d4 .