目录
一、问题描述
设计一个简单的算术表达式计算器
基本要求
实现四则运算表达式的求值(包含括号,可多层嵌套)
二、问题分析
目前的计算器大多使用后缀表达式来进行计算,因此基本思想是将用户输入的中缀表达式转为后缀表达式,即逆波兰式,进而由后缀表达式得到计算结果
实现思路
- 设置计算器界面,通过用户点击按键来得到待计算的中缀表达式
- 将中缀表达式转为后缀表达式
- 由后缀表达式得到计算结果,并在计算器界面中显示
三、逻辑设计
1.界面设计
基于Java用户界面设计Swing,采用JFrame框架作为计算器的主窗口,添加文本框来显示用户输入和输出计算结果,并添加具体的计算器按键用于数据输入
1)实现交互:通过为每个按键设置接收操作事件的监听器来得到用户输入数据,当用户鼠标点击按键时触发事件,将按键信息添加到输入文本text中,最终的计算结果会在用户点击等于键时在文本框中显示出来
2)异常处理:为保证系统运行时的稳定性,需要考虑到用户在使用时可能出现的异常情况并进行处理,目前已经实现了以下情况的异常处理,会在文本框内提示Error信息
- 表达式中出现除以0的情况
- 括号不匹配
- 多个运算符连续出现的情况,如1++*2
- 一个数字中出现多个小数点,如1.25.9
2.程序设计
设置两个方法分别用于将用户输入的字符串文本转为后缀表达式,以及由后缀表达式得到计算结果
四、物理设计
1.calculator方法
为主窗口设置大小和位置,设置文本框以及每个按键的布局和相关信息,为每个按键添加接收操作事件的监听器
public Calculator() {
super("算术表达式计算器"); //窗体名称
this.setLayout(null);
this.setBounds(600, 200, 350, 400); //设置窗体位置和大小
resultText.setBounds(20, 15, 300, 50); //设置文本框位置和大小
resultText.setHorizontalAlignment(JTextField.RIGHT); //设置文本右对齐
resultText.setFont(new Font("宋体", Font.PLAIN, 22)); //设置文本字体
resultText.setEditable(false);
this.add(resultText);
int x = 20, y = 80;
for (int i = 0; i < KEYS.length; i++) { //设置按键布局
keys[i] = new JButton();
keys[i].setText(KEYS[i]); //设置按键名
keys[i].setFont(new Font(null, 4, 16)); //设置按键字体
keys[i].setBounds(x, y, 60, 40);
if (x < 215) {
x += 80;
} else {
x = 20;
y += 55;
}
this.add(keys[i]);
}
for (int i = 0; i < KEYS.length; i++) { //为每个按键添加接收操作事件的监听器
keys[i].addActionListener(this);
}
//主窗体的相关设置
this.setResizable(false); //用户不可调节窗体大小
this.setDefaultCloseOperation(EXIT_ON_CLOSE); //窗口关闭方式
this.setVisible(true); //是否可见
}
2.actionPerformed方法
设置点击按键后触发的具体事件
public void actionPerformed(ActionEvent e) { //设置点击按键时触发的事件
String tag = e.getActionCommand();
if (tag.equals("=")) {
String[] suffix = infixToSuffix(this.text);
String result = result(suffix);
this.text = result + "";
resultText.setText(this.text);
} else if (tag.equals("AC")) {
this.text = "";
resultText.setText("0");
} else if (tag.equals("π")) {
this.text += "3.1415926535";
resultText.setText(this.text);
} else {
this.text += tag;
resultText.setText(this.text);
}
}
3.infixToSuffix方法
用于将中缀表达式转为后缀表达式
主要思想:
- 遍历中缀表达式,遇到数字直接加入后缀表达式
- 遇到操作符时,若比栈顶操作符优先级高则直接压入栈,否则将栈顶运算赋出栈并加入后缀表达式,并继续与栈顶操作赋的优先级比较,直至高于栈顶操作符的优先级时压入栈
- 遇到左括号时,将其入栈
- 遇到右括号时将操作符从栈顶依次出栈,并加入到后缀表达式中,直至遇到左括号
- 遍历完中缀表达式后,将栈内剩余操作符依次出栈并加入后缀表达式
流程图:
具体代码:
public String[] infixToSuffix(String text) { //将中缀表达式转为后缀表达式
String temp = ""; //存放多位字符的情况
String[] suffix = new String[200]; //存放后缀表达式
Stack<Character> operator = new Stack<>(); //存放运算符
int index = 0;
for (int i = 0; i < text.length(); i++) {
temp = "";
char k = text.charAt(i); //当前字符
if (isNumberOrPoint(k)) {
while (isNumberOrPoint(text.charAt(i))) {
temp += String.valueOf(text.charAt(i));
i++;
if (i >= text.length()) break;
}
i--;
suffix[index++] = temp;
} else if (k == '+' || k == '-') {
while (!operator.empty() && operator.peek() != '(') {
suffix[index++] = String.valueOf(operator.pop());
}
operator.push(k);
} else if (k == '*' || k == '/') {
operator.push(k);
} else if (k == '(') operator.push(k); //遇到做括号直接入栈
else if (k == ')') {
while (operator.peek() != '(') { //将左括号上方的操作符出栈并加入后缀表达式
suffix[index++] = String.valueOf(operator.pop());
}
operator.pop(); //左括号出栈
}
}
while (!operator.empty()) //将栈中剩余运算符加入后缀表达式
suffix[index++] = String.valueOf(operator.pop());
return suffix;
}
4.result方法
由后缀表达式得到计算结果
主要思想:
- 遍历后缀表达式,遇到数字时直接入栈
- 遇到操作符时,依次从栈顶取出两个数,根据操作符进行运算,将结果再次入栈,注意操作数顺序,先出栈的应在操作符的右边
流程图:
具体代码:
public String result(String[] suffix) { //根据后缀表达式得到结果
Stack<String> result = new Stack<>();
for (int i = 0; suffix[i] != null; i++) {
if (isNumber(suffix[i])) result.push(suffix[i]);
else { //为运算符时出栈两个数字经运算后入栈
double a = Double.parseDouble(result.pop());
double b = Double.parseDouble(result.pop());
if (suffix[i].equals("+")) {
result.push(String.valueOf(b + a));
} else if (suffix[i].equals("-")) {
result.push(String.valueOf(b - a)); //注意顺序是反过来的
} else if (suffix[i].equals("*")) {
result.push(String.valueOf(b * a));
} else if (suffix[i].equals("/")) {
if (a == 0) return "Error";
else result.push(String.valueOf(b / a));
}
}
}
return result.peek();
}
五、源代码
1.Main类
public class Main {
public static void main(String[] args) { //程序入口
Calculator start = new Calculator();
}
}
2.calculator类
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Stack;
public class Calculator extends JFrame implements ActionListener {
private String[] KEYS = {"(", ")", "AC", "/", "7", "8", "9", "*", "4", "5", "6", "-", "1", "2", "3", "+", "π", "0", ".", "=",};
private JButton keys[] = new JButton[KEYS.length]; //计算器中的按钮
private JTextField resultText = new JTextField("0"); //用于显示结果的文本框,设初值为0
private String text = ""; //存放文本
public Calculator() {
super("算术表达式计算器"); //窗体名称
this.setLayout(null);
this.setBounds(600, 200, 350, 400); //设置窗体位置和大小
resultText.setBounds(20, 15, 300, 50); //设置文本框位置和大小
resultText.setHorizontalAlignment(JTextField.RIGHT); //设置文本右对齐
resultText.setFont(new Font("宋体", Font.PLAIN, 22)); //设置文本字体
resultText.setEditable(false);
this.add(resultText); //添加文本框
int x = 20, y = 80;
for (int i = 0; i < KEYS.length; i++) { //设置按键布局
keys[i] = new JButton();
keys[i].setText(KEYS[i]); //设置按键名
keys[i].setFont(new Font(null, 4, 16)); //设置按键字体
keys[i].setBounds(x, y, 60, 40);
if (x < 215) {
x += 80;
} else {
x = 20;
y += 55;
}
this.add(keys[i]);
}
for (int i = 0; i < KEYS.length; i++) { //为每个按键添加接收操作事件的监听器
keys[i].addActionListener(this);
}
//主窗体的相关设置
this.setResizable(false); //用户不可调节窗体大小
this.setDefaultCloseOperation(EXIT_ON_CLOSE); //窗口关闭方式
this.setVisible(true); //是否可见
}
public void actionPerformed(ActionEvent e) { //设置点击按键时触发的事件
String tag = e.getActionCommand();
if (tag.equals("=")) {
String[] suffix = infixToSuffix(this.text);
String result = result(suffix);
this.text = result + "";
resultText.setText(this.text);
} else if (tag.equals("AC")) {
this.text = "";
resultText.setText("0");
} else if (tag.equals("π")) {
this.text += "3.1415926535";
resultText.setText(this.text);
} else {
this.text += tag;
resultText.setText(this.text);
}
}
public String[] infixToSuffix(String text) { //将中缀表达式转为后缀表达式
String temp = ""; //存放多位字符的情况
String[] suffix = new String[200]; //存放后缀表达式
Stack<Character> operator = new Stack<>(); //存放运算符
int index = 0;
for (int i = 0; i < text.length(); i++) {
temp = "";
char k = text.charAt(i); //当前字符
if (isNumberOrPoint(k)) {
while (isNumberOrPoint(text.charAt(i))) {
temp += String.valueOf(text.charAt(i));
i++;
if (i >= text.length()) break;
}
i--;
suffix[index++] = temp;
} else if (k == '+' || k == '-') {
while (!operator.empty() && operator.peek() != '(') {
suffix[index++] = String.valueOf(operator.pop());
}
operator.push(k);
} else if (k == '*' || k == '/') {
operator.push(k);
} else if (k == '(') operator.push(k); //遇到做括号直接入栈
else if (k == ')') {
while (!operator.empty() && operator.peek() != '(') { //将左括号上方的操作符出栈并加入后缀表达式
suffix[index++] = String.valueOf(operator.pop());
}
if (!operator.empty()) operator.pop(); //左括号出栈
}
}
while (!operator.empty()) //将栈中剩余运算符加入后缀表达式
suffix[index++] = String.valueOf(operator.pop());
return suffix;
}
public boolean isNumberOrPoint(char i) {
if ((i >= '0' && i <= '9') || i == '.') return true;
return false;
}
public String result(String[] suffix) { //根据后缀表达式得到结果
Stack<String> result = new Stack<>();
for (int i = 0; suffix[i] != null; i++) {
if (isNumber(suffix[i])) result.push(suffix[i]);
else { //为运算符时出栈两个数字经运算后入栈
double a = 0;
double b = 0;
try {
a = Double.parseDouble(result.pop());
b = Double.parseDouble(result.pop());
} catch (Exception e) {
e.printStackTrace();
return "Error";
}
if (suffix[i].equals("+")) {
result.push(String.valueOf(b + a));
} else if (suffix[i].equals("-")) {
result.push(String.valueOf(b - a)); //注意顺序是反过来的
} else if (suffix[i].equals("*")) {
result.push(String.valueOf(b * a));
} else if (suffix[i].equals("/")) {
if (a == 0) return "Error";
else result.push(String.valueOf(b / a));
}
}
}
try {
return result.peek();
} catch (Exception e) {
e.printStackTrace();
return "Error";
}
}
public boolean isNumber(String t) { //判断是否为数字串
char judge = t.charAt(0);
if (judge >= '0' && judge <= '9') return true;
return false;
}
}