词法分析器是编译器或者解析器的第一个处理模块,它的核心作用是将输入的字符流拆分成一个个有意义的词法单元,也就是我们常说的token。在简单公式解析的场景中,我们需要识别的token通常包括数字、四则运算符、括号以及可能的空白字符。通过C++的字符串处理能力,我们可以逐个遍历输入公式的字符,根据字符的类型完成token的提取和分类。

词法分析的核心设计思路
实现简单公式解析的词法分析器,首先需要定义清楚要识别的token类型,常见类型如下:
- 数字类型:包括整数和小数,由连续的数字字符和最多一个小数点组成
- 运算符类型:包括加、减、乘、除四种基础四则运算符
- 括号类型:左括号和右括号,用于改变运算优先级
- 结束符:表示输入公式已经遍历完成
整个分析流程可以分为三个步骤:首先跳过输入中的空白字符,然后判断当前字符的类型,接着提取对应类型的完整token,最后返回token给后续的语法分析模块使用。
Token结构定义
我们首先需要定义一个结构体来存储每个token的信息,包含token的类型和对应的字符串值:
#include <string>
#include <vector>
#include <cctype>
// 定义token类型枚举
enum class TokenType {
NUMBER, // 数字
PLUS, // 加号 +
MINUS, // 减号 -
MULTIPLY, // 乘号 *
DIVIDE, // 除号 /
LPAREN, // 左括号 (
RPAREN, // 右括号 )
END // 结束符
};
// 定义token结构体
struct Token {
TokenType type; // token类型
std::string value; // token对应的字符串值
};
词法分析器类实现
接下来我们实现一个词法分析器类,它接收输入的公式字符串,提供获取下一个token的方法:
class Lexer {
private:
std::string input; // 输入的公式字符串
int pos; // 当前遍历到的位置索引
char currentChar; // 当前指向的字符
// 前进到下一个字符
void advance() {
pos++;
if (pos < input.size()) {
currentChar = input[pos];
} else {
currentChar = ' '; // 到达末尾
}
}
// 跳过空白字符
void skipWhitespace() {
while (currentChar != ' ' && isspace(currentChar)) {
advance();
}
}
// 提取数字token,支持整数和小数
Token getNumber() {
std::string numStr;
// 收集整数部分
while (currentChar != ' ' && isdigit(currentChar)) {
numStr += currentChar;
advance();
}
// 处理小数点和小数部分
if (currentChar == '.') {
numStr += currentChar;
advance();
while (currentChar != ' ' && isdigit(currentChar)) {
numStr += currentChar;
advance();
}
}
return {TokenType::NUMBER, numStr};
}
public:
// 构造函数,初始化输入和位置
Lexer(const std::string& inputStr) : input(inputStr), pos(0) {
if (!input.empty()) {
currentChar = input[0];
} else {
currentChar = ' ';
}
}
// 获取下一个token
Token getNextToken() {
while (currentChar != ' ') {
// 处理空白字符
if (isspace(currentChar)) {
skipWhitespace();
continue;
}
// 处理数字
if (isdigit(currentChar)) {
return getNumber();
}
// 处理运算符和括号
switch (currentChar) {
case '+':
advance();
return {TokenType::PLUS, "+"};
case '-':
advance();
return {TokenType::MINUS, "-"};
case '*':
advance();
return {TokenType::MULTIPLY, "*"};
case '/':
advance();
return {TokenType::DIVIDE, "/"};
case '(':
advance();
return {TokenType::LPAREN, "("};
case ')':
advance();
return {TokenType::RPAREN, ")"};
default:
// 遇到无法识别的字符,抛出异常
throw std::runtime_error("无法识别的字符: " + std::string(1, currentChar));
}
}
// 遍历完成,返回结束符
return {TokenType::END, ""};
}
};
公式解析测试示例
我们可以编写一个简单的测试程序,验证词法分析器是否能正确拆分公式中的各个元素:
#include <iostream>
// 打印token信息的辅助函数
void printToken(const Token& token) {
switch (token.type) {
case TokenType::NUMBER:
std::cout << "数字: " << token.value << std::endl;
break;
case TokenType::PLUS:
std::cout << "加号" << std::endl;
break;
case TokenType::MINUS:
std::cout << "减号" << std::endl;
break;
case TokenType::MULTIPLY:
std::cout << "乘号" << std::endl;
break;
case TokenType::DIVIDE:
std::cout << "除号" << std::endl;
break;
case TokenType::LPAREN:
std::cout << "左括号" << std::endl;
break;
case TokenType::RPAREN:
std::cout << "右括号" << std::endl;
break;
case TokenType::END:
std::cout << "结束符" << std::endl;
break;
}
}
int main() {
// 测试公式
std::string formula = "12.5 + 3 * (4 - 2) / 2";
Lexer lexer(formula);
// 逐个获取并打印token
Token token;
do {
token = lexer.getNextToken();
printToken(token);
} while (token.type != TokenType::END);
return 0;
}
实现注意事项
在实际使用这个简单的词法分析器时,需要注意几个问题:首先是输入公式的合法性校验,如果公式中存在无法识别的字符,程序会直接抛出异常,实际使用中可以根据需求调整错误处理方式。其次是小数点的处理,当前实现只允许一个小数点,如果出现多个小数点的情况会被识别为非法字符。另外这个实现只支持基础的四则运算和括号,如果需要扩展支持更多运算符或者函数,只需要在token类型枚举中添加对应的类型,然后在getNextToken方法中增加对应的识别逻辑即可。
这个简单的词法分析器已经可以完成基础公式的token拆分工作,后续的语法分析模块可以基于这些token完成表达式的求值或者语法树构建,实现完整的公式解析功能。