一.题目要求
1、用户可以输入一段用于求值的表达式;
2、根据表达式中运算符(目前有的运算符为加减乘除)的优先级和结合性,将原表达式转化为后缀表达式的形式;
3、计算后缀表达式的值并输出。
4、用户(类的使用者)可根据自身应用需求的不同而制定更多的运算符(皆为双目运算符),每个运算符的名称、运算规则和优先级可以由用户来设置;在不改变软件主架构的前提下(对修改关闭),该运算符可被运用到该软件中,从而实现对软件的扩展(也即对扩展开放)。
二.题目解析
根据题目的描述,我们可以将要求细分:
1.做出一个用户交互的界面,在这个界面中能实现获取用户的输入表达式,展示转换后的表达式以及计算的结果。
2.中缀表达式转换成后缀表达式要正确。
3.后缀表达式的求值要正确。
4.定义一个接口,方便用户自定义新的运算符以及优先级。
根据上述的细致要求,我们可以这样实现:
1.所有的交互界面使用一个函数来完成:MyJFram 函数,扩展自 Frame 函数库,能够实现用户交互的界面以及设置相应的文本框、按钮来完成相应的功能。另,此次程序的扩展功能之一是点击op按钮能弹出一个新的框架,展示已经在运算器中的所有运算符及其优先级。
2.中缀表达式转后缀表达式单独作为一个函数。
3.后缀表达式的计算单独作为一个函数。
4.定义一个接口。
5.定义一个运算符管理函数,以便实现扩展运算符、展示运算符、修改运算符的优先级等功能。
三.各部分详解
1.中缀表达式转换成后缀表达式的转换函数Convert
中缀表达式就是我们平常所写的类似于 3+7*(8-9*3)/3 的运算式,这种运算式的运算符都在相应的运算数中。
后缀表达式(也称为逆波兰表达式)则是通过将所有操作符都放在相应操作数的后面来表示数学表达式的一种方式,例如 3 4 +,2 5 * 1 +,3 4 + 5 * 等。相比较中缀表达式,后缀表达式更加直观,计算机也更容易处理,因此在计算机中广泛使用。
二者的区别主要在于操作符的位置不同。中缀表达式中,操作符在操作数的中间;后缀表达式中,操作符在操作数的后面。中缀表达式需要考虑操作符的优先级和括号,而后缀表达式不需要,因为后缀表达式中操作符的优先级已经隐含在操作数的顺序中了
根据上述对概念的理解,我们可以知道:后缀表达式中操作数的顺序应与中缀表达式相同,而后缀中运算符的顺序则要根据运算符的优先级来存放。以此引出转换的规则:
1.由于栈的先入后出原则,我们使用栈来分别存放操作数与运算符。设两个栈分别为
operatorStack 、postfixExpression(既表示操作数的存放栈,也表示最后的后缀表达式的结果栈)。
2.将输入的表达式看作是一个字符串,首先要将字符串中的所有字符(操作数和运算符)分割成单独的字符,存入一个新列表容器中,方便存取。
3.依次遍历这个列表,遇到操作数时,直接将该数压入 postfixExpression 栈中。
4.遇到运算符时:
(1)如果运算符栈中为空 或 运算符栈顶元素是 “ ( ” ,直接压入。
(2)如果当前运算符是 “ )” ,则判断,当前运算符栈中栈顶元素是不是 (,若不是,则要移动当前运算符栈中的元素至 postfixExpression 栈中,直到遇见了 (,弹出 (,相当于与 右括号消除了。
(3)如果当前运算符的优先级低于栈顶元素,直接压入。
(4)如果当前运算符的优先级高于栈顶元素,则要将当前运算符栈中的元素至 postfixExpression 栈中,直到遇到一个运算符,其优先级高于当前元素的为止。
5.这样重复操作直到该 list 容器为空,即遍历完了字符串,postfixExpression 栈中的元素逆序输出,就是该中缀表达式的转换结果了。
6.不过,上述逆序输出后,还是一个一个单独的字符,要想变成常规的后缀表达式,还要添加一个 joins 函数,使之进行连接。
大致思路讲完了,以下附代码:
public class Convert {
// 将输入字符串分割为项列表
public static List<String> endList(String s) {
List<String> items = new ArrayList<>();
// 使用正则表达式匹配数字和运算符
Pattern pattern = Pattern.compile("\\d+|[()+\\-*/^%]");
Matcher matcher = pattern.matcher(s);
while (matcher.find()) {
items.add(matcher.group());
}
return items;
}
// 转换中缀表达式为后缀表达式
public static List<String> convertS(List<String> strings) {
// 创建运算符栈
Stack<String> operatorStack = new Stack<>();
// 创建结果列表即后缀表达式的栈,但由于这个栈不需要出栈,所以使用列表即可
List<String> postfixExpression = new ArrayList<>();
for (String s : strings) {
// 判断是否是数字,如果是数字直接添加到后缀表达式中
if (s.matches("\\d+")) {
postfixExpression.add(s);
}
// 如果是运算符
else {
// 当前是"(",直接压入运算符栈
if (s.equals("(") || operatorStack.isEmpty() || operatorStack.peek().equals("(")) {
operatorStack.push(s);
}
// 当前是")"
else if (s.equals(")")) {
while (!operatorStack.isEmpty() && !(operatorStack.peek().equals("("))) {
postfixExpression.add(operatorStack.pop());
}
if (!operatorStack.isEmpty() && operatorStack.peek().equals("(")) {
operatorStack.pop();
} else {
throw new IllegalArgumentException("括号不匹配");
}
}
// 当前是"/"
else if (s.equals("/")) {
// 检查除数是否为0
if (postfixExpression.isEmpty() || postfixExpression.get(postfixExpression.size() - 1).equals("/") || postfixExpression.get(postfixExpression.size() - 1).equals("*")) {
throw new IllegalArgumentException("除数不能为0,请重新输入!");
}
operatorStack.push(s);
}
// 其他运算符
else {
BinaryOperator currentOperator = OperatorManager.getOperator(s);
while (!operatorStack.isEmpty() && currentOperator != null &&
OperatorManager.getOperator(operatorStack.peek()) != null &&
currentOperator.getPriority() < OperatorManager.getOperator(operatorStack.peek()).getPriority()) {
postfixExpression.add(operatorStack.pop());
}
operatorStack.push(s);
}
}
}
while (!operatorStack.isEmpty()) {
postfixExpression.add(operatorStack.pop());
}
return postfixExpression;
}
// 将后缀表达式项列表拼接成字符串
public static String joinS(List<String> strings) {
return String.join("", strings);
}
}
分为三个函数:
1.分割函数 endlist
(1)正则表达式
基础知识看:http://t.csdnimg.cn/Q5Z8O
Pattern pattern = Pattern.compile("\\d+|[()+\\-*/^%]");
在这句代码中,Pattern 是 Java.util.regex 包中的一个类。它提供了编译后的正则表达式的表示形式,并且包含了用于匹配、搜索和替换的方法。
而 Pattern.compile("\\d+|[()+\\-*/^%]") 是 Pattern 类中的一个静态方法,它用于编译给定的正则表达式并返回相应的 Pattern 对象。这个方法接受一个字符串作为参数,字符串表示一个正则表达式。在这个例子中,传入的正则表达式是 "\\d+|[()+\\-*/^%]",它用于匹配连续的数字字符或者括号或运算符字符。
所以,整句代码的作用是编译给定的正则表达式,使其成为一个 Pattern 对象,然后可以使用这个对象进行字符串匹配、搜索等操作。
Matcher matcher = pattern.matcher(s);
用于在字符串中执行模式匹配操作。
具体来说,Matcher
对象是用于执行正则表达式的匹配操作的工具。通过调用 pattern.matcher(s)
,我们将给定的字符串 s
与 pattern
所代表的正则表达式进行匹配。返回的 Matcher
对象可以用于执行各种与模式匹配相关的操作,例如查找匹配项、提取匹配结果等。
在这个特定的例子中,pattern.matcher(s)
的作用是将字符串 s
与 Pattern.compile("\\d+|[()+\\-*/^%]")
所编译的正则表达式模式进行匹配操作。所以,matcher
对象将会匹配字符串 s
中符合该正则表达式模式的部分。
也就是说,通过这两步,匹配、分割出了正确的数字、操作符,并过滤了非数字字符。
while (matcher.find()) {
items.add(matcher.group());
}
这段代码是用来遍历 Matcher 对象的匹配结果并将其添加到一个列表中。
通过调用 matcher.find()
方法,代码会在匹配到下一个匹配项时返回 true,并将匹配项作为当前匹配结果。在循环中,matcher.find()
方法用于不断往前查找匹配项,直到没有更多匹配项为止。
在循环的每次迭代中,matcher.group()
方法返回当前匹配项的值,并将其添加到名为 “items” 的列表中。这样就实现了将匹配到的每个部分存储到列表中的功能。
(2)为什么要使用正则表达式来分割字符串?
a.错误一
事实上,在使用正则表达式进行分割之前,我所使用的方法是 Arrays.asList(s.split(“”))
split 函数用于将字符串按照指定的分隔符切割,并返回一个字符串数组。
Arrays.asList 是一个静态方法,用于将数组转换为列表。
例如我要将 “42 3 + ” 进行分割,使用上述方法得到的最终结果是:
[“”, “4”, “2”, “”, " ", “”, “3”, “”, " ", “”, “+”, “”]。
它将字符串按照每个字符进行分割。由于在空字符串上进行分割,它会将字符串拆分成单个字符,包括连续的空格字符。这就解释了为什么数字被拆分成单个数字字符。那么在后续对后缀表达式的计算中,4 2 不会再被当成“42” 而是“4” “2”.这是导致最终结果错误的原因之一。
b.错误二
对于错误一,有些朋友可能会说,那把分割条件改成按照 “ ” 空格字符不就行了吗?
这样的修改对于 纯数字、合法操作符 的字符串是有用的,但是不能排除用户会误输非数字字符,例如 xyz。
如果按照上述方法,分割例如:“ 42 3 + 6 * 8xyz” ,所得结果 [“”, “42”, “3”, “+”, “6”, “*”, “8xyz”]
非数字字符 xyz 也被分进后缀表达式中了。而这样的式子我们是无法计算的。
c.正则表达式的优点
首先,上述的错误统统被解决。
针对第一个错误,数字被按照单个字符分割。
- “\d+” 匹配一个或多个数字字符,表示将连续的数字字符作为一个整体进行匹配,而不是将其作为分隔符进行分割。
这样就解决了。
针对第二个错误,过滤非数字字符。
当这个正则表达式用于匹配字符串时,它会从左到右依次检查字符串中的每个字符。如果字符串中的某个部分能够匹配上 “\d+” 或者 “[()+\-*/^%]” 中的任意一个规则,那么这部分就会被作为一个整体匹配出来。
我们这里并没有添加匹配非数字字符的规则,则从效果上看,就类似于其被过滤了(详情代码看1)
使用正则表达式进行匹配分割,还有诸多优点,例如可扩展性强、代码简洁等等,这里就不细说了。
2.转换函数 covertS
转换规则在上面已经叙述过了,这里就不赘述了。
3.连接函数 joinS
`String.join("", strings)` 是 Java 中的一个静态方法,它的作用是将一个字符串数组(或者其他实现了 Iterable 接口的对象)中的元素连接成一个字符串,中间用指定的分隔符分隔。其中第一个参数是指定的分隔符,第二个参数是要连接的字符串数组(或者其他实现了 Iterable 接口的对象)。
2.接口 - 扩展符号类 BinaryOperator
interface BinaryOperator {
String getName();
BigInteger apply(BigInteger operand1, BigInteger operand2);
int getPriority();
void setPriority(int priority);
}
class Add implements BinaryOperator {
private int priority = 1;
public String getName() {
return "+";
}
public BigInteger apply(BigInteger operand1, BigInteger operand2) {
return operand1.add(operand2);
}
public int getPriority() {
return priority;
}
public void setPriority(int pro){
this.priority = pro;
}
}
这个接口中,主要定义四个函数:
String getName():获取操作符的名称 BigInteger apply(BigInteger operand1, BigInteger operand2) :获取两个操作数,并定义其间的运算 int getPriority():获取操作符的优先级 void setPriority(int priority):设置操作符的优先级
接口主要是便于用户自定义操作符及其优先级
3.管理运算符的类 - OperatorManager
{
// 使用哈希映射存储运算符及其对应的二元操作
private static Map<String, BinaryOperator> operatorMap = new HashMap<>();
// 添加运算符及对应的二元操作到映射中
public static void addOperator(String operatorName, BinaryOperator operator) { // operatorName 运算符名称 operator 对应的二元操作
operatorMap.put(operatorName, operator);
}
//获取指定运算符对应的二元操作
public static BinaryOperator getOperator(String operatorName) {
if (operatorMap.containsKey(operatorName)) {
return operatorMap.get(operatorName);
} else { //找不到指定的运算符
throw new IllegalArgumentException("Operator not found");
}
}
//获取指定运算符的优先级
public static int getOperatorPriority(String operatorName) {
if (operatorMap.containsKey(operatorName)) {
return operatorMap.get(operatorName).getPriority();
} else {
throw new IllegalArgumentException("Operator not found");
}
}
//获取存储运算符及其对应的二元操作的映射(副本)
public static Map<String, BinaryOperator> getOperatorMap() {
return new HashMap<>(operatorMap);
}
}
这段代码就是为了获取用户自定义的所有操作符及其优先级,将他们保存到一个容器中。
最后一段返回一个存储了运算符及其对应的二元操作的映射的副本。
在代码中,operatorMap
是一个存储了运算符及其对应的二元操作的映射的变量。通过调用 getOperatorMap()
方法,会返回 operatorMap
的一个副本,而不是直接返回原始的 operatorMap
对象。
返回副本的作用是为了确保在其他部分使用这个映射时不会直接影响到原始的 operatorMap
对象。这样做可以保护原始的 operatorMap
对象,防止在其他地方进行意外的修改或破坏。
通过返回副本,可以提供对运算符和操作的安全访问,并允许其他部分在需要的情况下对映射进行操作。这种做法有助于保证代码的灵活性和可维护性。
4.计算后缀表达式类 - calculate
package Calculator;
import javax.swing.*;
import java.math.BigInteger;
import java.util.List;
import java.util.Stack;
public class Calculate {
public static BigInteger calculate(List<String> postfixList) { //postfixList 后缀表达式
try {
Stack<BigInteger> dataStack = new Stack<>();
if (postfixList == null || postfixList.isEmpty()) {
throw new IllegalArgumentException("postfixList is empty or null");
}
for (String token : postfixList) {
if (token.matches("\\d+")) { // 若是数字,则将其转为 BigInteger 并压入栈中
dataStack.push(new BigInteger(token));
} else { // 若是运算符
if (dataStack.size() < 2) { //栈内缺少运算数
throw new IllegalStateException("Insufficient operands in the stack");
}
BigInteger num2 = dataStack.pop(); // 弹出栈顶的第二个操作数
BigInteger num1 = dataStack.pop(); // 弹出栈顶的第一个操作数
BinaryOperator operator = OperatorManager.getOperator(token); // 获取运算符对应的二元操作
if (operator == null) {
throw new IllegalArgumentException("Invalid operator: " + token);
} else if (operator.getName().equals("/")) { // 若为除法运算符,则需要检查除数是否为0
if (num2.equals(BigInteger.ZERO)) {
// 若除数为0,弹出错误提示框
JOptionPane pane = new JOptionPane("除数不能为0!", JOptionPane.ERROR_MESSAGE);
JDialog dialog = pane.createDialog(null, "错误");
dialog.setAlwaysOnTop(true);
dialog.setSize(400, 200);
dialog.setVisible(true);
return null;
}
}
BigInteger result = operator.apply(num1, num2); // 应用运算符将结果计算出来
dataStack.push(result); // 将计算结果压入栈中
}
}
if (dataStack.size() != 1) {
throw new IllegalStateException("Invalid postfix expression");
}
return dataStack.pop(); // 返回计算结果
} catch (ArithmeticException e) {
return null; // 若出现算术异常,返回null
} catch (IllegalArgumentException | IllegalStateException e) {
// 若出现非法或无效操作异常,弹出错误提示框
JOptionPane pane = new JOptionPane(e.getMessage(), JOptionPane.ERROR_MESSAGE);
JDialog dialog = pane.createDialog(null, "错误");
dialog.setAlwaysOnTop(true);
dialog.setSize(400, 200);
dialog.setVisible(true);
return null; // 返回null
}
}
}
除了一些个错误提示外,最主要的代码是中间计算的部分:首先遍历转换后的后缀表达式,是数字就直接压入栈中(这个栈是用来暂时存放操作数以及最后计算结果的,因为每次计算后的值还要再压回该栈中,所以最后栈中的数就是计算结果)如果操作数不够两个就会弹出错误提示;否则接着往下执行。当操作数足够且遇到一个运算符时,系统会首先判断该运算符是不是已经在计算器中,如果不在弹出错误提示,如果在,获取该运算符的计算方法执行,结果再压回栈中,一直计算直到出错或计算完毕。
if (dataStack.size() != 1) {
throw new IllegalStateException("Invalid postfix expression");
}
最后的这一步判断就是看最后栈中的结果是不是只有一个元素,若不是,例如最后结果是 5+ 这两个元素,就说明上述计算中出现了错误。
5.框架类 - MyJFrame
GridBagLayout
是 Java Swing 提供的一种布局管理器。它是一种基于网格的布局管理器,可以根据组件的约束条件自适应地调整组件的大小和位置。
GridBagLayout
使用一个网格来布局组件,并可以在每个网格单元中指定组件的位置和大小。每个组件可以具有不同的约束条件,通过设置这些约束条件,可以控制组件在网格中的位置和如何扩展或收缩。
GridBagLayout
使用 GridBagConstraints
类来描述组件的约束条件。通过设置 GridBagConstraints
的属性,如 gridx
、gridy
、gridwidth
、gridheight
、fill
、anchor
等,可以控制组件在网格中的位置和大小,以及在组件的空间不足时如何扩展或收缩。
ContentPane
是 JFrame
、JDialog
、JApplet
等容器组件的容器窗口。在 Swing 中,一个容器窗口通常由多个面板组成,而每个面板上可以再嵌套其他子组件。ContentPane
是容器窗口的顶级面板,它负责管理容器中的所有其他组件。
通过调用 getContentPane()
方法,可以获取容器窗口的 ContentPane
,然后可以使用 setLayout()
方法将布局管理器设置为 ContentPane
的布局管理器。在你的示例中,通过设置 GridBagLayout
为 ContentPane
的布局管理器,表示你希望使用 GridBagLayout
对 ContentPane
的子组件进行布局。
使用 GridBagLayout
布局管理器可以更灵活地控制组件的位置和大小,以适应不同的布局需求。通过设置合适的约束条件和使用适当的方法来操作 GridBagConstraints
,可以实现各种复杂的布局效果。
`setLocationRelativeTo(null)` 是设置窗口在屏幕中居中显示的方法。具体来说,它是一个用于设置窗口位置的方法,常用于在创建窗口后设置窗口的显示位置。
当你调用 `setLocationRelativeTo(null)` 时,它会将窗口的位置设置为屏幕的中央。这意味着无论用户的显示器分辨率如何,窗口都会显示在屏幕中心位置。
这个方法通常在创建并设置好窗口内容后调用,以确保窗口在打开时能够居中显示,让用户更容易注意到并操作窗口中的内容。
总之,调用 `setLocationRelativeTo(null)` 方法可以让窗口在屏幕中居中显示,提升用户体验。
在网格布局中,行和列是通过将容器划分为一个网格来定义的。每个网格单元都可以容纳一个组件。
布局的行和列由容器中的组件位置决定。每个组件在网格布局中被放置在一个确定的行和列。在 `GridBagLayout` 中,可以通过设置 `GridBagConstraints` 的行和列索引来指定组件在布局中的位置。
对于每个容器,可以根据需要定义多少行和多少列。可以根据实际需求调整行和列的数量。默认情况下,`GridBagLayout` 会自动调整行和列的数量以适应容器中的所有组件。
总结一下,网格布局的行和列是通过将容器分割成一个个网格单元来定义的,每个组件占据一个或多个网格单元,并根据设置的行和列索引来决定它们在布局中的位置。
在 `GridBagLayout` 中,横向是指网格布局中的列,而纵向是指网格布局中的行。
这段代码创建了一个名为 `table1` 的 `JTable` 对象,并使用提供的 `data` 数组作为表格的数据模型,`columnNames` 作为表格的列名。
`JTable` 是Swing组件库中的一个表格组件,用于显示和编辑二维表格数据。`data` 数组包含了要显示在表格中的实际数据,而 `columnNames` 数组包含了表格每一列的标题。
接下来,通过创建 `JScrollPane` 对象 `scrollPane1`,将 `table1` 放入滚动面板中。这是因为,如果表格中的数据过多超过了表格的可见区域,可以通过滚动面板来滚动查看整个表格。
package Calculator;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import Calculator.OperatorManager.*;
import static Calculator.Calculate.calculate;
import static Calculator.Convert.*;
class MyFrame extends JFrame{
private JTextArea expressionTextField; // 声明为类成员变量
private JTextArea postfixTextField; // 声明为类成员变量
private JTextArea resultTextField; // 声明为类成员变量
private JTable table;
private DefaultTableModel model;
public MyFrame(String title){
super(title);
// 设置窗口框架的默认关闭操作为退出应用程序
setDefaultCloseOperation(EXIT_ON_CLOSE);
// 创建一个GridBagLayout布局管理器,并将其设置为ContentPane的布局管理器
GridBagLayout layout = new GridBagLayout();
getContentPane().setLayout(layout);
// 设置窗口框架的大小和位置
setSize(900, 700);
setLocationRelativeTo(null); // 居中显示窗口
GridBagConstraints gbc = new GridBagConstraints();
// Labels
Font labelFont = new Font("Arial", Font.PLAIN, 24); // 创建字体
// 创建Expression标签,并设置字体
JLabel expressionLabel = new JLabel("Expression:");
expressionLabel.setFont(labelFont);
// 将Expression标签添加到窗口中
addComponent(this, layout, gbc, expressionLabel, 0, 0, 1, 1);
// 创建postfix标签,并设置字体
JLabel postfixLabel = new JLabel("postfix:");
postfixLabel.setFont(labelFont);
// 将postfix标签添加到窗口中
addComponent(this, layout, gbc,postfixLabel, 0, 1, 1, 1);
// 创建result标签,并设置字体
JLabel resultLabel = new JLabel(" result:");
resultLabel.setFont(labelFont);
// 将result标签添加到窗口中
addComponent(this, layout, gbc, resultLabel, 0, 2, 1, 1);
// Text fields
// 创建Expression文本框,并设置样式和字体
expressionTextField = new JTextArea(5, 30);
expressionTextField.setBorder(BorderFactory.createLineBorder(Color.BLACK)); // 添加边框
expressionTextField.setWrapStyleWord(true);
expressionTextField.setLineWrap(true);
expressionTextField.setFont(expressionTextField.getFont().deriveFont(24f)); // 设置字体大小
// 将Expression文本框添加到窗口中
addComponent(this, layout, gbc, expressionTextField, 1, 0, 1, 1);
// 创建postfix文本框,并设置样式和字体
postfixTextField = new JTextArea(5, 30);
postfixTextField.setBorder(BorderFactory.createLineBorder(Color.BLACK)); // 添加边框
postfixTextField.setWrapStyleWord(true);
postfixTextField.setLineWrap(true);
postfixTextField.setFont(postfixTextField.getFont().deriveFont(24f)); // 设置字体大小
// 将postfix文本框添加到窗口中
addComponent(this, layout, gbc, postfixTextField, 1, 1, 1, 1);
// 创建result文本框,并设置样式和字体
resultTextField = new JTextArea(5, 30);
resultTextField.setBorder(BorderFactory.createLineBorder(Color.BLACK)); // 添加边框
resultTextField.setWrapStyleWord(true);
resultTextField.setLineWrap(true);
resultTextField.setFont(resultTextField.getFont().deriveFont(24f)); // 设置字体大小
// 将result文本框添加到窗口中
addComponent(this, layout, gbc, resultTextField, 1, 2, 1, 1);
// 创建"OP"按钮,并设置字体
JButton opBt = new JButton("OP");
opBt.setFont(labelFont);
addComponent(this, layout, gbc, opBt, 0, 3, 1, 1);
// 创建"Calculate"按钮,并设置字体
JButton calBt= new JButton("Calculate");
calBt.setFont(labelFont);
addComponent(this, layout, gbc, calBt, 1, 3, 1, 1);
// 创建"Clear"按钮,并设置字体
JButton clearBt = new JButton("Clear");
clearBt.setFont(labelFont);
addComponent(this, layout, gbc, clearBt, 2, 3, 1, 1);
// 给"Calculate"按钮添加点击事件监听器,处理计算逻辑
calBt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 获取中缀表达式并转换为后缀表达式
String infixExpression = expressionTextField.getText();
try {
List<String> postfixExpression = convertS(endList(infixExpression));
if (postfixExpression.isEmpty()) {
postfixTextField.setText("中缀表达式为空,请输入!");
resultTextField.setText("");
} else {
// 在界面上显示后缀表达式
String postfixString = joinS(postfixExpression);
postfixTextField.setText(postfixString);
// 计算后缀表达式的值并显示在界面上
BigInteger result = calculate(postfixExpression);
resultTextField.setText(String.valueOf(result));
}
} catch (IllegalArgumentException ee) {
postfixTextField.setText("运算符优先级设置不合理,请重新输入!");
resultTextField.setText("");
}
}
});
// 给"Clear"按钮添加点击事件监听器,清空输入和计算结果
clearBt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
expressionTextField.setText("");
postfixTextField.setText("");
resultTextField.setText("");
}
});
opBt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 创建新的弹出框架
JFrame popUpFrame = new JFrame("Operators");
popUpFrame.setSize(600, 500);
popUpFrame.setLocationRelativeTo(null);
// 从OperatorManager中获取所有运算符及其优先级
Map<String, BinaryOperator> operatorMap = OperatorManager.getOperatorMap();
String[] columnNames = {"Names", "Priorities"};
// 根据运算符数量创建合适大小的二维数组
Object[][] data = new Object[operatorMap.size()][3];
int index = 0;
Map<String, Integer> priorityChanges = new HashMap<>(); // 保存运算符优先级的修改
for (Map.Entry<String, BinaryOperator> entry : operatorMap.entrySet()) {
data[index][0] = entry.getKey(); // 运算符名称
data[index][1] = entry.getValue().getPriority(); // 运算符优先级
JButton editButton = new JButton("Edit");
// 创建表格及其滚动面板
JTable table1 = new JTable(data, columnNames);
JScrollPane scrollPane1 = new JScrollPane(table1);
// 设置字体
table1.setFont(new Font("Arial", Font.BOLD, 18));
// 新增确定按钮
JButton confirmButton = new JButton("OK");
confirmButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
// 在此处实现保存修改后的优先级到OperatorManager中的逻辑
// 更新OperatorManager中对应运算符的优先级
for (Map.Entry<String, Integer> changeEntry : priorityChanges.entrySet()) {
String operatorName = changeEntry.getKey();
int newPriority = changeEntry.getValue();
operatorMap.get(operatorName).setPriority(newPriority);
}
// 清空priorityChanges,以便下次使用
priorityChanges.clear();
}
});
// 将表格和确定按钮添加到弹出框中
JPanel panel = new JPanel();
panel.add(confirmButton);
panel.add(editButton);
popUpFrame.add(panel, BorderLayout.SOUTH); // 放置在底部
popUpFrame.add(scrollPane1, BorderLayout.CENTER); // 放置在中间
popUpFrame.setVisible(true);
// 为编辑按钮添加事件监听器
editButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
// 在此处实现更改运算符优先级的逻辑
String operatorName = JOptionPane.showInputDialog("请输入要修改的运算符:");
// 获取用户输入的新优先级
String input = JOptionPane.showInputDialog("请输入新的优先级:");
int newPriority;
try {
newPriority = Integer.parseInt(input);
if (newPriority < 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(popUpFrame, "无效的输入,请输入一个非负整数!", "错误", JOptionPane.ERROR_MESSAGE);
return; // 提示错误后终止逻辑继续执行
}
// 保存优先级的修改
priorityChanges.put(operatorName, newPriority);
// 更新表格中对应行的优先级显示
for (int i = 0; i < data.length; i++) {
if (data[i][0].equals(operatorName)) {
data[i][1] = newPriority;
break;
}
}
// 刷新表格
table1.repaint();
}
});
data[index][2] = editButton; // 编辑按钮
index++;
}
}
});
setResizable(true); // 可调整窗口大小
this.setVisible(true);
}
//添加组件到容器中的方法
private static void addComponent(Container container,
GridBagLayout layout,
GridBagConstraints gbc, // gbc GridBagConstraints约束
Component component, // component 待添加的组件
int x, // 组件的x坐标,列坐标
int y,
int width, // 组件的宽度
int height) {
gbc.gridx = x;
gbc.gridy = y;
gbc.gridwidth = width;
gbc.gridheight = height;
gbc.weightx = 1; // 横向填充权重
gbc.weighty = 1; // 纵向填充权重
layout.setConstraints(component, gbc);
container.add(component);
}
public void setInputText(String text) {
expressionTextField.setText(text);
}
public void setPostfixText(String text) {
postfixTextField.setText(text);
}
public void setResultText(int text) {
resultTextField.setText(String.valueOf(text));
}
}
写在最最后:本次Java作业做的匆忙,坦白地说大部分是根据csdn上现有的代码结合chatgpt一起改的,所以好多地方表面上看逻辑都对,但是实际上都是错的。
比如说,明明设置了除数为0时不能再转换,他还是转换了;明明设置了当运算符没有预先被存放到计算器内就要弹出错误提示,但是系统会将为输入的运算符看作是“数字”而检测不出来是运算符,所以也会出错;还有特别明显的错误:运算符的优先级是不能对用户开放进行修改的,否则会导致计算错误,这里当初是想着作为扩展,但实际上也是一种逻辑错误,时间紧我也没进行修改就直接交了;还有一些看着十分冗杂的错误提示也没来得及细改;还有括号不匹配时也不能错误提示......
总而言之,这份代码如果是为了糊弄,还是能糊弄过去的,毕竟正常的计算也都能计算而且结果也正确,但是细究那就是漏洞百出了。
如果有时间的话,我会再细改学习的,这次是为了应付期末考,就先这样吧。