实验要求:
1)能通过设计的按钮控件输入并实现算术表达式,表达式在文本框中显示,运算结果输出显示;保存和浏览历史运算记录;
2)能够检验算术表达式的合法性;
3)能够实现混合运算的求解,算术表达式中包括加、减、乘、除、括号等运算符;
4)要求交互界面友好,程序健壮。
实验步骤
1、确定如何实现基本四则运算:
涉及运算符:+,-,*,/,(,)
首先是将输入的表达式存放在字符串str中,当遇到输入的字符为"="时,开始逐个分析字符串str中的字符。
如果遇到的字符是数字,则直接放入堆栈stack2中;
如果是运算符,则先分析堆栈stack1中的情况:若stack1为空,则直接放入stack1中,若不为空,则将运算符与stack1中顶部运算符的优先级进行比较,栈顶优先级不小于当前运算符,则弹出栈顶,并在stack2中依次取出两个数字进行运算,运算后将结果存入stack2栈(符号为'+'||'-',且栈顶不为'('||'+'||'-'弹出,符号为'*'||'/'入栈,符号为')'弹出,直到peak优先级小于符号)并继续将当前运算符与stack1栈顶优先级进行比较。当遇到运算符是"="时,stack1全部出栈,依次进行运算。
具体实现代码:
public class Program {
//存放运算符;
Stack<Character> stack1 = new Stack<>();
//存放中间结果;
Stack<Double> stack2 = new Stack<>();
Actionlistener al = new Actionlistener();
//判断运算符优先级;
public boolean judge(char ch) {
if (stack1.empty()) {
return true;
}
//返回值为true,则入栈,反之出栈
if (ch == '(') {
return true;
}
if (ch == ')') {
return false;
}
//比较优先级,当优先级小于peak时,弹出;
if ((ch == '*' || ch == '/')) {
if (stack1.peek() != '*' && stack1.peek() != '/') {
return true;
}
return false;
}
if (ch == '+' || ch == '-') {
if (stack1.peek() == '/' || stack1.peek() == '*' || stack1.peek() == '+' || stack1.peek() == '-') {
return false;
}
return true;
}
return true;
}
//stack1入栈及其条件;
public void pushChar(char ch) {
//如果符号为'=',代表表达式已经输完,stack1全部出栈;
if (ch == '=') {
while (!stack1.empty()) {
stack2.push(cacular());
}
}
//栈为空,直接入栈;
if (stack1.empty()) {
stack1.push(ch);
}
//判断优先级,如果为true,则入栈;
else if (judge(ch)) {
stack1.push(ch);
}
//出栈,直到栈顶优先级小于ch,或为空;
else {
while ((!(stack1.empty())) && (!judge(ch)) && (stack1.peek() != '(')) {
stack2.push(cacular());
System.out.println(al.show);
}
if ((!stack1.empty()) && stack1.peek() == '(') {
stack1.pop();
}
if (ch != ')') {
stack1.push(ch);
}
}
}
//计算中间结果;
public double cacular() {
double a1 = 0, a2 = 0;
switch (stack1.pop()) {
case '+':
return stack2.size() > 1 ? stack2.pop() + stack2.pop() : stack2.pop();
case '-':
return stack2.size() > 1 ? -(stack2.pop() - stack2.pop()) : -stack2.pop();
case '*':
return stack2.size() > 1 ? stack2.pop() * stack2.pop() : stack2.pop();
case '/': {
if (stack2.size() > 1 && stack2.peek() != 0) {
a1 = stack2.pop();
a2 = stack2.pop();
return a2 / a1;
}
if (stack2.peek() == 0) {
stack1.removeAll(stack1);
al.left.removeAll(al.left);
return 0;
}
if (stack2.size() == 1) return stack2.pop();
}
case '(':
return stack2.pop();
default:
break;
}
return stack2.pop();
}
}
一些问题和注意事项:
1)、注意输入等号后,使stack1中符号全部出栈时,要用循环来调用,不然就只进行了一次运 算,若还有符号就无法参与运算
2)、出栈条件:当栈不为空&&judge()==false&&栈顶不为'('
3)、括号,要考虑 当出栈符号"("的时候,应当如何操作:忽视,并且 stack1取栈中下一个元素。4)、输入括号后所有答案均为0(?):
测试时主函数在将字符串入栈的时候,应当考虑当符号为'(',')','='这三种特殊情况,当输入了 这三个符号后就应进入下一循环,否则stack2中会错误的入栈一个0。
5)、操作数超过3个,多项式运算出错:
注意当操作stack1中的运算符时,里面的符号已经弹出来了,再进行栈顶是否是'('的判断 时,由于栈可能是空的,所以会出错
6)、当stack2中只有一个操作数,且stack1中有除括号以外的操作符时,运算后后返回的结果应是stack2栈顶的元素。
2、JAVA Swing搭建基本计算器页面
主要涉及的类:
JFrame – java的GUI程序的基本思路是以JFrame为基础,它是屏幕上window的对象,能够最大化、最小化、关闭。
JPanel – Java图形用户界面(GUI)工具包swing中的面板容器类,包含在javax.swing 包中,可以进行嵌套,功能是对窗体中具有相同逻辑功能的组件进行组合,是一种轻量级容器,可以加入到JFrame窗体中。。
JTextField –一个轻量级组件,它允许编辑单行文本。
JButton – JButton 类的实例。用于创建按钮类似实例中的 "Login"。
1)、首先用JFrame搭建基本框架
设置好计算器的名称,大小,以及位置,并设置窗体可见
JFrame jf = new JFrame("简单计算器");
jf.setBounds(200,200,350,470);
//设置同步开关,关窗口的同时停止程序运行
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
2)、向搭建好的框架中添加组件
利用JPanel新建一个面板jpanel,并设置布局为边框布局管理器(BorderLayout)
此后的所有组件都将安装在这个面板上
利用JTextField新建一个文本框ans,作为计算器显示表达式的地方,并将其存放在jpanel的NORTH区域
//创建文本框;
JTextField ans = new JTextField();
ans.setFont(new Font("", Font.PLAIN, 20));
//文本框中文本左对齐;
ans.setHorizontalAlignment(SwingConstants.LEFT);
jpanel.add(ans, BorderLayout.NORTH);
再创建2个面板panel,panel1用于装计算器按钮(这里我为了排版美观创建了两个面板)
panel用于存放数字和四则运算符按钮,界面布局为网格布局管理器(GridLayout);panel1用于存放"AC"和"=",并将panel放于CENTRAL,panel1放于RIGHT
创建按钮
//设置按钮;
JButton n1 = new Button("1");
一共所需按钮:数字按钮0~9,符号:+,-,*,/,(,),=,AC,C,H,.,D
D:删除一个输入字符
H:查看历史记录
3)、建立事件监听
在事件处理的过程中,主要涉及三类对象。
Event(事件):用户对组件的一次操作称为一个事件,以类的形式出现。例如,键盘操作对应的事件类是 KeyEvent。
Event Source(事件源):事件发生的场所,通常就是各个组件,例如按钮 Button。
Event Handler(事件处理者):接收事件对象并对其进行处理的对象事件处理器,通常就是某个 Java 类中负责处理事件的成员方法。
此处的计算器触发事件是鼠标点击按钮:
当鼠标点击按钮时,就向字符串str中输入一个对应字符,当点击代表"="的 按钮时,就调用上述Program类进行运算。将结果作为字符串存入字符串show中,并显示在文本框中,最后将该字符串放入列表list中,用于存储历史记录。此后,所有字符串清零,避免干扰下一次运算。
输入字符时需要注意监控输入的表达式是否符合规则,是否能进行正常运算。如果符合规则,则存入字符串show中,并进行压栈处理
st.pushChar(str.charAt(i));
show = show + str.charAt(i);
i++;
4)、界面美观设计
//不显示聚焦点
n0.setFocusPainted(false);
//不显示边框
n0.setBorderPainted(false);
//设置字体大小
n0.setFont(new Font("", Font.PLAIN, 30));
//设置背景色
n0.setBackground(new Color(153, 204, 204));
//设置前景色
n0.setForeground(new Color(255, 255, 255));
注意:
1)、界面颜色是基于RGB的参数进行设置
2)、若list为空时,此时调用会提示指针出错,为避免出错,应先判断list是否为空,再进行调用
if (!click.list.isEmpty()) {
if (click.i < 0) {
click.i = click.list.size() - 1;
}
ans.setText(click.list.get(click.i));
click.i = click.i - 1;
}
ans.setText("没有历史记录,请输入");
3、细节化考虑,健壮程序
1)、小数点前后自动补零问题
当遇到小数点,且show中最后一个字符是非操作数时,在将小数点加入show之前先添加一个''0"字符;
if (str.charAt(i) == '.') {
show = show + "0.";
j = j + 2;
i++;
}
当遇到运算符,且show中最后一个字符是小数点时,在运算符加入show之前先添加一个''0"字符;
if (j >= 1 && show.charAt(j - 1) == '.') {
show = show + '0';
}
2)、同一操作数中多个小数点
运用while循环,只输入遇到的第一个小数点,其余小数点被舍弃
第一种情况,遇到小于1的数
if (str.charAt(i) == '.') {
show = show + "0.";
j = j + 2;
i++;
while ((str.charAt(i) <= '9' && str.charAt(i) >= '0') || (str.charAt(i) == '.')) {
if (str.charAt(i) <= '9' && str.charAt(i) >= '0') {
a = a + ((str.charAt(i) - '0') / b);
show = show + str.charAt(i);
i++;
j++;
b = b * 10;
}
if (str.charAt(i) == '.') {
i++;
}
}
st.stack2.push(a);
a = 0;
b = 10;
}
第二种情况,遇到大于1的数
if (str.charAt(i) <= '9' && str.charAt(i) >= '0') {
while (str.charAt(i) <= '9' && str.charAt(i) >= '0') {
a = (str.charAt(i) - '0') + a * 10;
show = show + str.charAt(i);
j++;
i++;
}
//遇到第一个点,可以输入;
if (str.charAt(i) == '.') {
show = show + '.';
j++;
i++;
while ((str.charAt(i) <= '9' && str.charAt(i) >= '0') || (str.charAt(i) == '.')) {
if (str.charAt(i) == '.') {
i++;
}
if (str.charAt(i) <= '9' && str.charAt(i) >= '0') {
a = a + ((str.charAt(i) - '0') / b);
show = show + str.charAt(i);
j++;
i++;
b = b * 10;
}
}
}
st.stack2.push(a);
}
3)、连续输入多个操作符
只取第一个操作符进行运算,其余的将被舍弃
if (str.charAt(i) != '(' && str.charAt(i) != ')' && !((str.charAt(i) <= '9' && str.charAt(i) >= '0') || (str.charAt(i) == '.'))) {
st.pushChar(str.charAt(i));
show = show + str.charAt(i);
j++;
i++;
while ((i < str.length() - 1) && ((str.charAt(i + 1) == '+' || str.charAt(i + 1) == '-' || str.charAt(i + 1) == '*' || str.charAt(i + 1) == '/'))) {
i++;
}
}
4)、左右括号配对问题
左右括号必须匹配,多余的右括号将被舍弃
建立一个列表bracket来进行左右括号配对。当遇到左括号时,向列表中输入一个"(",当遇到右括号时,先检查bracket中有无与之配对的左括号,如有就进行相关运算,并删去bracket中一个左括号;若没有,则舍去,不参与运算。
if (str.charAt(i) == '(' || (str.charAt(i) == ')' && bracket.contains("("))) {
if (str.charAt(i) == '(') {
bracket.add("(");
st.pushChar(str.charAt(i));
show = show + str.charAt(i);
i++;
}
if (str.charAt(i) == ')') {
bracket.remove(bracket.size() - 1);
st.pushChar(str.charAt(i));
show = show + str.charAt(i);
j++;
i++;
}
}
while (str.charAt(i) == ')' && !bracket.contains("(")) {
i++;
}
设置一个存放左括号指针的列表left,当有右括号与之配对时,就删除第一组数据,如果最后还省有数据,则根据地址指针删除show中多余左括号
while (!left.isEmpty()){
show=show.substring(0,left.get(0))+show.substring(left.get(0)+1,j);
j--;
left.remove(0);
if(!left.isEmpty()){
left.set(0,left.get(0)-1);
}
5)、除数为0
停止后面的计算,清空相关堆栈
if(stack2.peek()==0) {
al.show="出错,被除数不能为0";
stack1.removeAll(stack1);
al.left.removeAll(al.left);
return stack2.pop();
}