本文用Java的swing来实现一个简单计算器,主要内容为图形用户界面GUI的实现以及运算表达式核心算法的设计编写。
程序运行环境为Windows10 ,编译环境为MyEclipse 。
一、具体功能:
1、:输入,输出
输入:允许输入带有括号的完整计算式(例 8*(4-95)+5÷2*e-pi)
输出:输出Double类型的结果
输出:整个运算表达式并保存于历史记录中
2、:功能
基本的加,减,乘,除,四则运算
平方运算
开方运算
求余运算
最终界面如下图:
除了常规的数字按钮和运算符,还有两个常数e,pi(π),清空键AC,括号运算符(),平方(x^x)和开方(sqrt)运算符,输入显示框以及历史记录文本框,文本框的垂直滚动条和水平滚动条。
二、主要思想:
1:中缀表达式转为后缀表达式
准备:
①后缀表达式队列:postQueue,用于存储逆波兰表达式(其实不用队列排序直接输出也行)
②操作符栈:opStack,对用户输入的操作符进行处理,用于存储运算符
算法思想:
从左向右依次读取算术表达式的元素X,分以下情况进行不同的处理:
(1)如果X是操作数,直接入队
(2)如果X是运算符,再分以下情况:
a)如果栈为空,直接入栈。
b)如果X==”(“,直接入栈。
c)如果X==”)“,则将栈里的元素逐个出栈,并入队到后缀表达式队列中,直到第一个配对的”(”出栈。(注:“(”和“)”都不 入队)
d)如果是其他操作符(+ - * /),则和栈顶元素进行比较优先级。 如果栈顶元素的优先级大于等于X,则出栈并把栈中弹出的元素入队,直到栈顶元素的优先级小于X或者栈为空。弹出完这些元素后,才将遇到的操作符压入到栈中。
(3)最后将栈中剩余的操作符全部入队。
示意图:
2、计算后缀表达式
准备:
需要用到一个结果栈Res_Stack :用于存放计算的中间过程的值和最终结果
算法思想:
1、从左开始向右遍历后缀表达式的元素。
2、如果取到的元素是操作数,直接入栈Res_Stack,如果是运算符,从栈中弹出2个数进行运算,然后把运算结果入栈
3、当遍历完后缀表达式时,计算结果就保存在栈里了。
示意图:
三、结果测试
分析:
1、可实现基本四则运算及平方、开方、求余运算。
2、运算表达式可显示于输入界面并保存于历史记录栏
3、输入界面和历史记录栏皆可实现不断字自动换行功能以及滚动条功能
四、完整源代码( 代码已附有详细注释 )
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Objects;
import java.util.Stack;
import javax.swing.*;
//Calculator类,继承JFrame框架,实现事件监听器接口
public class Calculator extends JFrame implements ActionListener {
private final String[] KEYS = {"7", "8", "9", "AC", "4", "5", "6", "-", "1", "2", "3", "+", "0", "e", "pi", "/", "sqrt", "%", "x*x", "*", "(", ")", ".", "="};
private JButton keys[] = new JButton[KEYS.length];
private JTextArea resultText = new JTextArea("0.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 JLabel label = new JLabel("历史记录");
private String input = ""; //计算文本框输入的中缀表达式
// 构造方法
public Calculator() {
super("Caculator");//“超”关键字,表示调用父类的构造函数,
resultText.setBounds(20, 18, 255, 115); // 设置文本框大小
resultText.setAlignmentX(RIGHT_ALIGNMENT); // 文本框内容右对齐
resultText.setEditable(false); // 文本框不允许修改结果
resultText.setFont(new Font("monospaced", Font.PLAIN, 18)); //设置结果文本框输入文字的字体、类型、大小
History.setFont(new Font("monospaced", Font.PLAIN, 18)); //设置历史记录文本框输入文字的字体、类型、大小
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); // 新建“历史记录”标签
// 放置按钮 x,y为按钮的横纵坐标
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, 40);
if (x < 215) {
x += 65;
} else {
x = 20;
y += 45;
}
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) {
String label = e.getActionCommand(); //获得事件源的标签
if (Objects.equals(label, "AC")) //清空按钮,消除显示屏文本框前面所有的输入和结果
{
input = "";
resultText.setText("0.0"); //更新文本域的显示,显示初始值;
} else if (Objects.equals(label, "sqrt")) {
String n;
if(input.isEmpty()) n="error!"; //加判断,先输入数字再输入开方符号才是合法的
else n = String.valueOf(kfys(input));
resultText.setText("sqrt" + "(" + input + ")" + "=" + n); //使运算表达式显示在输入界面
History.setText(History.getText() + resultText.getText() + "\n"); //获取输入界面的运算表达式并使其显示在历史记录文本框
input = n;
} else if (Objects.equals(label, "x*x")) {
String m;
if(input.isEmpty()) m="error!";
else m = String.valueOf(pfys(input));
resultText.setText(input + "^2" + "=" + m);
History.setText(History.getText() + resultText.getText() + "\n");
input = m;
} else if (Objects.equals(label, "=")) {
if (input.isEmpty()) return;
String[] s = houzhui(input); //将中缀表达式转换为后缀表达式
double result = Result(s); //计算后缀表达式得出最终算式结果
resultText.setText(input + "=" + result);
History.setText(History.getText() + resultText.getText() + "\n");
} else {
if (Objects.equals(label, "e")) {
String m = String.valueOf(2.71828); //将e的值以字符串的形式传给m
label = m;
} else if (Objects.equals(label, "pi")) {
String m = String.valueOf(3.14159);
label = m;
}
input = input + label;
resultText.setText(input);
}
}
//将中缀表达式转换为后缀表达式
private String[] houzhui(String infix) { //infix 中缀
String s = "";// 用于承接多位数的字符串
Stack<String> opStack = new Stack<String>(); // 操作符静态栈,对用户输入的操作符进行处理,用于存储运算符
Stack<String> postQueue = new Stack<String>(); // 后缀表达式,为了将多位数存储为独立的字符串
System.out.println("中缀:" + infix);
for (int i = 0; i < infix.length(); i++) // 遍历中缀表达式
// indexof函数,返回字串首次出现的位置;charAt函数返回index位置处的字符;
{
if ("1234567890.".indexOf(infix.charAt(i)) >= 0) { // 遇到数字字符直接入队
//判断并记录多位操作数,比如,中缀表达式:234+4*2,我们扫描这个字符串的时候,s的作用相当于用来存储长度为3个字符的操作数:234
s = "";// 作为承接字符,每次开始时都要清空
for (; i < infix.length() && "0123456789.".indexOf(infix.charAt(i)) >= 0; i++) {
s = s + infix.charAt(i);
}
i--; //避免跳过对非数字字符的处理
postQueue.push(s); // 数字字符直接加入后缀表达式
} else if ("(".indexOf(infix.charAt(i)) >= 0) { // 遇到左括号
opStack.push(String.valueOf(infix.charAt(i))); // 左括号入栈
} else if (")".indexOf(infix.charAt(i)) >= 0) { // 遇到右括号
while (!opStack.peek().equals("(")) { // 栈顶元素循环出栈,直到遇到左括号为止
postQueue.push(opStack.pop());
}
opStack.pop(); //删除左括号
} else if ("*%/+-".indexOf(infix.charAt(i)) >= 0) // 遇到运算符
{
if (opStack.empty() || "(".contains(opStack.peek())) { // 若栈为空或栈顶元素为左括号则直接入栈
opStack.push(String.valueOf(infix.charAt(i)));
} else {
// 当栈顶元素为高优先级或同级运算符时,让栈顶元素出栈进入后缀表达式后,直到符合规则后,当前运算符再入栈
boolean rule = ("*%/+-".contains(opStack.peek()) && "+-".indexOf(infix.charAt(i)) >= 0) || ("*%/".contains(opStack.peek()) && "*%/".indexOf(infix.charAt(i)) >= 0);
while (!opStack.empty() && rule) {
postQueue.push(opStack.peek()); //peek()方法:返回栈顶的元素但不移除它
opStack.pop();
}
opStack.push(String.valueOf(infix.charAt(i))); // 当前元素入栈
}
}
}
while (!opStack.empty()) {// 遍历结束后将栈中剩余元素依次出栈进入后缀表达式
postQueue.push(opStack.pop());
}
//将后缀表达式栈转换为字符串数组格式
String[] suffix = new String[postQueue.size()];
for (int i = postQueue.size() - 1; i >= 0; i--) {
suffix[i] = postQueue.pop();
}
System.out.println("后缀:" + Arrays.toString(suffix.clone()));
return suffix;
}
//开方运算方法
public double kfys(String str) {
double a = Double.parseDouble(str);
return Math.sqrt(a);
}
//平方运算方法
public double pfys(String str) {
double a = Double.parseDouble(str);
return Math.pow(a, 2);
}
// 计算后缀表达式,并返回最终结果
public double Result(String[] suffix) { //suffix 后缀
Stack<String> Result = new Stack<>();// 顺序存储的栈,数据类型为字符串
int i;
for (i = 0; i < suffix.length; i++) {
if ("1234567890.".indexOf(suffix[i].charAt(0)) >= 0) { //遇到数字,直接入栈
Result.push(suffix[i]);
} else { // 遇到运算符字符,将栈顶两个元素出栈计算并将结果返回栈顶
double x, y, n = 0;
x = Double.parseDouble(Result.pop()); // 顺序出栈两个数字字符串,并转换为double类型
y = Double.parseDouble(Result.pop());
switch (suffix[i]) {
case "*":
n = y * x;
break;
case "/":
if (x == 0) return 000000;
else n = y / x;
break;
case "%":
if (x == 0) return 000000;
else n = y % x;
break;
case "-":
n = y - x;
break;
case "+":
n = y + x;
break;
}
Result.push(String.valueOf(n)); // 将运算结果重新入栈
}
}
System.out.println("return:" + Result.peek());
return Double.parseDouble(Result.peek()); // 返回最终结果
}
// 主函数
public static void main(String[] args) {
Calculator a = new Calculator();
}
}