一、实验准备
学习逆波兰法
引用百度百科的定义:
一个表达式E的后缀形式可以如下定义:
(1)如果E是一个变量或常量,则E的后缀式是E本身。
(2)如果E是E1 op E2形式的表达式,这里op是任何二元操作符,则E的后缀式为E1’E2’ op,这里E1’和E2’分别为E1和E2的后缀式。
(3)如果E是(E1)形式的表达式,则E1的后缀式就是E的后缀式。
使用方法:
将一个普通的中缀表达式转换为逆波兰表达式的一般算法是:
首先需要分配两个栈,一个作为临时存储运算符的栈S1(含一个结束符号),一个作为存放结果(逆波兰式)的栈S2(空栈),S1栈可先放入优先级最低的运算符#,注意,中缀式应以此最低优先级的运算符结束。可指定其他字符,不一定非#不可。从中缀式的左端开始取字符,逐序进行如下步骤:
(1)若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈。
(2)若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,如果该运算符(不包括括号运算符)优先级高于S1栈栈顶运算符(包括左括号)优先级,则将该运算符进S1栈,否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符(包括左括号)低于(不包括等于)该运算符优先级时停止弹出运算符,最后将该运算符送入S1栈。
(3)若取出的字符是“(”,则直接送入S1栈顶。
(4)若取出的字符是“)”,则将距离S1栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入S2栈,此时抛弃“(”。
(5)重复上面的1~4步,直至处理完所有的输入字符。
(6)若取出的字符是“#”,则将S1栈内所有运算符(不包括“#”),逐个出栈,依次送入S2栈。
完成以上步骤,S2栈便为逆波兰式输出结果。不过S2应做一下逆序处理。便可以按照逆波兰式的计算方法计算了!
我的思路是使用逆波兰法求解后缀表达式,再根据后缀表达式求解最终算术值
二、项目总体框架
1.模块划分图及描述
运算模块:
运算模块主要包括四则运算和非四则运算。
四则运算:有加、减、乘、除的运算,这种运算有优先级,利用链表的方法进行编程。
优先级的实现:只有加减时,没有优先级,进行计算。
有加、减、乘、除时,优先进行乘、除,利用栈方法进行扫描,判断每符个号的优先级。
有括号时,判断括号的位置,优先判断括号的起始位置和结束位置,然后计算括号里面的,
2.程序流程图
3.项目初步设计
1.设计主窗口
private final JFrame jf = new JFrame("my calculator");
2.设置文本框
private final JTextField jt = new JTextField("输入算式");
3.定义按钮
private JButton but_0, but_1, but_2, but_3, but_4, but_5, but_6, but_7, but_8, but_9,
but_add, but_sub, but_mult, but_div, but_lt, but_rt, but_org, but_equ, but_C, but_Del;
4.配置按钮信息
具体代码在下
三、遇到需要解决的问题
问题1:括号必须成对出现
我们可以使用栈(Stack)来解决该问题。在遍历数组时,每当读取到(,将其压入栈;每当读到),将栈中的(弹出栈。
如果当读取到)时栈为空,或遍历结束后栈未空,代表括号未成对出现,即表达式不合法。
private void check() throws FormatException {
// 使用Stack变量来存储左右括号的出现
Stack<Character> brackets = new Stack<>();
// 将算术表达式转换成字符数组
char[] chars = expression.toCharArray();
// 循环遍历整个字符数组
for (int i = 0; i < chars.length; i++) {
switch (chars[i]) {
case '(':
// 遇到`(`压入栈
brackets.push(chars[i]);
break;
case ')':
// 遇到`)`弹出栈,若栈空,则代表括号未成对出现,抛出异常
if (brackets.empty() || brackets.pop() != '(') throw new FormatException("算术表达式不合法");
break;
}
}
// 若遍历结束后,栈未空,则代表括号未成对出现,抛出异常
if (!brackets.empty()) throw new FormatException("算术表达式不合法");
}
问题2:除数不为0
关于这条规则,我们可以引入一个局部变量lastChar,用来存储上一个出现的字符,不包括左右括号。如果循环遍历到字符0时,程序即开始检验lastChar中的值是否为/,若是则抛出异常,即除数不能为0。
private void check() throws FormatException {
// 使用Stack变量来存储左右括号的出现
Stack<Character> brackets = new Stack<>();
// 上一个出现的字符,不含括号
char lastChar = ' ';
// 将算术表达式转换成字符数组
char[] chars = expression.toCharArray();
// 循环遍历整个字符数组
for (int i = 0; i < chars.length; i++) {
switch (chars[i]) {
case '(':
// 遇到`(`压入栈
brackets.push(chars[i]);
break;
case '+':
case '-':
case '*':
case '/':
// 存储这次读取到的运算符
lastChar = chars[i];
break;
case '0':
if (lastChar == '/') throw new FormatException("除数不能为0");
break;
case ')':
// 遇到`)`弹出栈,若栈空,则代表括号未成对出现,抛出异常
if (brackets.empty() || brackets.pop() != '(') throw new FormatException("算术表达式不合法");
break;
default:
// 存储这次读取到的运算符
lastChar = chars[i];
break;
}
}
// 若遍历结束后,栈未空,则代表括号未成对出现,抛出异常
if (!brackets.empty()) throw new FormatException("算术表达式不合法");
}
问题3:运算符出现位置
根据规则:
运算符不能出现在表达式开始或末尾,也不能出现在左括号的右侧或右括号的左侧,运算符不能连续出现
/**
* 表达式合法性检验
*
* @throws FormatException
*/
private void check() throws FormatException {
// 使用Stack变量来存储左右括号的出现
Stack<Character> brackets = new Stack<>();
// 上一个出现的字符,不含括号
char lastChar = ' ';
// 将算术表达式转换成字符数组
char[] chars = expression.toCharArray();
// 如果输入为空,则抛出异常
if (chars.length == 0) throw new FormatException("输入不能为空");
// 循环遍历整个字符数组
for (int i = 0; i < chars.length; i++) {
switch (chars[i]) {
case '(':
// 遇到`(`压入栈
brackets.push(chars[i]);
break;
case ')':
// 遇到`)`弹出栈,若栈空,则代表括号未成对出现,抛出异常
if (brackets.empty() || brackets.pop() != '(') throw new FormatException("算术表达式不合法");
break;
case '+':
case '-':
case '*':
case '/':
// 如果运算符出现在开头或末尾,抛出异常
if (i == 0 || i == chars.length - 1) throw new FormatException("算术表达式不合法");
// 如果运算符的前一位是运算符或左括号,抛出异常
if (chars[i - 1] == '+' || chars[i - 1] == '-' || chars[i - 1] == '*' || chars[i - 1] == '/' || chars[i - 1] == '(')
throw new FormatException("算术表达式不合法");
// 如果运算符的后一位是运算符或右括号,抛出异常
if (chars[i + 1] == '+' || chars[i + 1] == '-' || chars[i + 1] == '*' || chars[i + 1] == '/' || chars[i + 1] == ')')
throw new FormatException("算术表达式不合法");
// 存储这次读取到的运算符
lastChar = chars[i];
break;
case '0':
if (lastChar == '/') throw new FormatException("除数不能为0");
break;
default:
// 存储这次读取到的运算符
lastChar = chars[i];
break;
}
}
// 若遍历结束后,栈未空,则代表括号未成对出现,抛出异常
if (!brackets.empty()) throw new FormatException("算术表达式不合法");
}
四、具体代码(代码后有功能解释)
StartMyCalculator.java
package calculator;
public class StartMyCalculator {
public static void main(String[] args) {
// 启动计算器图形界面窗口
new MyGUICalculator();
}
}
MyGUICalculator.java
package calculator;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Stack;
public class MyGUICalculator implements ActionListener {
// 定义主窗口并命名
private final JFrame jf = new JFrame("my calculator");
// 定义文本框
private final JTextField jt = new JTextField("输入算式");
// 定义按钮
private JButton but_0, but_1, but_2, but_3, but_4, but_5, but_6, but_7, but_8, but_9,
but_add, but_sub, but_mult, but_div, but_lt, but_rt, but_org, but_equ, but_C, but_Del;
// 初始化表达式
private String input = "";
MyGUICalculator() {
// 设置各按钮的信息
setButton();
// 设置文本框信息
setTextField();
//设置主窗口信息
setThis();
// 添加各组件
addBut();
}
// 设置按钮信息的方法
private void setButton() {
but_0 = new JButton("0");
but_0.setFont(new Font("宋体", Font.BOLD, 20));
but_0.setMargin(new Insets(0, 0, 0, 0));
but_1 = new JButton("1");
but_1.setFont(new Font("宋体", Font.BOLD, 20));
but_1.setMargin(new Insets(0, 0, 0, 0));
but_2 = new JButton("2");
but_2.setFont(new Font("宋体", Font.BOLD, 20));
but_2.setMargin(new Insets(0, 0, 0, 0));
but_3 = new JButton("3");
but_3.setFont(new Font("宋体", Font.BOLD, 20));
but_3.setMargin(new Insets(0, 0, 0, 0));
but_4 = new JButton("4");
but_4.setFont(new Font("宋体", Font.BOLD, 20));
but_4.setMargin(new Insets(0, 0, 0, 0));
but_5 = new JButton("5");
but_5.setFont(new Font("宋体", Font.BOLD, 20));
but_5.setMargin(new Insets(0, 0, 0, 0));
but_6 = new JButton("6");
but_6.setFont(new Font("宋体", Font.BOLD, 20));
but_6.setMargin(new Insets(0, 0, 0, 0));
but_7 = new JButton("7");
but_7.setFont(new Font("宋体", Font.BOLD, 20));
but_7.setMargin(new Insets(0, 0, 0, 0));
but_8 = new JButton("8");
but_8.setFont(new Font("宋体", Font.BOLD, 20));
but_8.setMargin(new Insets(0, 0, 0, 0));
but_9 = new JButton("9");
but_9.setFont(new Font("宋体", Font.BOLD, 20));
but_9.setMargin(new Insets(0, 0, 0, 0));
but_add = new JButton("+");
but_add.setFont(new Font("宋体", Font.BOLD, 20));
but_add.setMargin(new Insets(0, 0, 0, 0));
but_sub = new JButton("-");
but_sub.setFont(new Font("宋体", Font.BOLD, 20));
but_sub.setMargin(new Insets(0, 0, 0, 0));
but_mult = new JButton("×");
but_mult.setFont(new Font("宋体", Font.BOLD, 20));
but_mult.setMargin(new Insets(0, 0, 0, 0));
but_div = new JButton("÷");
but_div.setFont(new Font("宋体", Font.BOLD, 20));
but_div.setMargin(new Insets(0, 0, 0, 0));
but_lt = new JButton("(");
but_lt.setFont(new Font("宋体", Font.BOLD, 20));
but_lt.setMargin(new Insets(0, 0, 0, 0));
but_rt = new JButton(")");
but_rt.setFont(new Font("宋体", Font.BOLD, 20));
but_rt.setMargin(new Insets(0, 0, 0, 0));
but_Del = new JButton("Del");
but_Del.setFont(new Font("宋体", Font.BOLD, 20));
but_Del.setMargin(new Insets(0, 0, 0, 0));
but_C = new JButton("C");
but_C.setFont(new Font("宋体", Font.BOLD, 20));
but_C.setMargin(new Insets(0, 0, 0, 0));
but_equ = new JButton("=");
but_equ.setFont(new Font("宋体", Font.BOLD, 20));
but_equ.setMargin(new Insets(0, 0, 0, 0));
but_org = new JButton(".");
but_org.setFont(new Font("宋体", Font.BOLD, 20));
but_org.setMargin(new Insets(0, 0, 0, 0));
}
// 设置文本框信息方法
private void setTextField() {
jt.setEditable(false);
jt.setFont(new Font("宋体", Font.PLAIN, 18));
}
// 定义主窗口的方法
private void setThis() {
jf.setSize(500, 335);
jf.setLocation(600, 350);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setResizable(false);
jf.setVisible(true);
}
// 添加各组件的方法
private void addBut() {
jf.setLayout(null);
jt.setBounds(0, 0, 500, 80);
jf.add(jt);
but_7.setBounds(0, 100, 100, 50);
jf.add(but_7);
but_8.setBounds(100, 100, 100, 50);
jf.add(but_8);
but_9.setBounds(200, 100, 100, 50);
jf.add(but_9);
but_add.setBounds(300, 100, 100, 50);
jf.add(but_add);
but_lt.setBounds(400, 100, 100, 50);
jf.add(but_lt);
but_4.setBounds(0, 150, 100, 50);
jf.add(but_4);
but_5.setBounds(100, 150, 100, 50);
jf.add(but_5);
but_6.setBounds(200, 150, 100, 50);
jf.add(but_6);
but_sub.setBounds(300, 150, 100, 50);
jf.add(but_sub);
but_rt.setBounds(400, 150, 100, 50);
jf.add(but_rt);
but_1.setBounds(0, 200, 100, 50);
jf.add(but_1);
but_2.setBounds(100, 200, 100, 50);
jf.add(but_2);
but_3.setBounds(200, 200, 100, 50);
jf.add(but_3);
but_mult.setBounds(300, 200, 100, 50);
jf.add(but_mult);
but_Del.setBounds(400, 200, 100, 50);
jf.add(but_Del);
but_org.setBounds(0, 250, 100, 50);
jf.add(but_org);
but_0.setBounds(100, 250, 100, 50);
jf.add(but_0);
but_equ.setBounds(200, 250, 100, 50);
jf.add(but_equ);
but_div.setBounds(300, 250, 100, 50);
jf.add(but_div);
but_C.setBounds(400, 250, 100, 50);
jf.add(but_C);
initial();
}
// 各组件添加监听器
private void initial() {
but_0.addActionListener(this);
but_1.addActionListener(this);
but_2.addActionListener(this);
but_3.addActionListener(this);
but_4.addActionListener(this);
but_5.addActionListener(this);
but_6.addActionListener(this);
but_7.addActionListener(this);
but_8.addActionListener(this);
but_9.addActionListener(this);
but_add.addActionListener(this);
but_sub.addActionListener(this);
but_mult.addActionListener(this);
but_div.addActionListener(this);
but_org.addActionListener(this);
but_C.addActionListener(this);
but_lt.addActionListener(this);
but_rt.addActionListener(this);
but_Del.addActionListener(this);
but_equ.addActionListener(this);
}
// 遍历到操作符时的压栈方法
public double cal(double num1, double num2, String oper) {
double result = 0;
switch (oper) {
case "+":
result = num1 + num2;
break;
case "-":
result = num2 - num1; //靠近栈底的做被减数
break;
case "*":
result = num1 * num2;
break;
case "/":
if (num1 != 0) {
result = num2 / num1; //靠近栈底的做被除数
} else {
JOptionPane.showOptionDialog(jf, "除数不能为0", "提示",
JOptionPane.CLOSED_OPTION, JOptionPane.ERROR_MESSAGE, null, null, null);
input = "";
jt.setText(input);
}
break;
default:
JOptionPane.showOptionDialog(jf, "无法计算", "提示",
JOptionPane.CLOSED_OPTION, JOptionPane.ERROR_MESSAGE, null, null, null);
input = "";
jt.setText(input);
break;
}
return result;
}
// 后缀表达式值算法
public String calculate(String input) {
// 创建字符串数组用 " " 标志分割输入字符串
String[] compute = input.split(" ");
Stack<String> stack1 = new Stack<>(); // 存后缀表达式
Stack<String> stack2 = new Stack<>(); // 临时放操作符
Stack<String> stack3 = new Stack<>(); // 最后运算栈
for (int i = 0; i < compute.length; ++i) {
// 加减乘除直接入栈
if (compute[i].equals("+") || compute[i].equals("-") || compute[i].equals("*") || compute[i].equals("/") || compute[i].equals("(")) {
stack2.push(compute[i]);
}
// 遇到 “)”,弹出操作符,直到遇见 “(”
else if (compute[i].equals(")")) {
while (!stack2.peek().equals("(")) {
stack1.push(stack2.pop());
}
// 弹出 “(”
stack2.pop();
}
// 数字小数点直接入栈
else stack1.push(compute[i]);
}
// 当操作符栈不为空,将操作符全部弹入 stack1
while (!stack2.empty()) {
stack1.push(stack2.pop());
}
// 遍历后缀表达式
for (String l : stack1) {
// 正则表达式匹配浮点数
if (l.matches("-?\\d+(\\.\\d+)?")) {
//if (l.matches("-?\\d+")) { //正则表达式匹配数字
stack3.push(l);
} else {
String num1 = stack3.pop();
String num2 = stack3.pop();
double calculate = cal(Double.parseDouble(num1), Double.parseDouble(num2), l);
stack3.push(String.valueOf(calculate));
}
}
double result = Double.parseDouble(stack3.pop());
return String.valueOf(result);
}
@Override
public void actionPerformed(ActionEvent e) {
int cnt = 0;
String actionCommand = e.getActionCommand(); //获取按钮上的字符串
if (actionCommand.equals("+") || actionCommand.equals("-") || actionCommand.equals("*") || actionCommand.equals("/")) {
input += " " + actionCommand + " ";
} else if (actionCommand.equals("Del")) {
input = input.substring(0, input.length() - 1);
} else if (actionCommand.equals("C")) {
input = "";
} else if (actionCommand.equals("=")) {
try {
input += " " + "=" + " " + calculate(input);
} catch (Exception exception) {
exception.printStackTrace();
}
jt.setText(input);
input = "";
cnt = 1;
} else if (actionCommand.equals("(")) {
input += actionCommand + " ";
} else if (actionCommand.equals(")")) {
input += " " + actionCommand;
} else input += actionCommand;
// 设置标志 cnt ,让文本框始终显示输入字符串
if (cnt == 0) {
jt.setText(input);
}
}
}
五、运行
六、程序总结分析
1、项目的难点和关键点
在这个项目中,难点在于代码的编写,在编写代码过程中最难的还是优先级的完成,在完成优先级时主要利用了栈的方法,利用压栈和出栈的方法判断优先级
2、项目的评价
此项目还有很多不足之处,比如不能运算很复杂的表达式,不能用电脑键盘输入表达式,只能按键。界面不美观等缺点。