一.计算器说明
这里笔者实现的是一个能进行整数,浮点数,正负数复合运算的计算器,每次输入可以输入一个算式,然后点击=后会同时显示算式和结果,设计的界面如下所示:
二.界面设计
- 界面布局采用GridBagLayout;
- 界面顶级容器为JFrame,在顶层容器中添加继承了JPanel的面板类BackgroundPanel,使用这个类可以为面板添加背景图片;
- 面板中的控件有两个JTextField控件、20个JButton控件;
- 在两个JTextField控件中,最上面的一个JTextField控件showexp用来在按下“=”后,展示算式,下面一个JTextField控件show用来在输入时展示算式以及在按下“=”后展示结果,show在没有输入算式时显示“0”;
- 20个按钮依次为数值键从0~9、算符键“CE”(清除当前输入数据)、“C”(清空整个算式)、“DEL”(删除一位)、“+”、“-”、“*”、“/”、“+/-”(负号键) 、“.”(小数点)、“=”;
- 所有的控件的布局上,第一个JTextField占用一行网格,第二个JTextField占用两行网格,剩下的按钮按每行四个依次排布,JTextField和JButton的上、下、左、右都与周围留有空隙,但是为了美观,第一个JTextField的下边界和第二个JTextField的上边界没有空隙;
- 为了进一步美观,两个JTextField控件和20个JButton的显示字符的字体样式设置为Serif、粗体,按钮中字体为蓝色,第一个JTextField和按钮中字体大小为16磅、第二个JTextField中字体大小为40磅,两个JTextField的边框都设置为空边框,字符都是靠右显示。另外,每个按钮都设置为透明效果;
三.功控件响应事件处理
1) 首先需要为按钮添加监控器,这样才能响应按键事件,另外我定义了两个字符串exp和outcome(初值都为空串),分别用来存储运算式和运算结果。
2) 对“CE”键的响应
由于要实现清除当前输入数据,在这里我分为两种情况:
- 第一种是只输入了一个操作数,这种情况下直接设置exp=“0”,并显示在变量名为show的JTextField控件上就可以了;
- 第二种是当前已经输入了多个操作数,这种情况下只需要找到当前输入的算式中最后一个运算符(+、-、*、/)的位置pos找到,然后以pos+1为提取的末位置,利用字符串提取字串的函数,提取从开始到pos+1的字串存放在exp中并显示在变量名为show的JTextField控件上既可。
3) 对“C”键的响应
“C”键的功能是清空算式内容,因此只需要存储算式的字符串exp设置为“0”即可。
4) 对“del”键的响应
由于按下此键的功能是删除一个数字或算符等,因此我使用字符串提取子串函数substring来提起从字符串开始到字符串倒数第二个字符为止的子串,然后将其显示在JTextField控件中即可,但需要注意的是,需要考虑到空串的情况,当存储算式的字符串exp为空串时,将该字符串设置为“0”然后在JTextField控件上显示0。
5) 对运算符(+、-、*、/)键的响应
由于运算符必须要在数字的后面,因此需要判别当前存储算式的字符串是否为空串,只有不为空串且串的最后一个字符不为运算符和“.”的情况下才能将当前按下的运算符加入到存储算式的字符串exp中去。
6) 对取负数键“+/-”的响应
这里我设置取负数键必须要在输入操作数后按下该键才能起作用,因此需要找到最后一个操作数,所以需要先寻找存储算式的字符串exp中最后一个运算符的位置pos:
- 若算式中存在运算符且exp的最后一个字符若是数字则exp串为原串分割为位置为0~pos+1的子串+“(”+位置从pos+1到字符串末尾的子串+“)”
- 若算式中不存在运算符,说明算式中只有一个操作数或为空串,当算式是一个操作数时,直接在算式字符串前加上“-”即可。
7) 对“=”键的响应
当按下等号按键时,在算式后加上“#”,然后利用中缀式求值来计算结果,并将double类型的结果转化为字符串并被outcome引用,在获取到outcome后,需要将算式字符串exp末尾的“#”去掉,然后将算式显示在第一个JTextField控件中,并将结果显示在第二个JTextField控件之中,最后为了下次计算,需要将exp串置为空串。
8) 对“.”键的响应
小数点必须要在数字的后面,且两个小数点间不能都是数字,即不能出现2.33.3这种情况,因此,我先在算式中寻找最后一个小数点的位置pos:
- 当算式中存在小数点时,在算式最后一个字符为数字且算式从pos+1开始到末尾的子串不都是数字的情况下,才能将小数点加入到算式中去。
- 当算式中不存在小数点时,只要算式最后一个字符为数字时即可将小数点加入算式。
9) 对数字按键的响应
对于数字按键,一般是直接将当前按下的数字加入到算式中即可,但需要注意不能在算式为“0”的情况下直接将按下的数字直接加入到算式中去,即不能出现0123这种类似的情况,因此当exp为“0”时先将其置为空串,然后将按下的数字加入。
四.算式求值设计
中缀表达式求值中建立一个算符栈(存放Character类型)和一个操作数栈(存放Double类型)。
然后,定义栈内栈外运算符的优先级,具体见下图所示:
- 对于操作数入栈的实现我采用了一个特别的思路,我在开始处理中缀式之前利用String的spilt函数和正则式将字符串中的操作数提取出来并存储在一个列表中,因此,当读到一个数字或小数点“.”时则继续往下读直至读到一个算符,这时将对应的已提取出来的操作数入栈即可,这样就不需要对算式中的数字进行处理求操作数;
- 当读到运算符时,需要判断该运算符与算符栈顶的运算符的优先级进行比较,若栈外运算符的优先级更高则栈外运算符入栈,否则取出算符栈的运算符以及操作数进行运算,并将运算结果入栈;
- 对于“-”号这个一元运算符的实现,则添加了判断语句,从算符栈中取出的是“-”,则先中操作数栈中取出一个数,然后再判断此时操作数栈是否为空且算符栈的栈顶是否为“(”,若上述条件满足,则将该操作数的相反数入栈。
五.完整源程序
//BackgroundPanel类中的代码
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;
//继承Jpanel类创建一个可以添加背景的面板类
public class BackgroundPanel extends JPanel{
private Image image = null;
public BackgroundPanel(Image image) {
this.image = image;
}
// 固定背景图片,允许这个JPanel可以在图片上添加其他组件
protected void paintComponent(Graphics g) {
g.drawImage(image, 0, 0, this.getWidth(), this.getHeight(), this);
}
}
//Calculator类中的代码
import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
public class Calculator implements ActionListener {
private JFrame f;
private JPanel mp;
//定义两个文本框用来显示算式和结果
private JTextField show,showexp;
//定义数字按钮
private JButton zero,one,two,three,four,five,six,seven,eight,nine;
//定义控制按钮
private JButton CE,C,del,point,neg;
//定义运算符号按钮
private JButton div,mul,plus,minus,eql;
//定义算式的字符串表示
private String exp,outcome;
public static void main(String[] args) {
Calculator c=new Calculator();
c.display();
}
//在构造函数中初始化控件和变量
Calculator()
{
f=new JFrame("Calculator");
Image image=new ImageIcon("C:/Users/12849/Pictures/background/bk1.jpg").getImage(); //添加背景图片
mp=new BackgroundPanel(image);
show=new JTextField("0");//创建单行文本控件
showexp=new JTextField();
zero=new JButton("0");//创建按钮
one=new JButton("1");
two=new JButton("2");
three=new JButton("3");
four=new JButton("4");
five=new JButton("5");
six=new JButton("6");
seven=new JButton("7");
eight=new JButton("8");
nine=new JButton("9");
CE=new JButton("CE");
C=new JButton("C");
del=new JButton("DEL");
plus=new JButton("+");
minus=new JButton("-");
mul=new JButton("*");
div=new JButton("/");
neg=new JButton("+/-");
point=new JButton(".");
eql=new JButton("=");
exp=outcome="";//初始设置存储算式和结果的字符串为空串
}
public void display()
{
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭就退出程序
//设置显示结果的单行文本框相关属性
show.setFont(new Font(Font.SERIF, Font.BOLD, 40));//设置字体样式
show.setBorder(BorderFactory.createEmptyBorder());//设置单行文本控件无边框
show.setHorizontalAlignment(SwingConstants.RIGHT);//设置文本靠右显示
show.setEnabled(false);//设置单行文本框不能点击
showexp.setFont(new Font(Font.SERIF, Font.BOLD, 16));
showexp.setBorder(BorderFactory.createEmptyBorder());
showexp.setHorizontalAlignment(SwingConstants.RIGHT);
showexp.setEnabled(false);
//设置按钮为透明
CE.setForeground(Color.BLUE);//设置按钮字体颜色为蓝色
CE.setFont(new Font(Font.SERIF, Font.BOLD, 16));//设置按钮字体样式
CE.setContentAreaFilled(false);//设置按钮为透明效果
C.setForeground(Color.BLUE);
C.setFont(new Font(Font.SERIF, Font.BOLD, 16));
C.setContentAreaFilled(false);
div.setForeground(Color.BLUE);
div.setFont(new Font(Font.SERIF, Font.BOLD, 16));
div.setContentAreaFilled(false);
mul.setFont(new Font(Font.SERIF, Font.BOLD, 16));
mul.setForeground(Color.BLUE);
mul.setContentAreaFilled(false);
plus.setForeground(Color.BLUE);
plus.setFont(new Font(Font.SERIF, Font.BOLD, 16));
plus.setContentAreaFilled(false);
minus.setForeground(Color.BLUE);
minus.setFont(new Font(Font.SERIF, Font.BOLD, 16));
minus.setContentAreaFilled(false);
point.setForeground(Color.BLUE);
point.setFont(new Font(Font.SERIF, Font.BOLD, 16));
point.setContentAreaFilled(false);
del.setForeground(Color.BLUE);
del.setFont(new Font(Font.SERIF, Font.BOLD, 16));
del.setContentAreaFilled(false);
eql.setForeground(Color.BLUE);
eql.setFont(new Font(Font.SERIF, Font.BOLD, 16));
eql.setContentAreaFilled(false);
zero.setForeground(Color.BLUE);
zero.setFont(new Font(Font.SERIF, Font.BOLD, 16));
zero.setContentAreaFilled(false);
one.setForeground(Color.BLUE);
one.setFont(new Font(Font.SERIF, Font.BOLD, 16));
one.setContentAreaFilled(false);
two.setForeground(Color.BLUE);
two.setFont(new Font(Font.SERIF, Font.BOLD, 16));
two.setContentAreaFilled(false);
three.setForeground(Color.BLUE);
three.setFont(new Font(Font.SERIF, Font.BOLD, 16));
three.setContentAreaFilled(false);
four.setForeground(Color.BLUE);
four.setFont(new Font(Font.SERIF, Font.BOLD, 16));
four.setContentAreaFilled(false);
five.setForeground(Color.BLUE);
five.setFont(new Font(Font.SERIF, Font.BOLD, 16));
five.setContentAreaFilled(false);
six.setForeground(Color.BLUE);
six.setFont(new Font(Font.SERIF, Font.BOLD, 16));
six.setContentAreaFilled(false);
seven.setForeground(Color.BLUE);
seven.setFont(new Font(Font.SERIF, Font.BOLD, 16));
seven.setContentAreaFilled(false);
eight.setForeground(Color.BLUE);
eight.setFont(new Font(Font.SERIF, Font.BOLD, 16));
eight.setContentAreaFilled(false);
nine.setForeground(Color.BLUE);
nine.setFont(new Font(Font.SERIF, Font.BOLD, 16));
nine.setContentAreaFilled(false);
neg.setForeground(Color.BLUE);
neg.setFont(new Font(Font.SERIF, Font.BOLD, 16));
neg.setContentAreaFilled(false);
//为按钮添加监听事件
CE.addActionListener(this);
C.addActionListener(this);
del.addActionListener(this);
plus.addActionListener(this);
minus.addActionListener(this);
mul.addActionListener(this);
div.addActionListener(this);
point.addActionListener(this);
neg.addActionListener(this);
eql.addActionListener(this);
zero.addActionListener(this);
one.addActionListener(this);
two.addActionListener(this);
three.addActionListener(this);
four.addActionListener(this);
five.addActionListener(this);
six.addActionListener(this);
seven.addActionListener(this);
eight.addActionListener(this);
nine.addActionListener(this);
//设置网格布袋布局
GridBagLayout gblayout=new GridBagLayout();
mp.setLayout(gblayout);
GridBagConstraints g=new GridBagConstraints();
g.fill=GridBagConstraints.BOTH;//设置当某个单元格未填满时填满整个空间
g.weightx=1.0;//设置窗口变大时缩放比例
g.weighty=1.0;
g.gridx=0;//定位在第一行第一列
g.gridy=0;
g.gridwidth=GridBagConstraints.REMAINDER;//填满整行
g.gridheight=1;//占一行网格
g.insets=new Insets(5, 5, 0, 5);//设置该组件与其它组件的距离
gblayout.setConstraints(showexp, g);//将上述
g.gridx=0;
g.gridy=1;
g.gridheight=2;
g.insets=new Insets(0, 5, 5, 5);
gblayout.setConstraints(show, g);
g.insets=new Insets(5, 5, 5, 5);
g.gridwidth=1;
g.gridheight=1;
g.gridy=3;
g.gridx=0;
gblayout.setConstraints(CE, g);
g.gridx=1;
gblayout.setConstraints(C, g);
g.gridx=2;
gblayout.setConstraints(del, g);
g.gridx=3;
gblayout.setConstraints(div, g);
g.gridy=4;
g.gridx=0;
gblayout.setConstraints(seven, g);
g.gridx=1;
gblayout.setConstraints(eight, g);
g.gridx=2;
gblayout.setConstraints(nine, g);
g.gridx=3;
gblayout.setConstraints(mul, g);
g.gridy=5;
g.gridx=0;
gblayout.setConstraints(four, g);
g.gridx=1;
gblayout.setConstraints(five, g);
g.gridx=2;
gblayout.setConstraints(six, g);
g.gridx=3;
gblayout.setConstraints(minus, g);
g.gridy=6;
g.gridx=0;
gblayout.setConstraints(one, g);
g.gridx=1;
gblayout.setConstraints(two, g);
g.gridx=2;
gblayout.setConstraints(three, g);
g.gridx=3;
gblayout.setConstraints(plus, g);
g.gridy=7;
g.gridx=0;
gblayout.setConstraints(neg, g);
g.gridx=1;
gblayout.setConstraints(zero, g);
g.gridx=2;
gblayout.setConstraints(point, g);
g.gridx=3;
gblayout.setConstraints(eql, g);
mp.add(showexp);
mp.add(show);
mp.add(CE);
mp.add(C);
mp.add(del);
mp.add(div);
mp.add(seven);
mp.add(eight);
mp.add(nine);
mp.add(mul);
mp.add(four);
mp.add(five);
mp.add(six);
mp.add(minus);
mp.add(one);
mp.add(two);
mp.add(three);
mp.add(plus);
mp.add(neg);
mp.add(zero);
mp.add(point);
mp.add(eql);
f.setContentPane(mp);
f.setSize(440, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
outcome="";
if((e.getSource())==CE){//清除最后一个输入的操作数
int pos=posOfLastOperator(exp);//获取最后一个运算符的位置
if(pos>=0)
exp=exp.substring(0, pos+1);
else//只有一个操作数直接清空
exp="0";
show.setText(exp);
}
else if((e.getSource())==C) {//清空算式
exp="0";
show.setText(exp);
}
else if((e.getSource())==del) {
//提取字符串开始到倒数第二个字符
exp=exp.substring(0, exp.length()-1);
if(exp.length()==0)//删除全部的输入后设置显示0
exp="0";
show.setText(exp);
}
else if((e.getSource())==plus||(e.getSource())==minus||(e.getSource())==mul||(e.getSource())==div) {
if(exp.length()!=0&&(!isOperator(exp.charAt(exp.length()-1))))//确认必须有数字才能输入运算符
exp+=e.getActionCommand();
show.setText(exp);
}
else if((e.getSource())==neg) {
int pos=posOfLastOperator(exp);
if(pos>=0)//算符中存在多个操作数
{
if(isDigit(exp.charAt(exp.length()-1)))//只有有数字才能置为负数
exp=exp.substring(0, pos+1)+"(-"+exp.substring(pos+1)+")";//设置为负数时加上括号以区分与减号
}
else//只有一个操作数
{
if(exp!=""&&isDigit(exp.charAt(exp.length()-1)))
exp="-"+exp;
}
if(exp=="")//设置当算式为空时显示0
exp="0";
show.setText(exp);
}
else if((e.getSource())==eql) {
exp+='#';//算式末尾添加’#’
//从算式中拆分出数字
String []nums=exp.split("[^.0-9]");
List<Double> numLst = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {//将每个数字串转化为Double类型
if (!"".equals(nums[i]))
numLst.add(Double.parseDouble(nums[i]));
}
double out=getValueOfMid(exp, numLst);//利用中缀式求值
outcome=""+out;//将求得的结果转为字符串
exp=exp.substring(0,exp.length()-1);//去除算式后的’#’
showexp.setText(exp);//第一个单行文本框展示算式
show.setText(outcome);//第二个单行文本框显示结果
exp="";//存储算式的字符串清空
}
else if((e.getSource())==point) {
int pos=exp.lastIndexOf('.');//找到最后一个小数点的位置
if(pos>=0)//前后两个小数点间不能都是数字,即不能2.33时又添加一个小数点变为2.33.
{
if(isDigit(exp.charAt(exp.length()-1))&&!isDigitSring(exp.substring(pos+1)))
exp+=e.getActionCommand();
}
else {//小数点前一个必须是数字
if(isDigit(exp.charAt(exp.length()-1)))
exp+=e.getActionCommand();
}
show.setText(exp);
}
else {
if(exp=="0")
exp="";
exp+=e.getActionCommand();
show.setText(exp);
}
}
public static boolean isDigit(char ch)//判断一个字符是否为数字
{
return (ch >= '0'&&ch <= '9');
}
public boolean isDigitSring(String s)//判断一个字符是否都为数字
{
for(int i=0;i<s.length();i++)
{
if(!isDigit(s.charAt(i)))
return false;
}
return true;
}
public boolean isOperator(char c)//判断一个字符是否为运算符或’.’
{
return (c=='+')||(c=='-')||(c=='*')||(c=='/')||(c=='.');
}
public int posOfLastOperator(String s)//寻找字符串中最后一个运算符(+,-,*,/)的位置
{
for(int i=s.length()-1;i>=0;i--)
{
if(s.charAt(i)!='.'&&isOperator(s.charAt(i)))
return i;
}
return -1;//找不到返回-1
}
public static int isp(char ch)//定义栈中运算符优先级,并将相应算符的优先级返回
{
switch (ch)
{
case')':return 4;
case'*':return 3;
case'/':return 3;
case'+':return 2;
case'-':return 2;
case'(':return 1;
case'#':return 0;
}
return -1;
}
public static int icp(char ch)//定义栈外运算符优先级,并将相应算符的优先级返回
{
switch (ch)
{
case')':return 1;
case'*':return 3;
case'/':return 3;
case'+':return 2;
case'-':return 2;
case'(':return 4;
case'#':return 0;
}
return 0;
}
public static double compute(double a,char ch,double b)//将取出的两个操作数与对应的算符进行计算并返回计算结果
{
switch (ch)
{
case '+':return a + b;
case '-':return a - b;
case '*':return a * b;
case '/':return a / b;
default:break;
}
return 0;
}
//对输入的算式(中缀式)进行求值
public static double getValueOfMid(String exp, List<Double> numLst)
{
Stack<Character> OPTR = new Stack<>();//定义算符栈
Stack<Double> OPND = new Stack<>();//定义操作数栈
double outcome=0;//最终结果
double a,b;//定义两个操作数
char sym;//定义运算符
OPTR.push('#');
int i=0,j=0;
while(exp.charAt(i)!='#'||OPTR.peek()!='#')
{
if(isDigit(exp.charAt(i)))//遍历到数字时则跳过数字字符串,并将之前划分的double类型数据代替压栈
{
while(isDigit(exp.charAt(i))||exp.charAt(i)=='.')
{
i++;
if(i==exp.length())
break;
}
i--;
OPND.push(numLst.get(j));
j++;
}
else
{
sym=OPTR.peek();
int m=isp(sym);
int n=icp(exp.charAt(i));
if(m<n)//比较栈内和栈外运算符的优先级
OPTR.push(exp.charAt(i));//栈内算符优先级小于栈外,则栈外运算符压栈
else//栈内运算符优先级大于栈外运算符优先级
{
sym = OPTR.peek();
OPTR.pop();//取出栈内的运算符
if (sym != '('&&m == n || m>n)
{
b = OPND.peek();//从操作数栈取出一个操作数
OPND.pop();
if (!OPND.empty()&&OPTR.peek()!='(')//当操作数栈不为空且不为’(’时继续取出栈中的数进行运算
{
a = OPND.peek();
OPND.pop();
OPND.push(compute(a, sym, b));
continue;
}
else//处理负数
{
switch (sym)//实现一元运算符的运算
{
case '+':OPND.push(b); break;
case '-':OPND.push(-b);break;
}
continue;
}
}
}
}
i++;
}
outcome=OPND.peek();
return outcome;
}
}
以上便是实现的全部过程及源代码,要是觉得好的话就点个赞或关注一下吧!!!