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);
}
}