# 数据结构课设 ## ——计算器的双栈类实现 ## 一、总体架构 ### 1.数据结构: ##### 双栈结构(操作数栈+运算符栈) 操作数栈:用于后缀表达式求值时的数据储存。 运算符栈:用于中缀表达式向后缀表达式转换时的运算符存储 ### 2.基本框架: 中缀表达式->中缀转后缀->后缀表达式->后缀表达式求值 ### 3.方法实现: #### (1).表达式解析方法(Tokenizer类) 功能:将字符串形式的数学表达式拆解为基本单元(token),包括数字、运算符、括号、函数名等。 输出:Token序列(如["3","+","sin","(","2","*","5",")"]) #### (2).表达式转换方法(InfixToPostfix类) 功能:基于逆波兰表示法,将中缀表达式(带优先级)转换为后缀表达式 核心:结合栈实现优先级比较、括号处理、函数名识别 输出:后缀表达式序列(如["3","2","5","*","sin","+"]) #### (3).后缀表达式求值方法(PostfixEval类) 功能:使用操作数栈对后缀表达式逐步计算,得出最终结果。 支持:基础四则运算,基本初等函数,负号 输出:最终计算结果(double类型) #### (4).总控协调方法(Calc类) 功能:统一调度调用表达式解析、转换、求职模块,作为对外唯一调用入口 接口:`double eval(string expression)` 支持debug、异常处理、扩展模块对接等功能 ## 二、具体实现 ### 0.0需要的STL库头文件 ```cpp #include // 标准输入输出(调试、主程序) #include // 处理字符串表达式 #include // 中缀转后缀、后缀求值都需用到栈 #include // 储存 Token 序列 #include // isdigit, isalpha 等字符判断 #include // sin, cos, log 等函数计算(用于 PostfixEval) #include // 抛出非法表达式等异常 ``` ### 0.1需要的自定义头文件 ```cpp #include "Token.h" // Token 结构体 #include "Tokenizer.h" // 表达式解析类 #include "InfixToPostfix.h" // 中缀转后缀类 #include "PostfixEval.h" // 后缀求值类 #include "Calc.h" // 总控类(对外调用接口) ``` ### 1.Token类 类型:数字、操作符、左括号、右括号、函数、变量 属性: 类型(enum) 内容(string) 优先级(仅运算符) 结合性(左/右) ```cpp enum class TokenType{ Number, Operator, LeftParen, RightParen, Function, Variable }; enum class Associativity { Left, Right, None }; struct Token { TokenType type; std::string value; // 以下属性仅对运算符或函数适用 int precedence = -1; // 运算优先级(* > +) Associativity associativity = Associativity::None; // 构造函数 Token(TokenType t, const std::string& v, int p = -1, Associativity assoc = Associativity::None) : type(t), value(v), precedence(p), associativity(assoc) {} // 工具函数:是否为数字 bool isNumber() const { return type == TokenType::Number; } // 是否为操作符 bool isOperator() const { return type == TokenType::Operator; } // 是否为函数 bool isFunction() const { return type == TokenType::Function; } // 是否为左括号 bool isLeftParen() const { return type == TokenType::LeftParen; } // 是否为右括号 bool isRightParen() const { return type == TokenType::RightParen; } // 是否为变量(如 x、y) bool isVariable() const { return type == TokenType::Variable; } }; ``` ### 2.Tokenizer类 方法: `run(string expr) → vector`:表达式字符串解析主函数 `readNumber(string, int&)`:识别多位数字及小数点 `readFunction(string, int&)`:识别函数名称如 sin、cos `handleUnaryMinus()`:识别一元负号 ```cpp class Tokenizer { public: // 主函数:输入表达式字符串,输出 token 序列 std::vector run(const std::string& expr); private: // 当前处理的表达式和指针位置 std::string expression; size_t pos = 0; // 工具函数:跳过空格 void skipWhitespace(); // 识别多位数字及小数点(支持负数符号) Token readNumber(); // 识别函数名,如 sin, cos, log Token readFunction(); // 识别变量名(可扩展,如 x, y, z) Token readVariable(); // 识别符号、括号、运算符等 Token readOperatorOrParen(); // 判断是否需要处理一元负号 bool isUnaryMinusContext(const std::vector& tokens); // 工具判断函数 bool isDigit(char c) const; bool isLetter(char c) const; bool isOperatorChar(char c) const; }; ``` ### 3.InfixToPostfix 类 方法: run(vector) → vector:中缀转后缀主函数 precedence(Token):判断运算符优先级 associativity(Token):左结合/右结合判断 popUntilLeftParen():用于处理括号结束时的栈弹出过程 ```cpp class InfixToPostfix { public: // 主转换函数:将中缀表达式转换为后缀表达式 std::vector run(const std::vector& infix); private: // 返回指定运算符的优先级 int precedence(const Token& token) const; // 判断运算符的结合性(左结合或右结合) Associativity associativity(const Token& token) const; // 遇到右括号时,从栈中弹出操作符直到遇到左括号 void popUntilLeftParen(std::stack& opStack, std::vector& output); }; ``` ### 4. PostfixEval 类 方法: `run(vector) → double`:后缀表达式求值` `applyBinaryOp(op, a, b)`:执行加减乘除幂 `applyUnaryFunction(func, x)`:执行函数(如 sin、sqrt) ```cpp class PostfixEval { public: // 主函数:对后缀表达式进行求值 double run(const std::vector& postfix); private: // 应用于二元操作符,如 +, -, *, /, ^ double applyBinaryOp(const std::string& op, double a, double b); // 应用于一元基本初等函数,如 sin, cos, log double applyUnaryFunction(const std::string& func, double x); }; ``` 包含的数学库函数 ```cpp sin(); cos(); log(); sqrt(); exp(); fabs(); pow(); ``` ### 5. Calc 类(对外接口) 方法: `eval(string) → double`:用户主调用入口 `debug()`:调试辅助,可打印 token 列表、后缀表达式 `safeEval(string) → optional`:异常捕获 ```cpp class Calc { public: // 主接口函数:输入表达式字符串,返回计算结果 static double eval(const std::string& expression); // 安全接口:带异常处理,返回 optional static std::optional safeEval(const std::string& expression); // 调试函数:打印中间的 Token 序列和后缀表达式 void debug(const std::string& expr); }; ``` ### 6.main方法 ```c++ #include #include #include "Calc.h" int main() { std::string input; std::cout << "欢迎使用科学计算器(支持 + - * / ^ 和基本初等函数)\n"; std::cout << "输入表达式(输入 q 退出):\n"; while (true) { std::cout << "\n>>> "; std::getline(std::cin, input); if (input == "q" || input == "Q") break; try { double result = Calc::eval(input); std::cout << "结果 = " << result << "\n"; } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << "\n"; } catch (...) { std::cerr << "未知错误。\n"; } } std::cout << "感谢使用,再见!\n"; return 0; } ```