一、项目要求
- 学习GUI图形界面的设计,Python Tinker或 Java Swing/Awt 或C++ QT框架,创建交互友好的应用程序;
- 能通过界面按钮控件输入并实现算术表达式,输入的表达式即时在控件中显示,按下“=”按钮能实现运算,并将运算结果输出在控件中显示;要求能保存和浏览历史表达式的运算记录。
- 算术表达式求解,是指算术表达式中包括加、减、乘、除、括号等运算符,能求解包含括号的四则混合运算;并且能够检验表达式的合法性。
二、 算法思想:
从左向右依次读取算术表达式的元素X,分以下情况进行不同的处理:
(1)如果X是操作数,直接入队
(2)如果X是运算符,再分以下情况:
a)如果栈为空,直接入栈。
b)如果X==”(“,直接入栈。
c)如果X==”)“,则将栈里的元素逐个出栈,并入队到后缀表达式队列中,直到第一个配对的”(”出栈。(注:“(”和“)”都不 入队)
d)如果是其他操作符(+ - * /),则和栈顶元素进行比较优先级。 如果栈顶元素的优先级大于等于X,则出栈并把栈中弹出的元素入队,直到栈顶元素的优先级小于X或者栈为空。弹出完这些元素后,才将遇到的操作符压入到栈中。
(3)最后将栈中剩余的操作符全部入队。
三、 解决思路:双栈算符优先级法
双栈算符优先级法为了实现表达式求值,需要设置两个栈:
- 一个是运算符栈op,用于寄存运算符;
- 另一个成为操作数栈Opnd,用于寄存运算数和运算结果。
求值的处理过程是自左至右扫描表达式的每一个字符:
1、当扫描到的是运算数,则将其压入栈OPND,
2、当扫描到的是运算符时:
- 如这个运算符比OP栈顶运算符的优先级高,则入栈;
- 如这个运算符比OP栈顶运算符优先级低,则从OPND栈中弹出两个运算符,从栈OP中弹出栈顶运算符进行运算,并将运算结果压入栈OPND。
3、继续处理当前字符,直到遇到结束符为止。
四、代码实现:
GUI设计部分:(JavaSwing 实现)
package Proj_Calculator;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Stack;
public class Calculator implements MouseListener {
private Stack<BigDecimal> numberStack;
private Stack<Character> symbolStack;
private JFrame f = new JFrame("计算器");
private JPanel p1 = new JPanel();//p1放置文本框
private JPanel p2 = new JPanel();//p2放置按钮
private JPanel p3 = new JPanel();//p3作为主栏
private JPanel p4 = new JPanel();//p4作为侧栏
private JButton Btn_Lpar = new JButton("(");
private JButton Btn_Rpar = new JButton(")");
private JButton Btn_ac = new JButton("AC");
private JButton Btn_c = new JButton("C");
private JButton Btn_1 = new JButton("1");
private JButton Btn_2 = new JButton("2");
private JButton Btn_3 = new JButton("3");
private JButton Btn_add = new JButton("+");
private JButton Btn_4 = new JButton("4");
private JButton Btn_5 = new JButton("5");
private JButton Btn_6 = new JButton("6");
private JButton Btn_sub = new JButton("-");
private JButton Btn_7 = new JButton("7");
private JButton Btn_8 = new JButton("8");
private JButton Btn_9 = new JButton("9");
private JButton Btn_mul = new JButton("*");
private JButton Btn_dot = new JButton(".");
private JButton Btn_0 = new JButton("0");
private JButton Btn_div = new JButton("/");
private JButton Btn_equ = new JButton("=");
private JButton Btn_hcl = new JButton("Clear");
private JTextField Tex_res = new JTextField("0");
private JTextArea Tex_history = new JTextArea();
private StringBuffer text1 = new StringBuffer("");
private StringBuffer text2 = new StringBuffer("");
private boolean dot_f = true;
public static void main(String[] args) {
Calculator start = new Calculator();
start.go();
}
void go() {
f.setSize(500, 500);
f.setBackground(Color.white);
f.setLayout(new BorderLayout(10, 10));
p1.setLayout(new BorderLayout(5, 5));
p1.setBorder(new EmptyBorder(10, 10, 10, 10));
p2.setLayout(new GridLayout(5, 4, 5, 5));
p3.setLayout(new BorderLayout(5, 5));
p3.setBorder(new EmptyBorder(10, 10, 10, 10));
p4.setLayout(new BorderLayout(5, 5));
p4.setBorder(new EmptyBorder(10, 0, 10, 10));
Tex_res.setHorizontalAlignment(JTextField.RIGHT);
Tex_res.setEditable(false);
Tex_res.setBackground(Color.white);
Tex_res.setPreferredSize(new Dimension(50, 50));
Tex_history.setLineWrap(true);
Tex_history.setEditable(false);
Tex_history.setBounds(0,0,150,250);//如果设置setPreferredSize则无法使用JScrollPane滚动条
Tex_history.setBackground(Color.white);
p2.add(Btn_Lpar);
p2.add(Btn_Rpar);
p2.add(Btn_ac);
p2.add(Btn_c);
p2.add(Btn_1);
p2.add(Btn_2);
p2.add(Btn_3);
p2.add(Btn_add);
p2.add(Btn_4);
p2.add(Btn_5);
p2.add(Btn_6);
p2.add(Btn_sub);
p2.add(Btn_7);
p2.add(Btn_8);
p2.add(Btn_9);
p2.add(Btn_mul);
p2.add(Btn_dot);
p2.add(Btn_0);
p2.add(Btn_div);
p2.add(Btn_equ);
p1.add(new JLabel("操作栏"),BorderLayout.NORTH);
p1.add(Tex_res, BorderLayout.CENTER);
p4.add(new JScrollPane(Tex_history),BorderLayout.CENTER);
p4.add(Btn_hcl,BorderLayout.SOUTH);
Btn_0.addMouseListener(this);
Btn_1.addMouseListener(this);
Btn_2.addMouseListener(this);
Btn_3.addMouseListener(this);
Btn_4.addMouseListener(this);
Btn_5.addMouseListener(this);
Btn_6.addMouseListener(this);
Btn_7.addMouseListener(this);
Btn_8.addMouseListener(this);
Btn_9.addMouseListener(this);
Btn_add.addMouseListener(this);
Btn_sub.addMouseListener(this);
Btn_mul.addMouseListener(this);
Btn_div.addMouseListener(this);
Btn_dot.addMouseListener(this);
Btn_ac.addMouseListener(this);
Btn_c.addMouseListener(this);
Btn_equ.addMouseListener(this);
Btn_Lpar.addMouseListener(this);
Btn_Rpar.addMouseListener(this);
Btn_hcl.addMouseListener(this);
p3.add(p1, BorderLayout.NORTH);
p3.add(p2, BorderLayout.CENTER);
p4.add(new JLabel("历史记录"),BorderLayout.NORTH);
f.add(p4,BorderLayout.EAST);
f.add(p3, BorderLayout.CENTER);
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setResizable(false);
f.setVisible(true);
}
/*
* 设置鼠标按压事件响应
* */
@Override
public void mousePressed(MouseEvent e) {
if (e.getSource().equals(Btn_0))
text1 = text1.append("0");
else if (e.getSource().equals(Btn_1))
text1 = text1.append("1");
else if (e.getSource().equals(Btn_2))
text1 = text1.append("2");
else if (e.getSource().equals(Btn_3))
text1 = text1.append("3");
else if (e.getSource().equals(Btn_4))
text1 = text1.append("4");
else if (e.getSource().equals(Btn_5))
text1 = text1.append("5");
else if (e.getSource().equals(Btn_6))
text1 = text1.append("6");
else if (e.getSource().equals(Btn_7))
text1 = text1.append("7");
else if (e.getSource().equals(Btn_8))
text1 = text1.append("8");
else if (e.getSource().equals(Btn_9))
text1 = text1.append("9");
else if (e.getSource().equals(Btn_dot)) {
if (dot_f) {
text1 = text1.append(".");
dot_f = false;
}
} else if (e.getSource().equals(Btn_add)) {
if(!text1.isEmpty()&&!isSymbol(text1.charAt(text1.length()-1))) {
text1 = text1.append("+");
dot_f = true;
}
} else if (e.getSource().equals(Btn_sub)) {
if(text1.isEmpty()){
text1 = text1.append("-");
dot_f = true;
}
else if(!isSymbol(text1.charAt(text1.length()-1))){
text1 = text1.append("-");
dot_f = true;
}
} else if (e.getSource().equals(Btn_mul)) {
if(!text1.isEmpty()&&!isSymbol(text1.charAt(text1.length()-1))) {
text1 = text1.append("*");
dot_f = true;
}
} else if (e.getSource().equals(Btn_div)) {
if(!text1.isEmpty()&&!isSymbol(text1.charAt(text1.length()-1))) {
text1 = text1.append("/");
dot_f = true;
}
} else if (e.getSource().equals(Btn_Lpar)) {
text1 = text1.append("(");
dot_f = true;
} else if (e.getSource().equals(Btn_Rpar)) {
text1 = text1.append(")");
dot_f = true;
} else if (e.getSource().equals(Btn_ac)) {
text1 = new StringBuffer();
dot_f = true;
}
else if (e.getSource().equals(Btn_c)) {
text1.deleteCharAt(text1.length()-1);
}
Tex_res.setText(text1.toString());
if (e.getSource().equals(Btn_equ)) {
text1.append("=");
Tex_res.setText(String.valueOf(calculate(text1)));
//历史记录同步记录
if(isStandard(text1)) {
text2.append(new StringBuffer(addSymbol(text1).toString() + calculate(text1)));
Tex_history.append("\n");
Tex_history.append(text2.toString());
text2 = new StringBuffer();
}
}
if(e.getSource().equals(Btn_hcl)){
Tex_history.setText("");
}
}
功能实现部分:(包括功能主体和校验函数)
/*
* @param 输入的算术表达式
* @return 计算结果
*
* 使用BigDecimal的原因是为了保证浮点数的精确性,构造时建议不要使用BigDecimal(double)构造对象,
* 而是使用BigDecimal(String)
* 具体参考https://blog.csdn.net/qq_35868412/article/details/89029288
* */
public BigDecimal calculate(StringBuffer numStr) {
//将表达式规范化
numStr = addSymbol(numStr);
if (!isStandard(numStr)) {
JOptionPane.showMessageDialog(null, "算式违法!", "Calculator", JOptionPane.WARNING_MESSAGE);
return null;
}
if (numberStack == null) {
numberStack = new Stack<>();
}
numberStack.clear();
if (symbolStack == null) {
symbolStack = new Stack<>();
}
symbolStack.clear();
// temp用于缓存数字,因为数字可能是多位的
StringBuilder temp = new StringBuilder();
// 从表达式的第一个字符开始处理
for (int i = 0; i < numStr.length(); i++) {
char ch = numStr.charAt(i); // 获取一个字符
if (isNumber(ch)) { // 若当前字符是数字
temp.append(ch); // 加入到数字缓存中
} else { // 非数字的情况
String tempStr = temp.toString(); // 将数字缓存转为字符串
if (!tempStr.isEmpty()) {
BigDecimal num = new BigDecimal(tempStr);
numberStack.push(num); // 将数字压栈
temp = new StringBuilder(); // 重置数字缓存
}
// 判断运算符的优先级,若当前优先级低于栈顶的优先级,则先计算前面的数
while (!comparePri(ch) && !symbolStack.empty()) {
// 出栈,取出数字,后进先出
BigDecimal b = numberStack.pop();
BigDecimal a = numberStack.pop();
// 取出运算符进行相应运算,并把结果压栈进行下一次运算
switch (symbolStack.pop()) {
case '+':
numberStack.push(a.add(b));
break;
case '-':
numberStack.push(a.subtract(b));
break;
case '*':
numberStack.push(a.multiply(b));
break;
case '/':
//小数点后保留5位(舍入模式为四舍五入)
numberStack.push(a.divide(b, 5, RoundingMode.HALF_UP));
break;
default:
break;
}
} // while循环结束
if (ch != '=') {
symbolStack.push(ch); // 符号入栈
if (ch == ')') { // 去括号
symbolStack.pop();
symbolStack.pop();
}
}
}
} // for循环结束
return numberStack.pop(); // 返回计算结果
}
/*
* 符号位前补零,补省略的符号
* */
public StringBuffer addSymbol(StringBuffer numStr) {
StringBuffer str = new StringBuffer();
if (numStr.charAt(0) == '+' || numStr.charAt(0) == '-') {
str.append("0");
}
for (int i = 0; i < numStr.length(); i++) {
char n = numStr.charAt(i);
if (i != 0 && n == '(' && (isNumber(numStr.charAt(i - 1)) || numStr.charAt(i - 1) == ')')) {
str.append("*(");
continue;
}
if (i != numStr.length() - 1 && n == ')' && (isNumber(numStr.charAt(i + 1)) || numStr.charAt(i - 1) == '(')) {
str.append(")*");
continue;
}
if (i != 0 && n == '-' && numStr.charAt(i - 1) == '(') {
str.append("0-");
continue;
}
str.append(n);
}
return str;
}
/**
* 检查算术表达式的基本合法性,符合返回true,否则false
*/
public boolean isStandard(StringBuffer numStr) {
if (numStr == null || numStr.isEmpty()) // 表达式不能为空
return false;
Stack<Character> stack = new Stack<>(); // 用来保存括号,检查左右括号是否匹配
boolean b = false; // 用来标记'='符号是否存在多个
for (int i = 0; i < numStr.length(); i++) {
char n = numStr.charAt(i);
// 将左括号压栈,用来给后面的右括号进行匹配
if ("(".equals(n + "")) {
stack.push(n);
}
if (")".equals(n + "")) { // 匹配括号
if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) // 括号是否匹配
return false;
}
if(i!=0&&"(".equals(numStr.charAt(i-1)+"")&&")".equals(n+""))//不能有空括号
return false;
// 检查是否有多个'='号
if ("=".equals(n + "")) {
if (b)
return false;
b = true;
}
}
// 可能会有缺少右括号的情况
if (!stack.isEmpty())
return false;
// 检查'='号是否不在末尾
return "=".equals(numStr.charAt(numStr.length() - 1) + "");
}
/**
* 判断字符是否是0-9的数字
*/
public boolean isNumber(char num) {
return (num >= '0' && num <= '9') || num == '.';
}
/*
* 判读字符是否是符号
* */
public boolean isSymbol(char sym){
return ("+".equals(sym+"")||"-".equals(sym+"")
||"*".equals(sym+"")||"/".equals(sym+""));
}
/*
* 符号优先级说明(从高到低):
第1级: (
第2级: * /
第3级: + -
第4级: )
*/
private boolean comparePri(char symbol) {
if (symbolStack.empty()) { // 空栈返回ture
return true;
}
char top = symbolStack.peek(); // 查看堆栈顶部的对象
if (top == '(') {
return true;
}
// 比较优先级
switch (symbol) {
case '(': // 优先级最高
return true;
case '*': {
if (top == '+' || top == '-') // 优先级比+和-高
return true;
else
return false;
}
case '/': {
if (top == '+' || top == '-') // 优先级比+和-高
return true;
else
return false;
}
case '+':
return false;
case '-':
return false;
case ')': // 优先级最低
return false;
case '=': // 结束符
return false;
default:
break;
}
return true;
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
}
public void mouseMoved(MouseEvent e) {
}
}
五、效果展示:
界面效果:输入表达式后,计算结果可保存在历史框,并能计算包括括号在内的四则运算