基于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 }