详解图形化计算器并实现混合运算

本文主要分享用Java实现计算器的代码。先解析图形化界面基础设置,包括成员变量声明、构造方法及成员方法编写;接着介绍事件监听,通过按钮添加表达式并处理计算;还阐述用栈实现混合运算的原理和步骤;最后针对可能出现的空栈异常给出两种解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先放上代码:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EmptyStackException;
import java.util.Stack;
public class Main extends JFrame implements ActionListener{
        /**@*************成员变量的声明与实例化**************/
        //声明按钮数组
        JButton bt[]=new JButton[18];
        //面板
        JPanel JP1=new JPanel();
        JPanel JP2=new JPanel();
        JPanel JP3=new JPanel();
        JPanel JP4=new JPanel();
        JPanel JP5=new JPanel();
        JPanel JP6=new JPanel();
        JPanel JP7=new JPanel();
        JPanel JP8=new JPanel();
        //标签和文本框
        JLabel JL1=new JLabel("输入框:");
        JLabel JL2=new JLabel("输出框:");
        JTextArea JA1=new JTextArea(1/2,12);
        JTextField JT2=new JTextField(12);



        /**@**************成员变量的声明与实例化************/
    /**@构造方法*/
    public Main(){
        initialize();//初始化容器方法
        //窗体设置
        this.setTitle("计算器");
        this.setBounds(600,200,320,500);
        this.setSize(500,500);
        this.setVisible(true);
        this.setLayout(new GridLayout(8,1));
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    /**@初始化方法*/
    public void initialize() {
        //初始化与添加按钮
        String buttonName[] = {"清空", "退格", "%", "/", "*","+","-", "=", "7","8","9", "4","5" ,"6", "1", "2", "3", "0"};
        for (int i = 0; i < buttonName.length; i++) {
            bt[i] = new JButton(buttonName[i]);
            bt[i].setForeground(Color.BLUE);//设置按钮背景颜色
            bt[i].setFont(new Font("行书", Font.PLAIN, 40));//设置文字样式
            bt[i].addActionListener(this);//为此时的按钮添加监听器

            //添加按钮到面板上
            if (i >= 0 && i <= 1) {
                JP3.add(bt[i]);
            } else if (i >= 4 && i <= 7) {
                JP4.add(bt[i]);
            } else if (i >= 8 && i <= 10) {
                JP5.add(bt[i]);
            } else if (i >= 11 && i <= 13) {
                JP6.add(bt[i]);
            } else if (i >= 14 && i <= 16) {
                JP7.add(bt[i]);
            } else if ((i >= 17 && i <= 17)||(i>=2&&i<=3)) {
                JP8.add(bt[i]);
            }
        }
        //添加标签和文本框到面板上
        JP1.add(JL1);
        JP1.add(JA1);
        JP2.add(JL2);
        JP2.add(JT2);
        //设置文本区格式
        JA1.setFont(new Font("行书", Font.PLAIN, 30));
        JT2.setFont(new Font("行书", Font.PLAIN, 30));
        bt[1].setEnabled(false);//刚开始设置退格键不能操作
        this.add(JP1);
        this.add(JP2);
        this.add(JP3);
        this.add(JP4);
        this.add(JP5);
        this.add(JP6);
        this.add(JP7);
        this.add(JP8);
    }

/**@事件监听方法*/
    @Override
    public void actionPerformed(ActionEvent e) throws EmptyStackException {
        if(e.getSource()==bt[0]){
            //点击了清除键,将文本区清除
            JA1.setText("");
            JT2.setText("");
            JA1.setEnabled(true);
            isJA1empty();
        }
        else if(e.getSource()==bt[1]){
            //截取需退格字符前面的字符串
            int length=JA1.getText().length();
            JA1.setText(JA1.getText().substring(0,length-1));
            length--;
            isJA1empty();
        }
        else if(e.getSource()==bt[2]){
            JA1.append(bt[2].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[3]){
            JA1.append(bt[3].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[4]){
            JA1.append(bt[4].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[5]){
            JA1.append(bt[5].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[6]){
            JA1.append(bt[6].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[7]){
            try {
                isJA1empty();
            } catch (Exception ex) {
                throw new EmptyStackException();
            }
            try {
                isCharacter();
            } catch (Exception ex) {

            }
        }
        else if(e.getSource()==bt[8]){
            JA1.append(bt[8].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[9]){
            JA1.append(bt[9].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[10]){
            JA1.append(bt[10].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[11]){
            JA1.append(bt[11].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[12]){
            JA1.append(bt[12].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[13]){
            JA1.append(bt[13].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[14]){
            JA1.append(bt[14].getText());
            isJA1empty();
        }else if(e.getSource()==bt[15]){
            JA1.append(bt[15].getText());
            isJA1empty();
        }else if(e.getSource()==bt[16]){
            JA1.append(bt[16].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[17]){
            JA1.append(bt[17].getText());
            isJA1empty();
        }
        /*else if(e.getSource()==bt[18]){
            JA1.append(bt[2].getText());
            isJA1empty();
        }*/

    }//事件监听方法结束
        public void isJA1empty() throws EmptyStackException{
        //判断文本框是否为空,控制退格键是否可以操作
            if("".equals(JA1.getText())){
                bt[1].setEnabled(false);
            }else{
                bt[1].setEnabled(true);
            }
        }
        public void isCharacter(){
            if(JA1.getText().charAt(0)=='+'||JA1.getText().charAt(0)=='-'||JA1.getText().charAt(0)=='*'||JA1.getText().charAt(0)=='/'||JA1.getText().charAt(0)=='%'){
                JT2.setText("无效输入");
                JA1.setEnabled(false);
            }else{
                try {
                    JT2.setText(Double.toString(judgeExpression(JA1.getText())));
                } catch (Exception e) {
                    throw new EmptyStackException();
                }
            }
        }//判断表达式首位是否为运算符,若是,则无效输入
        /**@此方法用空格分隔数值和运算符,以便后面拆分字符为字符串数组*/
        public String delCharacter(String s){
            String result="";
            for(int i=0;i<s.length();i++){
                if(s.charAt(i)=='+'||s.charAt(i)=='-'||s.charAt(i)=='*'||s.charAt(i)=='/'||s.charAt(i)=='%'||s.charAt(i)=='('||s.charAt(i)==')'){
                    result+=" "+s.charAt(i)+" ";
                }else{
                    result+=s.charAt(i);
                }
            }
            //System.out.println(result);
            return result;
        }//表达式拆分完毕
        /**@开始进行对数值和运算符的入栈操作判断*/
        public double judgeExpression(String expression){
                        //创建两个栈-数据栈和运算符栈
            Stack<Double> numStack=new Stack<>();
            Stack<Character> charStack=new Stack<>();
                        //处理表达式
            expression=delCharacter(expression);
                        //通过给定的正则表达式将字符串分割成字符串数组

            String []strs=expression.split(" ");
            /**开始对字符进行判断入栈操作*/
            for(String str:strs){
                if(str.length()==0){//如果是空格的话,什么也不操作,直接进行下一步
                    continue;
                }else if(str.charAt(0)=='+'||str.charAt(0)=='-'){
                    //根据入栈规则,加减优先级最低,如果栈不为空,栈顶的运算符优先级一定大于等于目前运算符,所以直接弹出栈顶运算符进行运算
                    while(!charStack.isEmpty()&&(charStack.peek()=='+'||charStack.peek()=='-'||charStack.peek()=='*'||charStack.peek()=='/'||charStack.peek()=='%')){
                        calExpression(numStack,charStack);//开始运算
                    }
                    charStack.push(str.charAt(0));//运算完毕后,将目前运算符入栈
                }//加减入栈判断操作结束
                else if(str.charAt(0)=='*'||str.charAt(0)=='/'||str.charAt(0)=='%'){
                    //因为乘除优先级高于加减,所以要对栈顶的运算符进行判断是否为乘除,是则弹出进行运算,否则将目前运算符压入栈中
                    while(!charStack.isEmpty()&&(charStack.peek()=='*'||charStack.peek()=='/'||charStack.peek()=='%')){
                        calExpression(numStack,charStack);//开始运算
                    }
                    charStack.push(str.charAt(0));//运算完毕后,将目前运算符入栈
                }else if(str.trim().charAt(0)=='('){
                    charStack.push('(');
                }//如果是左括号则直接压入栈中
                else if (str.trim().charAt(0)==')') {//如果是右括号则直到左括号的运算符全部出栈
                    while(charStack.peek()!='('){
                        calExpression(numStack,charStack);//开始运算
                    }
                    charStack.pop();//运算结束后,让左括号出栈
                }else{//如果是数字的话,无需判断,直接入数据栈
                    numStack.push(Double.parseDouble(str));//parseInt是将数字字符直接转化为整型的方法,也可以用ascii表进行转换
                }
            }/**运算符判断循环结束*/
            //最后将栈中剩余的运算符出栈运算,知道栈空即可
            while(!charStack.isEmpty()){
                calExpression(numStack,charStack);//开始运算
            }
            try {
                return numStack.peek();//数据栈中最后的数就是运算结果
            } catch (Exception e) {
                throw new EmptyStackException();
            }
        }
        /**@此方法是对符合出栈规则的运算表达式进行计算*/
        public void calExpression(Stack<Double> numStack,Stack<Character> charStack){
            char op=charStack.pop();//弹出一个运算符
            double num1=numStack.pop();
            double num2=numStack.pop();//弹出两个数值
            if(op=='+'){
                numStack.push(num1+num2);
            }else if(op=='-') {
                numStack.push(num2-num1);
            }else if(op=='*'){
                numStack.push(num2*num1);
            }else if(op=='/'){
                if(num1!=0) {
                   numStack.push(num2 / num1);
                }else{
                    JA1.setText("被除数不能为0");
                }
            }else if(op=='%'){
                numStack.push(num2%num1);
            }//栈结构,后面数字在上面,先出栈,所以除法和减法num1在后
        }
    public static void main(String[] args){
        new Main();
    }
}

在写计算器之前,我还没有学过图形化和栈,借鉴了很多大佬的想法和思路,然后我对自己敲的时候遇到的问题做了一个总结。

首先,我先一步步解析代码。

首先是图形化界面基础设置部分。

我们按照一个规范的类来写。

1.首先是对成员变量的声明于实例化。

public class Main extends JFrame implements ActionListener{
        /**@*************成员变量的声明与实例化**************/
        //声明按钮数组
        JButton bt[]=new JButton[18];
        //面板
        JPanel JP1=new JPanel();
        JPanel JP2=new JPanel();
        JPanel JP3=new JPanel();
        JPanel JP4=new JPanel();
        JPanel JP5=new JPanel();
        JPanel JP6=new JPanel();
        JPanel JP7=new JPanel();
        JPanel JP8=new JPanel();
        //标签和文本框
        JLabel JL1=new JLabel("输入框:");
        JLabel JL2=new JLabel("输出框:");
        JTextArea JA1=new JTextArea(1/2,12);
        JTextField JT2=new JTextField(12);



        /**@**************成员变量的声明与实例化************/
    

这里的成员变量包括18个按钮,8个面板,两个标签(输入框和输出框)作为提示,以及文本域和文本框(这两个在实力过程中就顺便调整大小参数)。

2.接下来就是写实现类的构造方法(构造器)。

 public Main(){
        initialize();//初始化容器方法
        //窗体设置
        this.setTitle("计算器");
        this.setBounds(600,200,320,500);
        this.setSize(500,500);
        this.setVisible(true);
        this.setLayout(new GridLayout(8,1));
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

这里就有可能会有同学要问了。为什么我没有创建JFrame窗体,就可以用this.直接初始化窗体参数?

这个问题其实很简单,但是很多资料都没说到过,包括我的课本,我查了好久。

JFrame窗体的创建有两种方法。

一、构造方法创建。

import javax.swing.*;	
public class windowJFame_JDialog extends JFrame{
	public windowJFrame_JDialog() {
		
	}
}

在主类中继承JFrame这个类,那么他的构造方法就是一个窗体。在这个构造方法中我们无需再实例化JFrame这个类,我们可以直接调用关于JFrame类中的方法,例如设置大小和坐标。

而this在这里就是这个类的一个实例化对象。

二、实例化创建

public static void main(String[] args){
	JFrame jf = new JFrame();// 实例化JFrame窗体
}

我们在main方法中实例化了JFrame对象,使用jf来接收这个对象;这样jf就是一个窗体,在运行时就会实现这个窗体(前提设置窗体可见);

在实例化方式创建完窗体后,将不再提供默认对象了,也就是不能直接调用JFrame中的方法!在需要调用方法的时候我们需要借助接收的jf这个变量对象来调用JFrame中的方法;


这里引用了文章:https://blog.youkuaiyun.com/ZunXin_2580/article/details/118274548?spm=1001.2014.3001.5506

可以作为了解。

然后作为构造方法,我们在实例化对象的时候,这个类第一时间会先运行构造方法,所以窗体的基本设置最好放在构造方法里面。

布局管理器我选择的是网格布局管理器。我试过其它的管理器,最美观的是盒子布局管理器,也挺好用的。

3.接下来我们开始成员方法的编写。

/**@初始化方法*/
    public void initialize() {
        //初始化与添加按钮
        String buttonName[] = {"清空", "退格", "%", "/", "*","+","-", "=", "7","8","9", "4","5" ,"6", "1", "2", "3", "0"};
        for (int i = 0; i < buttonName.length; i++) {
            bt[i] = new JButton(buttonName[i]);
            bt[i].setForeground(Color.BLUE);//设置按钮背景颜色
            bt[i].setFont(new Font("行书", Font.PLAIN, 40));//设置文字样式
            bt[i].addActionListener(this);//为此时的按钮添加监听器

            //添加按钮到面板上
            if (i >= 0 && i <= 1) {
                JP3.add(bt[i]);
            } else if (i >= 4 && i <= 7) {
                JP4.add(bt[i]);
            } else if (i >= 8 && i <= 10) {
                JP5.add(bt[i]);
            } else if (i >= 11 && i <= 13) {
                JP6.add(bt[i]);
            } else if (i >= 14 && i <= 16) {
                JP7.add(bt[i]);
            } else if ((i >= 17 && i <= 17)||(i>=2&&i<=3)) {
                JP8.add(bt[i]);
            }
        }
        //添加标签和文本框到面板上
        JP1.add(JL1);
        JP1.add(JA1);
        JP2.add(JL2);
        JP2.add(JT2);
        //设置文本区格式
        JA1.setFont(new Font("行书", Font.PLAIN, 30));
        JT2.setFont(new Font("行书", Font.PLAIN, 30));
        bt[1].setEnabled(false);//刚开始设置退格键不能操作
        this.add(JP1);
        this.add(JP2);
        this.add(JP3);
        this.add(JP4);
        this.add(JP5);
        this.add(JP6);
        this.add(JP7);
        this.add(JP8);
    }

为了让代码看的简洁,我们把组件的初始化设置单独写成一个方法,在构造方法中进行调用。

我们首先对按钮进行一一命名,然后通过for循环进行初始化按钮参数并把按钮添加到面板上。

我们在成员变量的声明与实例化那里实例化了8个面板,对应内容面板上的八行:前两行添加标签和文本,后六行添加按钮。

同时我们可以通过setFont()方法对文本内容进行设置,关于这个Font方法,可以参考:

(8条消息) Java中----Font类简介_java font_锥子A的博客-优快云博客

感谢大佬的总结!

别忘了,我们的退格键刚开始没有输入的时候,是不允许使用的,所以我们在初始化时要先关闭它。

此时,我们已经把标签,文本框,按钮这些小组件一一安装到面板上。最后我们只需把所有的面板都添加到窗体里面去,这样,我们的图形化界面就大功告成啦!!!

然后是事件监听部分。

/**@事件监听方法*/
    @Override
    public void actionPerformed(ActionEvent e) throws EmptyStackException {
        if(e.getSource()==bt[0]){
            //点击了清除键,将文本区清除
            JA1.setText("");
            JT2.setText("");
            JA1.setEnabled(true);
            isJA1empty();
        }
        else if(e.getSource()==bt[1]){
            //截取需退格字符前面的字符串
            int length=JA1.getText().length();
            JA1.setText(JA1.getText().substring(0,length-1));
            length--;
            isJA1empty();
        }
        else if(e.getSource()==bt[2]){
            JA1.append(bt[2].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[3]){
            JA1.append(bt[3].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[4]){
            JA1.append(bt[4].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[5]){
            JA1.append(bt[5].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[6]){
            JA1.append(bt[6].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[7]){
            try {
                isJA1empty();
            } catch (Exception ex) {
                throw new EmptyStackException();
            }
            try {
                isCharacter();
            } catch (Exception ex) {

            }
        }
        else if(e.getSource()==bt[8]){
            JA1.append(bt[8].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[9]){
            JA1.append(bt[9].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[10]){
            JA1.append(bt[10].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[11]){
            JA1.append(bt[11].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[12]){
            JA1.append(bt[12].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[13]){
            JA1.append(bt[13].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[14]){
            JA1.append(bt[14].getText());
            isJA1empty();
        }else if(e.getSource()==bt[15]){
            JA1.append(bt[15].getText());
            isJA1empty();
        }else if(e.getSource()==bt[16]){
            JA1.append(bt[16].getText());
            isJA1empty();
        }
        else if(e.getSource()==bt[17]){
            JA1.append(bt[17].getText());
            isJA1empty();
        }
        /*else if(e.getSource()==bt[18]){
            JA1.append(bt[2].getText());
            isJA1empty();
        }*/

    }//事件监听方法结束

关于事件监听,我觉得两篇文章不错: (8条消息) java的事件监听_java监听事件和处理事件_晓之木初的博客-优快云博客

(8条消息) (JButton) e.getSource();是什么意思_爽快的方方面面的博客-优快云博客

至于e.getSource()方法,可以简单理解为“得到事件发生的信息,在这里也就是你点击了哪个按钮?,并通过if-else分支来对应判断。 

判断成功之后,通过append()方法,把获取到的按钮文本追加到JA1文本框里面。这样就实现了,通过按钮对表达式的添加。

至于isJA1empty()方法,它是一个判空方法,来控制退格键是否可以操作。

public void isJA1empty() throws EmptyStackException{
        //判断文本框是否为空,控制退格键是否可以操作
            if("".equals(JA1.getText())){
                bt[1].setEnabled(false);
            }else{
                bt[1].setEnabled(true);
            }
        }

敲重点!!!

                注意bt[7],这是一个”=“按钮,当我们点击等号的时候,说明计算开始,并且等号不会追加到文本框。方便大家看,我再显示一次:

else if(e.getSource()==bt[7]){
            try {
                isJA1empty();
            } catch (Exception ex) {
                throw new EmptyStackException();
            }
            try {
                isCharacter();
            } catch (Exception ex) {

            }
        }

我们调用了两个成员方法:isJA1empty()和isCharacter()。

方法1就不多说了,方法二看这里:

public void isCharacter(){
            if(JA1.getText().charAt(0)=='+'||JA1.getText().charAt(0)=='-'||JA1.getText().charAt(0)=='*'||JA1.getText().charAt(0)=='/'||JA1.getText().charAt(0)=='%'){
                JT2.setText("无效输入");
                JA1.setEnabled(false);
            }else{
                try {
                    JT2.setText(Double.toString(judgeExpression(JA1.getText())));
                } catch (Exception e) {
                    throw new EmptyStackException();
                }
            }
        }//判断表达式首位是否为运算符,若是,则无效输入

”=“的输入,说明要把表达式进行提交计算了。这个时候我们再去检查表达式的首位是否出现运算符的情况,如果出现,这明显是无效表达式。

如果表达式正确,我们再进行计算:

JT2.setText(Double.toString(judgeExpression(JA1.getText())));详解:

judgeExpression(String s)是计算表达式的方法,参数是一个字符串,通过文本框得到字符串,进行计算。再将计算结果Double类型转化为字符串类型,输出到输出框里面。

 这里面出现了很多关于异常抛出的代码。我们最后再说。

 通过栈实现混合运算

这里对初学者可能是一个难点,但更多的是找不到好的资料,这里作者我找了好久,已经备好了我认为最好理解的文章和视频:

栈实现混合运算的原理--转自bilibili:

数据结构与算法(合集)_哔哩哔哩_bilibili

(8条消息) 简易计算器(详解用栈实现算术表达式求值)-优快云博客

以及正则表达式:

(8条消息) 什么是正则表达式 ?_肥宅不死的博客-优快云博客

关于正则表达式可以稍作了解即可,有兴趣可以深究。

好了,把准备工作做完,就可以开始写代码啦!

 /**@此方法用空格分隔数值和运算符,以便后面拆分字符为字符串数组*/
        public String delCharacter(String s){
            String result="";
            for(int i=0;i<s.length();i++){
                if(s.charAt(i)=='+'||s.charAt(i)=='-'||s.charAt(i)=='*'||s.charAt(i)=='/'||s.charAt(i)=='%'||s.charAt(i)=='('||s.charAt(i)==')'){
                    result+=" "+s.charAt(i)+" ";
                }else{
                    result+=s.charAt(i);
                }
            }
            //System.out.println(result);
            return result;
        }//表达式拆分完毕

首先,我们用空格将表达式把数字和运算符进行拆分,并把最后拆分好的字符串赋值给result,并返回。结果是这样的:5+4➡5 + 4

然后我们开始通过两个栈--数据栈和运算符栈判断优先级,并得到后缀表达式。

 /**@开始进行对数值和运算符的入栈操作判断*/
        public double judgeExpression(String expression){
                        //创建两个栈-数据栈和运算符栈
            Stack<Double> numStack=new Stack<>();
            Stack<Character> charStack=new Stack<>();
                        //处理表达式
            expression=delCharacter(expression);
                        //通过给定的正则表达式将字符串分割成字符串数组

            String []strs=expression.split(" ");
            /**开始对字符进行判断入栈操作*/
            for(String str:strs){
                if(str.length()==0){//如果是空格的话,什么也不操作,直接进行下一步
                    continue;
                }else if(str.charAt(0)=='+'||str.charAt(0)=='-'){
                    //根据入栈规则,加减优先级最低,如果栈不为空,栈顶的运算符优先级一定大于等于目前运算符,所以直接弹出栈顶运算符进行运算
                    while(!charStack.isEmpty()&&(charStack.peek()=='+'||charStack.peek()=='-'||charStack.peek()=='*'||charStack.peek()=='/'||charStack.peek()=='%')){
                        calExpression(numStack,charStack);//开始运算
                    }
                    charStack.push(str.charAt(0));//运算完毕后,将目前运算符入栈
                }//加减入栈判断操作结束
                else if(str.charAt(0)=='*'||str.charAt(0)=='/'||str.charAt(0)=='%'){
                    //因为乘除优先级高于加减,所以要对栈顶的运算符进行判断是否为乘除,是则弹出进行运算,否则将目前运算符压入栈中
                    while(!charStack.isEmpty()&&(charStack.peek()=='*'||charStack.peek()=='/'||charStack.peek()=='%')){
                        calExpression(numStack,charStack);//开始运算
                    }
                    charStack.push(str.charAt(0));//运算完毕后,将目前运算符入栈
                }else if(str.trim().charAt(0)=='('){
                    charStack.push('(');
                }//如果是左括号则直接压入栈中
                else if (str.trim().charAt(0)==')') {//如果是右括号则直到左括号的运算符全部出栈
                    while(charStack.peek()!='('){
                        calExpression(numStack,charStack);//开始运算
                    }
                    charStack.pop();//运算结束后,让左括号出栈
                }else{//如果是数字的话,无需判断,直接入数据栈
                    numStack.push(Double.parseDouble(str));//parseInt是将数字字符直接转化为整型的方法,也可以用ascii表进行转换
                }
            }/**运算符判断循环结束*/
            //最后将栈中剩余的运算符出栈运算,知道栈空即可
            while(!charStack.isEmpty()){
                calExpression(numStack,charStack);//开始运算
            }
            try {
                return numStack.peek();//数据栈中最后的数就是运算结果
            } catch (Exception e) {
                throw new EmptyStackException();
            }
        }

我们先创建两个栈,分别储存数字和运算符。(要注意栈的泛型Double类型和char类型)

然后将表达式分割。

然后通过spilt()方法通过给定的正则表达式把字符串拆分为字符串数组。

再对字符串数组进行遍历,对字符进行判断入栈操作:

入栈的规则,在上面的视频里讲的很清楚,只有当栈顶的运算符优先级小于当前运算符,当前运算符可以直接入栈,否则要弹出栈顶的运算符,再弹出数据栈中的两个数字,组成后缀表达式,进行运算,并将计算结果压入数据栈中,最后再把当前运算符入栈。如果遇到左括号,就直接压入栈中,遇到右括号,就将右括号之前的运算符出栈并进行运算,直到遇到左括号,计算结果压入数据栈中,并让左括号出栈。当表达式遍历完后,让栈中剩余的运算符一一出栈进行运算,直到站空为止。数据栈中最后的数字,就是最终计算结果。

这里是运算方法:

public void calExpression(Stack<Double> numStack,Stack<Character> charStack){
            char op=charStack.pop();//弹出一个运算符
            double num1=numStack.pop();
            double num2=numStack.pop();//弹出两个数值
            if(op=='+'){
                numStack.push(num1+num2);
            }else if(op=='-') {
                numStack.push(num2-num1);
            }else if(op=='*'){
                numStack.push(num2*num1);
            }else if(op=='/'){
                if(num1!=0) {
                   numStack.push(num2 / num1);
                }else{
                    JA1.setText("被除数不能为0");
                }
            }else if(op=='%'){
                numStack.push(num2%num1);
            }//栈结构,后面数字在上面,先出栈,所以除法和减法num1在后
        }

        除法的话要考虑被除数不能为0。

最后解释一下异常处理。

还记得我们算出最终结果的原理吗?

        对,数据栈中最后的一个数字就是最终计算结果。但如果被除数为0,按照我们上面的if判断,我们的文本框会打印”被除数不能为0“。但是,没有计算结果,这个时候数据栈是空的,那我们在想从栈中得到计算结果并打印在文本框时,会出现空栈异常。

        有两种解决方案:

                一、通过抛出异常,解决在运行中出现Expection异常。

                二、当出现被除数为0时,我们手动想栈中压入一个数字,来代表计算出错,但是会覆盖提示”被除数不能为0“。

好的,小白的第一篇博客,有写的不好的请指出,感谢大佬们!

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值