本文用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;//<- 删除private JButton buttonDiv;//÷private JButton button7;//7private JButton button8;//8private JButton button9;//9private JButton buttonAdd;//+private JButton button4;//4private JButton button5;//5private JButton button6;//6private JButton buttonSub;//-private JButton button1;//1private JButton button2;//2private JButton button3;//3private JButton buttonMul;//xprivate JButton buttonequl;//=private JButton button0;//0private JButton buttonPoint;//.public void init() {JFrame frame = new JFrame("The Calculator Of 崔登辉"); //普通窗口顶层容器 创建标题为“计算器”;frame.setLayout(null); //设置使用特定的布局管理器;//单选项按钮//按键的排版以及设计需要提前考虑整体的布局合理性,不可出现按键重叠;为了保证美观又需要考虑排版整齐;//放置数字0button0 = new JButton("0"); //注:在给按键命名时要根据按键的大小,否则按键上的标注无法完全显示;button0.setBounds(100, 400, 110, 50);frame.add(button0);//放置数字1button1 = new JButton("1");button1.setBounds(40, 340, 50, 50);frame.add(button1);//放置数字2button2 = new JButton("2");button2.setBounds(100, 340, 50, 50);frame.add(button2);//放置数字3button3 = new JButton("3");button3.setBounds(160, 340, 50, 50);frame.add(button3);//放置数字4button4 = new JButton("4");button4.setBounds(40, 280, 50, 50);frame.add(button4);//放置数字5button5 = new JButton("5");button5.setBounds(100, 280, 50, 50);frame.add(button5);//放置数字6button6 = new JButton("6");button6.setBounds(160, 280, 50, 50);frame.add(button6);//放置数字7button7 = new JButton("7");button7.setBounds(40, 220, 50, 50);frame.add(button7);//放置数字8button8 = new JButton("8");button8.setBounds(100, 220, 50, 50);frame.add(button8);//放置数字9button9 = new JButton("9");button9.setBounds(160, 220, 50, 50);frame.add(button9);//放置 .buttonPoint = new JButton("."); //text:为自动生成的参数的解释词buttonPoint.setBounds(40, 400, 50, 50); //自动补齐的参数解释词frame.add(buttonPoint);//放置 +buttonAdd = new JButton("+");buttonAdd.setBounds(220, 400, 50, 50);frame.add(buttonAdd);//放置 -buttonSub = new JButton("-");buttonSub.setBounds(220, 340, 50, 50);frame.add(buttonSub);//放置 *buttonMul = new JButton("*");buttonMul.setBounds(220, 280, 50, 50);frame.add(buttonMul);//放置 /buttonDiv = new JButton("/");buttonDiv.setBounds(220, 220, 50, 50);frame.add(buttonDiv);//放置 =buttonequl = new JButton("=");buttonequl.setBounds(280, 340, 110, 110);frame.add(buttonequl);//退位键buttondele = new JButton("B");buttondele.setBounds(280, 220, 110, 110);frame.add(buttondele);//放置左括号(buttonzuo = new JButton("(");buttonzuo.setBounds(40, 160, 80, 50);frame.add(buttonzuo);//放置右括号)buttonyou = new JButton(")");buttonyou.setBounds(130, 160, 80, 50);frame.add(buttonyou);//放置C 消除所有输入buttonC = new JButton("C");buttonC.setBounds(220, 160, 80, 50);frame.add(buttonC);//放置CE 消除当前输入buttonCE = new JButton("CE");buttonCE.setBounds(310, 160, 80, 50);frame.add(buttonCE);//添加表达式文本框 用以输入计算公式textField1 = new JTextField(); //文本框textField1.setBounds(40, 20, 350, 60);frame.add(textField1);textField2 = new JTextArea();textField2.setBounds(400, 20, 280, 430);frame.add(textField2);textField1.addActionListener(this);buttonzuo.addActionListener(this);buttonyou.addActionListener(this);buttonC.addActionListener(this);buttonCE.addActionListener(this);buttondele.addActionListener(this);buttonDiv.addActionListener(this);button7.addActionListener(this);button8.addActionListener(this);button9.addActionListener(this);buttonAdd.addActionListener(this);button4.addActionListener(this);button5.addActionListener(this);button6.addActionListener(this);buttonSub.addActionListener(this);button1.addActionListener(this);button2.addActionListener(this);button3.addActionListener(this);buttonMul.addActionListener(this);buttonequl.addActionListener(this);button0.addActionListener(this);buttonPoint.addActionListener(this);frame.setBounds(0, 0, 700, 520); //设置整个图形窗口的大小;(通过窗口名调用)frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE),当点击窗口的关闭按钮时退出程序(没有这一句,程序不会退出)frame.setVisible(true);}String str = null;int pointbook = 0;int equalbook = 0;int zuonum = 0;int younum = 0;int equnum = 0;public void actionPerformed(ActionEvent e) {if (equnum == 1) {textField1.setText("0");equnum = 0;}//按0if (e.getSource().equals(button0)) {str = textField1.getText();if (str.length() > 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<Character, Integer> basic = new HashMap<Character, Integer>();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<String> queue = new ArrayList<String>(); //定义队列 用于存储 数字 以及最后的 后缀表达式List<Character> stack = new ArrayList<Character>(); //定义栈 用于存储 运算符 最后stack中会被 弹空char[] charArr = infix.trim().toCharArray(); //字符数组 用于拆分数字或符号String standard = "*/+-()"; //判定标准 将表达式中会出现的运算符写出来char ch = '&'; //在循环中用来保存 字符数组的当前循环变量的 这里仅仅是初始化一个值 没有意义int len = 0; //用于记录字符长度 【例如100*2,则记录的len为3 到时候截取字符串的前三位就是数字】for (int i = 0; i < charArr.length; i++) { //开始迭代ch = charArr[i]; //保存当前迭代变量if (Character.isDigit(ch)) { //如果当前变量为 数字len++;} else if (Character.isLetter(ch)) { //如果当前变量为 字母len++;} else if (ch == '.') { //如果当前变量为 . 会出现在小数里面len++;} else if (Character.isSpaceChar(ch)) { //如果当前变量为 空格 支持表达式中有空格出现if (len > 0) { //若为空格 代表 一段结束 ,就可以往队列中 存入了 【例如100 * 2 100后面有空格 就可以将空格之前的存入队列了】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<String> list = new ArrayList<String>(); //用于计算时 存储运算过程的集合【例如list中当前放置 100 20 5 / 则取出20/5 最终将结果4存入list 此时list中结果为 100 4 】for (int i = 0; i < arr.length; i++) { //此处就是上面说的运算过程, 因为list.remove的缘故,所以取出最后一个数个最后两个数 都是size-2int size = list.size();switch (arr[i]) {case "+":double a = Double.parseDouble(list.remove(size - 2)) + Double.parseDouble(list.remove(size - 2));list.add(String.valueOf(a));break;case "-":double b = Double.parseDouble(list.remove(size - 2)) - Double.parseDouble(list.remove(size - 2));list.add(String.valueOf(b));break;case "*":double c = Double.parseDouble(list.remove(size - 2)) * Double.parseDouble(list.remove(size - 2));list.add(String.valueOf(c));break;case "/":double d = Double.parseDouble(list.remove(size - 2)) / Double.parseDouble(list.remove(size - 2));list.add(String.valueOf(d));break;default:list.add(arr[i]);break; //如果是数字 直接放进list中}}return list.size() == 1 ? list.get(0) : "运算失败"; //最终list中仅有一个结果,否则就是算错了}public static void main(String[] args) {Calculator calculator = new Calculator();}
}