计算机软件实习项目一 基于Java/Swing 的四则运算计算器

一、项目要求

  • 学习GUI图形界面的设计Python TinkerJava Swing/Awt C++ QT框架,创建交互友好的应用程序;
  • 能通过界面按钮控件输入并实现算术表达式,输入的表达式即时在控件中显示,按下=”按钮能实现运算,并将运算结果输出在控件中显示;要求能保存和浏览历史表达式的运算记录。
  • 算术表达式求解,是指算术表达式中包括加、减、乘、除、括号等运算符,能求解包含括号的四则混合运算;并且能够检验表达式的合法性。

二、 算法思想:

左向右依次读取算术表达式的元素X,分以下情况进行不同的处理:

1)如果X是操作数,直接入队

2)如果X是运算符,再分以下情况:

      a)如果栈为空,直接入栈。

      b)如果X==”,直接入栈。

      c)如果X==”,则将栈里的元素逐个出栈,并入队后缀表达式队列中,直到第一个配对的”(”出栈。(注:“(”“)”都不 入队)

      d)如果是其他操作符(+ - * /,则和栈顶元素进行比较优先级。 如果栈顶元素的优先级大于等于X,则出栈并把栈中弹出的元素入队,直到栈顶元素的优先级小于X或者栈为空。弹出完这些元素后,才将遇到的操作符压入到栈中。

(3)最后将栈中剩余的操作符全部入队

三、 解决思路:双栈算符优先级法

双栈算符优先级法为了实现表达式求值,需要设置两个栈:
  • 个是运算符栈op,用于寄存运算符;
  • 另一个成为操作数栈Opnd,用于寄存运算数和运算结果。

      求值的处理过程是自左至右扫描表达式的每一个字符:

       1、当扫描到的是运算数,则将其压入栈OPND

       2、当扫描到的是运算符时:

  • 如这个运算符比OP栈顶运算符的优先级高,则入栈;
  • 如这个运算符比OP栈顶运算符优先级低,则从OPND栈中弹出两个运算符,从栈OP中弹出栈顶运算符进行运算,并将运算结果压入栈OPND

       3、继续处理当前字符,直到遇到结束符为止。

四、代码实现:

  GUI设计部分:(JavaSwing 实现)

package Proj_Calculator;

import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Stack;

public class Calculator implements MouseListener {
    private Stack<BigDecimal> numberStack;
    private Stack<Character> symbolStack;

    private JFrame f = new JFrame("计算器");
    private JPanel p1 = new JPanel();//p1放置文本框
    private JPanel p2 = new JPanel();//p2放置按钮
    private JPanel p3 = new JPanel();//p3作为主栏
    private JPanel p4 = new JPanel();//p4作为侧栏

    private JButton Btn_Lpar = new JButton("(");
    private JButton Btn_Rpar = new JButton(")");
    private JButton Btn_ac = new JButton("AC");
    private JButton Btn_c = new JButton("C");

    private JButton Btn_1 = new JButton("1");
    private JButton Btn_2 = new JButton("2");
    private JButton Btn_3 = new JButton("3");
    private JButton Btn_add = new JButton("+");

    private JButton Btn_4 = new JButton("4");
    private JButton Btn_5 = new JButton("5");
    private JButton Btn_6 = new JButton("6");
    private JButton Btn_sub = new JButton("-");

    private JButton Btn_7 = new JButton("7");
    private JButton Btn_8 = new JButton("8");
    private JButton Btn_9 = new JButton("9");
    private JButton Btn_mul = new JButton("*");

    private JButton Btn_dot = new JButton(".");
    private JButton Btn_0 = new JButton("0");
    private JButton Btn_div = new JButton("/");
    private JButton Btn_equ = new JButton("=");
    private JButton Btn_hcl = new JButton("Clear");

    private JTextField Tex_res = new JTextField("0");
    private JTextArea Tex_history = new JTextArea();

    private StringBuffer text1 = new StringBuffer("");
    private StringBuffer text2 = new StringBuffer("");
    private boolean dot_f = true;

    public static void main(String[] args) {
        Calculator start = new Calculator();
        start.go();
    }

    void go() {
        f.setSize(500, 500);
        f.setBackground(Color.white);
        f.setLayout(new BorderLayout(10, 10));
        p1.setLayout(new BorderLayout(5, 5));
        p1.setBorder(new EmptyBorder(10, 10, 10, 10));
        p2.setLayout(new GridLayout(5, 4, 5, 5));
        p3.setLayout(new BorderLayout(5, 5));
        p3.setBorder(new EmptyBorder(10, 10, 10, 10));
        p4.setLayout(new BorderLayout(5, 5));
        p4.setBorder(new EmptyBorder(10, 0, 10, 10));

        Tex_res.setHorizontalAlignment(JTextField.RIGHT);
        Tex_res.setEditable(false);
        Tex_res.setBackground(Color.white);
        Tex_res.setPreferredSize(new Dimension(50, 50));

        Tex_history.setLineWrap(true);
        Tex_history.setEditable(false);
        Tex_history.setBounds(0,0,150,250);//如果设置setPreferredSize则无法使用JScrollPane滚动条
        Tex_history.setBackground(Color.white);

        p2.add(Btn_Lpar);
        p2.add(Btn_Rpar);
        p2.add(Btn_ac);
        p2.add(Btn_c);

        p2.add(Btn_1);
        p2.add(Btn_2);
        p2.add(Btn_3);
        p2.add(Btn_add);

        p2.add(Btn_4);
        p2.add(Btn_5);
        p2.add(Btn_6);
        p2.add(Btn_sub);

        p2.add(Btn_7);
        p2.add(Btn_8);
        p2.add(Btn_9);
        p2.add(Btn_mul);

        p2.add(Btn_dot);
        p2.add(Btn_0);
        p2.add(Btn_div);
        p2.add(Btn_equ);

        p1.add(new JLabel("操作栏"),BorderLayout.NORTH);
        p1.add(Tex_res, BorderLayout.CENTER);
        p4.add(new JScrollPane(Tex_history),BorderLayout.CENTER);
        p4.add(Btn_hcl,BorderLayout.SOUTH);


        Btn_0.addMouseListener(this);
        Btn_1.addMouseListener(this);
        Btn_2.addMouseListener(this);
        Btn_3.addMouseListener(this);
        Btn_4.addMouseListener(this);
        Btn_5.addMouseListener(this);
        Btn_6.addMouseListener(this);
        Btn_7.addMouseListener(this);
        Btn_8.addMouseListener(this);
        Btn_9.addMouseListener(this);
        Btn_add.addMouseListener(this);
        Btn_sub.addMouseListener(this);
        Btn_mul.addMouseListener(this);
        Btn_div.addMouseListener(this);
        Btn_dot.addMouseListener(this);
        Btn_ac.addMouseListener(this);
        Btn_c.addMouseListener(this);
        Btn_equ.addMouseListener(this);
        Btn_Lpar.addMouseListener(this);
        Btn_Rpar.addMouseListener(this);
        Btn_hcl.addMouseListener(this);

        p3.add(p1, BorderLayout.NORTH);
        p3.add(p2, BorderLayout.CENTER);
        p4.add(new JLabel("历史记录"),BorderLayout.NORTH);
        f.add(p4,BorderLayout.EAST);
        f.add(p3, BorderLayout.CENTER);
        f.setLocationRelativeTo(null);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setResizable(false);
        f.setVisible(true);
    }
    /*
    * 设置鼠标按压事件响应
    * */
    @Override
    public void mousePressed(MouseEvent e) {
        if (e.getSource().equals(Btn_0))
            text1 = text1.append("0");
        else if (e.getSource().equals(Btn_1))
            text1 = text1.append("1");
        else if (e.getSource().equals(Btn_2))
            text1 = text1.append("2");
        else if (e.getSource().equals(Btn_3))
            text1 = text1.append("3");
        else if (e.getSource().equals(Btn_4))
            text1 = text1.append("4");
        else if (e.getSource().equals(Btn_5))
            text1 = text1.append("5");
        else if (e.getSource().equals(Btn_6))
            text1 = text1.append("6");
        else if (e.getSource().equals(Btn_7))
            text1 = text1.append("7");
        else if (e.getSource().equals(Btn_8))
            text1 = text1.append("8");
        else if (e.getSource().equals(Btn_9))
            text1 = text1.append("9");
        else if (e.getSource().equals(Btn_dot)) {
            if (dot_f) {
                text1 = text1.append(".");
                dot_f = false;
            }
        } else if (e.getSource().equals(Btn_add)) {
            if(!text1.isEmpty()&&!isSymbol(text1.charAt(text1.length()-1))) {
                text1 = text1.append("+");
                dot_f = true;
            }
        } else if (e.getSource().equals(Btn_sub)) {
            if(text1.isEmpty()){
                text1 = text1.append("-");
                dot_f = true;
            }
            else if(!isSymbol(text1.charAt(text1.length()-1))){
                text1 = text1.append("-");
                dot_f = true;
            }
        } else if (e.getSource().equals(Btn_mul)) {
            if(!text1.isEmpty()&&!isSymbol(text1.charAt(text1.length()-1))) {
                text1 = text1.append("*");
                dot_f = true;
            }
        } else if (e.getSource().equals(Btn_div)) {
            if(!text1.isEmpty()&&!isSymbol(text1.charAt(text1.length()-1))) {
                text1 = text1.append("/");
                dot_f = true;
            }
        } else if (e.getSource().equals(Btn_Lpar)) {
            text1 = text1.append("(");
            dot_f = true;
        } else if (e.getSource().equals(Btn_Rpar)) {
            text1 = text1.append(")");
            dot_f = true;
        } else if (e.getSource().equals(Btn_ac)) {
            text1 = new StringBuffer();
            dot_f = true;
        }
        else if (e.getSource().equals(Btn_c)) {
            text1.deleteCharAt(text1.length()-1);
        }

        Tex_res.setText(text1.toString());

        if (e.getSource().equals(Btn_equ)) {
            text1.append("=");
            Tex_res.setText(String.valueOf(calculate(text1)));
            //历史记录同步记录
            if(isStandard(text1)) {
                text2.append(new StringBuffer(addSymbol(text1).toString() + calculate(text1)));
                Tex_history.append("\n");
                Tex_history.append(text2.toString());
                text2 = new StringBuffer();
            }
        }

        if(e.getSource().equals(Btn_hcl)){
            Tex_history.setText("");
        }
    }

 功能实现部分:(包括功能主体和校验函数)

    /*
    * @param 输入的算术表达式
    * @return 计算结果
    *
    * 使用BigDecimal的原因是为了保证浮点数的精确性,构造时建议不要使用BigDecimal(double)构造对象,
    * 而是使用BigDecimal(String)
    * 具体参考https://blog.csdn.net/qq_35868412/article/details/89029288
    * */
    public BigDecimal calculate(StringBuffer numStr) {
        //将表达式规范化
        numStr = addSymbol(numStr);
        if (!isStandard(numStr)) {
            JOptionPane.showMessageDialog(null, "算式违法!", "Calculator", JOptionPane.WARNING_MESSAGE);
            return null;
        }
        if (numberStack == null) {
            numberStack = new Stack<>();
        }
        numberStack.clear();
        if (symbolStack == null) {
            symbolStack = new Stack<>();
        }
        symbolStack.clear();
        // temp用于缓存数字,因为数字可能是多位的
        StringBuilder temp = new StringBuilder();
        // 从表达式的第一个字符开始处理
        for (int i = 0; i < numStr.length(); i++) {
            char ch = numStr.charAt(i); // 获取一个字符
            if (isNumber(ch)) { // 若当前字符是数字
                temp.append(ch); // 加入到数字缓存中
            } else { // 非数字的情况
                String tempStr = temp.toString(); // 将数字缓存转为字符串
                if (!tempStr.isEmpty()) {
                    BigDecimal num = new BigDecimal(tempStr);
                    numberStack.push(num); // 将数字压栈
                    temp = new StringBuilder(); // 重置数字缓存
                }
                // 判断运算符的优先级,若当前优先级低于栈顶的优先级,则先计算前面的数
                while (!comparePri(ch) && !symbolStack.empty()) {
                    // 出栈,取出数字,后进先出
                    BigDecimal b = numberStack.pop();
                    BigDecimal a = numberStack.pop();
                    // 取出运算符进行相应运算,并把结果压栈进行下一次运算
                    switch (symbolStack.pop()) {
                        case '+':
                            numberStack.push(a.add(b));
                            break;
                        case '-':
                            numberStack.push(a.subtract(b));
                            break;
                        case '*':
                            numberStack.push(a.multiply(b));
                            break;
                        case '/':
                            //小数点后保留5位(舍入模式为四舍五入)
                            numberStack.push(a.divide(b, 5, RoundingMode.HALF_UP));
                            break;
                        default:
                            break;
                    }
                } // while循环结束
                if (ch != '=') {
                    symbolStack.push(ch); // 符号入栈
                    if (ch == ')') { // 去括号
                        symbolStack.pop();
                        symbolStack.pop();
                    }
                }
            }
        } // for循环结束

        return numberStack.pop(); // 返回计算结果
    }

    /*
    * 符号位前补零,补省略的符号
    * */
    public StringBuffer addSymbol(StringBuffer numStr) {
        StringBuffer str = new StringBuffer();
        if (numStr.charAt(0) == '+' || numStr.charAt(0) == '-') {
            str.append("0");
        }
        for (int i = 0; i < numStr.length(); i++) {
            char n = numStr.charAt(i);
            if (i != 0 && n == '(' && (isNumber(numStr.charAt(i - 1)) || numStr.charAt(i - 1) == ')')) {
                str.append("*(");
                continue;
            }
            if (i != numStr.length() - 1 && n == ')' && (isNumber(numStr.charAt(i + 1)) || numStr.charAt(i - 1) == '(')) {
                str.append(")*");
                continue;
            }
            if (i != 0 && n == '-' && numStr.charAt(i - 1) == '(') {
                str.append("0-");
                continue;
            }
            str.append(n);
        }
        return str;
    }
    /**
     * 检查算术表达式的基本合法性,符合返回true,否则false
     */
    public boolean isStandard(StringBuffer numStr) {
        if (numStr == null || numStr.isEmpty()) // 表达式不能为空
            return false;
        Stack<Character> stack = new Stack<>(); // 用来保存括号,检查左右括号是否匹配
        boolean b = false; // 用来标记'='符号是否存在多个
        for (int i = 0; i < numStr.length(); i++) {
            char n = numStr.charAt(i);
            // 将左括号压栈,用来给后面的右括号进行匹配
            if ("(".equals(n + "")) {
                stack.push(n);
            }
            if (")".equals(n + "")) { // 匹配括号
                if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) // 括号是否匹配
                    return false;
            }
            if(i!=0&&"(".equals(numStr.charAt(i-1)+"")&&")".equals(n+""))//不能有空括号
                return false;
            // 检查是否有多个'='号
            if ("=".equals(n + "")) {
                if (b)
                    return false;
                b = true;
            }
        }
        // 可能会有缺少右括号的情况
        if (!stack.isEmpty())
            return false;
        // 检查'='号是否不在末尾
        return "=".equals(numStr.charAt(numStr.length() - 1) + "");
    }

    /**
     * 判断字符是否是0-9的数字
     */
    public boolean isNumber(char num) {
        return (num >= '0' && num <= '9') || num == '.';
    }
    /*
    * 判读字符是否是符号
    * */
    public boolean isSymbol(char sym){
        return ("+".equals(sym+"")||"-".equals(sym+"")
                ||"*".equals(sym+"")||"/".equals(sym+""));
    }

    /*
    * 符号优先级说明(从高到低):
         第1级: (
         第2级: * /
         第3级: + -
         第4级: )
     */
    private boolean comparePri(char symbol) {
        if (symbolStack.empty()) { // 空栈返回ture
            return true;
        }

        char top = symbolStack.peek(); // 查看堆栈顶部的对象
        if (top == '(') {
            return true;
        }
        // 比较优先级
        switch (symbol) {
            case '(': // 优先级最高
                return true;
            case '*': {
                if (top == '+' || top == '-') // 优先级比+和-高
                    return true;
                else
                    return false;
            }
            case '/': {
                if (top == '+' || top == '-') // 优先级比+和-高
                    return true;
                else
                    return false;
            }
            case '+':
                return false;
            case '-':
                return false;
            case ')': // 优先级最低
                return false;
            case '=': // 结束符
                return false;
            default:
                break;
        }
        return true;
    }

    public void mouseEntered(MouseEvent e) {

    }

    public void mouseExited(MouseEvent e) {

    }

    public void mouseClicked(MouseEvent e) {

    }

    public void mouseReleased(MouseEvent e) {

    }

    public void mouseDragged(MouseEvent e) {

    }

    public void mouseMoved(MouseEvent e) {

    }

}

 五、效果展示:

 界面效果:输入表达式后,计算结果可保存在历史框,并能计算包括括号在内的四则运算

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值