Java實現圖形化計算器(支持括號、開平方)

Java實現圖形化計算器(支持括號、開平方)

Java實習作業,實現一個圖形化計算器,正好趕上週末作業不多就寫了一個計算器。功能不是特別全,但是能滿足一些最基本的需求(比如小學三年級及以下的數學題,,,也不一定能算對,,,)(手動滑稽)。代碼註釋寫的比較詳盡,就不多說贅述了。目前時間及能力有限,後續準備添加更多功能。

代碼中可(ken)能(ding)還有bug,歡迎測試,若發現bug或者計算有誤的表達式,歡迎私信,一定及時回覆並盡力修復存在的問題。

程序運行界面如下:
計算器界面上菜~

//@Calculater.java
import java.awt.Font;
import java.awt.Button;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;


/**
 * @版本:V1.0
 * @作者:豬豬俠的Laser_Cannon
 * @時間: 2019.10.26
 * @博客:https://blog.csdn.net/qq_43312718
 * 
 * @描述:
 * 簡易計算器的實現代碼,能夠計算開方,百分數,
 * 倒數及簡單的帶有小括號的中綴表達式。
 * 
 * @說明:
 * 代碼全部爲自己所寫,由於時間能力不足,代碼仍有很
 * 多問題和未實現的功能,今後會逐漸更新和完善。若發
 * 現bug或計算錯誤的表達式,歡迎私我,一定認真對待。
 * 
 * @已實現的功能:
 * 1.簡單的表達式計算,包括 + - * /運算符,正負號,
 *   開平方,求倒數,百分數及括號功能,以及退格,清空,
 * 2.輸入表達式時自動更新上下兩文本框(仿Windows自
 *   帶計算器界面),並將最後的運算結果顯示在下方文本
 *   框,將表達式顯示在上方文本框。
 * 3.簡單的表達式檢查功能,能夠檢查括號不匹配、除以零、
 *   等部分錯誤。
 * 4.運算符輸入限制和輔助:連續輸入運算符時,不會連續
 *   向表達式中添加運算符,而是進行運算符更新(將最近
 *   一次輸入的運算符更新),
 * 5.小數點限制:在一個運算數中若輸入小數點則小數點按
 *   鍵暫時失效,以保證每個運算數只有一個小數點;另外,
 *   當按下小數點時,若下方文本框中沒有內容或者僅有運
 *   算符,則自動在小數點前添加數字0。
 * 6.數字及小數點輸入限制和輔助:輸入右括號之後數字鍵
 *   及小數點鍵暫時失效,輸入運算符之後重新激活。
 * 7.其他輸入限制和輔助:無法在表達式爲空時輸入百分號。
 *   允許輸入前導零,計算時自動去除,允許輸入多層括號,
 *   將按照優先級進行計算。
 * 8.支持開平方函數(sqrt)的多層嵌套,當開平方的運算
 *   數小於零時將顯示錶達式錯誤(Bad str)。
 * 9.根據表達式長度動態改變上下文本框字體大小,以保證
 *   上下文本框內容能在文本框中完整顯示。
 * 10.計算結果格式化,自動確定是否輸出小數位,並將整數
 *    部分按照四位分級法顯示,即每三位數字用一個逗號隔
 *    開。
 * 
 * 
 * 
 * @待實現的功能:
 * 1.更多按鍵
 * 2.界面的美化
 * 3.表達式檢查功能
 * 4.表達式糾錯功能
 * 5.鍵盤的監聽功能
 * 6.支持多層開平方嵌套(遞歸實現)
 * 6.動態改變改變組件大小(跟隨窗口)
 * 7.一次計算完成後點擊運算符號會將上次運算結果保留
 *   並加入這次的運算表達式中
 * 8.菜單欄的更多功能:進制轉換計算器、運算曆史記錄
 * 9.更多及更強大的輸入限制及輔助功能
 * 10.高精度及大數類的使用,以實現更強的計算器功能
 * 11.百分號前可以添加括號括起來的表達式
 * 12.正負號按鍵功能的優化:當前僅是每次按下正負號
 *    就會添加“(-”,後續將更改爲判斷正負,若爲負
 *    則去掉負號,否則添加負號
 * 13.支持自行設置小數點後顯示的位數
 * 
 */

/**
 * @類說明:
 * 計算器的界面生成,以及事件監聽響應功能。
 * 
 * @類結構:
 * public class Calculater extends JFrame {
 * 	public static void main(String[] args){}
 * 	public Calculater(){}					//構造函數,生成主界面
 * 	class btnActionListener implements ActionListener{}	//按鍵監聽事件響應內部類
 * 	class btnKeyListener implements KeyListener{}		//鍵盤監聽(未實現)
 * }
 */

public class Calculater extends JFrame {
	private static final long serialVersionUID = 1L;
	 /** @flag
	 * 用於標記是否完成了一次運算,每次計算完成之後
	 * 即每次點擊'='之後其值更新爲真,之後再次點擊
	 * 任何按鍵將清空文本框,並將其值更新爲false。
	 * 
	 ** @point
	 * 若正在輸入運算數(即非運算符),則point生效,
	 * 用於標記當前運算數是否已經輸入小數點,以保證
	 * 每個運算數只能有一個小數點。
	 */
	private boolean flag = false;
	private boolean point = false;
	private JPanel contentPane;	//主窗格
	private JTextField txtUnder;	//上層文本框,用於顯示輸入的表達式
	private JTextField txtUpper;	//下層文本框,用於顯示當前的輸入
	private String strUpper = "";	//上層文本
	private String strUnder = "";	//下層文本
	

	public static void main(String[] args) {
		Calculater frame = new Calculater();
		frame.setVisible(true);
		//frame.setResizable(false);	//不可更改窗口大小
	}

	public Calculater() {
		setTitle("簡易計算器-豬豬俠的Laser_Cannon");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 416, 620);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);
		
		/**
		 * 生成文本窗格,窗格分兩層。輸入計算表達式時表達
		 * 式顯示在下層。開始時均爲空。輸入計算表達式時,
		 * 上下層文本框實時更新。點擊等於號之後下層文本框
		 * 顯示運算結果,上層顯示錶達式。再次點擊任何按鍵
		 * 重置計算器並顯示該鍵。
		 */
		JPanel paneText = new JPanel();		//添加文本窗格
		paneText.setBounds(0, 0, 400, 170);
		contentPane.add(paneText);
		paneText.setLayout(null);
		
		txtUpper = new JTextField(strUpper);	//添加上方文本框
		txtUpper.setHorizontalAlignment(JTextField.RIGHT);	//設置文本右對齊
		txtUpper.setFont(new Font("Consolas", Font.PLAIN, 30));
		txtUpper.setEditable(false);
		txtUpper.setBounds(0, 0, 400, 65);
		paneText.add(txtUpper);
		
		txtUnder = new JTextField(strUnder);	//添加下方文本框
		txtUnder.setHorizontalAlignment(JTextField.RIGHT);	//設置文本右對齊
		txtUnder.setFont(new Font("Consolas", Font.PLAIN, 50));
		txtUnder.setEditable(false);
		txtUnder.setBounds(0, 65, 400, 105);
		paneText.add(txtUnder);
		
		/**
		 * 生成按鍵窗格,採用字符串數組以循環形式生成按鍵。
		 */
		JPanel PaneButton = new JPanel();		//添加按鍵窗格
		PaneButton.setBounds(0, 170, 400, 412);
		contentPane.add(PaneButton);
		PaneButton.setLayout(new GridLayout(6, 4, 0, 0));
	
		String[] btn = {"(",")","CE","Del","%","sqrt","1/X","*",	//設置按鍵文字
				"7","8","9","/","4","5","6","+",
				"1","2","3","-","±","0",".","="};
		
		for(int i=0;i<btn.length;i++) {			//添加按鍵
			Button button = new Button(btn[i]);
			button.setFont(new Font("Consolas", Font.PLAIN, 25));
			button.addActionListener(new btnActionListener()); 	//添加監聽器
			//button.addKeyListener(new btnKeyListener());
			PaneButton.add(button);
		}
		
		
	}
	
	/**
	 * 內部類,實現按鍵的監聽功能,並實時更新上下
	 * 兩個文本窗格的顯示內容。
	 */
	class btnActionListener implements ActionListener{
		public void actionPerformed(ActionEvent e) {
			
			String cmd = e.getActionCommand();
			
			/**
			 * 檢查更新flag,若flag爲真,清空上下文本框。
			 * @flag
			 * 用於標記是否完成了一次運算,每次計算完成之後
			 * 即每次點擊'='之後其值更新爲真,之後再次點擊
			 * 任何按鍵將清空文本框,並將其值更新爲false。
			 */
			if(flag) {
				strUnder = "";
				strUpper = "";
				flag = false;
			}
			/** 
			 * 檢查更新point,(point爲真,小數點按鍵失效)
			 * 若point爲真且當前響應按鍵爲運算符,則將point
			 * 置爲假,此時小數點生效。
			 * @point
			 * 若正在輸入運算數(即非運算符),則point生效,
			 * 用於標記當前運算數是否已經輸入小數點,以保證
			 * 每個運算數只能有一個小數點。
			 */
			char a = cmd.charAt(0);
			if(point && cmd.length()==1 && (a<'0'||a>'9') && a!='.') {
				point = false;
			}
			
			
			//清空鍵,將上下文本框清空,等待下次輸入。
			if(cmd.equals("CE")) {
				strUpper = "";
				strUnder = "";
			}
			//刪除鍵,將下方文本框中的最後一個字符(若存在)
			//刪掉,但無法刪除上方文本框的字符。
			else if (cmd.equals("Del")) {
				if(strUnder.length()>0) {
					strUnder = strUnder.substring(0,strUnder.length()-1);
				}
			}
			//基本運算符,將下方文本框的內容移至上方文本框,
			//若下方文本框首字符爲左括號,則延遲上移操作至
			//下次輸入右括號時(此處待優化,功能將改爲當左右
			//括號數量匹配時再進行上移操作)
			else if(cmd.equals("+")||cmd.equals("-")||cmd.equals("*")||cmd.equals("/")) {
			    char s = ' ';
			    if(!strUnder.isEmpty()) {		//獲取下方文本框最後字符
				s = strUnder.charAt(strUnder.length()-1);
			    }
			    
			    if(strUpper.isEmpty()&&strUnder.isEmpty()) {}	//上下均空時運算符按鍵失效
			    else if(!strUnder.isEmpty()&&(s=='+'||s=='-'||s=='*'||s=='/')) { //當已經輸入一個運算符時,再次輸入視爲運算符更新
				strUnder = strUnder.substring(0,strUnder.length()-1)+cmd;
			    }
				    
			    else if(!strUnder.isEmpty()&&strUnder.charAt(0)!='(') {
				strUpper += strUnder;
				strUnder = cmd;
			    }
			    else {
				strUnder += cmd;
			    }
				
			}
			//左括號按鍵
			else if(cmd.equals("(")) {
				strUpper += strUnder;
				strUnder = cmd;
			}
			//右括號按鍵,將下方文本框內容上移
			else if(cmd.equals(")")) {
				strUpper = strUpper + strUnder + ")";
				strUnder = "";
			}
			//百分號按鍵,當下層問空時百分號鍵失效
			else if (cmd.equals("%")) {
			    if(strUpper.isEmpty()&&strUnder.isEmpty()) {}
			    else {
				strUpper += strUnder;
				strUnder = cmd;
			    }
			}
			//開平方按鍵
			else if (cmd.equals("sqrt")) {
				strUpper += strUnder;
				strUnder = "sqrt(";
			}
			//求倒數按鍵
			//避免了下層文本框有運算符時點擊“1/X”鍵會出現“1/(+”的問題。
			else if (cmd.equals("1/X")) {
			    	if(!strUnder.isEmpty()) {
			    	    char t = strUnder.charAt(0);
			    	    if(t=='+'||t=='-'||t=='*'||t=='/') {
			    		strUpper += t;
			    		strUnder = strUnder.substring(1,strUnder.length());
			    	    }
			    	}
				strUnder = "1/(" + strUnder;
			}
			//正負號按鍵,在下方文本框中的文本前加上“(-”(此處待優化)
			else if (cmd.equals("±")) {
				strUnder = strUnder + "(-";
			}
			//小數點按鍵。若point爲真,按鍵失效。否則,下方文本框添加
			//小數點並point的值賦爲真,開始生效。另外,若按下小數點時
			//表達式爲空或前一位非數字,則自動在小數點前添加數字0。
			//下層爲空上層非空時小數點按鍵失效。
			else if(cmd.equals(".")) {
				if(point) {return;}
				else if(strUnder.isEmpty()&&!strUpper.isEmpty()) {}	
				else if(strUnder.isEmpty()) {
				    strUnder += "0.";
				    point = true;
				}
				else {
					char t = strUnder.charAt(strUnder.length()-1);
					if(t<'0'||t>'9') {
						strUnder += "0.";
					}
					else strUnder += ".";
					point = true;
				}
			}
			//等於號按鍵,按下即觸發計算表達式,並將flag賦爲真,下次點
			//擊任何按鍵將清空上下文本框(此處待優化)。
			else if (cmd.equals("=")) {
				flag = true;
				strUpper += strUnder;
				strUnder = Calculate.calc(strUpper);
			}
			//數字按鍵,若之前下方文本框有基本運算符則將其上移。
			//下層爲空上層非空時數字鍵失效
			else {
			    	if(strUnder.isEmpty()&&!strUpper.isEmpty()) {}	
			    	else if(strUnder.isEmpty()) {
					strUnder += cmd;
				}
				else {
					char t = strUnder.charAt(strUnder.length()-1);
					if((t=='+'||t=='-'||t=='*'||t=='/') && (strUnder.charAt(0)!='(')) {
						strUpper += strUnder;
						strUnder = cmd;
					}
					else {
						strUnder += cmd;
					}
				}
			}
			txtUpper.setText(strUpper);	//更新上方文本框的文本
			if(strUpper.length()>23) {
			    txtUpper.setFont(new Font("Consolas", Font.PLAIN, 690/strUpper.length()));
			}
			else {
			    txtUpper.setFont(new Font("Consolas", Font.PLAIN, 30));
			}
			
			txtUnder.setText(strUnder);	//更新下方文本框的文本
			if(strUnder.length()>15) {
			    txtUnder.setFont(new Font("Consolas", Font.PLAIN, 700/strUnder.length()));
			}
			else {
			    txtUnder.setFont(new Font("Consolas", Font.PLAIN, 50));
			}
		}
	}
	
//	class btnKeyListener implements KeyListener{ 	//鍵盤監聽功能
//		@Override
//		public void keyTyped(KeyEvent e) {
//		}
//		@Override
//		public void keyPressed(KeyEvent e) {
//		}
//		@Override
//		public void keyReleased(KeyEvent e) {
//		}
//	}
	
}

//@Calculate.java
import java.util.Stack;
import java.util.StringTokenizer;

/**
 * @類說明:
 * 表達式計算靜態方法類,包括表達式計算、表達式糾錯、
 * 表達式預處理三個靜態方法。若表達式有誤返回包含錯誤
 * 說明的字符串,否則返回計算結果的字符串形式。
 * 
 * @類結構:
 * public class Calculate {
 * 	public static String calc(String str) {}		//表達式計算(調用各個方法)
 * 	private static boolean checkString(String str) {}	//表達式檢查(檢查括號匹配)
 * 	private static String Preprocess(String str) {}		//表達式預處理(計算sqrt及%)
 * 	private static String getcalc(String str) {}		//初等表達式計算(中綴轉後綴)
 * }
 */
public class Calculate {
    /**
     * 表達式計算方法,首先調用糾錯方法及預處理方法進行初步處理,
     * 再通過棧進行中綴表達式轉後綴表達式,最後返回計算結果的字符
     * 串,是否返回小數位具取決於小數部分是否爲0。
     */
    public static String calc(String str) {
	if (checkString(str) == false) { //檢查括號匹配
	    return "Bad str";
	}

	str = Preprocess(str);
	if(str.equals("Bad sqrt")) {	//檢查開平方數是否爲正
	    return str;
	}
	
	str = getcalc(str);		//進行計算(此時僅包含基本運算符和小括號)

	return str;
    }

    /**
     * 表達式糾錯方法,目前功能僅限於檢查左右小括號的匹配, 
     * 後續準備加入更多的糾錯功能,如運算符與運算數的匹配。
     */
    private static boolean checkString(String str) {
	// 檢查括號括號匹配
	Stack<Character> sta = new Stack<Character>();
	for (int i = 0; i < str.length(); i++) {
	    if (str.charAt(i) == '(') {
		sta.push(str.charAt(i));
	    } else if (str.charAt(i) == ')') {
		if (sta.empty() || sta.peek() != '(') {
		    return false;
		} else {
		    sta.pop();
		}
	    }
	}
	if (sta.size() > 0)
	    return false;
	return true;
    }

    /**
     * 表達式預處理方法,計算表達式中的一元運算符的
     * 部分(sqrt及%)。並將負號做處理以便計算。
     */
    private static String Preprocess(String str) {
	for (int i = 0; i < str.length()-1; i++) {
	    if (str.charAt(i) == '(' && str.charAt(i + 1) == '-') { //將“(-”轉化爲“(0-”
		str = str.substring(0, i + 1) + "0" + str.substring(i + 1, str.length());
	    }
	}

	for (int i = str.length()-1; i>=0; i--) {//從後向前遍歷,以支持sqrt的多層嵌套
	    if (str.charAt(i) == 's') { //計算開平方
		int cnt = 1;
		for (int j = i + 5; j < str.length(); j++) {
		    if(str.charAt(j)=='(') {
			cnt++;
		    }
		    else if(str.charAt(j)==')') {
			cnt--;
		    }
		    if (cnt==0) {//左右括號達到匹配,即可獲得根號下的完整表達式
			double sqrt = Double.parseDouble(getcalc(str.substring(i + 5, j)));
			if(sqrt<0) {
			    return "Bad sqrt";
			}
			str = str.substring(0, i)
				+ String.valueOf(Math.sqrt(sqrt))
				+ str.substring(j + 1, str.length());
			break;
		    }
		}
	    }
	}
	
	
	for (int i = 0; i<str.length(); i++) {//從後向前遍歷,以支持sqrt的多層嵌套
	    if (str.charAt(i) == '%') { //計算百分比
		for (int j = i - 1; j >= 0; j--) {
		    if (j == 0) {
			if (i < str.length() - 1) {
			    str = String.valueOf(Double.parseDouble(str.substring(0, i)) / 100)
				    + str.substring(i + 1, str.length());
			} else {
			    str = String.valueOf(Double.parseDouble(str.substring(0, i)) / 100);
			}
			break;
		    }
		    char t = str.charAt(j);
		    if ((t > '9' || t < '0') && t != '.') {
			if (i < str.length() - 1) {
			    str = str.substring(0, j + 1)
				    + String.valueOf(Double.parseDouble(str.substring(j + 1, i)) / 100)
				    + str.substring(i + 1, str.length());
			} else {
			    str = str.substring(0, j + 1)
				    + String.valueOf(Double.parseDouble(str.substring(j + 1, i)) / 100);
			}
			break;
		    }
		}
	    }
	}

	return str;
    }

    private static String getcalc(String str) {
	if (str.isEmpty()) {
	    return str;
	}
	/**
	 * 將中綴表達式轉換爲後綴表達式,並在運算數與運算符之間
	 * 添加分隔符‘$’,最後進行後綴表達式的計算。中綴表達式
	 * 儲存在str中,後綴表達式儲存在s中。
	 */
	Stack<Character> sta = new Stack<Character>();
	String s = "$";
	int i = 0;
	if (str.charAt(0) == '(') {
	    sta.push('(');
	    i++;
	}
	boolean flag = false;
	for (; i < str.length(); i++) {
	    if (str.charAt(i) == '+' && (i == 0 || (str.charAt(i - 1) < '0' && str.charAt(i - 1) != ')')))
		i++;
	    if (str.charAt(i) == '-') {
		flag = true;
		i++;
	    }
	    while (i < (int) str.length() && ((str.charAt(i) >= '0' && str.charAt(i) <= '9') || str.charAt(i) == '.')) {
		if (flag) {
		    s += "-$";
		    flag = false;
		}
		s += str.charAt(i);
		i++;
	    }
	    s += "$";
	    if (i < (int) str.length()) {
		if (sta.empty() || str.charAt(i) == '(')
		    sta.push(str.charAt(i));
		else if (str.charAt(i) == '+' || str.charAt(i) == '-') {
		    while (sta.size() > 0 && sta.peek() != '(') {
			s += sta.pop() + "$";
		    }
		    sta.push(str.charAt(i));
		} else if (str.charAt(i) == '*' || str.charAt(i) == '/') {
		    while (sta.size() > 0 && (sta.peek() == '*' || sta.peek() == '/')) {
			s += sta.pop() + "$";
		    }
		    sta.push(str.charAt(i));
		} else {
		    while (sta.size() > 0 && sta.peek() != '(') {
			s += sta.pop() + "$";
		    }
		    sta.pop();
		}
	    }
	}

	while (sta.size() > 0) {
	    s += sta.pop() + "$";
	}

	/**
	 * 進行後綴表達式的計算
	 */
	Stack<Double> stack = new Stack<Double>();
	StringTokenizer token = new StringTokenizer(s, "$", false);
	while (token.hasMoreTokens()) {
	    String t = token.nextToken();
	    if (t.equals("+")) {
		stack.push(stack.pop() + stack.pop());
	    } else if (t.equals("-")) {
		double a = stack.pop();
		stack.push(stack.pop() - a);
	    } else if (t.equals("*")) {
		stack.push(stack.pop() * stack.pop());
	    } else if (t.equals("/")) {
		double a = stack.pop();
		stack.push(stack.pop() / a);
	    } else {
		stack.push(Double.parseDouble(t));
	    }
	}

	double c = stack.pop();
	if(sta.size()>0) {
	    return "Bad Str";
	}
	
	java.text.DecimalFormat format = new java.text.DecimalFormat("#,###.######");//數值格式化
	
	return format.format(c);
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章