使用 c++ 进行简单的自定义解析

Posted

技术标签:

【中文标题】使用 c++ 进行简单的自定义解析【英文标题】:Simple, Custom Parsing with c++ 【发布时间】:2010-04-23 19:34:36 【问题描述】:

我已经阅读 SO 有一段时间了,但我真的无法为我的问题找到任何帮助。

我有一个创建 IAS 模拟器的 c++ 任务。

这里是一些示例代码...

0   1   a
1   2   b
2   c
3   1
10  begin
11  . load a, subtract b and offset by -1 for jump+
11  load M(0)
12  sub M(1)
13  sub M(3)
14  halt

使用 c++,我需要能够读取这些行并将它们存储在我已经构建的“内存寄存器”类中......

例如,第一行需要在寄存器 0 中存储“1 a”。

如何解析出行首的数字,然后将其余的存储为字符串?

我使用一个使用mem.set(int, string); 调用的类来设置存储。 int 是行首的内存位置,string 是存储的指令。

编辑:一些澄清:

我必须使用标准库 输入文件的语法在这里:http://www.cs.uwyo.edu/~seker/courses/2150/iascode.pdf 加载程序将覆盖重复的行条目。这意味着示例中的第一行 11 将被第二行覆盖。

【问题讨论】:

在 C++ 中读取文件和解析字符串非常烦人(比 C# 或其他现代语言困难得多)。一点小提示:***.com/questions/194465/… 感谢您的提醒!我刚刚给我的教授发了电子邮件,我们只能使用 STANDARD 库,所以这会很痛苦。 @brad 看起来你手上有一位严厉的老师 :) 下面的一些例子怎么样:codeproject.com/KB/recipes/Tokenizer.aspx 它们非常高效而且有点优雅。字符串工具包库使 C++ 中的复杂字符串处理变得简单易行。 有人对大部分答案投了反对票... 【参考方案1】:

我建议看看<ifstream> 库。

【讨论】:

【参考方案2】:
#include <iostream> // #include <fstream> for file objects as others suggest
#include <string>
#include <map>
using namespace std;

map<int, string> my_program;

int line_num;
string line_text;
while ( cin >> line_num )  // or any input stream such as a file
    getline( cin, line_text ); // standard function defined in <string>
    my_program[ line_num ] = line_text; // store line for next phase

这将读取文件的行,直到遇到结尾或以数字以外的其他内容开头的行。如果您关心,请使用cin.eof() 验证是否已读取整个文件。

当然,由于map 对其内容进行排序,因此下一阶段的行将按数字顺序排列。

【讨论】:

详细信息:getline 也应该进行错误检查。为了更加简洁,利用 operator>> 返回流:while(getline(std::cin&gt;&gt;line_num, line_text))... @Eric:听起来很合理,但这会用数字去掉最后一行。 getline 保证在 (cin) 时清除字符串(这是保证的)并返回尽可能多的输入,即使最终状态是 (!cin) @Éric Malenfant:顺便问一下,可以称呼你为 Eric 吗? SO 的邮件系统会处理丢失的变音符号吗? 推荐using namespace std;?多么不寻常。妥协当然是说using std::string; using std::map;,等等。有一个明确的进口清单肯定比“进口血腥的一切”声明更好。 @Jon:这是一个非常古老的论点。我通常的反应是using namespace std; 导致代码更易于维护,因为要指定的依赖项更少,使用标准工具的增量开销更少,并且对意外别名名称的混淆更少。但是,在头文件中使用对其他程序员不友好。【参考方案3】:

如果该行的第一部分始终是一个数字,请查看strtoul 函数。来自man 页面:

strtoul -- 将字符串转换为无符号长整数

图书馆

标准 C 库 (libc, -lc)

概要

 #include <stdlib.h>
 unsigned long strtoul(const char *restrict str, char **restrict endptr, int base);

说明

strtoul() 函数将 str 中的字符串转换为 unsigned long 价值。转换是根据给定的基数完成的, 必须介于 2 和 36 之间,或者是特殊值 0。

字符串可以以任意数量的空格(由isspace(3) 确定)开头,后跟一个可选的+- 符号。如果 base 是 0 或 16,则字符串可能包含 0x 前缀,并且 数字将以 16 为基数读取;否则,零基数取为 10 (十进制)除非下一个字符是0,在这种情况下它被视为 8(八进制)。

字符串的其余部分在 明显的方式,在字符串的末尾或在给定基数中不产生有效数字的第一个字符处停止。 (在基地 10以上,大写或小写字母A代表10,B 代表 11,依此类推,Z 代表 35。)

如果endptr不是NULLstrtoul()存储第一个无效的地址 *endptr 中的字符。但是,如果根本没有数字,strtoul()str 的原始值存储在*endptr 中。 (因此,如果 *str 不是 \0 但是**endptr 在返回时是\0,整个字符串都是有效的。)

返回值

strtoul() 函数返回 转换的结果,或者,如果有一个前导减号 符号,转换结果的否定,除非原来的 (非否定)值会溢出;在后一种情况下,strtoul() 返回 ULONG_MAX。在所有情况下,errno 是 设置为ERANGE。如果无法执行转换,则返回 0 并 全局变量 errno 设置为 EINVAL


这里的关键是endptr 参数。它设置了一个指向您需要继续解析的位置的指针。如果endptr == str,那么你就知道该行不是以数字开头的。

比起ato__ 函数,我更喜欢strto___ 系列函数,因为您可以设置base(包括上下文感应“base 0”)并且因为endptr 告诉我在哪里从继续。 (对于嵌入式应用程序,strto___ 的占用空间比 __scanf 函数要小得多。)

编辑:很抱歉错过您的评论。要使用endptr,请编写如下代码:

char* restOfLine = NULL;
unsigned long result = strtoul(lineBuffer, 10, &restOfLine);
if(restOfLine == NULL || restOfLine == lineBuffer)

     /* Handle error. */

else

    // Use result, and do further parsing starting at restOfLine.

通常,“处理错误”子句会返回或中断或抛出异常或执行其他操作来避免进一步处理,因此您不需要显式的 else 子句。

【讨论】:

这很好地解析了行号。目前,我正在喂 endptr NULL。双指针 (**) 是怎么回事。我如何设置一个变量来捕捉它。【参考方案4】:

拆分前导数字和行的其余部分并不是一项太难的任务。使用getline 之类的东西从输入文件中一次读取一行,并将该行存储在字符串char cur_line[] 中。对于每一行,尝试这样的操作:

声明一个指针char* pString和一个整数int line_num 使用strstr 函数查找第一个空白字符,并将结果分配给pString。 将pString 一次向前移动一个字符,直到它指向一个非空白字符。这是包含“其余行”的字符串的开头。 在cur_line 上使用atoi 将字符串中的第一项转换为整数,并将结果存储在line_num 现在,您应该可以像mem.set(line_num, pString) 这样调用您的函数了

然而,解释这些字符串会变得更加困难......

编辑:正如 Mike DeSimone 所提到的,如果您使用 strto* 函数之一而不是 atoi,则可以组合上述 strstratoi 步骤。

【讨论】:

这很好用。谢谢!我花了很长时间才让strstr 正常工作。【参考方案5】:

这样的事情可能是对 Boost.Spirit 库的一个很好的使用。它是 C++ 中的 EBNF 解析器生成器,与 flex 和 yacc 类似,无需额外的编译步骤。

【讨论】:

我只能使用标准库——这有点令人生畏。 是的,这令人生畏。你应该考虑在别处工作。为患有 NDIH(非内部完成)综合症的公司工作太痛苦了。【参考方案6】:

下面是简单的部分:

std::string text_to_parse;
unsigned int register_number;

void Parse_Line(std::istream& data_file)

  // Read in the register number.
  if(data_file >> register_number)
  
    // Read the remaining line as a string variable
    getline(data_file, text_to_parse);

    //  Now do something with the text...
  
  return;

您的数据文件的一个问题是它没有遵循简单的语法或句法。例如,您有两个以 11 开头的文本行。第 10 行不是“内存寄存器”行,而是指令行。另外,第 2 行和第 3 行的语法与第 0 行和第 1 行不同。

如需更多帮助,请发布语法规则(最好是 BNF 语法或 ASCII 艺术)。

【讨论】:

抱歉!我的意思是包括 IAS 语法。 cs.uwyo.edu/~seker/courses/2150/iascode.pdf我只需要完成数据传输指令(但我认为我已经处理了这些......)如果有任何行多次声明,则加载最后一行。以 11 开头的两行是注释和指令……注释将被指令覆盖,因为它们是逐行加载的……所有这些指令和“内存寄存器”都只是加载到我的记忆课。

以上是关于使用 c++ 进行简单的自定义解析的主要内容,如果未能解决你的问题,请参考以下文章

Spring的自定义标签

在代码中解析XAML样式并加载自定义控件

C++ STL中SORT的自定义比较函数?

我们如何使用 '=' 运算符为 C++ 中的自定义数据类型赋值?

如何实现一个简单的rpc

(C++) 我的自定义数组无法初始化(编译错误)