利用ArrayList, swing, ActionListener創造簡單GUI計算器

廢話很少說,在網上搜索了編寫計算器相關的源碼,要麼算法低效要麼不全。因而這周小編決定簡單講講如何製做一個簡易計算器,首先你要清楚GUI裏要顯示什麼:java

  1. 結果顯示框
  2. 0~9的數字
  3. 刪除功能
  4. 清楚功能
  5. 搜尋歷史記錄功能
  6. 計算結果的功能
  7. 括號優先計算功能

接下來經過流程圖簡單介紹一下思路:
流程圖git

GUI 源碼

如下代碼是根據個人設計來編寫的github

/**
 * @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);
            }
        }
    }
}

效果

GUI

事件監聽器源碼

有了按鈕後下一步就是要想辦法實現按鈕功能,個人思路在上面流程圖裏給過了,再也不累贅,直接看如何利用代碼實現:算法

/**
 * @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<String> list=new ArrayList<String>();
    private ArrayList<String> his=new ArrayList<String>();//這個鏈表用來添加每一次獲得的最終的結果
    private ArrayList<String> arr = new ArrayList<String>();//把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);
            }
        }
        /**
         * 若是點擊"DEL",刪除表達式裏最後一個字符,每點一次刪一個
         */
        else if(button.getText().equals("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);
            }
        }
        /**
         * 若是點擊"AC",刪除list鏈表,再刪除以前先把表達式保留到his的鏈表裏
         */
        else if(button.getText().equals("AC"))
        {
            his.add(out);
            list.clear();
            output="";
             cl.text.setText(output);
        }
        /**
         * 若是點擊"Replay",在文本框裏顯示上一條表達式
         */
        else if(button.getText().equals("Replay"))
        {
            output=his.get(his.size()-1);
            cl.text.setText(output);
            arr.clear();
            //把上一條表達式分割成單個字符的字符數組
            char[] a=output.toCharArray();
            for(int i=0;i<a.length;i++)
            {
                arr.add(String.valueOf(a[i]));
            }
            his.remove(his.size()-1);
        }
        /**
         * 其他按鈕能夠直接加入表達式
         */
        else
        {
            if(list.isEmpty())
            {
                arr.add(button.getText());
                output+=button.getText();
                out=output;
                cl.text.setText(output);
            }
            else
            {
                list.add(button.getText());
                output+=button.getText();
                out=output;
                cl.text.setText(output);
            }
        }
    }
}

計算器表達式算法

關於如何分析整個表達式並計算出正確值,小編實踐了一下,仍是後綴表達式比較方便,不用考慮括號這種優先級問題。對於理論算法再也不這累贅了,小編在查閱資料的時候發現算法無論用棧仍是正則等等,彷佛只處理操做符是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 <Character> 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]);
          }
          //遇到右括號出棧(追加到後綴表達式),直到出棧的元素爲左括號或爲0
          else if(ch[i]==')')
          {
              char popValue=stack.pop();
              do 
              {
                  convertToPostfix=convertToPostfix.concat(String.valueOf(popValue));
                  popValue=stack.pop();
              }while(!stack.isEmpty()&&popValue!='(');
          }
          /*
           * 遇到運算符須要判斷:
           * 1.是否爲空棧,是的話直接入棧
           * 2.即將入棧的運算符是否比棧頂元素優先級高
           *     是,直接入棧
           *    否,棧頂元素出棧(追加到後綴表達式),當前運算符入棧
           */
          else if(checkOperator(ch[i]))
          {
              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])==false)
                  {
                      stack.push(popValue);
                  }
                  stack.push(ch[i]);
              }
          }
          /*
           * 單個數字直接追加到後綴表達式
           * 含有不止一個數字的操做符須要作記錄:
           *     1.計算該操做符的起始位置和終止位置
           *     2.把數字傳到字符串數組裏(全局變量,下一步須要用到)
           */
          else if(checkDigital(ch[i]))
          {
              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<String> 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 <Double> 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("\n");
                double num1=stack.pop();
                System.out.println("num1"+num1);
                System.out.print("\n");
                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);
            }
            /*
             * 對於多位操做符,須要把單個字符鏈接起來而後做爲一個雙精度數放入棧中
             * 一位數的操做符直接放入棧便可,注意從字符變成數字時要減去48(0的字符型數據)
             */
            else
            {
                int stop=0;
                for(int j=0;j<begin;j++)
                {
                    if(i==index[j])
                    {
                        int start=i;
                        List<String> 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("\n");
        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;
        }
    }
}

小結

  • 凡事靠本身,多研究多思考
  • 算法比語言更重要
  • Github 源碼
相關文章
相關標籤/搜索