不能直接用正则一次性匹配所有 token,因为 std::regex 开销大、不支持最长前缀匹配与回溯分词,且难以精确定位非法字符错误;状态机可逐字符推进、实时记录行列号并即时报错。

为什么不能直接用正则一次性匹配所有 Token
因为 C++ 标准库的 std::regex 在多次匹配时开销大,且无法自然处理「最长前缀匹配」和「回溯式分词」(比如 == 必须优先于 =),更关键的是:遇到非法字符(如 @)或不完整 token(如孤立的 / 后跟换行)时,正则难以给出精确位置和错误类型。状态机跳转能逐字符推进、随时记录当前行号/列号,并在任意点中断并报错。
如何设计最小可行的状态机结构
只保留 5 个核心状态:START(初始)、IN_NUMBER、IN_IDENTIFIER、IN_STRING、IN_COMMENT;其余如 IN_EQUAL、IN_SLASH 等作为临时子状态嵌在转移逻辑里,避免状态爆炸。每个状态只关心「当前字符能触发什么转移」,不保存冗余上下文。
实操建议:
- 用
enum class State { START, IN_NUMBER, ... };定义状态,别用整数魔法值 - 输入缓冲区用
std::string_view配合索引size_t pos移动,避免反复拷贝字符串 - 每进入新 token(如从
START → IN_IDENTIFIER),立刻记录起始位置start_pos = pos;每次成功生成 token 时,用source.substr(start_pos, pos - start_pos)提取原始文本 - 遇到空白符(空格、制表、换行)统一跳过,但需更新行号:
if (c == ' ') line++;
数字与标识符的冲突怎么拆解
典型冲突是 123abc:它既像数字又像标识符。按脚本语言惯例(如 JavaScript、Python),这应被识别为非法 token —— 数字后不能紧接字母。状态机必须在 IN_NUMBER 状态下,对下一个非数字字符做严格判断:
实操建议:
- 若当前在
IN_NUMBER且遇到字母,立即终止并报错"invalid number literal",而不是切到IN_IDENTIFIER -
0x开头走十六进制分支,0b走二进制,但都必须在后续字符合法时才接受;否则退回到0作为十进制数字 - 小数点只在已读数字后才允许出现(即
IN_NUMBER中遇到.才进IN_FLOAT),单独的.是运算符 token - 下划线
_在数字中仅允许出现在数字之间(如1_000),不能开头、结尾或连续出现
字符串字面量里的转义和换行怎么安全处理
双引号字符串中,"hello
world" 是合法的,但 "unclosed(无结束引号)或 "line1(反斜杠结尾)必须报错。状态机不能简单“见到 " 就结束”,要区分是否被转义、是否在行末被续行。
实操建议:
- 进入
IN_STRING后,用布尔标志escaped = false跟踪上一个字符是否为未转义的;只有当escaped == false && c == '"'时才退出字符串状态 - 若
escaped == true,则忽略当前字符的特殊性,重置escaped = false,继续读下一个字符 - 若读到
且未被转义(即escaped == false),直接报错"unterminated string literal"—— 脚本语言通常不允许裸换行出现在字符串中(除非用显式表示) - 支持常见转义:
\、"、、、;其他如z视为非法转义,报错
真正难的不是写满状态跳转表,而是让每个状态退出时,都能准确返回 token 类型、原始片段、行列号 —— 这些信息必须在跳转发生前就准备好,而不是事后补。漏掉一次 start_pos 更新,整个 token 的位置信息就全偏了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











