Как проверить, был ли оператор 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
и проанализировать следующий номер?
Ответы
Ответ 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
.