Альтернатива C++ для синтаксического анализа ввода с помощью sscanf
предполагая, что моя программа ожидает доводы форме [ 0.562 , 1.4e-2 ]
(т. е. пары поплавков), как я должен анализировать этот ввод в C++ без регулярных выражений? Я знаю, что есть много угловых случаев, чтобы рассмотреть, когда дело доходит до пользовательского ввода, но предположим, что данный ввод близко соответствует приведенному выше формату (кроме дальнейших пробелов).
в C, я мог бы сделать что-то вроде sscanf(string, "[%g , %g]", &f1, &f2);
для извлечения двух значений с плавающей запятой, что очень компактно.
в C++, это то, что Я придумал до сих пор:
std::string s = "[ 0.562 , 1.4e-2 ]"; // example input
float f1 = 0.0f, f2 = 0.0f;
size_t leftBound = s.find('[', 0) + 1;
size_t count = s.find(']', leftBound) - leftBound;
std::istringstream ss(s.substr(leftBound, count));
string garbage;
ss >> f1 >> garbage >> f2;
if(!ss)
std::cout << "Error while parsing" << std::endl;
как я могу улучшить этот код? В частности, меня беспокоит garbage
строку, но я не знаю, как еще пропустить ,
между двумя значениями.
4 ответов
очевидный подход-создать простой манипулятор и использовать его. Например, манипулятор с использованием статически предоставленного char
чтобы определить, является ли следующий символ без пробелов этим символом, и если да, то он может выглядеть так:
#include <iostream>
#include <sstream>
template <char C>
std::istream& expect(std::istream& in)
{
if ((in >> std::ws).peek() == C) {
in.ignore();
}
else {
in.setstate(std::ios_base::failbit);
}
return in;
}
затем вы можете использовать таким образом построить манипулятор для извлечения символов:
int main(int ac, char *av[])
{
std::string s(ac == 1? "[ 0.562 , 1.4e-2 ]": av[1]);
float f1 = 0.0f, f2 = 0.0f;
std::istringstream in(s);
if (in >> expect<'['> >> f1 >> expect<','> >> f2 >> expect<']'>) {
std::cout << "read f1=" << f1 << " f2=" << f2 << '\n';
}
else {
std::cout << "ERROR: failed to read '" << s << "'\n";
}
}
Я могу позволить себе использовать boost, вы можете использовать дух.
посмотреть
С
string
Жить На Coliru (в C++03):обновление и вот подход, если вы на самом деле пытались читать из потока (на самом деле это несколько проще и интегрируется действительно ну с другой поток чтения):
Живу На Coliru too (c++03)
хотя это кажется более многословным, дух также много более мощный и надежный, чем sscanf
. И он работает на потоках.
также обратите внимание, что inf
, -inf
, nan
будет обработан, как ожидалось.
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/qi_match.hpp>
#include <sstream>
namespace qi = boost::spirit::qi;
int main()
{
std::istringstream ss("[ 0.562 , 1.4e-2 ]"); // example input
ss.unsetf(std::ios::skipws); // we might **want** to handle whitespace in our grammar, not needed now
float f1 = 0.0f, f2 = 0.0f;
if (ss >> qi::phrase_match('[' >> qi::double_ >> ',' >> qi::double_ >> ']', qi::space, f1, f2))
{
std::cout << "Parsed: " << f1 << " and " << f2 << "\n"; // default formatting...
} else
{
std::cout << "Error while parsing" << std::endl;
}
}
кроме регулярных выражений, вероятно, что-то в Boost вы можете использовать. Но если вы не можете использовать Boost, вы можете определить std::ctype<char>
фасет, который эффективно игнорирует все ненужные символы, классифицируя их как пробелы. Вы можете установить этот фасет в локаль и внедрить его в ss
.
используя старую школу и простотой:
std::istringstream inp_str("[ 0.562 , 1.4e-2 ]");
double x;
double y;
char c;
inp_str >> c; // Eat the '['
inp_str >> x; // Input the first ordinate.
inp_str >> c >> c; // Eat the space and comma.
inp_str >> y; // Input the second ordinate.
это работает, потому что оператор извлечения пропускает ведущие пробелы для числовых типов.