Java实现简单计算器设计,原理及思路。

一、前言

        写这篇文章,是为了整理下思路,记录下自己的学习记录,Java小白一枚,代码仅供参考,如有不合理之处,请谅解。

二、计算器具体要求

        1.实现输入表达式完成计算(仅支持加减乘除)。
        2.支持正负数小数等运算。
        3.设计计算器界面。
        4.基本错误处理完成。

三、核心思路

        1.表达式处理

                  使用正则表达式判断表达式是否合理,算数表达式的规律,就是一个数后面接着一个运算符然后再接一个数,先暂时不考虑括号的出现问题,例如:1+3/4*3,分离开来就是1+,3/,4*,3。最后一个数字是不跟运算符的所以由这样的思考,我们用正则表达式来写,现在我们来考虑括号问题,我们目前这个部分的目的是实现检测输入的表达式是否合理,所以要考虑括号的位置和数量,但由于我的技术有限,我就采用了很朴素的方法来验证,就是想到什么情况就写什么情况的检测代码,如下图:

public static Boolean kh_pp(String str){
        try {
            int zkh = 0;
            int ykh = 0;
            for (int i =0;i<str.length();i++){
                if (String.valueOf(str.charAt(i)).equals("(")){
                    zkh++;
                } else if (String.valueOf(str.charAt(i)).equals(")")) {
                    ykh++;
                }
            }
            return zkh == ykh;
        }catch (Throwable e2){
            jLabel.setText("括号匹配错误,未知异常!");
            return false;
        }
    }

public static boolean str_pd(String str){
        //先是去掉空格
        //(-1)+4-(-2)
        //表达式处理规则,思路:
        //1.去掉里面所有的左右括号
        //2.表达式一个数字后面必定有个运算符,除了最后一个数后没有运算符,其他都有
        //3.括号数量相同
        //功能要求
        //1.实现多位正或负数的匹配,支持小数
        //2.不允许出现数字以及运算符外的字符
        //3.除数不能为0
        try {
            boolean flag1 = kh_pp(str); // 这里还有个方法是kh_pp用来判断括号的数量是否相等
            boolean flag = false;  //先定义表达式为不合理
            if (str.matches(".*(\\-?\\d+(\\.\\d+)?)?\\(\\).*")){ 
                return flag; // 检测如:1(),()这种情况
            } else if (str.matches(".*\\-?\\d+(\\.\\d+)?\\(.*")) { 
                return flag;  //检测如:1(3+2)这种情况
            } else if (str.matches(".*\\-\\(\\d+(\\.\\d+)?\\).*")) {
                return flag;  //检测如:-(5) 这种写法,应写为(-5)
            }
            str = str.replace(" ",""); // 去掉空格
            str = str.replace("(",""); // 将左右括号全部去掉,例如:(-1)+4-(-2) 变成 -1+4--2
            str = str.replace(")",""); // 
            for (int i =0;i<str.length();i++){
                String now = String.valueOf(str.charAt(i));
                if (!now.matches("[0-9\\+\\-\\*\\/\\(\\)\\.]")){
                    jLabel.setText("表达式错误,含有未知字符!");
                    return flag; // 检测是否有不符合算术表达式的字符
                }
            }
            if (str.matches("(\\-?\\d+(\\.\\d+)?[\\+\\-\\*\\/])+\\-?\\d+(\\.\\d+)?")){ //常规检测 一个数后面跟着一个运算符,最后一个数不跟运算符
                if (str.matches(".*\\/0.*")){ // 检测除数是否为零
                    jLabel.setText("除数不能为0!");
                }else {
                    if (flag1)flag = true;  //如果前面的检测和现在的检测都是true 则将flag改为真
                }
            }
            return flag;
        }catch (Throwable e2){ //检测一切异常,如果出粗就返回false
            jLabel.setText("表达式判断错误,未知异常!");
            return false;
        }
    }
   
        2.把表达式当成字符串处理,先将表达式中的数字和运算符分离。

                这个部分完成的目的是表达式的分割,就是将表达式的数字和运算符分离,需要注意的是:正负数的多位数判断以及小数位数判断,使用while遍历拼接(小数点前后小数点后原理一样,就是遍历)。负数和正数为两种检测情况,所以分开写(其实是一开始没想到,后面写的OvO),此功能模块的返回值是一个分割后的列表,比如:(-6)/(-6)+(3-5)/2 ——》》["-6","/","-6","+","(","3","-","5",")","/","2"],为什么“(-6)”的括号不见了,是因为在代码最后做了处理,去掉了包裹着负数的括号,因为这些括号并不是优先级的括号,所以去掉。

具体代码如下:

public static List<String> str_fg(String str1){
        //创建一个字符列表用来存储分割后的字符串表达式
        //防止用书输入诸如(-1)+(-4)-5+((-6)/(-6)) 无用括号太多 去掉非优先级括号 也就是负数旁的括号
        String str = str1;
        List<String> ls = new ArrayList<>();
        try {
            //遍历表达式
            String dws;  //  多位数处理提前声明
            char now=0;  //  当前字符
            int i = 0;
            do {
                //负数判断
                //如果当前是运算符就直接放入列表,如果不是则进行数字判断
                if ( (String.valueOf(str.charAt(i)).equals("-") && i==0) || // 负数在首位
                        (String.valueOf(str.charAt(i)).equals("-") &&          // 负数在左括号 如 (-3)
                                String.valueOf(str.charAt(i+1)).matches("[0-9]") &&  
                                String.valueOf(str.charAt(i-1)).equals("(")) ||
                        (String.valueOf(str.charAt(i)).equals("-") &&
                                String.valueOf(str.charAt(i+1)).matches("[0-9]") &&
                                String.valueOf(str.charAt(i-1)).matches("[\\+\\-\\*\\/]"))){   // 负数在运算符后 如 -6/-6
                    dws = "";  //  拼接多位负数先定义为空
                    String dws_1="";
                    while( i < str.length() && (now = str.charAt(i+1))<=57 && (now = str.charAt(i+1))>=48 || str.charAt(i+1) =='.'){  //  while为真执行,逐个遍历
                        if (i+1<str.length() && str.charAt(i+1)=='.' && str.charAt(i)>=48 && str.charAt(i)<=57){
                            int now_fs = i+1;  // 小数点的位置
                            while(now_fs+1<str.length() && now_fs<str.length() && str.charAt(now_fs+1)<=57 && str.charAt(now_fs+1)>=48 ){ // 小数点后数字拼接
                                dws_1+=str.charAt(now_fs+1);
                                now_fs++;
                                i++;
                            }
                            break;
                        }else {
                            dws+=now;  //  拼接
                            i++;
                            if (i+1>=str.length())break;
                        }
                    }
                    i++;  // 负号也占一位
                    if (!dws_1.isEmpty()){dws += "."+dws_1;i++;} //小数点也占一位
                    ls.add("-"+dws);  //多位负数处理
                } else if((now = str.charAt(i))<48 || (now = str.charAt(i))>57){    //  0的ASCII为48 9的ASCII为57
                    ls.add(""+now);  // now是字符 不能添加进String类型的列表 用""+now 即可解决
                    i++;
                }else {
                    dws = "";  //  拼接多位数先定义为空
                    String dws_1="";
                    while( (i < str.length() && (now = str.charAt(i))<=57 && (now = str.charAt(i))>=48) || str.charAt(i) =='.'){  //  while为真执行,逐个遍历
                        if (str.charAt(i)=='.' && str.charAt(i-1)>=48 && str.charAt(i-1)<=57){
                            int now_zs = i;  // 小数点的位置
                            while(now_zs+1<str.length() && now_zs<str.length() && str.charAt(now_zs+1)<=57 && str.charAt(now_zs+1)>=48 ){
                                dws_1+=str.charAt(now_zs+1);
                                now_zs++;
                                i++;
                            }
                            break;
                        }else {
                            dws+=now;  //  拼接
                            i++;
                            if (i>=str.length()) break;
                        }
                    }
                    if (!dws_1.isEmpty()){dws += "."+dws_1;i++;}
                    ls.add(dws);
                }
            }while (i<str.length());
            for(int j =0;j<ls.size();j++){
                if (ls.get(j).matches("\\-\\d+(\\.\\d+)?")){
                    if (j+1<ls.size() && j-1>=0 && ls.get(j-1).equals("(") && ls.get(j+1).equals(")") ){
                        ls.remove(j-1);
                        ls.remove(j);
                    }//去掉包裹着负数的括号
                }else {
                    if (j+1<ls.size() && j-1>=0 && ls.get(j-1).equals("(") && ls.get(j+1).equals(")") ){
                        ls.remove(j-1);
                        ls.remove(j);
                    }//去掉包裹着正数的括号,以防有人这样输入(5)+(-3)
                }
            }
            return ls;
        }catch (ArrayIndexOutOfBoundsException e2){ //基本错误检测
            List<String> ls2 = new ArrayList<>();
            jLabel.setText("表达式分割错误,数组越界异常!");
            return ls2; //返回空列表用于后面调用时的返回值检测
        }catch (IndexOutOfBoundsException e3){
            List<String> ls2 = new ArrayList<>();
            jLabel.setText("表达式分割错误,表达式遍历越界异常!");
            return ls2;
        }catch (Throwable e4){
            List<String> ls2 = new ArrayList<>();
            jLabel.setText("表达式分割错误,未知异常!");
            return ls2;
        }

    }
        3.将分割后的表达式传入后缀表达式处理模块,可以看b站韩老师的视频,讲的很详细,036_尚硅谷_前缀 中缀 后缀表达式规则_哔哩哔哩_bilibili p36-p42,还有这个逆波兰 - 上(中缀表达式 转 后缀表达式)_哔哩哔哩_bilibili

具体代码如下图:

public static int yxj(String a){  //传入运算符返回运算符优先级
        switch (a){
            case "+", "-":
                return 1;
            case "*", "/":
                return 2;
            default:
                return 0;
        }
    }

public static List<String> str_hz(List<String> ls){
        List<String> ls1 = new ArrayList<>();
        Stack<String> s1 = new Stack<>();  //  新建两个栈 因为不是计算部分 所以全部当成字符串处理
        Stack<String> s2 = new Stack<>();
        try{
            for (String ele:ls){
                //使用正则表达式判断
                //  \\-?\\d+ 表示匹配 -10 或者 -1 或则 10 或者 1 正负数多位数都考虑
                if (ele.matches("\\-?\\d+(\\.\\d+)?")){
                    s2.push(ele);  // 入栈
                }else {
                    //符号入栈要考虑好几种情况
                    //1.如果s1为空,则直接入栈,或则s1栈的栈顶元素为"("
                    //2.如果不为空,s2栈顶也不为"(",则判断当前栈顶运算符的和当前要ele元素运算符的优先级大小
                    //如果ele运算符优先级大于栈顶元素优先级则入栈,否则将当前栈顶元素弹出并压入s2中,
                    //然后再次进入符号入栈判断情况
                    //3.ele为")" 则判断s1栈顶元素是不是"("如果不是则弹出当前s1栈顶元素并压入s2中,然后再重复此判断操作
                    //直到s1栈顶为"(" 此时弹出”(“但不压入s2中
                    //4.如果ele为"("则直接入s1
                    if (ele.equals("(") || s1.isEmpty() || s1.get(s1.size()-1).equals("(")){
                        s1.push(ele);
                    } else if (!s1.isEmpty() && !s1.get(s1.size()-1).equals("(")) {
                        //开始判断优先级
                        if (yxj(ele)>yxj(s1.get(s1.size()-1))){
                            //情况1 ele优先级大于s1栈顶
                            s1.push(ele);
                        }else if(yxj(ele)<=yxj(s1.get(s1.size()-1)) && !ele.equals(")")) {
                            //情况2
                            int top = s1.size()-1;  // 获取s1栈顶下标
                            //jLabel.setText();("a"+top);
                            while (top!=-1 && yxj(ele) <= yxj(s1.get(top)) ){ //一直递减 直到s1栈元素为0或者s1中的栈顶优先级小于ele
                                //jLabel.setText();("b"+top);
                                s2.push(s1.pop());   // s1弹出的压入s2
                                top=top==0?-1:top-1;
                            }
                            s1.push(ele);
                        }else {
                            //情况3 如果为”)“
                            int top = s1.size()-1;  // 获取s1栈顶下标
                            while (!s1.get(top).equals("(")){ //一直递减 直到s1栈顶元素为"("
                                s2.push(s1.pop());   // s1弹出的压入s2
                                top--;
                            }
                            s1.pop();  // 这一步时栈顶已为"(" 直接弹出 不需要压入s2
                        }
                    }
                }
            }
            //执行完后 将s1的剩余运算符全部压入s2中
            while (!s1.isEmpty()){
                s2.push(s1.pop());
            }
            //将s2的元素从前往后取出即为 后缀
            for (int i = 0;i<s2.size();i++){
                ls1.add(s2.get(i));
            }
            //jLabel.setText();("当前s2"+s2);
            return ls1;
        }catch (EmptyStackException e1){
            List<String> ls2 = new ArrayList<>();
            jLabel.setText("后缀表达式转换错误,空栈异常!");
            return ls2;
        }catch (StackOverflowError e3){
            List<String> ls2 = new ArrayList<>();
            jLabel.setText("后缀表达式转换错误,栈溢出异常!");
            return ls2;
        }catch (Throwable e2){
            List<String> ls2 = new ArrayList<>();
            jLabel.setText("后缀表达式转换错误,未知异常!");
            return ls2;
        }
    }
4.最后一个模块就是后缀表达式的计算,具体思路也可观看上面我发的韩老师的视频,讲的也很不错,还有一个小视频也很有启发, 逆波兰 - 下(后缀表达式计算结果)_哔哩哔哩_bilibili,具体代码如下:
public static double str_js(List<String> ls){
        //创建一个double类型的栈s2 用来存数字
        try{
            Stack<Double> s2 = new Stack<>();
            for(String ele:ls){  //遍历
                if (ele.matches("\\-?\\d+(\\.\\d+)?")){
                    s2.push(Double.parseDouble(ele));   //  将ele转为double类型再压入栈中
                }else {
                    //符号计算 进行 加减乘除四种情况
                    double num1 = s2.pop();   //  获取栈顶元素 pop会返回栈顶元素的值并删除当前栈顶
                    double num2 = s2.pop();   //  次顶元素
                    //后缀表达式 是 次顶的数 加减乘除  栈顶的数
                    if (ele.equals("+")){
                        s2.push(num2+num1);
                    } else if (ele.equals("-")) {
                        s2.push(num2-num1);
                    } else if (ele.equals("*")) {
                        s2.push(num2*num1);
                    }else {
                        s2.push(num2/num1);
                    }
                }
            }
            return s2.pop(); // 栈中最后的一个结果就是运算结果
        }catch (EmptyStackException e1){
            jLabel.setText("计算出现错误,空栈异常!");
            return 0;
        }catch (StackOverflowError e3){
            jLabel.setText("计算出现错误,栈溢出异常!");
            return 0;
        }catch (Throwable e2){
            jLabel.setText("计算出现错误,未知异常!");
            return 0;
        }
    }

四、界面设计(仅支持鼠标点击)

        1.界面要求有一个文本框,显示结果和表达式,有10个数字按钮,以及+-*/()<—=等按钮,一共20个所以采用循环添加,自定义一个方法来添加组件,自定义一个鼠标单击事件,单击一个数字或运算符就将其添加到文本框中,如果单击”=“就运算,单击"<—"就是退格,具体实现代码如下:

static class MyMouseListener extends MouseAdapter{
        @Override
        public void mouseClicked(MouseEvent e){  // 单机事件
          super.mouseClicked(e);
          String str = e.toString();  // e返回的是这个按钮的一推属性 我们要的是按钮上的文字
          String[] str_lst = str.split(",");//所以用逗号分割 倒数第二个为我们要的属性 为:text=按钮名字
          if (str_lst[str_lst.length-2].charAt(5)=='='){ // 获取按钮上的字符
              if (jLabel.getText().isEmpty()){
                  jLabel.setText("表达式为空");
              }else {
                  String bds_str = jLabel.getText();  // -12
                  boolean flag = str_pd(bds_str);
                  if (flag){
                      List<String> str_fg = str_fg(bds_str);
                      if (str_fg.isEmpty()){
                          jLabel.setText("表达式分割Error!");
                      }else {
                          List<String> str_hz = str_hz(str_fg);
                          if (str_hz.isEmpty()){
                              jLabel.setText("后缀表达式Error!");
                          }else {
                              double str_js = str_js(str_hz);
                              if (str_js==0){
                                  jLabel.setText("后缀表达式计算Error!");
                              }else {
                                  DecimalFormat double_to3 = new DecimalFormat("#.###");//格式化 转为3位小数
                                  String str_js_end = double_to3.format(str_js);
                                  jLabel.setText(str_js_end);
                              }
                          }
                      }
                  } else {
                      jLabel.setText("表达式错误!");
                  }
              }
          }else if (str_lst[str_lst.length-2].charAt(5)=='C') {
              jLabel.setText("");
          }else if (str_lst[str_lst.length-2].charAt(5)=='<'){
              String[] jl_text = jLabel.getText().split("");
              if (jLabel.getText().matches("[\\u4e00-\\u9fa5]+.*")){
                  jLabel.setText("");
              }else {
                  jLabel.setText("");
                  for (int i =0;i<jl_text.length-1;i++){
                      jLabel.setText(jLabel.getText()+jl_text[i]);
                  }
              }
          } else {
              if (jLabel.getText().matches("[\\u4e00-\\u9fa5]+.*")){ //正则表达式检测汉字
                  jLabel.setText("");
                  jLabel.setText(jLabel.getText()+str_lst[str_lst.length-2].charAt(5));
              }else {
                  jLabel.setText(jLabel.getText()+str_lst[str_lst.length-2].charAt(5));
              }
          }
        }
    }

final static JLabel jLabel = new JLabel(""); //将文本框定义为一个全局变量

public static void main(String[] args) throws InterruptedException { //主函数
        final JFrame frame = new JFrame("计算器"); // 设置界面标题
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // 设置窗口退出模式
        frame.setLayout(new GridBagLayout()); //设置界面布局方式,类似HTML5的表格布局方式,可以随意设置如何合并单元格等
        addComponent(frame,jLabel,0,0,4,1); // 此行代码的意思是,这个Label 在第一行,占四列,前面的两个0是起始位置,后面的4和1表示占几列几行
        jLabel.setFont(new Font("宋体",Font.BOLD,20)); //设置字体
        JButton button;
        int num = 0;
        String[] text = {"(",")","C","<——","7","8","9","/","4","5","6","*","1","2","3","-","0",".","=","+"};
        for (int i = 1;i<6;i++){//一共20个按钮 遍历20次 因为有规律所以用循环添加
            for (int j =0;j<4;j++){
                button = new JButton(text[num]);
                button.setFont(new Font("宋体",Font.BOLD,20));
                button.addMouseListener(new MyMouseListener());  // 绑定按钮事件
                addComponent(frame,button,j,i,1,1); //设置按钮位置
                num++;
            }
        }
        frame.setSize(300,500); //设置窗口大小
        frame.setVisible(true);//设置窗口是否可见
    }

五、最后效果

  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值