资讯专栏INFORMATION COLUMN

Java 实现简单计算器

thekingisalwaysluc / 3365人阅读

摘要:对于理论算法不再这累赘了。在查阅资料的时候发现算法不管用栈还是正则等等,似乎只处理操作符是的数,这是很不可取的。所以需要先将中缀表达式转化成后缀并标记多位数的操作符,然后在处理后缀表达式。

最后一次更新于2019/07/08

效果演示图

功能与流程

要制作一个简易计算器,首先你要清楚GUI里要显示什么:

结果显示框

0~9的数字

删除功能

清楚功能

搜寻历史记录功能

计算结果的功能

括号优先计算功能

接下来通过流程图简单介绍一下思路:

GUI 源码

以下代码是根据我的设计来编写的

/**
 * @author Hephaest
 * @since  2018/04/19
 * JDK 1.6
 */
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
/**
 * Calculator类用来创造GUI
 */
public class Calculator extends JFrame
{
    //新建文本框
    JTextField text = new JTextField();
    // set up row 2
    JPanel row2 = new JPanel();
    //创建按钮们
    String[][] buttons = {{"7","8","9","DEL","AC"},{"4","5","6","×","÷"},{"1","2","3","+","-"},{"0","(",")","Replay","="}};
    JButton[][]button = new JButton[4][5];
    /**
     * 这个计算机的界面我模拟的是卡西欧fx-82ES PLUS A
     * 但是仅有其中的部分功能
     */
    public Calculator()
    {
        super("CASIO");
        setSize(400,300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        //设置文本框的尺寸、位置以及禁止键盘输入
        text.setPreferredSize(new Dimension(30, 40));
        text.setHorizontalAlignment(SwingConstants.TRAILING);
        text.setEditable(false);
        getContentPane().add(text, BorderLayout.NORTH);
        //声明每一个按钮代表的意义
        add(row2, BorderLayout.CENTER);
        GridLayout layout2 = new GridLayout(4,5,5,5);
        row2.setLayout(layout2);
        for(int i = 0; i < buttons.length; i++)
        {
            for(int j = 0; j < buttons[0].length; j++)
            {
                button[i][j] = new JButton(buttons[i][j]);
                row2.add(button[i][j]);
            }
        }
        add(row2);
        setResizable(false);
        setVisible(true);
    }
    
    private static void setLookAndFeel() 
    {
        //这条使跨操作系统也能看到计算机的GUI
        try {
            UIManager.setLookAndFeel(
                "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"
            );
        } catch (Exception exc) {
            // ignore error
        }
    }
    
    public static void main(String[] args) 
    {
        Calculator.setLookAndFeel();
        Calculator cl = new Calculator();
        cl.listener();
    }
    /**
     * 事件监听器,一旦按下按钮就要根据操作历史进行相应的反应
     */
    public void listener()
    {
        Listener l = new Listener(this);
        for(int i = 0; i < buttons.length; i++)
        {
            for(int j = 0; j < buttons[0].length; j++)
            {
                button[i][j].addActionListener(l);
            }
        }
    }
}
事件监听器源码

有了按钮后下一步就是要想办法实现按钮功能,我的思路在上面流程图里给过了,不再累赘,直接看如何利用代码实现:

/**
 * @author Hephaest
 * @since  2018/04/19
 * JDK 1.6
 */
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JButton;
import javax.swing.JTextField;
/**
 * Listener 类
 * 用来将按钮输入的结果通过链表的方式一个字一个字存储在字符串里,然后调用另一类计算整个字符串,返回一个值
 */
public class Listener implements ActionListener
{
    private Calculator cl;
    private ArrayList list=new ArrayList();
    private ArrayList his=new ArrayList();//这个链表用来添加每一次得到的最终的结果
    private ArrayList arr = new ArrayList();//把his里的一整串字符分割成单个字符,再连接
    private String[] arrayStr = new String[] {};//储存单次的历史记录
    private String out = "";
    private String output = "";
    
    public Listener(Calculator cl)
    {
        this.cl = cl;
    }
    
    public void actionPerformed(ActionEvent event)
    {
        JButton button = (JButton) event.getSource();
        /**
         * 如果点“=”,计算整个表达式的结果,如果是错误表达式,在文本框输入“Input Error!”
         */
        if(button.getText().equals("="))
        {
            try
            {    
                Function f = new Function();
                double result = f.compute(out);
                cl.text.setText(Double.toString(result));
            } catch(Exception e) {
                cl.text.setText("Input Error!");
            }
        } else if(button.getText().equals("×")) {
            /**
             * 如果点击"×",先把它转换为"*"
             */
            if(list.isEmpty())
            {
                arr.add("*");
                output += "*";
                out = output;
                cl.text.setText(output);
            } else {
                list.add("*");
                output += "*";
                out = output;
                cl.text.setText(output);
            }
        } else if(button.getText().equals("÷")) {
            /**
             * 如果点击"÷",把它转换为"/"
             */
            if(list.isEmpty())
            {
                arr.add("/");
                output += "/";
                out = output;
                cl.text.setText(output);
            } else {
                list.add("/");
                output += "/";
                out = output;
                cl.text.setText(output);
            }
        } else if(button.getText().equals("DEL")) {
            /**
             * 如果点击"DEL",删除表达式里最后一个字符,每点一次删一个
             */
            if(list.isEmpty())
            {
                arr.remove(arr.size()-1);
                output = "";
                for(int i = 0; i < arr.size(); i++) output += arr.get(i);
                 out = output;
                 cl.text.setText(output);
            } else {
                list.remove(list.size()-1);
                String output = "";
                for(int i = 0; i < list.size(); i++) output+=list.get(i);
                 out = output;
                 cl.text.setText(output);
            }
        } else if(button.getText().equals("AC")) {
            /**
             * 如果点击"AC",删除list链表,再删除之前先把表达式保留到his的链表里
             */
            his.add(out);
            list.clear();
            output="";
             cl.text.setText(output);
        } else if(button.getText().equals("Replay")) {
            /**
             * 如果点击"Replay",在文本框里显示上一条表达式
             */
            output=his.get(his.size()-1);
            cl.text.setText(output);
            arr.clear();
            //把上一条表达式分割成单个字符的字符数组
            char[] a=output.toCharArray();
            for(int i=0;i
计算器表达式算法

关于如何分析整个表达式并计算出正确值,实践了一下,还是后缀表达式比较方便,不用考虑括号这种优先级问题。对于理论算法不再这累赘了。在查阅资料的时候发现算法不管用栈还是正则等等,似乎只处理操作符是0~9的数,这是很不可取的。什么意思呢?就是像10/5这样的算数是没办法解决的,只能解决像5/5这样的。所以需要先将中缀表达式转化成后缀并标记多位数的操作符,然后在处理后缀表达式。

/**
 * @author Hephaest
 * @since  2018/07/13
 * JDK 1.6
 */
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
public class function {
    private String[] str=new String[10];
    private int begin;
    public function(){}
    /**
     * 中缀表达式转换成后缀表达式
     * @param exp 在计算器上显示的文本 中缀表达式
     * @return 正确的计算结果
     */
    public double compute(String exp) 
    {
        char[] ch = exp.toCharArray();
        Stack  stack = new Stack<>();
        String convertToPostfix = new String();
        int size = ch.length;
        begin = 0;
        for (int i = 0; i < size; i++) {
          //遇到左括号直接入栈
          if(ch[i] == "(") stack.push(ch[i]);
          else if(ch[i] == ")") {
              //遇到右括号出栈(追加到后缀表达式), 直到出栈的元素为左括号或为0
              char popValue = stack.pop();
              do 
              {
                  convertToPostfix = convertToPostfix.concat(String.valueOf(popValue));
                  popValue = stack.pop();
              }while(!stack.isEmpty() && popValue != "(");
          } else if(checkOperator(ch[i])) {
              /*
               * 遇到运算符需要判断:
               * 1.是否为空栈,是的话直接入栈
               * 2.即将入栈的运算符是否比栈顶元素优先级高
               *     是,直接入栈
               *    否,栈顶元素出栈(追加到后缀表达式),当前运算符入栈
               */
              if(stack.isEmpty()) stack.push(ch[i]);
              else {
                  char popValue = stack.pop();
                  while(checkPriority(popValue,ch[i]))
                  {
                      convertToPostfix = convertToPostfix.concat(String.valueOf(popValue));
                      if(stack.isEmpty()) break;
                      popValue = stack.pop();
                  }
                  if(!checkPriority(popValue,ch[i])) stack.push(popValue);
                  stack.push(ch[i]);
              }
          } else if(checkDigital(ch[i])) {
              /*
               * 单个数字直接追加到后缀表达式
               * 含有不止一个数字的操作符需要做记录:
               *     1.计算该操作符的起始位置和终止位置
               *     2.把数字传到字符串数组里(全局变量,下一步需要用到)
               */
              if(i + 1 < size && i - 1 >= 0)
              {
                  if(checkDigital(ch[i - 1]) && !checkDigital(ch[i + 1]))
                  {
                      int end = i;
                      int j = end;
                      while(checkDigital(ch[j]))
                      {
                          j--;
                      }
                      j++;
                      List elements = new LinkedList<>();
                      do
                      {
                          elements.add(String.valueOf(ch[j]));
                          j++;
                      } while(j <= end);
                      str[begin] = String.join("", elements);
                      System.out.println(str[begin]);
                      begin++; 
                  }
              }
              convertToPostfix=convertToPostfix.concat(String.valueOf(ch[i]));
            }
         }
        //第一遍结束后把栈中剩下的操作符依次出栈(追加到后缀表达式)
        while(!stack.isEmpty())
        {
            char popValue = stack.pop();
            convertToPostfix = convertToPostfix.concat(String.valueOf(popValue));
        }
        System.out.println(convertToPostfix);
        return computeResult(convertToPostfix);
    }
    
    /**
     * 计算后缀表达式
     * @param convertToPostfix 后缀表达式的字符串
     * @return 计算结果
     */
    public double computeResult(String convertToPostfix)
    {
        int[] index=new int[10];
        /*
         * 判断是否有多位数的操作符,有的话找到在后缀表达式的初始位置
         * 如果没有的话就不会执行
         */
        for(int i = 0;i < begin; i++)
        {
            index[i] = convertToPostfix.indexOf(str[i]);
            System.out.println(index[i]);
        }
        char[] ch = convertToPostfix.toCharArray();
        Stack  stack = new Stack<>();
        double result = 0;
        for (int i = 0; i < ch.length; i++) {
            //如果是运算符,pop出栈顶的两个元素,记住先进后出
            if(checkOperator(ch[i]))
            {
                double num2=stack.pop();
                System.out.println("num2" + num2);
                System.out.print("
");
                double num1=stack.pop();
                System.out.println("num1" + num1);
                System.out.print("
");
                switch(ch[i])
                {
                    case "*":
                        result = num2 * num1;
                        break;
                    case "/":
                        result = num1 / num2;
                        break;
                    case "+":
                        result = num1 + num2;
                        break;
                    case "-":
                        result = num1 - num2;
                        break;
                }
                System.out.println(result);
                stack.push(result);
            } else {
            /*
             * 对于多位操作符,需要把单个字符连接起来然后作为一个双精度数放入栈中
             * 一位数的操作符直接放入栈即可,注意从字符变成数字时要减去48(0的字符型数据)
             */
                int stop = 0;
                for(int j = 0; j < begin; j++)
                {
                    if(i == index[j])
                    {
                        int start=i;
                        List elements = new LinkedList<>();
                        do
                        {
                            elements.add(String.valueOf(ch[i]));
                            i++;
                        } while(i < str[j].length() + start);
                        i--;
                        String test=String.join("", elements);
                        stack.push(Double.valueOf(test));
                        stop=1;
                        break;
                    }
                }
                if(stop == 0) stack.push((double)ch[i]-48);
            }
        }
        System.out.print("
");
        System.out.print(result);
        return result;
    }
    
    /**
     * 判断是否是运算符
     * @param c 当前字符
     * @return 布尔型结果
     */
    public boolean checkOperator(char c)
    {
        int result;
        switch(c)
        {
            case "+":
            case "-":
            case "*":
            case "/":
                result = 1;
                break;
            default:
                result = 0;
        }
        if(result == 1) return true;
        else return false;
    }
    
    /**
     * 判断是否是数字
     * @param c 当前字符
     * @return 布尔型结果
     */
    public boolean checkDigital(char c)
    {
        int num = c;
        num -= 48;
        if(num >= 0 && num <= 9) return true;
        else return false;
    }
    
    /**
     * 判断即将入栈的优先级是否更高
     * @param popOne 栈顶元素
     * @param checkOne 即将入栈元素
     * @return 布尔型结果
     */
    public boolean checkPriority(char popOne,char checkOne)
    {
        if((popOne == "*" || popOne == "/") && (checkOne == "+" || checkOne == "-")) return true;
        else if(popOne == checkOne) return true;
        else return false;
    }
}
源码地址

如果我的文章可以帮到您,劳烦您点进源码点个 ★ Star 哦!
https://github.com/Hephaest/S...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69137.html

相关文章

  • 《十万字Java入门练习100例》1-10例——纸上得来终觉浅,绝知此事要躬行

    摘要:代码实现在控制台打印总结本篇文章带大家搭好环境,并体验了控制台打印。输出结果总结熟练掌握取余和整除运算,大有作用。终止本次循环,继续执行下一次循环。 ?本文收录...

    keithyau 评论0 收藏0
  • 来,了解一下Java内存模型(JMM)

    摘要:因为管理人员是了解手下的人员以及自己负责的事情的。处理器优化和指令重排上面提到在在和主存之间增加缓存,在多线程场景下会存在缓存一致性问题。有没有发现,缓存一致性问题其实就是可见性问题。 网上有很多关于Java内存模型的文章,在《深入理解Java虚拟机》和《Java并发编程的艺术》等书中也都有关于这个知识点的介绍。但是,很多人读完之后还是搞不清楚,甚至有的人说自己更懵了。本文,就来整体的...

    kviccn 评论0 收藏0
  • 来,了解一下Java内存模型(JMM)

    摘要:因为管理人员是了解手下的人员以及自己负责的事情的。处理器优化和指令重排上面提到在在和主存之间增加缓存,在多线程场景下会存在缓存一致性问题。有没有发现,缓存一致性问题其实就是可见性问题。 网上有很多关于Java内存模型的文章,在《深入理解Java虚拟机》和《Java并发编程的艺术》等书中也都有关于这个知识点的介绍。但是,很多人读完之后还是搞不清楚,甚至有的人说自己更懵了。本文,就来整体的...

    eccozhou 评论0 收藏0
  • [Java并发-7]java的线程小节

    摘要:在领域,实现并发程序的主要手段就是多线程。可运行状态指的是线程可以分配执行。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。导出线程栈,分析线程状态是诊断并发问题的一个重要工具。 在 Java 领域,实现并发程序的主要手段就是多线程。线程是操作系统里的一个概念,虽然各种不同的开发语言如 Java、C# 等都对其进行了封装,但原理和思路都是相同都。Java 语言里的线程本质上就是...

    Sunxb 评论0 收藏0

发表评论

0条评论

thekingisalwaysluc

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<