Подтвердить что ты не робот

Как проверить, был ли оператор stringstream >> проанализирован плохой тип и пропустить его

Мне интересно обсуждать методы использования stringstream для разбора строки с несколькими типами. Я бы начал с рассмотрения следующей строки:

"2.832 1.3067 nana 1.678"

Теперь предположим, что у меня длинная строка с несколькими strings и doubles. Очевидным способом решить эту проблему является токенизация строки, а затем проверка конвертации каждой из них. Мне интересно пропустить этот второй шаг и использовать stringstream непосредственно, чтобы найти числа.

Я решил, что хороший способ приблизиться к этому - это прочитать строку и проверить, был ли установлен failbit, что будет, если я попытаюсь разобрать строку в double.

Скажем, у меня есть следующий код:

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

Он выведет следующее сообщение:

2.832
1.3067
Failed!
0
Failed!
0

Я не удивлен, что ему не удается проанализировать строку, но , что происходит внутри, так что не удается очистить ее failbit и проанализировать следующий номер?

4b9b3361

Ответ 1

Следующий код хорошо работает, чтобы пропустить плохое слово и собрать допустимые значения 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;
}

Здесь полностью работающий образец.


Ваша выборка почти правильно поняла, просто не хватало использовать недопустимое поле ввода из потока после обнаружения неправильного формата

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

В вашем случае извлечение попытается снова прочитать из "nana" для последней итерации, следовательно, последние две строки на выходе.

Также обратите внимание на трюк о iostream::fail() и как на самом деле проверить iostream::eof() в моем первом примере. Там хорошо известный Q & A, почему простое тестирование EOF как условия цикла считается неправильным. И он хорошо отвечает, как разбить входной цикл, когда были встречены неожиданные/недопустимые значения. Но как пропустить/игнорировать недопустимые поля ввода там не объяснено (и не спрашивали).

Ответ 2

Несколько незначительных отличий от ответа πάντα ῥεῖ - делает его также обработанным, например, отрицательные числа и т.д., а также быть - ИМХО - немного проще читать.

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

Вывод:

2.832
1.3067
1.678
-100
0.05

(см. здесь здесь)

Ответ 3

Я создал для него более точную настроенную версию, которая может пропускать недопустимый входной символ (без необходимости отделять цифры double пробельными символами):

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

Выход

 2.832
 1.3067
 1.678
 0.05

Live Demo

Ответ 4

Если вам понравилась сводка - здесь другой вариант, который (ab?) использует &&, чтобы получить cout выполненный только тогда, когда число было успешно проанализировано, а когда число не разобрано, он использует оператор запятой, чтобы иметь возможность to clear() состояние ошибки потока внутри условного выражения перед чтением символа, который нужно игнорировать...

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

http://ideone.com/WvtvfU

Ответ 5

Вы можете использовать std::istringstream::eof() для подтверждения ввода следующим образом:

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

Это работает, потому что проверка eof() гарантирует, что был введен только двойной, а не мусор, например 12d4.