本文用Java的swing来实现一个简单计算器,主要内容为图形用户界面GUI的实现以及运算表达式核心算法的设计编写。
程序运行环境为Windows10 ,编译环境为IntelliJ IDEA Community Edition 2022.2.3
一、具体功能:
1、:输入,输出
输入:允许输入带有括号的完整计算式(例 8*(4-95)+5÷2*e-pi)
输出:输出Double类型的结果
输出:整个运算表达式并保存于历史记录中
2、:功能
基本的加,减,乘,除,四则运算
平方运算
开方运算
求余运算
最终界面如下图:
除了常规的数字按钮和运算符,还有两个常数e,pi(π),清空键AC,括号运算符(),平方(x^x)和开方(sqrt)运算符,输入显示框以及历史记录文本框,文本框的垂直滚动条和水平滚动条。
二、主要思想:
1:中缀表达式转为后缀表达式
准备:
①后缀表达式队列:postQueue,用于存储逆波兰表达式(其实不用队列排序直接输出也行)
②操作符栈:opStack,对用户输入的操作符进行处理,用于存储运算符
算法思想:
从左向右依次读取算术表达式的元素X,分以下情况进行不同的处理:
(1)如果X是操作数,直接入队
(2)如果X是运算符,再分以下情况:
a)如果栈为空,直接入栈。
b)如果X==”(“,直接入栈。
c)如果X==”)“,则将栈里的元素逐个出栈,并入队到后缀表达式队列中,直到第一个配对的”(”出栈。(注:“(”和“)”都不 入队)
d)如果是其他操作符(+ – * /),则和栈顶元素进行比较优先级。 如果栈顶元素的优先级大于等于X,则出栈并把栈中弹出的元素入队,直到栈顶元素的优先级小于X或者栈为空。弹出完这些元素后,才将遇到的操作符压入到栈中。
(3)最后将栈中剩余的操作符全部入队。
示意图:
2、计算后缀表达式
准备:
需要用到一个结果栈Res_Stack :用于存放计算的中间过程的值和最终结果
算法思想:
1、从左开始向右遍历后缀表达式的元素。
2、如果取到的元素是操作数,直接入栈Res_Stack,如果是运算符,从栈中弹出2个数进行运算,然后把运算结果入栈
3、当遍历完后缀表达式时,计算结果就保存在栈里了。
示意图:
三、结果测试
分析:
1、可实现基本四则运算及平方、开方、求余运算。
2、运算表达式可显示于输入界面并保存于历史记录栏
3、输入界面和历史记录栏皆可实现不断字自动换行功能以及滚动条功能
4、不足之处:进行平方和开方运算时其保存在历史记录中的表达式会出现两个等号及两个结果。
四、完整源代码
import javax.swing.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.util.*;import java.util.stream.Collectors;public class Calculator extends JFrame implements ActionListener {Calculator() {init();}/** * 定义按钮 */private JTextField textField1;private JTextArea textField2;private JButton buttonzuo;//(private JButton buttonyou;//)private JButton buttonC;//cprivate JButton buttonCE;//CEprivate JButton buttondele;// 16 || str.equals("0") || equalbook == 1) {} else {textField1.setText(str + "0");}}//按1if (e.getSource().equals(button1)) {str = textField1.getText();if (str.length() > 16 || equalbook == 1) {} else if (str.equals("0") || str.equals("")) {textField1.setText("1");} else {textField1.setText(str + "1");}}//当按钮为2时if (e.getSource().equals(button2)) {str = textField1.getText();if (str.length() > 16 || equalbook == 1) {} else if (str.equals("0") || str.equals("")) {textField1.setText("2");} else {textField1.setText(str + "2");}}//当按钮为3时if (e.getSource().equals(button3)) {str = textField1.getText();if (str.length() > 16 || equalbook == 1) {} else if (str.equals("0") || str.equals("")) {textField1.setText("3");} else {textField1.setText(str + "3");}}//当按钮为4时if (e.getSource().equals(button4)) {str = textField1.getText();if (str.length() > 16 || equalbook == 1) {} else if (str.equals("0") || str.equals("")) {textField1.setText("4");} else {textField1.setText(str + "4");}}//当按钮为5时if (e.getSource().equals(button5)) {str = textField1.getText();if (str.length() > 16 || equalbook == 1) {} else if (str.equals("0") || str.equals("")) {textField1.setText("5");} else {textField1.setText(str + "5");}}//当按钮为6时if (e.getSource().equals(button6)) {str = textField1.getText();if (str.length() > 16 || equalbook == 1) {} else if (str.equals("0") || str.equals("")) {textField1.setText("6");} else {textField1.setText(str + "6");}}//当按钮为7时if (e.getSource().equals(button7)) {str = textField1.getText();if (str.length() > 16 || equalbook == 1) {} else if (str.equals("0") || str.equals("")) {textField1.setText("7");} else {textField1.setText(str + "7");}}//当按钮为8时if (e.getSource().equals(button8)) {str = textField1.getText();if (str.length() > 16 || equalbook == 1) {} else if (str.equals("0") || str.equals("")) {textField1.setText("8");} else {textField1.setText(str + "8");}}//当按钮为9时if (e.getSource().equals(button9)) {str = textField1.getText();if (str.length() > 16 || equalbook == 1) {} else if (str.equals("0") || str.equals("")) {textField1.setText("9");} else {textField1.setText(str + "9");}}//当按钮为小数点时if (e.getSource().equals(buttonPoint)) {str = textField1.getText();if (str.length() > 15 || equalbook == 1) { //小数点位于操作数的最后一位不执行操作;}if (pointbook == 0) {//一个操作数仅能有一个小数点,若已经有小数点则不再添加小数点;textField1.setText(str + ".");pointbook = 1;//小数点判断位置1;}}//每次输入都是一个数字+运算符,一起从数字文本框转而输入到表达式文本框中;//当按钮为加号时if (e.getSource().equals(buttonAdd)) {str = textField1.getText();//获取运算符前一个操作数;char ch1[] = str.toCharArray();//把第一操作数连同+号 进行字符串转字符数组的操作 赋予ch1;int length1 = str.length() - 1;//length1获取除+号外的第一操作数的位数;if ((length1 == -1 || ch1[length1] != ')') && (str.equals("0") || str.equals("") || ch1[length1] == '.' || ch1[length1] == '+' || ch1[length1] == '-' || ch1[length1] == '*' || ch1[length1] == '/' || ch1[length1] == '(' || ch1[length1] == ')')) {//当数字为空或为0(操作无意义);或数字的最后一位是小数点(未输入完毕或输入出错,等待)} else {textField1.setText(str + "+"); //合并现有表达式和新增表达式// 这里解释以下为什么s没有提取到数字框里的符号,因为输入符号时并没有更新数字框,而是直接执行一系列操作,数字框从未出现过运算符;}pointbook = 0;}//当按钮为减号时if (e.getSource().equals(buttonSub)) {str = textField1.getText();char ch1[] = str.toCharArray();int length1 = str.length() - 1;if ((length1 == -1 || ch1[length1] != ')') && (ch1[length1] == '.' || ch1[length1] == '+' || ch1[length1] == '-' || ch1[length1] == '*' || ch1[length1] == '/' || ch1[length1] == '(' || ch1[length1] == ')')) {} else {textField1.setText(str + "-");}pointbook = 0;}//当按钮为乘号时if (e.getSource().equals(buttonMul)) {str = textField1.getText();char ch1[] = str.toCharArray();int length1 = str.length() - 1;if ((length1 == -1 || ch1[length1] != ')') && (str.equals("0") || str.equals("") || ch1[length1] == '.' || ch1[length1] == '+' || ch1[length1] == '-' || ch1[length1] == '*' || ch1[length1] == '/' || ch1[length1] == '(' || ch1[length1] == ')')) {} else {textField1.setText(str + "*");}pointbook = 0;}//当按钮为除号时if (e.getSource().equals(buttonDiv)) {str = textField1.getText();char ch1[] = str.toCharArray();int length1 = str.length() - 1;if ((length1 == -1 || ch1[length1] != ')') && (str.equals("0") || str.equals("") || ch1[length1] == '.' || ch1[length1] == '+' || ch1[length1] == '-' || ch1[length1] == '*' || ch1[length1] == '/' || ch1[length1] == '(' || ch1[length1] == ')')) {} else {textField1.setText(str + "/");}pointbook = 0;}//当按钮为左括号时if (e.getSource().equals(buttonzuo)) {str = textField1.getText();char ch[] = str.toCharArray();int length = str.length() - 1;if (length == -1 || ch[length] == '+' || ch[length] == '-' || ch[length] == '*' || ch[length] == '/') {//括号左边是否有数或符号类别的判断;textField1.setText(str + '('); //满足条件则加入左括号;zuonum++;//左括号数加一标记;}if (length == -1 || ch[length] == '+' || ch[length] == '-' || ch[length] == '*' || ch[length] == '/')pointbook = 0;if (length == 0 || ch[length] == 0) {textField1.setText("(");zuonum++;}}//当按钮为右括号时;if (e.getSource().equals(buttonyou)) {str = textField1.getText();char ch[] = str.toCharArray();int length = str.length() - 1;if (Character.isDigit(ch[length]) && zuonum > younum) {//只有前面是数字的时候且左括号的数量大于右括号的数量的时候才能加右括号;younum++; //右括号数加一标记;textField1.setText(str + ')');}pointbook = 0;}//当按下C键时;if (e.getSource().equals(buttonC)) {textField1.setText("0");//置当前数字框为0;zuonum = 0;//当一次计算完成之后,只有按CE按钮才能进行新的计算,因为要清除所有标志位否则会影响下一次操作;younum = 0;pointbook = 0;equalbook = 0;textField2.setText(" ");}//当按钮为CE时,if (e.getSource().equals(buttonCE)) {textField1.setText("0"); //清除当前数字框中内容;pointbook = 0; //更新小数点状态为0;}//当按下B时,if (e.getSource().equals(buttondele)) {str = textField1.getText();char []nums=str.toCharArray();if (nums[str.length()-1]=='('){zuonum--;}str = str.substring(0, str.length() - 1);textField1.setText(str);}//当按下=时,if (e.getSource().equals(buttonequl)) {str = textField1.getText();if (zuonum != younum) {textField1.setText("关系式错误。");} else {ans(str);}String s = str + "=" + textField1.getText();textField2.setText(s + "\r\n" + textField2.getText());//将表达式存放在历史记录里。equnum = 1;}}/** * 提前将 符号的优先级定义好 */private static final Map basic = new HashMap();static {basic.put('-', 1);basic.put('+', 1);basic.put('*', 2);basic.put('/', 2);basic.put('(', 0);//在运算中()的优先级最高,但是此处因程序中需要 故设置为0}public void ans(String str) {String a = toSuffix(str);//传入 一串 算数公式textField1.setText(dealEquation(a));}/** * 将中缀表达式转化为后缀表达式 */public String toSuffix(String infix) {List queue = new ArrayList();//定义队列用于存储 数字以及最后的后缀表达式List stack = new ArrayList(); //定义栈用于存储运算符最后stack中会被 弹空char[] charArr = infix.trim().toCharArray();//字符数组用于拆分数字或符号String standard = "*/+-()";//判定标准 将表达式中会出现的运算符写出来char ch = '&';//在循环中用来保存 字符数组的当前循环变量的这里仅仅是初始化一个值没有意义int len = 0;//用于记录字符长度 【例如100*2,则记录的len为3 到时候截取字符串的前三位就是数字】for (int i = 0; i 0) {//若为空格 代表 一段结束 ,就可以往队列中存入了【例如100 * 2100后面有空格 就可以将空格之前的存入队列了】queue.add(String.valueOf(Arrays.copyOfRange(charArr, i - len, i)));//往 队列存入 截取的 字符串len = 0;//长度置空}continue;//如果空格出现,则一段结束跳出本次循环} else if (standard.indexOf(ch) != -1) {//如果是上面标准中的 任意一个符号if (len > 0) {//长度也有queue.add(String.valueOf(Arrays.copyOfRange(charArr, i - len, i)));//说明符号之前的可以截取下来做数字len = 0;//长度置空}if (ch == '(') {//如果是左括号stack.add(ch);//将左括号 放入栈中continue;//跳出本次循环继续找下一个位置}if (!stack.isEmpty()) {//如果栈不为emptyint size = stack.size() - 1;//获取栈的大小-1即代表栈最后一个元素的下标boolean flag = false;//设置标志位while (size >= 0 && ch == ')' && stack.get(size) != '(') {//若当前ch为右括号,则 栈里元素从栈顶一直弹出,直到弹出到 左括号queue.add(String.valueOf(stack.remove(size)));//注意此处条件:ch并未入栈,所以并未插入队列中;同样直到找到左括号的时候,循环结束了,所以左括号也不会放入队列中【也就是:后缀表达式中不会出现括号】size--;//size-- 保证下标永远在栈最后一个元素【栈中概念:指针永远指在栈顶元素】flag = true;//设置标志位为true表明一直在取()中的元素}while (size >= 0 && !flag && basic.get(stack.get(size)) >= basic.get(ch)) {//若取得不是()内的元素,并且当前栈顶元素的优先级>=对比元素 那就出栈插入队列queue.add(String.valueOf(stack.remove(size)));//同样此处也是remove()方法,既能得到要获取的元素,也能将栈中元素移除掉size--;}}if (ch != ')') {//若当前元素不是右括号stack.add(ch);//就要保证这个符号 入栈} else {//否则就要出栈 栈内符号stack.remove(stack.size() - 1);}}if (i == charArr.length - 1) {//如果已经走到了中缀表达式的最后一位if (len > 0) {//如果len>0就截取数字queue.add(String.valueOf(Arrays.copyOfRange(charArr, i - len + 1, i + 1)));}int size = stack.size() - 1;//size表示栈内最后一个元素下标while (size >= 0) {//一直将栈内符号全部出栈 并且加入队列中【最终的后缀表达式是存放在队列中的,而栈内最后会被弹空】queue.add(String.valueOf(stack.remove(size)));size--;}}}return queue.stream().collect(Collectors.joining(","));//将队列中元素以,分割 返回字符串}/** * 将 后缀表达式 进行运算 计算出结果 * * @param equation * @return */public String dealEquation(String equation) {String[] arr = equation.split(",");//根据, 拆分字符串List list = new ArrayList();//用于计算时存储运算过程的集合【例如list中当前放置100 205/则取出20/5 最终将结果4存入list 此时list中结果为1004 】for (int i = 0; i