应使用 std::string_view 做输入切片以避免重复拷贝,配合 pos 下标移动实现零拷贝解析;数字字面量需按前缀(0x/0b/小数点/e)分路径处理,优先用 std::from_chars;注释与字符串需独立状态机,正确处理嵌套和转义。

用 std::string_view 做输入切片,避免重复拷贝
直接传 std::string 或 const char* 进状态机,每次识别 token 都要调 substr() 或手动算偏移,极易引入越界或漏字符。用 std::string_view 持有原始输入、配合下标移动,所有“切片”只是视图更新,零拷贝。
实操建议:
- 初始化时用
std::string_view input{src},全程只维护一个size_t pos = 0当前读取位置 - 每个状态函数(如
scan_identifier())接收std::string_view& input, size_t& pos,内部只做pos++或pos += n,不 new 不 copy - 遇到空格/换行/注释开头时,跳过逻辑统一写在主循环里,别塞进各状态函数——否则容易漏跳多字节分隔符(比如
//后面还有内容)
数字字面量必须区分 0x、0b 和小数点,否则会误判 0123 是八进制
C++ 源码中 0123 是合法八进制,但现代脚本语言(如 Lua/JS 风格)通常不支持,且 0x1F、0b1010、3.14、1e-5 必须各自走不同路径。若统一用 std::stod 回退解析,既慢又丢精度(比如大整数溢出转 double)。
实操建议:
- 先看前缀:
input.substr(pos, 2) == "0x"→ 十六进制;"0b"→ 二进制;isdigit(input[pos]) && input[pos] == '0' && isdigit(input[pos+1])→ 八进制警告或拒绝(按需) - 含小数点或
e/E的,走浮点路径,但注意1.和.5都合法,需分别处理 - 纯十进制整数用
std::from_chars(C++17),它不抛异常、不依赖 locale、返回精确的ptr偏移,比stol安全得多
/* */ 注释嵌套判断不能只靠计数,得进状态机流转
只用一个 in_comment 布尔变量 + 计数 /* 出现次数,无法处理 /* comment /* nested */ end */ 这种合法嵌套(C++ 标准不允许多层,但很多脚本语言允许)。更糟的是,*/ 出现在字符串字面量里(如 "abc */ def")也绝不能触发退出注释。
实操建议:
- 把注释识别拆成独立子状态:进入
/*后切换到STATE_IN_BLOCK_COMMENT,之后每读两个字符判断是否为*/;不是则继续;是则检查前一个字符是否为*(防**/误判) - 字符串字面量状态(
STATE_IN_STRING)里,遇到\就跳过下一个字符(无论是否为"或\n),这样"a\"b"或"line1\nline2"都不会打断状态 - 所有状态跳转必须显式写清条件,例如:
if (pos + 1
关键字与标识符必须查表后判别,不能靠首字母硬编码
把 if、while、return 等关键字写成 if (c == 'i') then check next... 看似快,但一旦加新关键字(如 constexpr)或支持 Unicode 标识符(αβγ),逻辑爆炸。而且 int 和 integer 冲突、in 是关键字但 inner 是标识符,仅靠前缀匹配会错。
实操建议:
- 用
static const std::unordered_set<:string_view> KEYWORDS = {"if", "else", "while", ...};</:string_view>预置,大小写敏感按需调整 - 扫描完一个标识符(由
isalnum或is_underscore定义)后,拿到完整std::string_view ident = input.substr(start, pos - start),再查表 - 查表失败才归为
TOKEN_IDENTIFIER;成功则返回对应关键字枚举值(如TOKEN_IF)——顺序无关,哈希表 O(1) - 别用
std::map,键是std::string_view时std::unordered_set更轻量,无构造开销
状态机真正难的不是跳转逻辑本身,而是所有边界:换行符是 \n 还是 \r\n,Unicode 字符占几个 char,空字符串 "" 后紧跟 + 是否要报错。这些不写进状态流转里,光靠外围 if-else 补丁,三个月后自己都看不懂哪条路径吃掉了那个反斜杠。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











