【问题标题】:what is good practice for parsing through long strings in c++? [duplicate]在 C++ 中解析长字符串的好习惯是什么? [复制]
【发布时间】:2021-06-25 04:27:11
【问题描述】:

我必须解析一个长字符串并将字符串的各个部分分配给不同的变量。我以一种非常迂回的方式做到了这一点,效果很好,但没有我想要的那么好。有没有更有效的循环方式?

我正在做的是从 studentdata 数组的第一个索引开始,在有逗号的地方停止,然后存储它们之间的内容,直到我到达每个字符串的末尾。

int rhs = studentData.find(","); 
string studentID = studentData.substr(0, rhs);

int lhs = rhs + 1;
rhs = studentData.find(",", lhs);
string firstName = studentData.substr(lhs, rhs - lhs);

lhs = rhs + 1;
rhs = studentData.find(",", lhs);
string lastName = studentData.substr(lhs, rhs - lhs);

lhs = rhs + 1;
rhs = studentData.find(",", lhs);
string eMail = studentData.substr(lhs, rhs - lhs);

lhs = rhs + 1;
rhs = studentData.find(",", lhs);
int age = stoi(studentData.substr(lhs, rhs - lhs));

lhs = rhs + 1;
rhs = studentData.find(",", lhs);
int daysInCourse1 = stoi(studentData.substr(lhs, rhs - lhs));

lhs = rhs + 1;
rhs = studentData.find(",", lhs);
int daysInCourse2 = stoi(studentData.substr(lhs, rhs - lhs));

lhs = rhs + 1;
rhs = studentData.find(",", lhs);
int daysInCourse3 = stoi(studentData.substr(lhs, rhs - lhs));

lhs = rhs + 1;
rhs = studentData.find(",", lhs);
to_string(degreeProgram) = studentData.substr(lhs, rhs - lhs);

要解析的字符串示例:

    "A1,John,Smith,John1989@gm ail.com,20,30,35,40,SECURITY",
    "A2,Suzan,Erickson,Erickson_1990@gmailcom,19,50,30,40,NETWORK",

感谢您向不同来源提供任何反馈或转发,以提供更好的洞察力。

【问题讨论】:

  • 这个长字符串有特定的模式吗?在这种情况下,您可以查看std::regex
  • 您也应该在问题中包含一些您想要解析的实际字符串。只需edit 问题并将它们放在代码块中。
  • 回想起来,我明白这应该是显而易见的。对不起。
  • 谢谢,泰德。我对编码很陌生,所以我没有意识到有一个缩写。感谢您的帮助!

标签: c++ loops


【解决方案1】:

您确切地知道输入字符串是如何由字段组成的。您可以使用“strtok_s / strtok_r”来获取由分隔符锯齿的标记(在您的情况下,分隔符是“,”);您可以将令牌存储在字符串数组中,也可以将令牌逐个分配给对应的变量。

注意:

(1) 输入字符串必须是可写的(它不能是只读的,因为 strtok_s/strtok_r 会改变字符串本身)。它也不会告诉您令牌由哪个字符终止。有时我们对分隔符 char 是什么感兴趣。

(2) strtok_s/strtok_r 会跳过空token,只返回非空token,所以如果输入字符串是空token(例如:"t1,,,,t2" 用逗号隔开,你会得到"t1 ", "t2",它不会给你任何空字符串),令牌索引将如预期的那样不正确。

因此,如果 strtok 的字符不能满足您的要求,您可以实现自己的函数版本。

【讨论】:

    【解决方案2】:

    有很多选择需要考虑

    • 使用正则解析字符串,以这段代码为例(需要GCC 4.9+编译)。请注意,使用手动编写的解析器或正则表达式解析电子邮件很棘手,下面的代码仅适用于简化的场景。为了使用 regex 获得良好的性能,建议将 std::regex 替换为 boost::regex 或 google 的 re2,因为众所周知 libstd++ 的 regex 实现很慢。
    #include <iostream>
    #include <regex>
    #include <string>
    
    struct student {
      std::string id;
      std::string firstName;
      std::string lastName;
      std::string eMail;
      int age = 0;
      int daysInCourse1 = 0;
      int daysInCourse2 = 0;
      int daysInCourse3 = 0;
      std::string degreeProgram;
    };
    
    std::ostream& operator<<(std::ostream& os, const student& st) {
      os << "["
         << "id:" << st.id << ",firstName:" << st.firstName
         << ",lastName:" << st.lastName << ",eMail:" << st.eMail
         << ",age:" << st.age << ",daysInCourse1:" << st.daysInCourse1
         << ",daysInCourse2:" << st.daysInCourse2
         << ",daysInCourse3:" << st.daysInCourse3
         << ",degreeProgram:" << st.degreeProgram << "]" << std::endl;
      return os;
    }
    
    int main(int argc, char* argv[]) {
      std::string data =
          "1,firstName,lastName,eMail@mail.com,18,1,2,3,degreeProgram";
      const std::regex kPattern(
          R"((\d+),(\w+),(\w+),((\w+)(\.|_)?(\w*)@(\w+)(\.(\w+))+),(\d+),(\d+),(\d+),(\d+),(\w+))");
      std::smatch base_match;
      student st;
      if (std::regex_match(data, base_match, kPattern)) {
        st.id = base_match[1];
        st.firstName = base_match[2];
        st.lastName = base_match[3];
        st.eMail = base_match[4];
        st.age = std::stoi(base_match[11]);
        st.daysInCourse1 = std::stoi(base_match[12]);
        st.daysInCourse2 = std::stoi(base_match[13]);
        st.daysInCourse3 = std::stoi(base_match[14]);
        st.degreeProgram = base_match[15];
    
        std::cout << st;
      }
      return 0;
    }
    

    要解析邮件部分,也建议试试boost.tokenizerboost.sprit2

    • 如果内容字符串本身是由您的代码生成的,我建议使用一些序列化/反序列化库来使您的代码更易于维护且不易出错。序列化/反序列化部分与我们的业务逻辑无关,所以我们最好使用库或框架来帮助我们:

    您可以考虑使用:

    1. boost serialization
    2. protobufers
    3. Cap'n Proto
    4. flatbuffers
    5. avro

    【讨论】:

    • 我知道您给出的只是示例,但我想提及 boost.tokenizer 也可能很好,尤其是对于这种特殊情况。 Spirit 也很生气提到恕我直言,尽管它对于简单的“逗号中断”任务似乎很重要。
    • @alagner 谢谢你的建议,我已经添加了sn-p。
    【解决方案3】:

    在 C++ 中解析长字符串的好习惯是什么?

    这在Dragon book 等书籍中有解释,解析技术在 C++、C 或 Ocaml 中类似。您还可以阅读 Fowler 的 Domain Specific Languages、Scott 的 Programming language pragmatics、Pitrat 的 Artificial Beings: the conscience of a conscious machine(更具推测性)和 ACM SIGPLAN 会议论文等书籍。当然,请阅读parsingpush down automatoncontext free grammars 上的维基页面。

    我的建议是:

    • 以书面形式记录(至少在纸上)可接受输入的语法。您可以使用 EBNF 表示法。请注意,一组示例并未定义任何语法。
    • 讨论并记录您的软件对于不可接受的输入应该做什么

    一旦您(以书面形式)指定了上述两点,请考虑编写recursive descent parser,或使用ANTLRGNU bison 之类的解析器生成器,或其他方式(请参阅this list)。

    您的文档(您的已解析语言)可能受到某些 C++ 规范的启发,例如 n3337(或更好),或 this C++ reference,或某些 C 规范,例如 n1570(或更好),或JSONYAMLHTMLCSV 的定义。

    您可能查看以获取灵感现有包含解析器的开源 C++ 项目的源代码(例如 fish、@ 987654342@、RefPerSysGCCClang static analyzer 等...)

    您可能希望在解析例程中避免(或限制)backtracking

    请注意,2021 年UTF-8 is used 无处不在。是

    "A3,Basile,Starynkévitch,basile@starynkevitch.net,19,50,30,40,СТАРЫНКЕВИЧ",

    一些可接受的输入(它包含法语 é 和西里尔字母 -Russian- СТАРЫНКЕВИЧ)?这应该记录在案! 解析 UTF-8 编码的文本不容易,但如果允许,您可以使用 GNU libunistring

    也许您想使用一些database 软件,例如sqlitePostGreSQL。两者都可以(技术上)从 C++ 代码中使用,并且您的示例数据看起来像某个数据库。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-18
      • 1970-01-01
      • 2011-07-05
      • 2023-03-22
      相关资源
      最近更新 更多