基于Swing库的科学计算器设计与开发

一、前记

  这是本人第一次作软件的设计与开发,计算器是各个行业的核心组件,我们可以在各个领域看见它的作用。而本软件的目标是解决小学生在学习数学时会遇到的问题/*小学生需要学开方吗?*/以及完成SIDE(Software integration development environment)课程的第一次作业,由于是给小学生学习使用的(其实是因为写异常类太麻烦),我们需要限制用户的输入。由于是一个简易的小型软件开发并且是第一次开发软件,本次软件工程的流程模型是瀑布模型(如下图),而需求分析已经在作业要求中基本上写好了(加减乘除,牛顿迭代法实现开方,ui界面)故不做赘述。

来源慕课

二、概要设计

  作为一个计算器程序,使用者需要输入数字或符号,最后得到结果,根据这层常识,我们设计出顶层流程图:

  现在我们对流程1(计算器)作进一步的设计,即如何保存待计算数据,如何进行计算。我计划设计一个双栈的计算器内核,将要计算的数字压入栈,再根据待入栈的符号和栈顶符号比较决定哪个符号先投入运算,哪个符号入栈出栈。得到了流程1的一层图:

  具体的计算也需要进一步设计说明,所以对流程2的一层图如下:


 

三、界面设计即代码

  有了计算的总体流程,我们还要设计计算器的界面布局,我对该软件的布局进行过多次修改,前三个版本由于功能难以实现,功能过少等原因被废除,到下图4,5为最终界面。

 

             

在有了界面设计图(上图5)后,我们就要设计出实际界面(上图4)来,根据设计,框架总体为一个固定大小的边缘框架(borderLayout),关键就在于我们框架的大小,为了适配所有的屏幕,框架大小是取决于用户的显示器的,我通过用户显示器的高(考虑到用户可能使用超宽带鱼屏之类的,所以计算器不能超过显示器高)获取一个比例数,而我们计算器界面上所有的大小(按钮大小,窗口大小,甚至是表盘大小)都是由比例数作乘法得到的。框架主题如下(打印的那段编码是一个小彩蛋,可以通过ASCII码破解):

复制代码
    // 获得显示屏大小
    public static final int SCREAM_HEIGHT = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
    public static final int SCREAM_WIDTH = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
    // 根据显示屏高度设定计算器界面大小
    private static final int EXAMPLE = (int) (SCREAM_HEIGHT / 4.32);


    // 字体大小
    Font cu = new Font("粗体", Font.BOLD, (int) (EXAMPLE * 0.2));


    /**********************************************超级初始化块*******************************************************/


    protected void __init__() {
        // 设置窗口名称
        this.setTitle("计算器");
        System.out.println("0b1100001 0b1110101 0b1110100 0b1101000 0b1101111 0b1110010 0b100000 0b1110100 0b1100101 0b1100001 0b1101101 0b100000 0b1101001 0b1110011 0b100000 0b1001110 0b1001111 0b101110 0b110001 0b100000 0b1001110 0b1001111 0b101110 0b111001 0b100000 0b1001110 0b1001111 0b101110 0b110001 0b110011 0b100000 0b1001110 0b1001111 0b101110 0b110010 0b110111 0b100000 0b1001110 0b1001111 0b101110 0b110011 0b110001 0b100000 0b1001110 0b1001111 0b101110 0b110100 0b110001 0b100000 0b1101001 0b1101110 0b100000 0b110010 0b110001 0b1000011 0b1010011");
        // 4比3固定窗口
        this.setSize(EXAMPLE * 3, EXAMPLE * 4);
        this.setResizable(false);
        this.setLocationRelativeTo(null);
        // 设置窗口可见
        this.setVisible(true);
        this.setBackground(Color.black);
        // 设置关闭按钮(释放进程)
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        // 设置方向布局
        this.setLayout(new BorderLayout());
    }
 

 

  根据设计图来看,我们在该边缘布局的北面应该设计一个带有计算器屏幕和清除按钮的仪表盘,我在前提里着重说明过,该计算器的最大特色在于限制用户输入,我们的输入输出框自然也是只允许通过按钮输入的。代码如下:

 
/**********************************************北国风光*******************************************************/


    // 北面组件
    private JPanel northBox = new JPanel(new FlowLayout());
    private JTextField input = new JTextField();
    private JButton clear = new JButton();


    // 设置北面组件
    private void setNorth() {
        // 设置数字栏
        this.input.setPreferredSize(new Dimension((int) (EXAMPLE * 2.2), (int) (EXAMPLE * 0.4)));
        this.input.setFont(this.cu);
        this.input.setForeground(Color.BLACK);      // 额好像没用,但限制用户输入更重要
        this.input.setEnabled(false);
        this.input.setHorizontalAlignment(SwingConstants.RIGHT);

        // 设置清空
        this.clear.setText("C");
        this.clear.setPreferredSize(new Dimension((int) (EXAMPLE * 0.4), (int) (EXAMPLE * 0.4)));
        this.clear.setFont(this.cu);
        this.clear.setForeground(Color.RED);

        // 安装北仪表
        this.northBox.add(this.input);
        this.northBox.add(this.clear);

        // 安装北仪表到主体
        this.add(this.northBox, BorderLayout.NORTH);
    }
 

  根据设计图来看,中部仪表盘的布局是一个网格布局。中部组件数目较多,我们可以通过java的collection类的子类去对每一个按钮的text快速赋值,但是该计算器其实也只设计了4*5=20个按钮在这里,所以用字符串代替集合类更加方便(如果作大型的计算器则要用集合类,又能增加可读性又方便后续的开发)。但我们有两个特殊的按钮(即pow运算的按钮)它并不是一个字符,根据Linux对代码的评价“代码有三层缩进就需要更改”来看,单独设置第一排按钮是可读性更高的选择,至于为什么要把剩下两个单独字符的也单独做是因为在一开始时我并没有决定好第一排按钮有哪些,怎么放。那么解释完第一排的功能按钮(pow运算和可恶的括号)后,剩下的按钮的text都是单个字符的,我们自然可以用String的切割函数来快速设置。代码如下:

/**********************************************中央处理器*******************************************************/


    // 中部组件
    private JPanel CPU = new JPanel();
    private JButton[] cal = new JButton[20];
    // 后16个按钮顺序,懒得写集合类了
    String str = "789/456x123-0.=+";

    // 设置中部组件
    private void setCenter() {
        // 划分20格
        this.CPU.setLayout(new GridLayout(5, 4));
        // 设置开方按钮
        this.cal[0] = new JButton();
        this.cal[0].setText("^-2");
        this.cal[0].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
        this.cal[0].setFont(this.cu);
        this.cal[0].setForeground(Color.BLUE);
        // 设置括号按钮
        this.cal[1] = new JButton();
        this.cal[1].setText("^2");
        this.cal[1].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
        this.cal[1].setFont(this.cu);
        this.cal[1].setForeground(Color.BLUE);
        if (!(version.equals("本计算器由SDT软件开发组开发,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏")))
            JOptionPane.showMessageDialog(this, "验证码被修改,您使用的为盗版。\n本计算器由SDT软件开发组开发,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏");
        if (!(version.equals("本计算器由SDT软件开发组开发,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏")))
            this.dispose();
        this.cal[2] = new JButton();
        this.cal[2].setText("(");
        this.cal[2].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
        this.cal[2].setFont(this.cu);
        this.cal[2].setForeground(Color.BLUE);

        // 设置清除按钮
        this.cal[3] = new JButton();
        this.cal[3].setText(")");
        this.cal[3].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
        this.cal[3].setFont(this.cu);
        this.cal[3].setForeground(Color.BLUE);

        // 设置后16个按钮
        for (int i = 4; i < 20; i++) {
            String temp = this.str.substring(i - 4, i - 3);
            this.cal[i] = new JButton();
            this.cal[i].setText(temp);
            this.cal[i].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
            this.cal[i].setFont(this.cu);
            if ("+-x/=".contains(temp)) {
                this.cal[i].setForeground(Color.GRAY);
            }
        }
        // 添加按钮
        for (int i = 0; i < 20; i++) {
            this.CPU.add(this.cal[i]);
        }
        this.add(this.CPU,BorderLayout.CENTER);
    }
 

上面注释位置有问题也是多次修改第一行按钮的text导致的。

  设计图上专门把南方的模块标出来是因为南方只有一段版本号,不需要表盘,代码如下:

 
/**********************************************南柯一梦*******************************************************/

    public static final String version = "本计算器由SDT软件开发组开发,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏";
    // 南面组件
    private JLabel message = new JLabel(version, SwingConstants.CENTER);

    // 设置南面组件
    private void setSouth() {
        this.message.setPreferredSize(new Dimension((int) (EXAMPLE * 0.1), (int) (EXAMPLE * 0.1)));
        this.message.setForeground(Color.WHITE);
        this.add(this.message, BorderLayout.SOUTH);
    }
 

如果你修改了这段版本号,软件会提示你使用了盗版并释放资源。

 

 

 


 

 四、详细设计

  在详细说明详细设计之前,我们先讲一下对按钮监听,这个功能直接决定了我们详细设计的设计原因,我先对所有按钮本身设置监听,让按钮本身返回监听事件(需要类实现监听的接口),监听事件就是返回按钮的text到函数bigWork,即按钮上的值,代码如下:

 
/*********************************************监听*********************************************************/

    // 给按钮添加监听
    private void setListener() {
        for (JButton j : cal) {
            j.addActionListener(this);
        }
        this.clear.addActionListener(this);
    }

    // 监听事件设置
    @Override
    public void actionPerformed(ActionEvent e) {
        String listen = e.getActionCommand();
        if ("0.1^23456789+-x/()^-2".contains(listen)) {
            this.input.setText(this.input.getText() + listen);
        }
        this.bigWork(listen);
    }
 

  可以遇见,bigWork就是处理大批数据的地方,也是前端后端所连接的地方,但是在说明该方法前,我要先讲解一下之后会遇到的几种输入状态。之前说过两次,该计算器最大的特色在于限制用户输入,用户已经只能通过按钮输入了,那么限制后用户输入的自然是正确的输入顺序,比如我们输入了一个符号后,就不能再输入符号(因此也不能直接输入减号当负号了,需要输入(0-),不过这样反而能帮助小学生更好得理解负数,后续也可以添加符号按钮),更具我的计算设计,列出以下状态:

 

根据上表可以编写代码,代码如下:

 
 /*****************************************状态**************************************************/

    // 小数点信号
    private Boolean pointSignal = false;
    // 括号信号
    private int barcketNum = 0;

    private String num = "0123456789";
    private String sign = "+-x/(";

    // 输入的最后一位为数字时的状态,详细见详细设计表格
    public void inNum() {
        // 只能输入pow函数,右括号,数字和符号按钮,不能输入左括号,若小数点信号为真,则可以输入小数点
        for (int i=0;i<20;i++) {
            if("(".equals(this.cal[i].getText())) {
                this.cal[i].setEnabled(false);
            }
            else {
                this.cal[i].setEnabled(true);
            }
        }
        // 根据信号设置
        this.cal[17].setEnabled(this.pointSignal);
    }

    // 输入的最后一位为符号或左括号时
    public void inSign() {
        // 只能输入非小数点数字及左括号,小数点信号开启
        for (int i=0;i<20;i++) {
            if("(".equals(this.cal[i].getText()) || this.num.contains(this.cal[i].getText())) {
                this.cal[i].setEnabled(true);
            }
            else {
                this.cal[i].setEnabled(false);
            }
        }
        this.pointSignal = true;
    }

    // 输入最后一位为右括号或pow运算时
    public void inPow() {
        // 只能输入符号和右括号和pow函数
        for (int i=0;i<20;i++) {
            if("(".equals(this.cal[i].getText()) || this.num.contains(this.cal[i].getText()) || ".".equals(this.cal[i].getText())) {
                this.cal[i].setEnabled(false);
            }
            else {
                this.cal[i].setEnabled(true);
            }
        }
    }

    // 输入最后一位为小数点时
    public void inPoint() {
        // 只能输入非小数点数字,小数点信号关闭
        for (int i=0;i<20;i++) {
            if(this.num.contains(this.cal[i].getText())) {
                this.cal[i].setEnabled(true);
            }
            else {
                this.cal[i].setEnabled(false);
            }
        }
        this.pointSignal = false;
    }

    public void inEqual() {
        for (int i=0;i<20;i++) {
            this.cal[i].setEnabled(false);
        }
    }
 

  现在我们可以讨论对bigWork的设计了,由于按一个按钮只返回他的text到bigWork里,且他的text是String类型的,所以我们接收数字也需要接收String类型的,然后通过java中字符串变量的和运算把数字连起来“123”+“456”==“123456”,现在问题在于我们应该什么时候将连接好的数字交由后端,答案是我们应该在输入符号时将数字交由后端。如程序流程图:

除此之外,我们还需要记录我们输入括号的个数,让括号个数匹配,代码如下:

 
    /*****************************************核酸隔离点*********************************************/


    // 真正的超级初始化块
    public calculator() throws HeadlessException {
        // 界面设置
        this.__init__();
        this.setNorth();
        this.setCenter();
        this.setSouth();
        // 交互设置
        this.setListener();
    }


    calculate calculate = new calculate();
    private String temStr = "";
    // 控制器
    public void bigWork(String listen) {
        
        // 记录括号信号
        if ("(".equals(listen)) {
            this.barcketNum++;
        }
        if (")".equals(listen)) {
            this.barcketNum--;
        }
        
        // 基础状体转换
        if (this.num.contains(listen)) {
            this.temStr = this.temStr +listen;
            this.inNum();
        } else if (this.sign.contains(listen)) {
            if(!"".equals(temStr)) {
                this.calculate.numPush(this.temStr);
                this.temStr = "";
            }
            this.calculate.calIOC(listen);
            this.inSign();
        } else if (")".equals(listen) || listen.contains("^")) {
            if(!"".equals(temStr)) {
                this.calculate.numPush(this.temStr);
                this.temStr = "";
            }
            if (listen.contains("^")) {
                calculate.powIOC(listen);
            } else {
                this.calculate.calIOC(listen);
            }
            this.inPow();
        } else if (".".equals(listen)) {
            this.temStr = this.temStr +listen;
            this.inPoint();
        }  else if ("=".equals(listen)) {
            if(!"".equals(temStr)) {
                this.calculate.numPush(this.temStr);
                this.temStr = "";
            }
            this.input.setText(this.calculate.equaIOC().toString());
            this.inEqual();
        }else if ("C".equals(listen)) {
            this.calculate.refresh();
            this.input.setText("");
            this.temStr = "";
            this.barcketNum = 0;
            this.inSign();
        } else {
            JOptionPane.showMessageDialog(this, "error : unvaild input");
        }
        
        // 限制用户输入
        if (this.barcketNum < 0) {
            JOptionPane.showMessageDialog(this,"error : wrong number of barcket");
        }
        if(this.barcketNum == 0) {
            this.cal[3].setEnabled(false);
        }

        if (this.barcketNum > 0) {
            this.cal[18].setEnabled(false);
        }
    }
 

  现在我们跟随数据流到达后端,之前说过,我们的科学计算器为双栈结构,如下:

 // 符号栈
    private Stack<String> signStack = new Stack();

    // 数字栈
    private Stack<BigDecimal> numStack = new Stack();

  我们把数字和符号入栈分开是为了达到科学计算的效果,如果使用数字等级标注每个符号的优先度,那么可恶的括号则无法实现,括号会带来很多问题,虽然也可以通过专门给括号写单独一块清空的计算器程序实现,但是那样太过繁琐。总的来说我们设计了运算优先度如下:

 

这种结构的计算器是大一下时数据结构课上讲的,由于我们设置了限制用户输入,我们可以不用考虑null的情况,而我们要单独考虑的是等号和pow运算(平方开方),因为pow运算都只需要对一个数字运算,而其他的运算符号是两个,而输入等号后我们的计算器会计算到停止,具体代码如下:

 
 /**********************************************入集中营**************************************************/

    // 索引,见详细设计
    private String index = "+-x/()=";

    // 数据,见详细设计^^_  ,>为0,<为1,=为2,null为3
    private int[][] compareToSign = {{0, 0, 1, 1, 1, 0, 0}, {0, 0, 1, 1, 1, 0, 0}, {0, 0, 0, 0, 1, 0, 0},
            {0, 0, 0, 0, 1, 0, 0}, {1, 1, 1, 1, 1, 2, 3}, {0, 0, 0, 0, 3, 0, 0}, {1, 1, 1, 1, 1, 3, 2}};


    // 数字入栈
    public void numPush(String decimal) {
        this.numStack.push(new BigDecimal(decimal));
    }

    public void numPush(BigDecimal decimal) {
        this.numStack.push(decimal);
    }


    // 控制流,详细见详细设计p1 :/
    public void calIOC(String topSign) {
        BigDecimal caled, cal;
        String temp;
        temp = this.signStack.peek();
        switch (this.compareToSign[index.indexOf(temp)][index.indexOf(topSign)]) {
            case 0:
//                if ("()".contains(temp)) {
//                    this.signStack.pop();             这种方法解决括号在多层括号时会产生问题
//                } else {
                cal = this.numStack.pop();
                caled = this.numStack.pop();
                temp = this.signStack.pop();
                this.numStack.push(this.work(caled, cal, temp));
                this.signStack.push(topSign);
//                }

                break;
            case 1:
                this.signStack.push(topSign);
                break;
            case 2:
                this.signStack.pop();
                break;
            default:
                System.out.println("Exception : wrong I/O");
                break;
        }
    }


    // 等号入栈
    public BigDecimal equaIOC() {
        BigDecimal ans, caled, cal;
        String topSign;
        while (!"=".equals(this.signStack.peek())) {
            topSign = this.signStack.pop();
//            if ("()".contains(topSign)) {
//                this.signStack.pop();             这种方法解决括号在多层括号时会产生问题
//            } else {
            cal = this.numStack.pop();
            caled = this.numStack.pop();
            this.numStack.push(this.work(caled, cal, topSign));
//            }
        }
        ans = this.numStack.pop();
        return ans;
    }

    // pow的IO流控制
    public void powIOC(String topSign) {
        BigDecimal temp;
        temp = this.numStack.pop();
        if (topSign.equals("^2")) {
            this.numStack.push(calculate.square(temp));
        } else {
            this.numStack.push(calculate.niuton(temp));
        }
    }

    public static void main(String[] args) {
        calculate c = new calculate();
        c.numPush("2");
        c.powIOC("^2");
        System.out.println(c.numStack.peek());
    }

}
 

可以看见一些被注释起来的代码,那些代码是我第一次解决可恶的括号时使用的代码,如注释所说,他会引起多层运算时数字的丢失。

  接下来就是基础功能的模块,也就是计算器的本体,到这里才引出该计算器的第三大特色其实有点晚了(因为不一定会看到这里),那么该计算器的第三大特色在于他使用了java的BigDecimal库作为基础载体,也就是说这个计算器里的数不存在二进制浮点问题和数字过大。还记得上面被注释的判断括号的代码吗,我把判断括号的代码移到了这里,就不会有上面的问题了。最后这里还有个刷新函数,是用于初始化和使用者输入Clear按钮后的情况。

 
 /*******************************************将军的恩情还不完*************************************************/


    // 额switch可以判断引用类型变量的值相等吗{0:'+', 1:'-', 2:'x', 3:'/'}
    private static final String signCode = "+-x/()";

    // 计算器主体模块
    public BigDecimal work(BigDecimal caled, BigDecimal cal, String sign) {
        BigDecimal ans;
        switch (signCode.indexOf(sign)) {
            case 0:
                ans = caled.add(cal);
                break;
            case 1:
                ans = caled.subtract(cal);
                break;
            case 2:
                ans = caled.multiply(cal);
                break;
            case 3:
                ans = caled.divide(cal);
                break;
            case 4:
            case 5:
                this.numStack.push(caled);
                ans = cal;
                break;
            default:
                ans = null;
        }
        return ans;
    }

    // 设计开方(牛顿莱布尼兹)
    public static BigDecimal niuton(BigDecimal caled) {
        BigDecimal ans;
        if (caled.doubleValue() < 0) {
            System.out.println("Exception : Negative caled");
            return BigDecimal.valueOf(32202);
        }
        double x = 1;
        double y = x - (x * x - caled.doubleValue()) / (2 * x);
        while (x - y > 0.00000001 || x - y < -0.00000001) {
            x = y;
            y = x - (x * x - caled.doubleValue()) / (2 * x);
        }
        ans = BigDecimal.valueOf(y);
        return ans;
    }

    // 设计平方
    public static BigDecimal square(BigDecimal caled) {
        return caled.pow(2);
    }

    // 设计清屏
    public void refresh() {
        this.numStack.clear();
        this.signStack.clear();
        this.signStack.push("=");
        // 解决计算当(x+y)后输入符号时,需要出栈两个数进行括号运算(即将数按顺序压回去)时数字栈只有一个栈的问题。
        this.numStack.push(new BigDecimal(0));
    }
 

最后就是平平无奇的启动代码块:

复制代码
    // 软件测试
    public static void main(String[] args) {
        calculator cal = new calculator();
        JOptionPane.showMessageDialog(cal, "由于框架原因,本计算器打开时可能按钮显示不全,请最小化后打开");
        System.out.println(SCREAM_WIDTH + " * " + SCREAM_HEIGHT + " due " + EXAMPLE);
        cal.inSign();
        cal.calculate.refresh();
    }
 

 

五、软件测试

  我从网上找了些测试用例,都通过了:

 

                     

这里给出最复杂的测试用例及其结果,是准确的,其他的也没问题。

 

好了以下为计算器源码:

 

calculator.java:

  1 package com.auqa.version;
  2 
  3 // SDT软件开发组作业,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏,禁止外传
  4 
  5 // 导入自己的计算类
  6 import POJO.calculate;
  7 
  8 import javax.swing.*;
  9 import java.awt.*;
 10 import java.awt.event.*;
 11 
 12 
 13 // 计算器界面
 14 public class calculator extends JFrame implements ActionListener {
 15 
 16     // 获得显示屏大小
 17     public static final int SCREAM_HEIGHT = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
 18     public static final int SCREAM_WIDTH = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
 19     // 根据显示屏高度设定计算器界面大小
 20     private static final int EXAMPLE = (int) (SCREAM_HEIGHT / 4.32);
 21 
 22 
 23     // 字体大小
 24     Font cu = new Font("粗体", Font.BOLD, (int) (EXAMPLE * 0.2));
 25 
 26 
 27     /**********************************************超级初始化块*******************************************************/
 28 
 29 
 30     protected void __init__() {
 31         // 设置窗口名称
 32         this.setTitle("计算器");
 33         System.out.println("0b1100001 0b1110101 0b1110100 0b1101000 0b1101111 0b1110010 0b100000 0b1110100 0b1100101 0b1100001 0b1101101 0b100000 0b1101001 0b1110011 0b100000 0b1001110 0b1001111 0b101110 0b110001 0b100000 0b1001110 0b1001111 0b101110 0b111001 0b100000 0b1001110 0b1001111 0b101110 0b110001 0b110011 0b100000 0b1001110 0b1001111 0b101110 0b110010 0b110111 0b100000 0b1001110 0b1001111 0b101110 0b110011 0b110001 0b100000 0b1001110 0b1001111 0b101110 0b110100 0b110001 0b100000 0b1101001 0b1101110 0b100000 0b110010 0b110001 0b1000011 0b1010011");
 34         // 4比3固定窗口
 35         this.setSize(EXAMPLE * 3, EXAMPLE * 4);
 36         this.setResizable(false);
 37         this.setLocationRelativeTo(null);
 38         // 设置窗口可见
 39         this.setVisible(true);
 40         this.setBackground(Color.black);
 41         // 设置关闭按钮(释放进程)
 42         this.setDefaultCloseOperation(EXIT_ON_CLOSE);
 43         // 设置方向布局
 44         this.setLayout(new BorderLayout());
 45     }
 46 
 47 
 48     /**********************************************北国风光*******************************************************/
 49 
 50 
 51     // 北面组件
 52     private JPanel northBox = new JPanel(new FlowLayout());
 53     private JTextField input = new JTextField();
 54     private JButton clear = new JButton();
 55 
 56 
 57     // 设置北面组件
 58     private void setNorth() {
 59         // 设置数字栏
 60         this.input.setPreferredSize(new Dimension((int) (EXAMPLE * 2.2), (int) (EXAMPLE * 0.4)));
 61         this.input.setFont(this.cu);
 62         this.input.setForeground(Color.BLACK);      // 额好像没用,但限制用户输入更重要
 63         this.input.setEnabled(false);
 64         this.input.setHorizontalAlignment(SwingConstants.RIGHT);
 65 
 66         // 设置清空
 67         this.clear.setText("C");
 68         this.clear.setPreferredSize(new Dimension((int) (EXAMPLE * 0.4), (int) (EXAMPLE * 0.4)));
 69         this.clear.setFont(this.cu);
 70         this.clear.setForeground(Color.RED);
 71 
 72         // 安装北仪表
 73         this.northBox.add(this.input);
 74         this.northBox.add(this.clear);
 75 
 76         // 安装北仪表到主体
 77         this.add(this.northBox, BorderLayout.NORTH);
 78     }
 79 
 80 
 81     /**********************************************中央处理器*******************************************************/
 82 
 83 
 84     // 中部组件
 85     private JPanel CPU = new JPanel();
 86     private JButton[] cal = new JButton[20];
 87     // 后16个按钮顺序,懒得写集合类了
 88     String str = "789/456x123-0.=+";
 89 
 90     // 设置中部组件
 91     private void setCenter() {
 92         // 划分20格
 93         this.CPU.setLayout(new GridLayout(5, 4));
 94         // 设置开方按钮
 95         this.cal[0] = new JButton();
 96         this.cal[0].setText("^-2");
 97         this.cal[0].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
 98         this.cal[0].setFont(this.cu);
 99         this.cal[0].setForeground(Color.BLUE);
100         // 设置括号按钮
101         this.cal[1] = new JButton();
102         this.cal[1].setText("^2");
103         this.cal[1].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
104         this.cal[1].setFont(this.cu);
105         this.cal[1].setForeground(Color.BLUE);
106         if (!(version.equals("本计算器由SDT软件开发组开发,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏")))
107             JOptionPane.showMessageDialog(this, "验证码被修改,您使用的为盗版。\n本计算器由SDT软件开发组开发,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏");
108         if (!(version.equals("本计算器由SDT软件开发组开发,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏")))
109             this.dispose();
110         this.cal[2] = new JButton();
111         this.cal[2].setText("(");
112         this.cal[2].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
113         this.cal[2].setFont(this.cu);
114         this.cal[2].setForeground(Color.BLUE);
115 
116         // 设置清除按钮
117         this.cal[3] = new JButton();
118         this.cal[3].setText(")");
119         this.cal[3].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
120         this.cal[3].setFont(this.cu);
121         this.cal[3].setForeground(Color.BLUE);
122 
123         // 设置后16个按钮
124         for (int i = 4; i < 20; i++) {
125             String temp = this.str.substring(i - 4, i - 3);
126             this.cal[i] = new JButton();
127             this.cal[i].setText(temp);
128             this.cal[i].setPreferredSize(new Dimension((int) (EXAMPLE * 0.2), (int) (EXAMPLE * 0.15)));
129             this.cal[i].setFont(this.cu);
130             if ("+-x/=".contains(temp)) {
131                 this.cal[i].setForeground(Color.GRAY);
132             }
133         }
134         // 添加按钮
135         for (int i = 0; i < 20; i++) {
136             this.CPU.add(this.cal[i]);
137         }
138         this.add(this.CPU,BorderLayout.CENTER);
139     }
140 
141 
142     /**********************************************南柯一梦*******************************************************/
143 
144     public static final String version = "本计算器由SDT软件开发组开发,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏";
145     // 南面组件
146     private JLabel message = new JLabel(version, SwingConstants.CENTER);
147 
148     // 设置南面组件
149     private void setSouth() {
150         this.message.setPreferredSize(new Dimension((int) (EXAMPLE * 0.1), (int) (EXAMPLE * 0.1)));
151         this.message.setForeground(Color.WHITE);
152         this.add(this.message, BorderLayout.SOUTH);
153     }
154 
155 
156     /*********************************************监听*********************************************************/
157 
158     // 给按钮添加监听
159     private void setListener() {
160         for (JButton j : cal) {
161             j.addActionListener(this);
162         }
163         this.clear.addActionListener(this);
164     }
165 
166     // 监听事件设置
167     @Override
168     public void actionPerformed(ActionEvent e) {
169         String listen = e.getActionCommand();
170         if ("0.1^23456789+-x/()^-2".contains(listen)) {
171             this.input.setText(this.input.getText() + listen);
172         }
173         this.bigWork(listen);
174     }
175 
176 
177     /*****************************************状态**************************************************/
178 
179     // 小数点信号
180     private Boolean pointSignal = false;
181     // 括号信号
182     private int barcketNum = 0;
183 
184     private String num = "0123456789";
185     private String sign = "+-x/(";
186 
187     // 输入的最后一位为数字时的状态,详细见详细设计表格
188     public void inNum() {
189         // 只能输入pow函数,右括号,数字和符号按钮,不能输入左括号,若小数点信号为真,则可以输入小数点
190         for (int i=0;i<20;i++) {
191             if("(".equals(this.cal[i].getText())) {
192                 this.cal[i].setEnabled(false);
193             }
194             else {
195                 this.cal[i].setEnabled(true);
196             }
197         }
198         // 根据信号设置
199         this.cal[17].setEnabled(this.pointSignal);
200     }
201 
202     // 输入的最后一位为符号或左括号时
203     public void inSign() {
204         // 只能输入非小数点数字及左括号,小数点信号开启
205         for (int i=0;i<20;i++) {
206             if("(".equals(this.cal[i].getText()) || this.num.contains(this.cal[i].getText())) {
207                 this.cal[i].setEnabled(true);
208             }
209             else {
210                 this.cal[i].setEnabled(false);
211             }
212         }
213         this.pointSignal = true;
214     }
215 
216     // 输入最后一位为右括号或pow运算时
217     public void inPow() {
218         // 只能输入符号和右括号和pow函数
219         for (int i=0;i<20;i++) {
220             if("(".equals(this.cal[i].getText()) || this.num.contains(this.cal[i].getText()) || ".".equals(this.cal[i].getText())) {
221                 this.cal[i].setEnabled(false);
222             }
223             else {
224                 this.cal[i].setEnabled(true);
225             }
226         }
227     }
228 
229     // 输入最后一位为小数点时
230     public void inPoint() {
231         // 只能输入非小数点数字,小数点信号关闭
232         for (int i=0;i<20;i++) {
233             if(this.num.contains(this.cal[i].getText())) {
234                 this.cal[i].setEnabled(true);
235             }
236             else {
237                 this.cal[i].setEnabled(false);
238             }
239         }
240         this.pointSignal = false;
241     }
242 
243     public void inEqual() {
244         for (int i=0;i<20;i++) {
245             this.cal[i].setEnabled(false);
246         }
247     }
248 
249 
250     /*****************************************核酸隔离点*********************************************/
251 
252 
253     // 真正的超级初始化块
254     public calculator() throws HeadlessException {
255         // 界面设置
256         this.__init__();
257         this.setNorth();
258         this.setCenter();
259         this.setSouth();
260         // 交互设置
261         this.setListener();
262     }
263 
264 
265     calculate calculate = new calculate();
266     private String temStr = "";
267     // 控制器
268     public void bigWork(String listen) {
269         
270         // 记录括号信号
271         if ("(".equals(listen)) {
272             this.barcketNum++;
273         }
274         if (")".equals(listen)) {
275             this.barcketNum--;
276         }
277         
278         // 基础状体转换
279         if (this.num.contains(listen)) {
280             this.temStr = this.temStr +listen;
281             this.inNum();
282         } else if (this.sign.contains(listen)) {
283             if(!"".equals(temStr)) {
284                 this.calculate.numPush(this.temStr);
285                 this.temStr = "";
286             }
287             this.calculate.calIOC(listen);
288             this.inSign();
289         } else if (")".equals(listen) || listen.contains("^")) {
290             if(!"".equals(temStr)) {
291                 this.calculate.numPush(this.temStr);
292                 this.temStr = "";
293             }
294             if (listen.contains("^")) {
295                 calculate.powIOC(listen);
296             } else {
297                 this.calculate.calIOC(listen);
298             }
299             this.inPow();
300         } else if (".".equals(listen)) {
301             this.temStr = this.temStr +listen;
302             this.inPoint();
303         }  else if ("=".equals(listen)) {
304             if(!"".equals(temStr)) {
305                 this.calculate.numPush(this.temStr);
306                 this.temStr = "";
307             }
308             this.input.setText(this.calculate.equaIOC().toString());
309             this.inEqual();
310         }else if ("C".equals(listen)) {
311             this.calculate.refresh();
312             this.input.setText("");
313             this.temStr = "";
314             this.barcketNum = 0;
315             this.inSign();
316         } else {
317             JOptionPane.showMessageDialog(this, "error : unvaild input");
318         }
319         
320         // 限制用户输入
321         if (this.barcketNum < 0) {
322             JOptionPane.showMessageDialog(this,"error : wrong number of barcket");
323         }
324         if(this.barcketNum == 0) {
325             this.cal[3].setEnabled(false);
326         }
327 
328         if (this.barcketNum > 0) {
329             this.cal[18].setEnabled(false);
330         }
331     }
332 
333     // 软件测试
334     public static void main(String[] args) {
335         calculator cal = new calculator();
336         JOptionPane.showMessageDialog(cal, "由于框架原因,本计算器打开时可能按钮显示不全,请最小化后打开");
337         System.out.println(SCREAM_WIDTH + " * " + SCREAM_HEIGHT + " due " + EXAMPLE);
338         cal.inSign();
339         cal.calculate.refresh();
340     }
341 
342 
343 }
 

 

 calculate.java:

 

 
  1 package POJO;
  2 
  3 // SDT软件开发组作业,小组成员:宋宇昂,邵麟钧,杨斌,苟小兵,白嘉欣,李博宏,禁止外传
  4 
  5 import java.util.*;
  6 import java.math.*;
  7 
  8 
  9 public class calculate {
 10 
 11     // 符号栈
 12     private Stack<String> signStack = new Stack();
 13 
 14     // 数字栈
 15     private Stack<BigDecimal> numStack = new Stack();
 16 
 17 
 18     /*******************************************将军的恩情还不完*************************************************/
 19 
 20 
 21     // 额switch可以判断引用类型变量的值相等吗{0:'+', 1:'-', 2:'x', 3:'/'}
 22     private static final String signCode = "+-x/()";
 23 
 24     // 计算器主体模块
 25     public BigDecimal work(BigDecimal caled, BigDecimal cal, String sign) {
 26         BigDecimal ans;
 27         switch (signCode.indexOf(sign)) {
 28             case 0:
 29                 ans = caled.add(cal);
 30                 break;
 31             case 1:
 32                 ans = caled.subtract(cal);
 33                 break;
 34             case 2:
 35                 ans = caled.multiply(cal);
 36                 break;
 37             case 3:
 38                 ans = caled.divide(cal);
 39                 break;
 40             case 4:
 41             case 5:
 42                 this.numStack.push(caled);
 43                 ans = cal;
 44                 break;
 45             default:
 46                 ans = null;
 47         }
 48         return ans;
 49     }
 50 
 51     // 设计开方(牛顿莱布尼兹)
 52     public static BigDecimal niuton(BigDecimal caled) {
 53         BigDecimal ans;
 54         if (caled.doubleValue() < 0) {
 55             System.out.println("Exception : Negative caled");
 56             return BigDecimal.valueOf(32202);
 57         }
 58         double x = 1;
 59         double y = x - (x * x - caled.doubleValue()) / (2 * x);
 60         while (x - y > 0.00000001 || x - y < -0.00000001) {
 61             x = y;
 62             y = x - (x * x - caled.doubleValue()) / (2 * x);
 63         }
 64         ans = BigDecimal.valueOf(y);
 65         return ans;
 66     }
 67 
 68     // 设计平方
 69     public static BigDecimal square(BigDecimal caled) {
 70         return caled.pow(2);
 71     }
 72 
 73     // 设计清屏
 74     public void refresh() {
 75         this.numStack.clear();
 76         this.signStack.clear();
 77         this.signStack.push("=");
 78         // 解决计算当(x+y)后输入符号时,需要出栈两个数进行括号运算(即将数按顺序压回去)时数字栈只有一个栈的问题。
 79         this.numStack.push(new BigDecimal(0));
 80     }
 81 
 82 
 83     /**********************************************入集中营**************************************************/
 84 
 85     // 索引,见详细设计
 86     private String index = "+-x/()=";
 87 
 88     // 数据,见详细设计^^_  ,>为0,<为1,=为2,null为3
 89     private int[][] compareToSign = {{0, 0, 1, 1, 1, 0, 0}, {0, 0, 1, 1, 1, 0, 0}, {0, 0, 0, 0, 1, 0, 0},
 90             {0, 0, 0, 0, 1, 0, 0}, {1, 1, 1, 1, 1, 2, 3}, {0, 0, 0, 0, 3, 0, 0}, {1, 1, 1, 1, 1, 3, 2}};
 91 
 92 
 93     // 数字入栈
 94     public void numPush(String decimal) {
 95         this.numStack.push(new BigDecimal(decimal));
 96     }
 97 
 98     public void numPush(BigDecimal decimal) {
 99         this.numStack.push(decimal);
100     }
101 
102 
103     // 控制流,详细见详细设计p1 :/
104     public void calIOC(String topSign) {
105         BigDecimal caled, cal;
106         String temp;
107         temp = this.signStack.peek();
108         switch (this.compareToSign[index.indexOf(temp)][index.indexOf(topSign)]) {
109             case 0:
110 //                if ("()".contains(temp)) {
111 //                    this.signStack.pop();             这种方法解决括号在多层括号时会产生问题
112 //                } else {
113                 cal = this.numStack.pop();
114                 caled = this.numStack.pop();
115                 temp = this.signStack.pop();
116                 this.numStack.push(this.work(caled, cal, temp));
117                 this.signStack.push(topSign);
118 //                }
119 
120                 break;
121             case 1:
122                 this.signStack.push(topSign);
123                 break;
124             case 2:
125                 this.signStack.pop();
126                 break;
127             default:
128                 System.out.println("Exception : wrong I/O");
129                 break;
130         }
131     }
132 
133 
134     // 等号入栈
135     public BigDecimal equaIOC() {
136         BigDecimal ans, caled, cal;
137         String topSign;
138         while (!"=".equals(this.signStack.peek())) {
139             topSign = this.signStack.pop();
140 //            if ("()".contains(topSign)) {
141 //                this.signStack.pop();             这种方法解决括号在多层括号时会产生问题
142 //            } else {
143             cal = this.numStack.pop();
144             caled = this.numStack.pop();
145             this.numStack.push(this.work(caled, cal, topSign));
146 //            }
147         }
148         ans = this.numStack.pop();
149         return ans;
150     }
151 
152     // pow的IO流控制
153     public void powIOC(String topSign) {
154         BigDecimal temp;
155         temp = this.numStack.pop();
156         if (topSign.equals("^2")) {
157             this.numStack.push(calculate.square(temp));
158         } else {
159             this.numStack.push(calculate.niuton(temp));
160         }
161     }
162 
163     public static void main(String[] args) {
164         calculate c = new calculate();
165         c.numPush("2");
166         c.powIOC("^2");
167         System.out.println(c.numStack.peek());
168     }
169 
170 }
 

                                                                                   

posted @ 2023-10-14 19:56  Soutcachesize  阅读(33)  评论(0编辑  收藏  举报