我有另一个程序的输出,这个程序更像是人类可读而不是机器可读,但我还是打算解析它。这没什么太复杂的。
然而,我想知道在C ++中最好的方法是什么。这更像是一种“一般惯例”的问题。
我研究了Boost.Spirit,甚至让它运转了一下。那东西太疯狂了!如果我正在设计我正在阅读的语言,那么它可能是正确的工具。但实际上,考虑到它的极端编译时间,当我做错任何事情时,g ++会出现几页错误,这不是我需要的。 (我也不太需要运行时性能。)
考虑使用C ++运算符<<,但这似乎毫无价值。如果我的文件中有“John有5个小部件”这样的行,而其他人“Mary在459 Ramsy street工作”,我怎么能确保我的程序中有第一个类型的行,而不是第二个类型?我必须阅读整行,然后使用像string::find
和string::substr
这样的内容。
离开sscanf
。它会很好地处理上述案件
if( sscanf( str, "%s has %d widgets", chararr, & intvar ) == 2 )
// then I know I matched "foo has bar" type of string,
// and I now have the parameters too
所以我只是想知道我是否遗漏了某些内容,或者C ++是否真的没有太多的内置替代方案。
答案 0 :(得分:3)
sscanf
确实听起来非常适合您的要求:
一个潜在的问题是它容易出错,如果你有很多不断变化的解析短语,那么测试工作和风险就会令人担忧。保持sscanf
的精神,但使用istream
来确保类型安全:
#include <iostream>
#include <sstream>
// Str captures a string literal and consumes the same from an istream...
// (for non-literals, better to have `std::string` member to guarantee lifetime)
class Str
{
public:
Str(const char* p) : p_(p) { }
const char* c_str() const { return p_; }
private:
const char* p_;
};
bool operator!=(const Str& lhs, const Str& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) != 0;
}
std::istream& operator>>(std::istream& is, const Str& str)
{
std::string s;
if (is >> s)
if (s.c_str() != str)
is.setstate(std::ios_base::failbit);
return is;
}
// sample usage...
int main()
{
std::stringstream is("Mary has 4 cats");
int num_dogs, num_cats;
if (is >> Str("Mary") >> Str("has") >> num_dogs >> Str("dogs"))
{
std::cout << num_dogs << " dogs\n";
}
else if (is.clear(), is.seekg(0), // "reset" the stream...
(is >> Str("Mary") >> Str("has") >> num_cats >> Str("cats")))
{
std::cout << num_cats << " cats\n";
}
}
答案 1 :(得分:2)
GNU工具flex
和bison
是非常强大的工具,您可以使用这些工具沿着Spirit的路线但是(根据某些人)更容易使用,部分原因是错误报告有点更好,因为这些工具有自己的编译器。这个,或Spirit,或其他一些解析器生成器,是“正确”的方法,因为它为您提供了最大的灵活性。
如果您正在考虑使用strtok
,您可能需要查看stringstream
,它会拆分空格,并允许您在字符串,基元等之间进行一些很好的格式转换。它也可以插入到STL算法中,避免了原始C风格字符串内存管理的所有混乱细节。
答案 2 :(得分:1)
我在C ++中编写了大量的解析代码。它的效果非常好,但我自己编写代码并且不依赖于其他人编写的更通用的代码。 C ++没有提供已编写的大量代码,但它是编写此类代码的一种很好的语言。
我不确定你的问题是什么,只是你想找到一个已经编写过的代码,它可以满足您的需求。部分问题在于您没有真正描述您的需求,或者就此问题提出了问题。
如果您可以更具体地提出问题,我很乐意尝试提供更具体的答案。
答案 3 :(得分:1)
我使用过Boost.Regex(我认为它也是tr1 :: regex)。易于使用。
答案 4 :(得分:0)
总是有strtok()我想
答案 5 :(得分:0)
查看strtok。
答案 6 :(得分:0)
根据您要解析的内容,您可能需要正则表达式库。 请参阅msdn或earlier question。
就个人而言,再次根据确切的格式,我会考虑使用perl初始转换为更易于机器读取的格式(例如变量记录CSV),然后更容易导入C ++。
如果坚持使用C ++,则需要:
以下行的基类:
class Handler
{
public:
Handler(const std::string& regexExpr)
: regex_(regexExpr)
{}
bool match(const std::string& s)
{
return std::tr1::regex_match(s,regex_);
}
virtual bool process(const std::string& s) = 0;
private:
std::tr1::basic_regex<char> regex_;
};
为每种记录类型定义派生类,在集合中粘贴每个记录的实例并搜索匹配。
class WidgetOwner : public Handler
{
public:
WidgetOwner()
: Handler(".* has .* widgets")
{}
virtual bool process(const std::string& s)
{
char name[32];
int widgets= 0;
int fieldsRead = sscanf( s.c_str(), "%32s has %d widgets", name, & widgets) ;
if (fieldsRead == 2)
{
std::cout << "Found widgets in " << s << std::endl;
}
return fieldsRead == 2;
}
};
struct Pred
{
Pred(const std::string& record)
: record_(record)
{}
bool operator()(Handler* handler)
{
return handler->match(record_);
}
std::string record_;
};
std::set<Handler*> handlers_;
handlers_.insert(new WidgetOwner);
handlers_.insert(new WorkLocation);
Pred pred(line);
std::set<Handler*>::iterator handlerIt =
std::find_if(handlers_.begin(), handlers_.end(), pred);
if (handlerIt != handlers_.end())
(*handlerIt)->process(line);