第一次作业
# Java编写个人计算器软件
## 一、所需要实现的功能
1.该计算器需要实现加、减、乘、除、开平方功能。
2.需要有一个运行的UI界面,可以和电脑自带的计算器相比较。
该界面要有一个文本输入框,用来显示输入的表达式;
若干个按钮,用来用来显示数字以及操作符;
当点击按钮时,按钮上面对应的文本要显示在文本输入框中,并完成计算。
## 二、项目思想
1.通过Java Swing包和Java awt包完成计算器的基本运算和UI界面的设计,达到美观以及方便运用。
2.通过将日常熟悉的中缀表达式转换成符合计算机运算规则的后缀表达式,这里会涉及到栈、数组和集合的数据结构与算法。
## 三、字符串的拆分
因为表达式的输入是以字符串的形式输入进去的,即一整个表达式是一个字符串,在将中缀表达式转换为后缀表达式时,需要将长字符串拆分成短字符串,也就是说一个数字是一个字符串,一个操作符也是一个字符串。当有小数输入时要特别注意,要判断最后有没有溢出。
## 四、中缀表达式转换为后缀表达式步骤
1.先创建一个字符栈,用于保存操作符;在创建一个数组,用于保存后缀表达式。
2.按照从左至右的顺序遍历表达式,
(1)如果遇到的字符为操作数,则直接进入数组;
(2)如果遇到的是操作符,则进行以下判断:
如果栈为空:操作符就直接入栈;
如果栈不为空:那就判断遇到的字符和栈顶元素的优先级,
如果遇到的操作符优先级高于栈顶元素优先级,遇到的操作符便入栈;
否则(遇到的操作符优先级小于等于栈顶元素),栈顶元素出栈,并追加到后缀表达式中,直到栈顶元素优先级小于遇到的操作符优先级,或者栈为空时停止出栈,最后将遇到的操作符入栈。
(3)如果遇到左括号“( ”,则直接入栈。
(4)如果遇到右括号“ )”,则栈中元素依次出栈,并追加到后缀表达式,直到左括号出栈为止(左括号不追加到后缀表达式)。
(5)当表达式扫描完后,检查一遍栈是否为空,若不为空,则将栈中元素依次出栈,并追加到后缀表达式中。
## 五、UI界面设计
涉及到界面设计时,Java有一个专门的UI设计工具类JFarme,在这个包下面我们可以进行UI界面的设计,比如窗口的创建与设置、控制面板的创建与设置,按钮的创建与设置,文本框的创建与设置,还有为按钮添加事件,来指定该按钮的作用。
## 六、详细代码
### 1.先写一个工具类calculate,用来存放计算器实现的功能
#### 1.1 将数学表达式拆分
~~~
private static List<String> split(String num) {// 返回值为数组list
List<String> list = new ArrayList<>();//List是一个集合
int len = num.length();
int start = 0;
int end = 0;
for (int i = 0; i < len; i++) {// i表示当前索引,指向一个个的字符
char tmp = num.charAt(i);// 获取当前字符
if (isOperator(tmp)) {// 如果tmp是运算符,就入栈
list.add(tmp + "");
continue;
}
// 如果是数字或者小数点
start = i;// start表示指向一个小数的开始,例:2.3的2
// if(i+1<len) {//判断是否还有下一个字符
// char next=num.charAt(i+1);//获取当前字符;
// //最后i+1可能会出现越界行为,
// //此时在外面再加一个判断语句,当i+1<len即不越界时
// //才判断next指向的是不是运算符
// while(i<len && !isOperator(next)) {
// i++;
// if(i+1>=len) break;//如果越界则退出循环,所有字符也都遍历结束
// next=num.charAt(i+1);
// }
// }
// 对上面部分做出优化
while (i + 1 < len && !isOperator(num.charAt(i + 1))) {
i++;
}
String str = num.substring(start, i + 1);// 字符串截取,得到小数5.2
list.add(str);// 把获得的小数添加到栈
}
return list;
}
~~~
#### 1.2 将拆分后的表达式转换为后缀表达式
这里我们就会需要到前面所说的后缀转化的方法,其中会涉及到栈、数组、队列的相关知识,忘了的小伙伴就要翻翻之前的数据结构了呦!
~~~
private static List<String> toPostfix(String num){
List<String> list=split(num);//获取表达式的各个元素
List<String> exp=new ArrayList<>(list.size()); //用于保存后缀表达式
//运算符暂存在栈中,数值保存在数组中
Stack<String> optStack=new Stack<>();//暂存运算符
//遍历集合中分割后的表达式(操作符或运算符)
for(String e:list ) {
//一、如果是操作数
if(!isOperator(e)){
exp.add(e);//添加
continue;
}
//二、如果是操作符
//1.如果栈为空,操作符入栈
if(optStack.isEmpty()) {
optStack.push(e);
continue;
}
//2.如果栈不为空,查看栈顶元素
String top=optStack.peek(); //查看栈顶元素
//2.1如果遇到的操作符元素的优先级高于栈顶元素,待入栈操作符入栈
if(judgePriority(e,top)) {//遇到的>栈顶,true
optStack.push(e);//入栈
continue;
}
//2.2否则(遇到的操作符优先级<=栈顶操作符优先级),栈顶元素出栈,
// 直到栈顶元素优先级小于遇到的操作符,或者栈为空时停止出栈
while(!optStack.isEmpty() && !judgePriority(e,top)) {
exp.add(top);//操作符入数组,跟在操作数后面
optStack.pop();//栈顶出栈
if(optStack.size()>0) {
top=optStack.peek();
}
}
//将当前操作符入栈
optStack.push(e);
}
//栈不为空,剩余操作符全部出栈,追加到后面
while(!optStack.isEmpty()) {
exp.add(optStack.pop());
}
return exp;
}
~~~
#### 1.3 通过后缀表达式得到结果
~~~
private static String getResultByPostFix(List<String> exp) {
//遍历集合中的元素
for(int i=0;i<exp.size();i++) {
String val=exp.get(i);//获取当前的元素
if(!isOperator(val)) {//如果当前元素是操作数,则继续遍历
continue;
}
//如果是运算符,则把该操作符前两位数进行计算
double result=0;
double prevOne=Double.parseDouble(exp.get(i-1));//获取操作符前一个数
double prevTwo=Double.parseDouble(exp.get(i-2));//前二的数
//判断运算符类型
switch(val) {
case "+":
result=prevOne+prevTwo;
break;
case "-":
result=prevOne-prevTwo;
break;
case "*":
result=prevOne*prevTwo;
break;
case "/":
result=prevOne/prevTwo;
break;
}
/* 计算完后,这三个字符串就要退出,不再参与
* 原来的操作符位置i被新的结果result替换,+""是因为都是字符串
* 原来的操作数退出
* 下标i变为i-2;
*/
exp.set(i, result+"");//替换
exp.remove(i-1);//移除
exp.remove(i-2);//移除
i=i-2;
}
return exp.get(0);
}
~~~
#### 1.4 操作符优先级的判断
~~~
/* 判断运算符优先级
* opt1优先级>opt2,则为true
* opt1优先级<=opt2,则为false
*/
private static boolean judgePriority(String opt1,String opt2) {
if( eq(opt1,"+","-")) {
return false;//opt1"+或-" <= opt2"*或/"
}
if(eq(opt2,"+","-")) {
return true;//opt1"*或/" > opt2"+或-"
}
return false;//opt1"*或/" = opt2"*或/"
}
~~~
这里我写了一个eq方法,用来判断是否有两个元素相等,就可以简化上面if语句的判断条件,不然就会写一大串,看起来比较麻烦。
~~~
//public用于判断strs是否存在一个元素和src相等,只要存在一个相等则返回true
public static boolean eq(String src,String...strs) {//strs是一个数组
for(String str:strs) {
if(src.equals(strs)) {
return true;
}
}
return false;
}
~~~
### 2.UI界面的实现
完成了前面的计算器的计算原理,难道我们每次都要在运行窗口去计算吗?这也太麻烦了对不对,嗯!是的呢!那么......激动人心到时候来啦!接下来就要设计好看的计圈器界面了。
#### 2.1 窗口的创建
这里我对比了电脑自带的计算器,参考他的布局做了自己的计算器界面。第一张图片是电脑自带的计算器,第二张是自己做的计算器界面,由于功能比较少,所以大家不要嫌弃哈!
![](https://img2023.cnblogs.com/blog/3221665/202310/3221665-20231011203847075-458748249.png)
![](https://img2023.cnblogs.com/blog/3221665/202310/3221665-20231011204040435-140736969.png)
~~~
//设置窗口名字
this.setTitle("我的个人计算器");
//设置窗口大小
this.setSize(400, 500);
//设置窗口不可改变大小
this.setResizable(false);
//设置窗口可视化
this.setVisible(true);
//设置窗口关闭
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
//DISPOSE_ON_CLOSE:点击右上角的“X”只关闭当前窗口,
//EXIT_ON_CLOSE:结束窗口所在的应用程序。在窗口被关闭的时候会退出JVM,软件所有窗口都会关闭
~~~
#### 2.2 添加文本框
~~~
//创建输入框
display=new JTextField();
//设置输入框宽高
display.setPreferredSize(new Dimension(400,50));
//设置输入框字体类型,样式,大小
display.setFont(new Font("Arail",Font.PLAIN,20));
//设置文本对齐方式,右对齐
display.setHorizontalAlignment(SwingConstants.RIGHT);
//设置边距
display.setMargin(new Insets(10,10,10,10));
//将输入框添加到窗口
this.add(display,BorderLayout.NORTH);
~~~
#### 2.3 添加按钮
因为添加的按钮比较多,且按钮的功能不同,0~9这十个数字以及加减乘除、小数点这些只需要点击时输入文本就行,不需要实质的运算,当然这里的实质运算是在calcuater工具类完成的,因此只需要把它输进去就行,可以批量添加事件;而“清除”“删除”“等于”这些就需要在点击后立即做出反应,所以是要单独添加事件的。
~~~
//创建按钮,添加事件
private JButton creatButton(String text) {
return creatButton(text,true);
}
//该方法用于,创建按钮
//flag=true添加事件,否则不添加
private JButton creatButton(String text,boolean flag) {
JButton btn=new JButton(text);//创建按钮,并指定文本内容
btn.setFont(new Font("Arail",Font.PLAIN,20));
//增加一个事件,让文本显示在输入框(所以输入框的定义也要放在之前)
if(flag) {
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
String msg=display.getText();//获取文本
display.setText(msg+text);//拼接,点一个数字输入一个数字
}
});
}
return btn;
}
//该方法用于,批量把按钮添加到面板,把js添加到com中
private void addToComponent(JComponent com,JComponent...js) {
for(JComponent j:js) {
com.add(j);
}
}
~~~
按钮要在控制面板上才会起作用,所以还要创建面板,一定要把按钮添加到面板中,不然它是不会显示出来的!
~~~
//创建控制面板
JPanel panel=new JPanel();
//分割区域
panel.setLayout(new GridLayout(5,4));
//添加按钮
for(int i=0;i<10;i++) {
JButton btn=creatButton(i+"");//添加数字按钮
btn.setFont(new Font("Arail",Font.PLAIN,20));//设置字体,样式,大小
panel.add(btn);//把按钮添加到面板中
}
JButton add=creatButton("+");
JButton substract=creatButton("-");
JButton multiply=creatButton("*");
JButton divide=creatButton("/");
JButton point=creatButton(".",true);//小数点
JButton eq=creatButton("=",false);
JButton clear=creatButton("c",false);//清除,整体删除
JButton del=creatButton("x",false);//删除,一个字符一个字符删
//批量添加按钮到面板
addToComponent(panel,add,substract,multiply,divide,point,eq,clear,del);
//把面板添加到窗口中
this.add(panel, BorderLayout.CENTER);
~~~
### 2.4 给按钮添加事件
给按钮添加事件的意思就是,点击这个按钮之后会有什么样的反应,对于特别的按钮就需要一个个的添加了。
给”清除”添加事件
~~~
clear.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
display.setText("");
}
});
~~~
给“删除”添加事件
~~~
del.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
String msg=display.getText();//获取文本
if(msg.length()>0) {//有输入时才删除
display.setText(msg.substring(0,msg.length()-1));
}
}
});
~~~
给“等于”添加事件
~~~
eq.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if(display.getText().length()>0) {
String result=calculatorUtils.start(display.getText());
display.setText(result);
}
}
});
~~~
那么经过上面的一系列操作,我们的计算器软件大概就做好了,如果想让我们的计算器更加美观,那么就需要多学习学习UI界面设计的一些知识了,不过设计一个基本的样式这些就大概足够了。
## 七、总结与心得
在做这个软件之前,可以说是从来没有完完整整的做出一个软件,之前的课程虽然学了很多,这些内容也是学过的,或者多多少少了解过一点边缘化对象,但是自己没有把知识体系建立起来,感觉像是一段一段的记忆在自己脑子里乱窜,没有秩序的胡乱用,给我一种学了很多但又不知道学了什么的错觉。通过这个小软件的设计与开发我感觉自己收获良多!