代码之家  ›  专栏  ›  技术社区  ›  Andreas Brinck

用C++实现简单字符串解析

c++
  •  33
  • Andreas Brinck  · 技术社区  · 14 年前

    我使用C++已经有很长时间了,但是我还是会退缩。 scanf 当我必须解析简单的文本文件时。例如,给定这样一个配置(也假设字段的顺序可能不同):

    foo: [3 4 5]
    baz: 3.0
    

    char line[SOME_SIZE];
    while (fgets(line, SOME_SIZE, file)) {
        int x, y, z;
        if (3 == sscanf(line, "foo: [%d %d %d]", &x, &y, &z)) {
            continue;
        }
        float w;
        if (1 == sscanf(line, "baz: %f", &w)) {
            continue;
        }
    }
    

    7 回复  |  直到 14 年前
        1
  •  30
  •   Nikko    14 年前

    大多数情况下,我使用std::istringstream和std::getline的组合(可以用来分隔单词)来获得我想要的东西。如果我能让我的配置文件看起来像:

    foo=1,2,3,4

    这样就容易了。

    文本文件如下:

    foo=1,2,3,4
    bar=0
    

    你可以这样分析:

    int main()
    {
        std::ifstream file( "sample.txt" );
    
        std::string line;
        while( std::getline( file, line ) )   
        {
            std::istringstream iss( line );
    
            std::string result;
            if( std::getline( iss, result , '=') )
            {
                if( result == "foo" )
                {
                    std::string token;
                    while( std::getline( iss, token, ',' ) )
                    {
                        std::cout << token << std::endl;
                    }
                }
                if( result == "bar" )
                {
                   //...
        }
    }
    
        2
  •  19
  •   Matthieu N. Matthieu N.    14 年前

    这个 C++ String Toolkit Library (StrTk) 您的问题有以下解决方案:

    #include <string>
    #include <deque>
    #include "strtk.hpp"
    
    int main()
    {
       std::string file_name = "simple.txt";
       strtk::for_each_line(file_name,
                           [](const std::string& line)
                           {
                              std::deque<std::string> token_list;
                              strtk::parse(line,"[]: ",token_list);
                              if (token_list.empty()) return;
    
                              const std::string& key = token_list[0];
    
                              if (key == "foo")
                              {
                                //do 'foo' related thing with token_list[1] 
                                //and token_list[2]
                                return;
                              }
    
                              if (key == "bar")
                              {
                                //do 'bar' related thing with token_list[1]
                                return;
                              }
    
                           });
    
       return 0;
    }
    

    Here

        3
  •  6
  •   rubenvb    12 年前

    精神不是用来分析复杂结构的。它也非常擅长微解析,几乎与C+scanf代码段的紧凑性相匹配:

    #include <boost/spirit/include/qi.hpp>
    #include <string>
    #include <sstream>
    
    using namespace boost::spirit::qi;
    
    
    int main()
    {
       std::string text = "foo: [3 4 5]\nbaz: 3.0";
       std::istringstream iss(text);
    
       std::string line;
       while (std::getline(iss, line))
       {
          int x, y, z;
          if(phrase_parse(line.begin(), line.end(), "foo: [">> int_ >> int_ >> int_ >> "]", space, x, y, z))
             continue;
          float w;
          if(phrase_parse(line.begin(), line.end(), "baz: ">> float_, space , w))
             continue;
       }
    }
    

    (我不明白为什么他们没有添加“容器”版本,如果我们可以写:

    if(phrase_parse(line, "foo: [">> int_ >> int_ >> int_ >> "]", space, x, y, z))
       continue;
    

    但事实上:

    • 它增加了大量的编译时开销。
    • 错误消息是残酷的。如果你在scanf上犯了一个小错误,你只要运行你的程序,立刻就会得到一个segfault或者一个荒谬的解析值。用spirit犯一个小错误,你会从编译器那里得到无望的巨大错误消息,而理解它们需要大量的boost.spirit练习。

    所以最终,对于简单的解析,我和其他人一样使用scanf。。。

        4
  •  3
  •   Roger Dahl    12 年前

    正则表达式通常可用于解析字符串。使用 capture groups (圆括号)获取要分析的行的各个部分。

    foo: [3 4 56] ,使用正则表达式 (.*): \[(\d+) (\d+) (\d+)\]

    如果有几种可能的字符串格式需要解析,比如在OP给出的示例中,可以逐个应用不同的正则表达式并查看哪一个匹配,或者编写一个正则表达式来匹配所有可能的变体,通常使用 | (集合并集)运算符。

    正则表达式非常灵活,因此可以对表达式进行扩展,以允许更多的变化,例如,表达式后面有任意数量的空格和其他空格 : 在这个例子中。或者只允许数字包含一定数量的数字。

    另外,正则表达式提供了隐式验证,因为它们需要完美匹配。例如,如果 56 在上面的例子中被替换为 56x

    Boost.Regex .

        5
  •  2
  •   rcollyer    14 年前

    我感觉到你的痛苦。我经常处理具有固定宽度字段(通过Fortran77代码输出)的文件,因此尝试以最小的麻烦加载它们总是很有趣的。就我个人而言,我想看看 boost::format 提供scanf实现。但是,除非我自己实现,否则我会使用类似于@Nikko的方法 boost::tokenizer lexical cast 用于转换。例如,

    typedef boost::token_iterator_generator< 
                                    boost::char_separator<char> >::type tokenizer;
    
    boost::char_separator<char> sep("=,");
    
    std::string line;
    std::getline( file_istream, line );
    tokenizer tok = boost::make_token_iterator< std::string > (
                                    line.begin(), line.end() sep );
    
    std::string var = *tok;  // need to check for tok.at_end() here
    ++tok;
    
    std::vector< int > vals;
    for(;!tok.at_end();++tok){
     vals.push_back( boost::lexical_cast< int >( trimws( *tok ) );
    }
    

    注: boost::lexical_cast

        6
  •  1
  •   user184968 user184968    14 年前

    我认为Boo.Sovil是一种描述C++代码中语法正确性的好方法。它需要一段时间来适应提神,但之后就很容易使用了。它可能没有你想要的那么简洁,但我认为它是处理简单语法的一种简便方法,它的性能可能会有问题,所以在需要速度的情况下,它可能不是一个好的选择。

        7
  •  0
  •   James Bern    4 年前

    我不认为提交的答案比OP提供的更简洁。此外,提交的答案会降低代码的可移植性、编译速度和阅读难度。如果有其他目标(例如某些特定的安全或速度要求),则提交的答案可能更合适,但考虑到问题中所述的目标,使用 sscanf 仍然是要击败的答案。