Альтернатива С++ для синтаксического анализа с помощью sscanf

Предполагая, что моя программа ожидает аргументы формы [ 0.562 , 1.4e-2 ] (т.е. пары поплавков), как я должен анализировать этот ввод на С++ без регулярных выражений? Я знаю, что есть много угловых случаев для рассмотрения, когда речь заходит о пользовательском вводе, но пусть предположить, что данный вход близко соответствует указанному выше формату (кроме дополнительных пробелов).

В C я мог бы сделать что-то вроде sscanf(string, "[%g , %g]", &f1, &f2);, чтобы извлечь два значения с плавающей запятой, что очень компактно.

В С++ это то, что я придумал до сих пор:

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, но я не знаю, как еще пропустить , между этими двумя значениями.

Ответ 1

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

Ответ 2

Если вы можете использовать повышение, вы можете использовать Дух.

Увидеть

  • Из string Live On Coliru (в С++ 03):

  • Обновление А вот подход, если вы на самом деле пытались читать из потока (на самом деле он несколько проще и очень хорошо интегрируется с другими вашими действиями по чтению потока):
    Жить на Колиру тоже (c++ 03)

Несмотря на то, что это кажется более многословным, Spirit также намного более мощный и безопасный для типов, чем sscanf. И это работает на потоках.

Также обратите внимание, что inf, -inf, nan будет обрабатываться как ожидалось.

Live On Coliru

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

Ответ 3

Помимо регулярных выражений, возможно, что-то в Boost можно использовать. Но если вы не можете использовать Boost, вы можете определить грань std::ctype<char>, которая эффективно игнорирует все ненужные символы, классифицируя их как пробельные символы. Вы можете установить этот фасет в локаль и наполнить его в ss.

Ответ 4

Использование старой школы и простоты:

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.

Это работает, потому что оператор извлечения пропускает ведущие пробелы для числовых типов.