一.实验要求
1.能通过设计的按钮控件输入并实现算术表达式,表达式在文本框中显示,运算结果输出显示;保存和浏览历史运算记录;
2.能够检验算术表达式的合法性;
3.能够实现混合运算的求解,算术表达式中包含加、减、乘、除、括号等运算符;
4.要求交互界面友好,程序健壮。
二.运算规则:
先乘除,后加减,从左到右计算,先括号内,后括号外;
中缀表达式 A+(B-C/D)*E
后缀表达式 ABCD/-E*+
三.算法思想
单栈
(1)利用一个字符可变序列postQueue和操作符栈(opStack)将键盘输入的中缀表达式转换为后缀表达式。其中,postQueue存放后缀表达式的结果,操作符栈(opStack)用于存储运算符。
算法思想:
把中缀表达式当作一个字符串,表达式从左到右一个个字符判断,规则如下:
① 如果是数字,直接存进postQueue。
② 如果字符加减乘除和左括号:
A.栈为空,直接将操作符入栈;
B.假设现在的操作符是c,栈顶的操作符是s
a. 若c的优先级大于s,直接入栈;
b.若c的优先级小于s,将栈顶操作符弹出到postQueue,知道栈顶元素s优先级低于c或栈为空。弹出完这些元素后,再将c压入到栈中;
c.若栈顶元素是(,则直接入栈;
③如果字符是) 右括号,则依次将栈顶元素添加进postQueue,直到遇到左括号,左括号不加进postQueue,直接丢弃。
④字符判断完了之后,如果栈不为空,则依次输出栈的元素到postQueue。
(2)利用栈来进行后缀表达式值的求解,此栈用于存放计算的中间过程和最终结果。
算法思想:
把后缀表达式看作是一个字符串,然后从左到右一个一个字符进行判断,规则如下:
①如果是数字,直接进栈。
②如果是+或*,则弹出栈顶两个元素,进行相加或相乘,将计算结果压入栈中。
③ 如果是-或/,则先弹出一个数字为x,再弹出一个数字为y,然后y-x或y/x,最后将计算结果压入栈中。
(3)输出计算结果。
四.源代码
package com.company;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Objects;
import javax.swing.*;
//Calculator类,继承JFrame框架,实现事件监听器接口
class Calculator extends JFrame implements ActionListener {
private final String[] KEYS = { "7", "8", "9", "C", "4", "5", "6", "+", "1", "2", "3", "-", "0", "*", "%", "/", "(", ")", ".", "=" };
private JButton keys[] = new JButton[KEYS.length];
private JTextArea resultText = new JTextArea("0");// 文本域组件TextArea可容纳多行文本;文本框内容初始值设为0.0
private JTextArea History = new JTextArea();// 历史记录文本框初始值设为空
private JPanel jp2=new JPanel();
private JScrollPane gdt1=new JScrollPane(resultText);//给输入显示屏文本域新建一个垂直滚动滑条
private JScrollPane gdt2=new JScrollPane(History);//给历史记录文本域新建一个垂直滚动滑条
// private JScrollPane gdt3=new JScrollPane(History);//给历史记录文本域新建一个水平滚动滑条
private JLabel label = new JLabel("历史记录");
private String b = "";
int flash=0;
int flash2=0;
int flash3=0;
// 构造方法
public Calculator() {
super("简易计算器");//“超”关键字,表示调用父类的构造函数,
resultText.setBounds(20, 18, 255, 115);// 设置文本框大小
resultText.setAlignmentX(RIGHT_ALIGNMENT);// 文本框内容右对齐
resultText.setEditable(false);// 文本框不允许修改结果
History.setBounds(290, 40, 250,370);// 设置文本框大小
History.setAlignmentX(LEFT_ALIGNMENT);// 文本框内容右对齐
History.setEditable(false);// 文本框不允许修改结果
label.setBounds(300, 15, 100, 20);//设置标签位置及大小
jp2.setBounds(290,40,250,370);//设置面板窗口位置及大小
jp2.setLayout(new GridLayout());
JPanel jp1 = new JPanel();
jp1.setBounds(20,18,255,115);//设置面板窗口位置及大小
jp1.setLayout(new GridLayout());
resultText.setLineWrap(true);// 激活自动换行功能
resultText.setWrapStyleWord(true);// 激活断行不断字功能
resultText.setSelectedTextColor(Color.RED);
History.setLineWrap(true);//自动换行
History.setWrapStyleWord(true);
History.setSelectedTextColor(Color.blue);
gdt1.setViewportView(resultText);//使滚动条显示出来
gdt2.setViewportView(History);
gdt1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);//设置让垂直滚动条一直显示
gdt2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);//设置让垂直滚动条一直显示
gdt2.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);//设置让水平滚动条一直显示
jp1.add(gdt1);//将滚动条添加入面板窗口中
jp2.add(gdt2);
this.add(jp1);//将面板添加到总窗体中
this.add(jp2);//将面板添加到总窗体中
this.setLayout(null);
this.add(label);// 新建“历史记录”标签
//this.add(resultText);// 新建文本框,该语句会添加进去一个新的JTextArea导致带有滚动条的文本无法显示或者发生覆盖
//this.add(History);// 新建历史记录文本框,该语句会添加进去一个新的JTextArea导致带有滚动条的文本无法显示
// 放置按钮
int x = 20, y = 150;
for (int i = 0; i < KEYS.length; i++)
{
keys[i] = new JButton();
keys[i].setText(KEYS[i]);
keys[i].setBounds(x, y, 60, 50);
if (x < 215) {
x += 65;
} else {
x = 20;
y += 53;
}
this.add(keys[i]);
}
for (int i = 0; i < KEYS.length; i++)// 每个按钮都注册事件监听器
{
keys[i].addActionListener(this);
}
this.setResizable(false);
this.setBounds(500, 200, 567, 480);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
}
// 事件处理
public void actionPerformed(ActionEvent e)
{
//History.setText(b);//使输入的表达式显示在历史记录文本框中
String label=e.getActionCommand();//获得事件源的标签int flash=1;
if(Objects.equals(label, "="))//
{
resultText.setText(this.b);
History.setText(History.getText()+resultText.getText());
if(label.equals("="))//调用计算方法,得出最终结果
{
String[] s =houzhui(this.b);
String result=Result(s);
this.b=result+"";
//更新文本框,当前结果在字符串b中,并未删除,下一次输入接着此结果以实现连续运算
resultText.setText(this.b);
History.setText(History.getText()+"="+resultText.getText()+"\n");
}
}
else if(Objects.equals(label, "C"))//清空按钮,消除显示屏文本框前面所有的输入和结果
{
this.b="";
resultText.setText("0");//更新文本域的显示,显示初始值;
flash=0;
}
else if(label.equals("+")){
if(b.charAt(b.length()-1)!='+'&&b.charAt(b.length()-1)!='-'&&b.charAt(b.length()-1)!='*'&&b.charAt(b.length()-1)!='/'&&b.charAt(b.length()-1)!='('&&b.charAt(b.length()-1)!='.'&&b.charAt(b.length()-1)!='%'){
this.b = this.b + label;
resultText.setText(this.b);
flash=1;
}
}
else if(label.equals("-")){
if(b.charAt(b.length()-1)!='-'&&b.charAt(b.length()-1)!='+'&&b.charAt(b.length()-1)!='*'&&b.charAt(b.length()-1)!='/'&&b.charAt(b.length()-1)!='.'&&b.charAt(b.length()-1)!='%') {
this.b = this.b + label;
resultText.setText(this.b);
flash=1;
}
}else if(label.equals("*")){
if(b.charAt(b.length()-1)!='*'&&b.charAt(b.length()-1)!='-'&&b.charAt(b.length()-1)!='+'&&b.charAt(b.length()-1)!='/'&&b.charAt(b.length()-1)!='('&&b.charAt(b.length()-1)!='.'&&b.charAt(b.length()-1)!='%') {
this.b = this.b + label;
resultText.setText(this.b);
flash=1;
}
}else if(label.equals("/")){
if(b.charAt(b.length()-1)!='/'&&b.charAt(b.length()-1)!='-'&&b.charAt(b.length()-1)!='*'&&b.charAt(b.length()-1)!='+'&&b.charAt(b.length()-1)!='('&&b.charAt(b.length()-1)!='.'&&b.charAt(b.length()-1)!='%') {
this.b = this.b + label;
resultText.setText(this.b);
flash=1;
}
}
else if(label.equals("%")){
if(b.charAt(b.length()-1)!='*'&&b.charAt(b.length()-1)!='-'&&b.charAt(b.length()-1)!='+'&&b.charAt(b.length()-1)!='/'&&b.charAt(b.length()-1)!='('&&b.charAt(b.length()-1)!='.'&&b.charAt(b.length()-1)!='%'){
this.b=this.b+label;
resultText.setText(this.b);
flash=1;
}
}
else if(label.equals(".")){
if (flash==0){
this.b=this.b+label;
resultText.setText(this.b);
flash=1;
}
}
else if(label.equals("(")) {
flash2++;
if (b.charAt(b.length()-1)!=')'&&b.charAt(b.length()-1)!='1'&&b.charAt(b.length()-1)!='2'&&b.charAt(b.length()-1)!='3'&&b.charAt(b.length()-1)!='4'&&b.charAt(b.length()-1)!='5'&&b.charAt(b.length()-1)!='6'&&b.charAt(b.length()-1)!='7'&&b.charAt(b.length()-1)!='8'&&b.charAt(b.length()-1)!='9'&&b.charAt(b.length()-1)!='0'&&b.charAt(b.length()-1)!='.'&&b.charAt(b.length()-1)!=')'&&b.charAt(b.length()-1)!='%') {
this.b = this.b + label;
resultText.setText(this.b);
}
}
else if(label.equals(")")) {
flash3++;
if (b.charAt(b.length()-1)!=' '&&b.charAt(b.length()-1)!='.'&&b.charAt(b.length()-1)!='-'&&b.charAt(b.length()-1)!='*'&&b.charAt(b.length()-1)!='/'&&b.charAt(b.length()-1)!='('&&b.charAt(b.length()-1)!='+'&&b.charAt(b.length()-1)!='('&&flash3<=flash2) {
this.b = this.b + label;
resultText.setText(this.b);
}
}
else
{ if(label.equals("+")||label.equals("-")||label.equals("*")||label.equals("/"))flash=0;
this.b=this.b+label;
resultText.setText(this.b);
// History.setText(History.getText()+this.b);
}
//History.setText(History.getText()+this.b);//使输入的表达式显示在历史记录文本框中
}
//将中缀表达式转换为后缀表达式
private String[] houzhui(String str) {
String s = "";// 用于承接多位数的字符串
char[] opStack = new char[100];// 静态栈,对用户输入的操作符进行处理,用于存储运算符
String[] postQueue = new String[100];// 后缀表达式字符串数组,为了将多位数存储为独立的字符串
int top = -1, j = 0;// 静态指针top,控制变量j
for(int i=0;i<str.length();i++){
if(str.charAt(i)=='('&&str.charAt(i+1)=='-'){//将"(-"变为”(0-“
str=str.substring(0,i+1)+"0"+str.substring(i+1);
}
}
for (int i = 0; i < str.length(); i++)// 遍历中缀表达式
// indexof函数,返回字串首次出现的位置;charAt函数返回index位置处的字符;
{
if ("0123456789.".indexOf(str.charAt(i)) >= 0) // 遇到数字字符的情况直接入队
{
s = "";// 作为承接字符,每次开始时都要清空
for (; i < str.length() && "0123456789.".indexOf(str.charAt(i)) >= 0; i++) {
s = s + str.charAt(i);
//比如,中缀表达式:234+4*2,我们扫描这个字符串的时候,s的作用相当于用来存储长度为3个字符的操作数:234
}
i--;
postQueue[j] = s;// 数字字符直接加入后缀表达式
j++;
}
else if ("(".indexOf(str.charAt(i)) >= 0) {// 遇到左括号
top++;
opStack[top] = str.charAt(i);// 左括号入栈
}
else if (")".indexOf(str.charAt(i)) >= 0) {// 遇到右括号
for (;;)// 栈顶元素循环出栈,直到遇到左括号为止
{
if (opStack[top] != '(') {// 栈顶元素不是左括号
postQueue[j] = opStack[top] + "";// 栈顶元素出栈
j++;
top--;
} else { // 找到栈顶元素是左括号
top--;// 删除栈顶左括号
break;// 循环结束
}
}
}
else if ("*%/+-".indexOf(str.charAt(i)) >= 0)// 遇到运算符
{
if (top == -1)
{// 若栈为空则直接入栈
top++;
opStack[top] = str.charAt(i);
}
else if ("*%/".indexOf(opStack[top]) >= 0)
{// 当栈顶元素为高优先级运算符时,让栈顶元素出栈进入后缀表达式后,当前运算符再入栈
postQueue[j] = opStack[top] + "";
j++;
opStack[top] = str.charAt(i);
}
else
{
top++;
opStack[top] = str.charAt(i);// 当前元素入栈
}
}
}
while (top != -1) {// 遍历结束后将栈中剩余元素依次出栈进入后缀表达式
postQueue[j] = opStack[top] + "";
j++;
top--;
}
return postQueue;
}
// 计算后缀表达式,并返回最终结果
public String Result(String[] str) {
String[] Result = new String[100];// 顺序存储的栈,数据类型为字符串
int Top = -1;// 静态指针Top
for (int i = 0; str[i] != null; i++) {
if ("+-*%/".indexOf(str[i]) < 0) { //遇到数字,直接入栈
Top++;
Result[Top] = str[i];
}
if ("+-*%/".indexOf(str[i]) >= 0)// 遇到运算符字符,将栈顶两个元素出栈计算并将结果返回栈顶
{
double x, y, n;
x = Double.parseDouble(Result[Top]);// 顺序出栈两个数字字符串,并转换为double类型
Top--;
y = Double.parseDouble(Result[Top]);
Top--;
if ("*".indexOf(str[i]) >= 0) {
n = y * x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新入栈
}
if ("/".indexOf(str[i]) >= 0)
{
if (x == 0)// 被除数不允许为0
{
String s = "error!";
return s;
} else {
n = y / x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新入栈
}
}
if ("%".indexOf(str[i]) >= 0)
{
if (x == 0)// 被除数不允许为0
{
String s = "error!";
return s;
} else {
n = y % x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新入栈
}
}
if ("-".indexOf(str[i]) >= 0) {
n = y - x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新入栈
}
if ("+".indexOf(str[i]) >= 0) {
n = y + x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新入栈
}
}
}
return Result[Top];// 返回最终结果
}
// 主函数
public static void main(String[] args) {
Calculator a = new Calculator();
}
}