学习使用图形用户界面( GUI) 设计实现—个简单的计算器,能进行 +‐ * / 四则运算(注意数字大小的越界考虑,—般计算器输入的数字个数不超过16个)
页面布局设计图
监听程序设计流程图
源代码
Calculator.java
package calculatorDemo;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.text.DecimalFormat;
public class Calculator implements ActionListener {
private JFrame frame = new JFrame();
//计算器上各按键的名字(从上到下,从左到右)
private String[] keys = {"(",")","Back","/","7","8","9","*","4","5","6","-","1","2","3","+","+/-","0",".","="};
//计算器上按键的按钮
private JButton[] buttons = new JButton[keys.length];
private JButton buttonClear = new JButton("Clear");
//显示输入式子文本框
private JTextField inText = new JTextField("");
//显示计算结果文本框
private JTextField resultText = new JTextField("0");
//标志用户按的是否是整个表达式的第一个数字,或者是运算符后的第一个数字
private boolean firstDigit = true;
//输入表达式
private String expression = "";
//计算的中间结果
private double resultNum = 0.0000;
public Calculator() {
//初始化计算器
init();
//窗口标题
frame.setTitle("计算器 -- @author: rimianxing");
//设置窗口位置及大小
frame.setSize(500,700);
frame.setLocation(500,300);
//允许修改计算器窗口的大小
frame.setResizable(true);
//设置窗口可见
frame.setVisible(true);
//点关闭则退出程序
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//初始化计算器
private void init() {
//功能键和运算符颜色
Color color1 = new Color(181,181,181);
//等于号专属颜色
Color color2 = new Color(126,192,238);
//背景颜色
Color color3 = new Color(232,232,232);
//建立一个画板放文本框
JPanel textPanel = new JPanel();
textPanel.setLayout(new BorderLayout());
textPanel.add(BorderLayout.NORTH,inText);
textPanel.add(BorderLayout.CENTER,resultText);
//设置文本框中文字的字体以及大小,加粗
inText.setFont(new Font("楷体",Font.BOLD,20));
resultText.setFont(new Font("楷体",Font.BOLD,43));
//文本框中的内容采用右对齐方式
inText.setHorizontalAlignment(JTextField.RIGHT);
resultText.setHorizontalAlignment(JTextField.RIGHT);
//不能修改结果文本框
inText.setEditable(false);
resultText.setEditable(false);
//删除文本框的边框
inText.setBorder(null);
resultText.setBorder(null);
//设置文本框背景颜色
inText.setBackground(color1);
resultText.setBackground(color1);
//初始化计算器上键的按钮,将键放在一个画板内
JPanel keysPanel = new JPanel();
//用网格布局器,5行,4列的网格,网格之间的水平方向垂直方向间隔均为2个像素
keysPanel.setLayout(new GridLayout(5, 4, 2, 2));
//初始化功能按钮
for(int i = 0; i < 4; i++) {
buttons[i] = new JButton(keys[i]);
keysPanel.add(buttons[i]);
buttons[i].setBackground(color3);
buttons[i].setForeground(Color.black);
buttons[i].setFont(new Font(Font.SERIF, Font.PLAIN, 18));
//去除按钮的边框
buttons[i].setBorderPainted(false);
}
//初始化运算符及数字键按钮
for(int i = 4; i < keys.length; i++) {
buttons[i] = new JButton(keys[i]);
keysPanel.add(buttons[i]);
if((i+1) % 4 == 0)
buttons[i].setBackground(color3);
else
buttons[i].setBackground(Color.white);
buttons[i].setForeground(Color.black);
buttons[i].setFont(new Font(Font.SERIF, Font.PLAIN, 18));
//去除按钮的边框
buttons[i].setBorderPainted(false);
}
//'='符键用特殊颜色
buttons[keys.length-1].setBackground(color2);
keysPanel.setBackground(color1);
//建立一个画板放清除按钮
JPanel clearPanel = new JPanel();
clearPanel.setLayout(new BorderLayout());
clearPanel.add(buttonClear);
buttonClear.setBackground(color3);
buttonClear.setForeground(Color.black);
buttonClear.setFont(new Font(Font.SERIF, Font.PLAIN, 18));
//去除按钮的边框
buttonClear.setBorderPainted(false);
clearPanel.setBackground(color1);
//将文本框所在的面板放在北部,将keysPanel面板放在计算器的中部,将清除按钮所在的面板放在南部
frame.getContentPane().add("North", textPanel);
frame.getContentPane().add("Center", keysPanel);
frame.getContentPane().add("South", clearPanel);
//设置三个面板的边框,尽量还原win10计算器
textPanel.setBorder(BorderFactory.createMatteBorder(25,3,1,3,color1));
keysPanel.setBorder(BorderFactory.createMatteBorder(6,3,3,3,color1));
clearPanel.setBorder(BorderFactory.createMatteBorder(0,3,3,3,color1));
//为各按钮添加事件监听器,都使用同一个事件监听器。
for(int i=0; i<keys.length; i++) {
buttons[i].addActionListener(this);
}
buttonClear.addActionListener(this);
}
//处理事件
public void actionPerformed(ActionEvent ev) {
//获取事件源
String command = ev.getActionCommand();
if (command.equals("Back")) {
//用户按了"Back"键
doBackspace();
}
else if (command.equals("Clear")) {
//用户按了"Clear"键
doClear();
}
else if(command.equals("=")) {
//用户按了"="键
doResult();
}
else {
//更新输入表达式
doExpression(command);
}
}
//处理Back键被按下的事件
private void doBackspace() {
String text = inText.getText();
int i = text.length();
if(i > 0) {
//退格,将文本最后一个字符去掉
text = text.substring(0, i - 1);
expression = expression.substring(0, i - 1);
if(text.length() == 0) {
//如果文本框没有内容了,则初始化计算器的各种值
inText.setText("");
firstDigit = true;
expression = "";
}
else {
//显示新的文本
inText.setText(text);
}
}
}
//处理Clear键被按下的事件
private void doClear() {
//初始化计算器的各种值
inText.setText("");
resultText.setText("0");
firstDigit = true;
expression = "";
}
//处理=键被按下的事件
private void doResult() {
if(expression.equals(""))
resultText.setText("请输入表达式");
else {
resultNum = new Calculate().calculate(expression);
//在long类型数据的取值范围内,结果为小数保留小数点后4位,整数正常输出
if(resultNum <= (Math.pow(2,63)-1) && resultNum >= -Math.pow(2,63)) {
long t1;
double t2;
t1 = (long)resultNum;
t2 = resultNum - t1;
if(t2 == 0) {
resultText.setText(String.valueOf(t1));
}
else {
resultText.setText(new DecimalFormat("0.0000").format(resultNum));
}
}
//否则按科学计数法的形式输出
else
resultText.setText(Double.toString(resultNum));
firstDigit = true;
}
}
//处理数字键或操作符被按下的事件
private void doExpression(String key) {
if(firstDigit) {
//输入的为第一个数或操作符
if(key.equals("+/-")) {
inText.setText("-");
key = "!";
}
else
inText.setText(key);
resultText.setText("0");
expression = "";
expression += key;
}
else if(key.equals("+/-")) {
//如果输入的是+/-,则在表达式后面加上!号表示接下来输入的是负数,但输入文本框显示"-"号
inText.setText(inText.getText() + "-");
expression += "!";
}
else {
//如果输入的不是+/-,则将数字或操作符附在结果文本框的后面
inText.setText(inText.getText() + key);
expression += key;
}
firstDigit = false;
}
public static void main(String[] args) {
new Calculator();
}
}
Calculate.java
package calculatorDemo;
import java.util.*;
public class Calculate {
//将表达式转为list
private List<String> expressionToList(String expression) {
int index = 0;
List<String> list = new ArrayList<>();
do{
char ch = expression.charAt(index);
if(ch != '!' && (ch < '0' || ch > '9')) {
//是操作符,直接添加至list中
index ++ ;
list.add(ch+"");
}
else {
//是数字(不论正负、整数、小数),判断多位数的情况
String s = "";
do{
s += expression.charAt(index);
index ++;
}while(index < expression.length() && ((expression.charAt(index) >='0' && expression.charAt(index) <= '9') || expression.charAt(index) == '.'));
list.add(s);
}
}while(index < expression.length());
return list;
}
//将中缀表达式转换为后缀表达式
private List<String> parseToSuffixExpression(List<String> expressionList) {
//创建一个栈用于保存操作符
Stack<String> opStack = new Stack<>();
//创建一个list用于保存后缀表达式
List<String> suffixList = new ArrayList<>();
for(String item: expressionList) {
//操作符
if(isOperator(item)){
//为空或者栈顶元素为左括号或者当前操作符优先级大于栈顶操作符直接压栈
if(opStack.isEmpty() || "(".equals(opStack.peek()) || priority(item) > priority(opStack.peek())) {
opStack.push(item);
}
else {
//否则将栈中元素出栈入队,直到遇到大于当前操作符或者遇到左括号时
while(!opStack.isEmpty() && !"(".equals(opStack.peek())){
if(priority(item) <= priority(opStack.peek())){
suffixList.add(opStack.pop());
}
}
//当前操作符压栈
opStack.push(item);
}
}
else if(isNumber(item))
//是数字则直接入队
suffixList.add(item);
else if("(".equals(item))
//是左括号,压栈
opStack.push(item);
else if(")".equals(item)) {
//是右括号 ,将栈中元素弹出入队,直到遇到左括号,左括号出栈,但不入队
while (!opStack.isEmpty()) {
if("(".equals(opStack.peek())) {
opStack.pop();
break;
}
else {
suffixList.add(opStack.pop());
}
}
}
else {
throw new RuntimeException("有非法字符!");
}
}
//循环完毕,如果操作符栈中元素不为空,将栈中元素出栈入队
while(!opStack.isEmpty()) {
suffixList.add(opStack.pop());
}
return suffixList;
}
//判断字符串是否为操作符
private boolean isOperator(String op) {
return op.equals("+") || op.equals("-") || op.equals("*") || op.equals("/");
}
//判断是否为数字
private boolean isNumber(String num) {
//负数则去掉前面的!号
if(num.charAt(0) == '!')
num = num.substring(1, num.length());
//正则表达式,\\中第1个\为转义字符,匹配整数或小数
return num.matches("\\d+(\\.\\d+)?");
}
//获取操作符的优先级
private int priority(String op) {
if(op.equals("*") || op.equals("/")) {
return 1;
}
else if(op.equals("+") || op.equals("-")) {
return 0;
}
return -1;
}
//根据后缀表达式list计算结果
public double calculate(String expression) {
//中缀表达式转为list结构
List<String> expressionList = expressionToList(expression);
//将中缀表达式转换为后缀表达式
List<String> suffixList = parseToSuffixExpression(expressionList);
Stack<Double> stack = new Stack<>();
for(int i=0;i<suffixList.size();i++) {
String item = suffixList.get(i);
//负数则去掉前面的!号,并乘上-1
if(item.charAt(0) == '!') {
item = item.substring(1, item.length());
stack.push(-Double.parseDouble(item));
}
//正数
else if(item.matches("\\d+(\\.\\d+)?"))
stack.push(Double.parseDouble(item));
else {
//是操作符,取出栈顶两个元素,经操作符作用后将结果压栈
double num2 = stack.pop();
double num1 = stack.pop();
double res = 0;
if(item.equals("+")) {
res = num1 + num2;
}
else if(item.equals("-")) {
res = num1 - num2;
}
else if(item.equals("*")) {
res = num1 * num2;
}
else if(item.equals("/")) {
res = num1 / num2;
}
else {
throw new RuntimeException("运算符错误!");
}
stack.push(res);
}
}
return stack.pop();
}
}
测试结果
输入表达式,输出整数
输出小数,测试+/-、.按钮功能
0做除数
输入为空时按=
结果<263-1或>-2^63
结果>263-1或<-2^63,以科学计数法形式输出
16位数*16位数
总结
我设计的计算器和win10自带的标准计算器界面相似,但计算处理有所不同。相比之下增加了(、)两个按钮,可以达到输入表达式的目的。文本框第1行用于记录输入表达式,第2行在按下=按钮后显示结果(在long类型的取值范围内整数正常输出,小数则保留4位小数;在long类型的取值范围外以科学记数法显示)。Clear按钮清空文本框第1行,让第2行显示0,并初始化计算器。
计算方式是重点考虑的地方,我将数据结构的知识与java结合:输入表达式是个中缀表达式,首先将字符串转换成list结构,便于操作;然后通过相关算法将中缀表达式转换成后缀表达式(通过后缀表达式计算具有无需考虑运算符优先级的优点),转换过程中用到了栈;最后利用栈通过后缀表达式求值。
基于数字大小的越界考虑,中间计算及最终结果都采用double类型。可以改进的地方有:增加指数运算(指数运算可以把平方、开方、倒数运算都包括进去),但是指数运算是从右到左结合的,而±*/的运算顺序是从左到右,处理起来有所困难。由于时间关系,就没有再做进一步的考虑。