Более безопасная, но простая в использовании и гибкая C ++ альтернатива sscanf () - PullRequest
22 голосов
/ 22 марта 2012

Когда мне нужно сканировать значения из набора строк, я часто оказываюсь в обращении к C sscanf() строго из-за его простоты и простоты использования. Например, я могу очень кратко извлечь пару двойных значений из строки с помощью:

string str;
double val1, val2;
if (sscanf(str.c_str(), "(%lf,%lf)", &val1, &val2) == 2)
{
    // got them!
}

Это явно не очень C ++. Я не обязательно считаю это мерзостью, но я всегда ищу лучший способ сделать общее задание. Я понимаю, что «способ C ++» для чтения строк - это istringstream, но дополнительная обработка, необходимая для обработки скобок и запятой в приведенной выше строке формата, просто делает его слишком громоздким, чтобы я захотел его использовать.

Есть ли хороший способ либо согнуть встроенные средства под мою волю способом, подобным описанному выше, или есть хорошая библиотека C ++, которая делает вышеописанное более безопасным для типов способом? Похоже, Boost.Format действительно хорошо решил проблему с выводом, но я не нашел ничего похожего краткого для ввода.

Ответы [ 3 ]

15 голосов
/ 23 марта 2012

Я написал немного кода, который может читать строковые и символьные литералы.Как и при обычном чтении потока, если он получает неверные данные, он устанавливает плохой бит потока.Это должно работать для всех типов потоков, включая широкие потоки.Вставьте этот бит в новый заголовок:

#include <iostream>
#include <string>
#include <array>
#include <cstring>

template<class e, class t, int N>
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, const e(&sliteral)[N]) {
        std::array<e, N-1> buffer; //get buffer
        in >> buffer[0]; //skips whitespace
        if (N>2)
                in.read(&buffer[1], N-2); //read the rest
        if (strncmp(&buffer[0], sliteral, N-1)) //if it failed
                in.setstate(in.rdstate() | std::ios::failbit); //set the state
        return in;
}
template<class e, class t>
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, const e& cliteral) {
        e buffer;  //get buffer
        in >> buffer; //read data
        if (buffer != cliteral) //if it failed
                in.setstate(in.rdstate() | std::ios::failbit); //set the state
        return in;
}
//redirect mutable char arrays to their normal function
template<class e, class t, int N>
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, e(&carray)[N]) {
        return std::operator>>(in, carray);
}

И это сделает ввод символов очень простым:

std::istringstream input;
double val1, val2;
if (input >>'('>>val1>>','>>val2>>')') //less chars than scanf I think
{
    // got them!
}

ДОКАЗАТЕЛЬСТВО КОНЦЕПЦИИ .Теперь вы можете cin строковые и символьные литералы, и если ввод не является точным соответствием, он действует так же, как любой другой тип, который не был введен правильно.Обратите внимание, что это соответствует только пробелу в строковых литералах, которые не являются первым символом.Это всего лишь четыре функции, каждая из которых просто умопомрачительна.

EDIT

Разбор с потоками - плохая идея.Используйте регулярное выражение.

6 голосов
/ 22 марта 2012

Лучшее, что я когда-либо использовал для разбора строк, это boost.spirit.Это быстро, безопасно и очень гибко.Большим преимуществом является то, что вы можете написать правила синтаксического анализа в форме, близкой к грамматике EBNF

using namespace boost::spirit;

boost::fusion::vector < double, double > value_;

std::string string_ = "10.5,10.6 ";

bool result_ = qi::parse(
    string_.begin(),
    string_.end(),
    qi::double_ >> ',' >> qi::double_, // Parsing rule
    value_); // value
3 голосов
/ 22 марта 2012

Я думаю, что с помощью регулярных выражений это можно сделать легко.Так что boost :: regex или std :: regex в новом стандарте.После этого просто конвертируйте ваши токены в float, используя lexical_cast или напрямую потоки.

...