Files
ScienceCalculator/README.md
2025-07-01 23:00:11 +08:00

7.8 KiB
Raw Permalink Blame History

Updata

更新反三角函数的定义域判定取整函数floor、ceil以及round和阶乘运算的支持同时更新变量暂存计算

数据结构课设

——计算器的双栈类实现

一、总体架构

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库头文件

#include <iostream>     // 标准输入输出(调试、主程序)
#include <string>       // 处理字符串表达式
#include <stack>        // 中缀转后缀、后缀求值都需用到栈
#include <vector>       // 储存 Token 序列
#include <cctype>       // isdigit, isalpha 等字符判断
#include <cmath>        // sin, cos, log 等函数计算(用于 PostfixEval
#include <stdexcept>    // 抛出非法表达式等异常

0.1需要的自定义头文件

#include "Token.h"             // Token 结构体
#include "Tokenizer.h"         // 表达式解析类
#include "InfixToPostfix.h"    // 中缀转后缀类
#include "PostfixEval.h"       // 后缀求值类
#include "Calc.h"              // 总控类(对外调用接口)

1.Token类

类型:数字、操作符、左括号、右括号、函数、变量

属性:

类型enum

内容string

优先级(仅运算符)

结合性(左/右)

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<Token>:表达式字符串解析主函数

readNumber(string, int&):识别多位数字及小数点

readFunction(string, int&):识别函数名称如 sin、cos

handleUnaryMinus():识别一元负号

class Tokenizer {
public:
    // 主函数:输入表达式字符串,输出 token 序列
    std::vector<Token> 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<Token>& 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():用于处理括号结束时的栈弹出过程

class InfixToPostfix {
public:
    // 主转换函数:将中缀表达式转换为后缀表达式
    std::vector<Token> run(const std::vector<Token>& infix);

private:
    // 返回指定运算符的优先级
    int precedence(const Token& token) const;

    // 判断运算符的结合性(左结合或右结合)
    Associativity associativity(const Token& token) const;

    // 遇到右括号时,从栈中弹出操作符直到遇到左括号
    void popUntilLeftParen(std::stack<Token>& opStack, std::vector<Token>& output);
};

4. PostfixEval 类

方法:

run(vector<Token>) → double:后缀表达式求值`

applyBinaryOp(op, a, b):执行加减乘除幂

applyUnaryFunction(func, x):执行函数(如 sin、sqrt

class PostfixEval {
public:
    // 主函数:对后缀表达式进行求值
    double run(const std::vector<Token>& postfix);

private:
    // 应用于二元操作符,如 +, -, *, /, ^
    double applyBinaryOp(const std::string& op, double a, double b);

    // 应用于一元基本初等函数,如 sin, cos, log
    double applyUnaryFunction(const std::string& func, double x);
};

包含的数学库函数

sin();
cos();
log();
sqrt();
exp();
fabs();
pow();

5. Calc 类(对外接口)

方法:

eval(string) → double:用户主调用入口

debug():调试辅助,可打印 token 列表、后缀表达式

safeEval(string) → optional<double>:异常捕获

class Calc {
public:
    // 主接口函数:输入表达式字符串,返回计算结果
    static double eval(const std::string& expression);

    // 安全接口:带异常处理,返回 optional
    static std::optional<double> safeEval(const std::string& expression);
     // 调试函数:打印中间的 Token 序列和后缀表达式
    void debug(const std::string& expr);
};

6.main方法

#include <iostream>
#include <string>
#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;
}