【支持算术表达式求解的计算器】——软件实习项目一

一、 项目准备

需求分析:
在这里插入图片描述
编程语言及开发工具:

JAVA,使用IDEA

二、实现过程

1.中缀表达式->后缀表达式

基于堆栈的算法:从左到右扫描每一个字符。

(1)如果扫描到的字符是操作数(如a、b等),就直接输出这些操作数。

(2)如果扫描到的字符是一个操作符,分三种情况:
①如果堆栈是空的,直接将操作符存储到堆栈中(push it)
②如果该操作符的优先级大于堆栈出口的操作符,就直接将操作符存储到堆栈中(push it)
③如果该操作符的优先级低于堆栈出口的操作符,就将堆栈出口的操作符导出(pop it), 直到该操作符的优先级大于堆栈顶端的操作符。将扫描到的操作符导入到堆栈中(push)。

(3)如果遇到的操作符是左括号"(”,就直接将该操作符输出到堆栈当中。该操作符只有在遇到右括号“)”的时候移除。这是一个特殊符号该特殊处理。

(4)如果扫描到的操作符是右括号“)”,将堆栈中的操作符导出(pop)到output中输出,直到遇见左括号“(”。将堆栈中的左括号移出堆栈(pop )。继续扫描下一个字符

(5)如果输入的中缀表达式已经扫描完了,但是堆栈中仍然存在操作符的时候,我们应该讲堆栈中的操作符导出并输入到output 当中。

2.后缀表达式的计算。

堆栈法计算后缀表达式的值:
(1)从左到右扫描后缀表达式字符串
(2)初始化一个空栈
(3)如果扫描到数字,那么就直接入栈
(4)如果被扫描的字符是一个二元运算符,那么就连续出栈两次,获得两个字符,元素出栈后,应用运算符进行计算,并将结果压栈
(5)重复3)和4)的操作,直至扫描完字符串
(6)扫描完所有字符串之后,栈中只剩一个元素,该元素就是最终结果,将其出栈并返回。

3.GUI界面的设计
1.JAVA swing 各组件的混合运用;
2.按钮的监听;
3.界面的设计;

关于界面设计的学习详见以下博客 ,带着耐心从中慢慢学习使我受益良多。
https://blog.csdn.net/xietansheng/article/details/72814492

三、完整代码

具体细节和难点详见代码注释。

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.math.BigDecimal;
import java.math.RoundingMode;
import javax.swing.*;

//主类,继承JFrame框架,实现事件监听器接口
public class Main extends JFrame implements ActionListener {
    String []KEYS = { "7", "8", "9", "C", "4", "5", "6", "back", "1", "2", "3", "+", "0", ".", "pi", "-", "sqrt",
            "%", "/", "*", "AC", "(", ")", "=" };
    JButton []keys = new JButton[KEYS.length];
    JTextArea resultText = new JTextArea("0.0");// 文本域组件TextArea可容纳多行文本;文本框内容初始值设为0.0
    JTextArea History = new JTextArea();// 历史记录文本框初始值设为空
    JPanel jp1=new JPanel();
    JPanel jp2=new JPanel();
    JScrollPane gdt1=new JScrollPane(resultText);//给输入显示屏文本域新建一个垂直滚动滑条
    JScrollPane gdt2=new JScrollPane(History);//给历史记录文本域新建一个垂直滚动滑条
    JLabel label = new JLabel("历史记录");
    String b="";
    static int index;
    static int left,right;//左右括号标记
    static int count;//成功运算标记
    Font font = new Font("TIMES NEW ROMAN", Font.PLAIN, 14);//设置字体
    // 构造方法
    public Main() {
        super("实用计算器");
        resultText.setBounds(20, 18, 255, 115);// 设置文本框大小
        resultText.setAlignmentX(RIGHT_ALIGNMENT);// 文本框内容右对齐
        //resultText.setEditable(false);// 文本框不允许修改结果
        History.setBounds(290, 40, 250,370);// 设置文本框大小
        History.setAlignmentX(LEFT_ALIGNMENT);// 文本框内容右对齐
        //History.setEditable(false);// 文本框不允许修改结果
        label.setBounds(300, 15, 100, 20);//设置标签位置及大小
        jp2.setBounds(290,40,250,370);//设置面板窗口位置及大小
        jp2.setLayout(new GridLayout());
        jp1.setBounds(20,18,255,115);//设置面板窗口位置及大小
        jp1.setLayout(new GridLayout());
        resultText.setLineWrap(true);// 激活自动换行功能
        resultText.setWrapStyleWord(true);// 激活断行不断字功能
        resultText.setSelectedTextColor(Color.RED);
        History.setLineWrap(true);//自动换行
        History.setWrapStyleWord(true);
        History.setSelectedTextColor(Color.blue);
        gdt1.setViewportView(resultText);//使滚动条显示出来
        gdt2.setViewportView(History);
        jp1.add(gdt1);//将滚动条添加入面板窗口中
        jp2.add(gdt2);
        this.add(jp1);//将面板添加到总窗体中
        this.add(jp2);//将面板添加到总窗体中
        this.setLayout(null);
        this.add(label);// 新建“历史记录”标签

        // 放置按钮
        int x = 20, y = 150;
        for (int i = 0; i < KEYS.length; i++) {
            keys[i] = new JButton();
            keys[i].setText(KEYS[i]);
            keys[i].setBounds(x, y, 62, 40);
            keys[i].setBackground(new Color(144, 201, 108));//设置按钮颜色
            keys[i].setFont(font);//设置按钮字体
            if (x < 215) {
                x += 65;
            } else {
                x = 20;
                y += 45;
            }
            this.add(keys[i]);
        }
        keys[16].setBackground(Color.ORANGE);
        for (int i = 0; i < KEYS.length; i++){// 每个按钮都注册事件监听器
            keys[i].addActionListener(this);
        }
        this.setResizable(false);
        this.setBounds(500, 200, 567, 480);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setVisible(true);
    }



    // 事件处理
    public void actionPerformed(ActionEvent e) {
        String label=e.getActionCommand();//获得事件源的标签
        left=0;
        right=0;
        index=resultText.getText().length()-1;
        if(!resultText.getText().equals("")){
            //计算括号的数量
            for(int i=resultText.getText().length()-1;i>=0;i--){
                switch (resultText.getText().charAt(i)) {
                    case '(' -> left++;
                    case ')' -> right++;
                }
            }
            //找到最后一个数字的下标
            if(!"+-*/()".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")){
                for(;index>0;index--){
                    if("+-*/(".contains(resultText.getText().charAt(index)+"")){
                        break;
                    }
                }
            }
        }

        if("0123456789".contains(label)){//输入数字时:
            if(count==1){//文本框内为上一个有效算式的答案,则替换为后输入的数字
                b=label;
                resultText.setText(b);
                count=0;
            }
            else if(("0".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")&&
                    resultText.getText().length()<=1)){//无法输入0开头的整数
                b=label;
                resultText.setText(b);
            }
            else if(("0".contains(resultText.getText().charAt(resultText.getText().length()-1)+""))&&
                    ("+-*/%".contains(resultText.getText().charAt(resultText.getText().length()-2)+""))){
                StringBuilder BK = new StringBuilder(this.b);//无法输入0开头的整数
                resultText.setText(BK.substring(0, this.b.length() - 1)+label);
                b=resultText.getText();
            }
            else{
                this.b = this.b + label;
                resultText.setText(b);
            }
        }

        //输入左括号时
        else if(label.equals("(")){
            if(b.equals("")){
                resultText.setText("(");
                b=resultText.getText();
            }
            //前面是数字和右括号时补乘,是小数点时无法输入
            else if("0123456789)".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")){
                b=b+"*(";
                resultText.setText(resultText.getText()+"*(");
            }
            else if(".".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")){
            }
            else{
                b=b+"(";
                resultText.setText(resultText.getText()+"(");
            }
        }

        else if (label==")"){//输入右括号时
            if (b.equals("")){//表达式为空时不做响应
            }
            else if("+-*/(".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")
            ||left==right){//运算符、左括号后不能跟右括号,左右括号数量匹配时不能输入右括号
            }
            //前面是右括号时补乘
        else if(b.length()>0&&(")".contains(resultText.getText().charAt(resultText.getText().length()-1)+""))){
            b=b+"*"+label;
            resultText.setText(b);
        }
            else {
                resultText.setText(resultText.getText()+")");
                b=resultText.getText();
            }
        }

        else if(label=="."){//输入小数点时
            if(count==1){//文本框内为上一个有效算式的答案,则替换
                b="0"+label;
                resultText.setText(b);
                count=0;
            }
            if((b.equals(""))
                    ||"(+-*/".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")){
                b=b+"0.";
                resultText.setText(b);
            }
            else if(")".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")){
                b=b+"*0.";//前面是右括号时补乘
                resultText.setText(b);
            }
            else if (".".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")){
            }//前面是小数点时不作响应

            else if(!b.substring(index+1).contains(".")){//前面是不带小数点的数字时正常输入
                b=b+".";
                resultText.setText(b);
            }
            else{//前面是带小数点的数字时不响应
            }
        }

        else if(label.equals("=")){//输入等于号时,调用计算方法,得出最终结果
            //resultText.setText(this.b);
            History.setText(History.getText()+resultText.getText());
            if (".".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")){
                StringBuilder BK = new StringBuilder(this.b);
                resultText.setText(BK.substring(0, this.b.length() - 1));
                this.b=resultText.getText();
            }
                try {
                    String s[] = Calculate(this.b);
                    String result = Result(s);
                    this.b = result + "";
                    double Test1;
                    Test1=Double.parseDouble(this.b);//将答案字符串转换成双精度型数据检测是否有错误
                    //更新文本框,当前结果在字符串b中,并未删除,下一次输入接着此结果以实现连续运算
                    resultText.setText(this.b);
                    History.setText(History.getText() + "=" + resultText.getText() + "\n");
                    count=1;
                }catch (Exception e1){//抓取错误,不参与运算并提示输入错误,并将文本区域初始化
                    JOptionPane.showMessageDialog(null,"表达式错误");
                    History.setText(History.getText() +":表达式不完整或将0设置为了除数" + "\n");
                    this.b="";
                    resultText.setText("0");
                }

        }

        else if(label=="C"){ //清空按钮,消除显示屏文本框前面所有的输入和结果
            this.b="";
            resultText.setText("0");//更新文本域的显示,显示初始值;
        }

        else if(label=="sqrt") {//开方功能并不完善,目前只能用于单目运算
            String n=kFys(this.b);
            resultText.setText("sqrt"+"("+this.b+")"+"="+n);//使运算表达式显示在输入界面
            History.setText(History.getText()+resultText.getText()+"\n");//获取输入界面的运算表达式并使其显示在历史记录文本框
            this.b=n;
        }

        else if(label=="AC") {
            this.b="";
            resultText.setText("0");
            History.setText(" ");
        }

        else if(label=="pi") {
            String m=String.valueOf(3.14159265);
            this.b=this.b+m;
            resultText.setText(this.b);
        }

        else if(label=="back"&&this.b.length()>1) {//退格
            StringBuilder BK = new StringBuilder(this.b);
            resultText.setText(BK.substring(0, this.b.length() - 1));
            this.b=resultText.getText();
        }

        else if((label=="back")&&(this.b.length()<=1)){//防止文本框为空时打印退格键
            this.b="";
            resultText.setText("0");
        }

        else if (label=="-"){//运算符后不能输入减号
            if("+-*/.".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")){
            }
            else{
                this.b = this.b + label;
                resultText.setText(this.b);
            }
        }

        else if(label=="+"||label=="*"||label=="/"||label=="%"||label=="sqrt") {//输入运算符时
            if("+-*/(%.".contains(resultText.getText().charAt(resultText.getText().length()-1)+"")){
            }//运算符后不能输入运算符
            else if(b.length()==0) {//当文本框为空时无法输入减号以外的运算符
            }
            else{
                this.b = this.b + label;
                resultText.setText(this.b);
            }
        }

        else {
            b = b + label;
            resultText.setText(this.b);
        }
    }

    //将中缀表达式转换为后缀表达式
    public   String[] Calculate(String str) {
        char opStack[] = new char[100];// 静态栈,对用户输入的操作符进行处理,用于存储运算符
        String postQueue[] = new String[100];// 后缀表达式字符串数组,为了将多位数存储为独立的字符串
        int top = -1, j = 0; // 静态指针top,控制变量j

        if(str.charAt(0)=='-'){//如果表达式以负号开头,在最前面补一个0,方便计算
            str="0"+str;
        }
        StringBuilder addZero= new StringBuilder(str);//将表达式中的所有"(-"变为"(0-"以方便计算
        if(str.contains("(-")){
            do{
                addZero.insert(addZero.indexOf("(-")+1,'0');
                str=addZero.toString();
            }while (str.contains("(-"));
        }
        // 用于承接多位数的字符串
        String s = "";
        for (int i = 0; i < str.length(); i++){// 遍历中缀表达式
            // indexOf函数,返回字串首次出现的位置;charAt函数返回index位置处的字符;
            if ("0123456789.".indexOf(str.charAt(i)) >= 0){ // 遇到数字字符的情况
                s = "";// 作为承接字符,每次开始时都要清空
                for (; i < str.length() && "0123456789.".indexOf(str.charAt(i)) >= 0; i++) {
                    s = s + str.charAt(i);
                }
                i--;
                postQueue[j] = s;// 数字字符直接加入后缀表达式
                j++;
            }
            else if ("(".indexOf(str.charAt(i)) >= 0) {// 遇到左括号
                top++;
                opStack[top] = str.charAt(i);// 左括号入栈
            }
            else if (")".indexOf(str.charAt(i)) >= 0) {// 遇到右括号
                for (;;){// 栈顶元素循环出栈,直到遇到左括号为止
                    if (opStack[top] != '(') {// 栈顶元素不是左括号
                        postQueue[j] = opStack[top] + "";// 栈顶元素出栈
                        j++;
                        top--;
                    }
                    else { // 找到栈顶元素是左括号
                        top--;// 删除栈顶左括号
                        break;// 循环结束
                    }
                }
            }
            else if ("*%/+-".indexOf(str.charAt(i)) >= 0)// 遇到运算符
            {
                if (top == -1)
                {// 若栈为空则直接入栈
                    top++;
                    opStack[top] = str.charAt(i);
                }
                else if ("*%/".indexOf(opStack[top]) >= 0)
                {// 当栈顶元素为高优先级运算符时,让栈顶元素出栈进入后缀表达式后,当前运算符再入栈
                    postQueue[j] = opStack[top] + "";
                    j++;
                    opStack[top] = str.charAt(i);
                }
                else
                {
                    top++;
                    opStack[top] = str.charAt(i);// 当前元素入栈
                }
            }
        }
        while (top != -1) {// 遍历结束后将栈中剩余元素依次出栈进入后缀表达式
            postQueue[j] = opStack[top] + "";
            j++;
            top--;
        }
        return postQueue;
    }

    //开方运算方法
    public String kFys(String str) {
        String result = "";
        double a = Double.parseDouble(str), b = 0;
        b = Math.sqrt(a);
        result = String.valueOf(b);//将运算结果转换为string类型并赋给string类型的变量result
        return result;
    }

    // 计算后缀表达式,并返回最终结果
    public String Result(String str[]) {
        String[] Result = new String[100];// 顺序存储的栈,数据类型为字符串
        int Top = -1;// 静态指针Top
        for (int i = 0; str[i] != null; i++) {
            if ("+-*%/".indexOf(str[i]) < 0) {
                Top++;
                Result[Top] = str[i];
            }
            if ("+-*%/".indexOf(str[i]) >= 0)// 遇到运算符字符,将栈顶两个元素出栈计算并将结果返回栈顶
            {
                BigDecimal x, y, n;
                x = BigDecimal.valueOf(Double.parseDouble(new BigDecimal(Result[Top]).toString()));// 顺序出栈两个数字字符串,并转换为double类型
                Top--;
                y = BigDecimal.valueOf(Double.parseDouble(new BigDecimal(Result[Top]).toString()));
                Top--;
                if ("-".indexOf(str[i]) >= 0) {
                    n = y.subtract(x);
                    Top++;
                    Result[Top] = String.valueOf(n);// 将运算结果重新入栈
                }
                if ("+".indexOf(str[i]) >= 0) {
                    n = y.add(x);
                    Top++;
                    Result[Top] = String.valueOf(n);// 将运算结果重新入栈
                }
                if ("*".indexOf(str[i]) >= 0) {
                    n = y.multiply(x);
                    Top++;
                    Result[Top] = String.valueOf(n);// 将运算结果重新入栈

                }
                if ("/".indexOf(str[i]) >= 0){
                        n = y.divide(x,5,RoundingMode.HALF_UP);
                        Top++;
                        Result[Top] = String.valueOf(n);// 将运算结果重新入栈
                }
                if ("%".indexOf(str[i]) >= 0) {
                   double num1= Double.parseDouble(String.valueOf(y));
                   double num2= Double.parseDouble(String.valueOf(x));
                        n = BigDecimal.valueOf(num1%num2);
                        Top++;
                        Result[Top] = String.valueOf(n);// 将运算结果重新入栈
                }
            }
        }
        return Result[Top];// 返回最终结果
    }

    // 主函数
    public static void main(String[] args) {
        Main test = new Main();
    }
}

四、遇到的问题及其解决过程

1.非法表达式的识别
请添加图片描述

当输入如图非法表达式时,会因为括号数未匹配而无法返回正确值。
解决方法:
在“=”的执行语句后使用以下代码抓取错误并提示

catch (Exception e1){//抓取错误,不参与运算并提示输入错误
                    JOptionPane.showMessageDialog(null,"表达式错误");
                    History.setText(History.getText() +":此表达式错误" + "\n");

添加后结果如下:
在这里插入图片描述
2.负数的运算问题
程序会因为无法运算负数而直接报错

解决方法:
加入以下“补零”代码

if(str.charAt(0)=='-'){//如果表达式以负号开头,在最前面补一个0,方便计算
          str="0"+str;
      }
      StringBuilder addZero= new StringBuilder(str);//将表达式中的所有"(-"变为"(0-"以方便计算
      if(str.contains("(-")){
          do{
              addZero.insert(addZero.indexOf("(-")+1,'0');
              str=addZero.toString();
          }while (str.contains("(-"));
      }

在这里插入图片描述

五、程序功能演示

在这里插入图片描述
程序界面分为左边的操作显示区和右侧的历史记录区。
能提示被除数不允许为0等表达式异常产生的错误。
在这里插入图片描述
有退格、清空历史记录等便利功能
其中开方功能并不完善,目前只能进行单目运算。
在这里插入图片描述

12.20日补充
在经过修改后,计算器有了更智能的输入功能,例如所输入的右括号数量只能小于等于左括号数量,小数点前无数字时自动补零,无法连续输入运算符等等,期间也遇到bug越写越多的情况,但耐心写下来收获甚至不小于刚写完框架,之前没注意到的各种细节被慢慢完善很有成就感。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值