Java 计算器的实现(两种不同思路)

来源:互联网 发布:淘宝上电吉他哪家好 编辑:程序博客网 时间:2024/04/29 23:00

这几天java课上老师要我们实现一个计算器。由于刚开始学习java,其中界面显示部分的代码老师已经准备好了,并且整个程序是采用MVCModel–view–controller,点击打开链接 的设计模式,我们要实现的只是其中的Model,即核心的算法模型。先看看用户界面(View部分)吧。


一、最初仅提供了基本用户界面的代码

为了让大家方便试验计算器程序,现把计算器的实现代码发上来。下面的是老师发布题目的代码。

     其中只有Calculator.java是MVC中的Model部分,这次也只需要修改这部分代码。其它部分提供了界面的布局、按钮等,不需要改变。

(1)最初的 Calculator类 定义

// Calculator.java
// The core of the great calculator// Check the "TODO"s!!class Calculator {String expression = "0";// TODO: modify the method to return a proper expression //which will be shown in the screen of the calculatorString getExpression() {return expression;}// TODO: modify the method to handle the key press eventvoid keyPressed(char key) {expression += key;}// TODO: you can modify this method to print any debug//information (It will be called by CalculatorCmd)void debugPrintStatus() {System.out.println("Expression = " + expression);}}

(2)计算器图形版 程序入口

// CalculatorApp.java
// Define entry point of the calculator applicationclass CalculatorApp {public static void main(String[] args) {CalculatorWindow mainwnd = new CalculatorWindow();CalculatorController control = new CalculatorController();mainwnd.setController(control);mainwnd.setVisible(true);}}

(3)计算器命令行版 程序入口

// CalculatorCmd.java
// A calculator with command line interfaceimport java.io.*;public class CalculatorCmd {public static void main(String[] args) throws IOException {System.out.println("Welcome to use commoand line calculator.");Calculator calculator = new Calculator();while (true) {calculator.debugPrintStatus();System.out.println("\nEXP = " + calculator.getExpression());System.out.print("input: ");char c = (char)System.in.read();if (c == 'x' || c == 'X') break;calculator.keyPressed(c);}System.out.println("\nBye.");}}
(4)控制器(Control)部分,将按下的 按键key 移交给keypressed(char key)函数,这也是计算器需要编写的重点。
// CalculatorController.java
// The controller of the calculator applicationimport java.awt.event.*;import javax.swing.*;class CalculatorController implements ActionListener {Calculator calc = new Calculator();JLabel window;public void actionPerformed(ActionEvent e) {char key = e.getActionCommand().charAt(0);calc.keyPressed(key);if (window!=null) {window.setText(calc.getExpression());}}void setDisplayWindow(JLabel w) {window = w;}}
(5)计算器的窗口布局,有点繁琐,暂时可以不用细究。
// The window of the calculator application// Layout the buttons, and register the button action eventsimport java.awt.*;import javax.swing.*;import javax.swing.border.*;import java.awt.event.*;class CalculatorWindow extends JFrame {JLabel disp;JButton cancelButton, equalButton, dotButton;JButton signButton, addButton, subButton, mulButton, divButton;JButton[] numButton;CalculatorWindow() {this.setSize(400, 440);this.setResizable(false);this.setTitle("Java Calculator");this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);Container wnd = getContentPane();wnd.setLayout(null);JPanel dispPanel = new JPanel();JPanel controlPanel = new JPanel();wnd.add(dispPanel);wnd.add(controlPanel);dispPanel.setBounds(0,0,400,60);controlPanel.setBounds(0,60,400,360);dispPanel.setBorder(new LineBorder(Color.GRAY));disp = new JLabel("0");// disp.setBorder(new LineBorder(Color.RED));disp.setSize(new Dimension(380, 60));Font dispFont = new Font("Arial", Font.PLAIN, 24);disp.setFont(dispFont);disp.setHorizontalAlignment(SwingConstants.RIGHT);dispPanel.setLayout(null);dispPanel.add(disp);// dispPanel.setMinimumSize(new Dimension(400, 500));GridBagLayout gridbag = new GridBagLayout();// controlPanel.setLayout(new GridLayout(4,4,10,20));controlPanel.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);controlPanel.setLayout(gridbag);controlPanel.setBorder(new EmptyBorder(20,10,20,10));cancelButton = new JButton("c");signButton = new JButton("+/-");addButton = new JButton("+");subButton = new JButton("-");mulButton = new JButton("*");divButton = new JButton("/");equalButton = new JButton("=");numButton = new JButton[10];for(int i=0; i<10; i++) {numButton[i] = new JButton(String.valueOf(i));}dotButton = new JButton(".");GridBagConstraints c = new GridBagConstraints();c.insets = new Insets(3,2,3,2);c.fill = GridBagConstraints.BOTH;    c.weightx = 1.0;    c.weighty = 1.0;    gridbag.setConstraints(mulButton, c);controlPanel.add(mulButton);    gridbag.setConstraints(divButton, c);controlPanel.add(divButton);    gridbag.setConstraints(signButton, c);controlPanel.add(signButton);c.gridwidth = GridBagConstraints.REMAINDER;    gridbag.setConstraints(cancelButton, c);controlPanel.add(cancelButton);c.gridwidth = 1;    gridbag.setConstraints(subButton, c);controlPanel.add(subButton);    gridbag.setConstraints(numButton[9], c);controlPanel.add(numButton[9]);    gridbag.setConstraints(numButton[8], c);controlPanel.add(numButton[8]);c.gridwidth = GridBagConstraints.REMAINDER;    gridbag.setConstraints(numButton[7], c);controlPanel.add(numButton[7]);c.gridwidth = 1;    gridbag.setConstraints(addButton, c);controlPanel.add(addButton);    gridbag.setConstraints(numButton[6], c);controlPanel.add(numButton[6]);    gridbag.setConstraints(numButton[5], c);controlPanel.add(numButton[5]);c.gridwidth = GridBagConstraints.REMAINDER;    gridbag.setConstraints(numButton[4], c);controlPanel.add(numButton[4]);c.gridwidth = 1;c.gridheight = 2;    gridbag.setConstraints(equalButton, c);controlPanel.add(equalButton);c.gridheight = 1;    gridbag.setConstraints(numButton[3], c);controlPanel.add(numButton[3]);    gridbag.setConstraints(numButton[2], c);controlPanel.add(numButton[2]);c.gridwidth = GridBagConstraints.REMAINDER;    gridbag.setConstraints(numButton[1], c);controlPanel.add(numButton[1]);c.gridwidth = 1;gridbag.setConstraints(dotButton, c);controlPanel.add(dotButton);c.gridwidth = GridBagConstraints.REMAINDER;gridbag.setConstraints(numButton[0], c);controlPanel.add(numButton[0]);this.addWindowListener(new WindowAdapter() {       public void windowOpened(WindowEvent e) {              equalButton.requestFocus();       }    });}class CalculatorHotKey extends KeyAdapter {public void keyPressed(KeyEvent e) {char key = e.getKeyChar();if (key >= '0' && key <= '9') {numButton[key - '0'].doClick();} else switch(e.getKeyChar()) {case 'c':cancelButton.doClick();break;case '~':signButton.doClick();break;case '+':addButton.doClick();break;case '-':subButton.doClick();break;case '*':mulButton.doClick();break;case '/':divButton.doClick();break;case '=':equalButton.doClick();break;case '.':dotButton.doClick();break;case '\n':equalButton.doClick();break;}}}void setController(CalculatorController control) {control.setDisplayWindow(disp);CalculatorHotKey keyMap = new CalculatorHotKey();cancelButton.addKeyListener(keyMap);cancelButton.addActionListener(control);cancelButton.setActionCommand("c");signButton.addKeyListener(keyMap);signButton.addActionListener(control);signButton.setActionCommand("~");addButton.addKeyListener(keyMap);addButton.addActionListener(control);addButton.setActionCommand("+");subButton.addKeyListener(keyMap);subButton.addActionListener(control);subButton.setActionCommand("-");mulButton.addKeyListener(keyMap);mulButton.addActionListener(control);mulButton.setActionCommand("*");divButton.addKeyListener(keyMap);divButton.addActionListener(control);divButton.setActionCommand("/");equalButton.addKeyListener(keyMap);equalButton.addActionListener(control);equalButton.setActionCommand("=");dotButton.addKeyListener(keyMap);dotButton.addActionListener(control);dotButton.setActionCommand(".");for(int i=0; i<10; i++) {numButton[i].addKeyListener(keyMap);numButton[i].addActionListener(control);numButton[i].setActionCommand(String.valueOf(i));}}}


==========================================

二、采用状态机(State Machine)思路编写的计算器核心模型(Calculator.java)

       基于从老师课上演示得到的灵感,感觉整个计算器的行为表现得就像一个状态机(StateMachine)。上学期学EDA时,利用VHDL语言在FPGA硬件平台上完成许多任务时也是建立状态机模型来实现,我想那不妨就用状态机模型来指导整个程序的编写吧。我整理了一下计算器的各种状态,画了一张状态转移图。当然,或许还可以再设计出更简单的状态图。

       就像图例所示,每个状态有各自的状态编号st(i),i=1,2,..,5;初始状态为复位(st0)。

每个状态根据键盘不同的输入跳转到不同的下一个状态,在每个状态中执行相应的操作。如下图:

      [1] 操作数置换:因为输入完第二操作数后,不是按等号,而是继续按下运算符,于是先把结果记作第1操作数,清空第二操作数,并继续进入输入运算符的状态。


在程序中,状态的编号是用枚举变量实现的,这个状态机的结构是在keyPressed(char key)函数中利用一个switch(c_state)实现的,其中c_state意味着当前状态(current state)。在每一个状态下会执行那个状态的服务函数fun_sti(),i=1,2,..,5;在这些函数内实现当前状态的任务以及状态跳转的控制

       我自己觉得用这种思路去写程序,容易方便程序的不断“升级壮大”,因为每次我只需要仔细地完成某个状态的代码便可开始试运行,就算是某个状态的代码出错了,还不会影响到其它状态的执行,容易找出错误位置。还容易依此添加新的控制能力,就比如说要控制往屏幕上输出消息,可以拿当前状态为依据输出不同的消息:

完整的实现代码如下:

// Calculator.java
// The core of the great calculator// Check the "TODO"s!!class Calculator {private double result=0.0; private char[] symbols={'+','-','*','/'};  // 定义将会使用到的符号private char symbol_use=' '; // 最近一次运算被选中的符号,默认为空private enum STATE {st0, st1, st2, st3, st4, st5}; // 状态图中使用的状态private STATE c_state= STATE.st1;// 初始状态直接从 st1 开始好了private OperaNum op_left = new OperaNum(),// 定义左、右操作数 op_right = new OperaNum();// 在屏幕上显示的内容,依据当前的不同状态来定。String getExpression() {switch(c_state){case st0:case st1:return op_left.STR_value();case st2:return op_left.STR_value() + symbol_use;case st3:return op_left.STR_value() + symbol_use + op_right.STR_value();case st4:return "= " + result;default:return "0";}}// 下面是处于各状态需要做的事情private void fun_st0(){op_left.setClear();op_right.setClear();result = 0.0;c_state = STATE.st1;}private void fun_st1_basic(char key){if( (key>='0' && key<='9') || key=='.' || key=='~' ){op_left.pushDigital(key);c_state = STATE.st1;}else if( key=='=' ){// result = symbol_use = '=';fun_result();c_state = STATE.st4;}else if( key=='c' ){ // 按下C键fun_st0();c_state = STATE.st0;}else { // 输入运算符号 fun_st2(key); c_state = STATE.st2;}}private void fun_st2(char key){if( key=='+' || key=='-' || key=='*' || key=='/'){symbol_use = key;c_state = STATE.st2;}else if( (key>='0' && key<='9') || key=='.' || key=='~' ){fun_st3(key);c_state = STATE.st3;}}private void fun_st3(char key){if( (key>='0' && key<='9') || key=='.' || key=='~' ){op_right.pushDigital(key);c_state = STATE.st3;}else if( key=='=' ){// result = fun_result();c_state = STATE.st4;}else if( key=='c' ){ // 按下C键fun_st0();c_state = STATE.st0;}else{c_state = STATE.st5; // 按下运算符号fun_st5(key);}}void fun_st4(char key){ // 此时,已经显示出了结果if( (key>='0' && key<='9') || key=='.' || key=='~' ){fun_st0();op_left.pushDigital(key);c_state = STATE.st1;}else if( key=='+' || key=='-' || key=='*' || key=='/' ){ // 输入运算符号op_left.setValue(result);op_right.setClear();fun_st2(key);c_state = STATE.st2;}}void fun_st5(char key){fun_result();op_left.setValue(result);op_right.setClear();fun_st2(key);c_state = STATE.st2;}private void fun_result(){switch(symbol_use){case '+':result = op_left.value() + op_right.value();break;case '-':result = op_left.value() - op_right.value();break;case '*':result = op_left.value() * op_right.value();break;case '/':if( op_right.value()==0.0 )result = Double.POSITIVE_INFINITY;elseresult = op_left.value() / op_right.value();break;default: // 等号,或来自 st1 的直接按下等号result = op_left.value();break;}}// 在此函数中设定状态机模型void keyPressed(char key) {//expression += key; if(key=='c')c_state = STATE.st0; // 复位一下switch(c_state){case st0: fun_st0(); // 初始状态,并且等待输入case st1: fun_st1_basic(key); break; // 输入左操作数// ===============case st2: fun_st2(key);break;case st3: fun_st3(key);break;case st4: fun_st4(key); break;case st5: fun_st5(key); break;default:fun_st0();c_state = STATE.st0;break;}}// TODO: you can modify this method to print any debug//information (It will be called by CalculatorCmd)void debugPrintStatus() {// System.out.println("Expression = " + expression);System.out.println( "c_state" + c_state );}}


上面还需要补充一点,为了方便进行左、右操作数的运算,操作数单独提出来作为一种OperaNum类来实现,另写为OperaNum.java如下:

/* 操作数用类来实现,统一各种运算 --> 与数字有关的所有符号:【0-9】,【.】,【~】 */public class OperaNum{String expression="0";// 操作数的字符串表达式private double v=0.0;// 操作数的值private boolean SIGN=true; // true= pos, false= minus.OperaNum(double v){setValue(v);}OperaNum(){}void pushDigital(char key){ // 用于实现状态图中的“输入操作数”// (1) 输入普通数字if( key>='0' && key<='9')if( expression.equals("0") ){ // 若字符串是初始状态0,再按0无反应if( key=='0')expression="0";elseexpression = String.valueOf(key);}elseexpression +=key;if(key=='.')  // (2) 处理小数点pushDot(key);if(key=='~')  // (3) 处理符号SIGN = !SIGN;v= value();}void pushDot(char key){  // 帮助pushDigital()函数来输入小数点,这里要检查是否合乎规范if( expression.indexOf('.')<=0 ) // 若不成立,则表达式已经有小数点了expression += '.';}void setValue(double init_num){ // 强制设定操作数,一般不用v = init_num;expression = String.valueOf(v);}void setClear(){v = 0;expression = "0";SIGN = true;}double value(){// 返回当前操作数的值,还要根据是否加入了负号来决定取值v= Double.parseDouble(expression);return SIGN? v : (-1)*v;}String STR_value(){ // 返回当前操作数的字符串表达式,return SIGN? expression : "(-"+expression+")";}}


三、用简单的if语句控制的计算器核心模型(Calculator.java)

换一种思路,你说要简单来看嘛,可以把整个运行就分作3种状态:输入第1个操作数(INPUT_1)、输入运算符(INPUT_OP)、输入第二个操作数(INPUT_2)、已经输出了结果(SHOW)。这可以在Calculator类的开头定义出几个状态来用:

private final int INPUT_1  = 1;private final int INPUT_OP = 2;private final int INPUT_2  = 3;private final int SHOW     = 4;private int INPUT_STATE = 1;  // 存放程序当前所处状态

当程序处在INPUT_1时,若按下了运算符,则进入INPUT_OP状态;

        在INPUT_OP状态时若按下了数字则进入了INPUT_2状态;之后若按下了等号则进入SHOW状态,并再自动进入INPUT_1状态。

在INPUT_2状态时,若按下了运算符,则先把当前结果记作第1操作数,清空第2操作数,并进入INPUT_OP状态。

实现代码如下:

// Calculator.java
// The core of the great calculator// Check the "TODO"s!!class MyNum{String expression="0";// 操作数的字符串表达式private double value=0.0;// 操作数的值boolean SIGN=true;// 表示数字是正数还是负数MyNum(){}void pushDigital(char key){if( key>='0' && key<='9')// 输入数字if( expression.equals("0") ){ // 若字符串是初始状态0,再按0无反应if( key=='0')expression="0";elseexpression = String.valueOf(key);}elseexpression += key;else if(key=='.') // 小数点pushDot(key);else if(key=='~')  // 正负数的转变SIGN = !SIGN;}void pushDot(char key){  // 如果已经输入过小数点了,就不再输入if( expression.indexOf('.')<0 ) expression += '.';}void set(double num){ // 强制设定操作数,一般不用value = num;expression = String.valueOf(value);}void clear(){value = 0;expression = "0";SIGN = true;}double read_value(){// 返回当前操作数的值,还要根据是否加入了负号来决定取值value = Double.parseDouble(expression);return SIGN? value : (-1)*value;}String get_exp(){ // 返回当前操作数的字符串表达式,return SIGN? expression : " -"+expression;}}class Calculator {String expression = "0";char operator=' ';double result=0.0;MyNum left_op = new MyNum(), right_op = new MyNum();private final int INPUT_1  = 1;private final int INPUT_OP = 2;private final int INPUT_2  = 3;private final int SHOW     = 4;private int INPUT_STATE = 1;  // 存放程序当前所处状态// TODO: modify the method to return a proper expression //which will be shown in the screen of the calculatorString getExpression() {if(INPUT_STATE==INPUT_1){expression = left_op.get_exp();}else if(INPUT_STATE==INPUT_OP){expression = "" + left_op.get_exp() + operator;}else if(INPUT_STATE==INPUT_2){expression = "" + left_op.get_exp() + operator + right_op.get_exp();}else if(INPUT_STATE==SHOW)expression = "= " + result;return expression;}// TODO: modify the method to handle the key press eventvoid keyPressed(char key) {// expression += key;if(key=='c'){left_op.clear();right_op.clear();expression = "0";operator=' ';INPUT_STATE = INPUT_1;}if( key>='0' && key<='9' || key=='.' || key=='~'){if (INPUT_STATE == SHOW) {left_op.clear();right_op.clear();expression = "0";operator=' ';INPUT_STATE = INPUT_1;left_op.pushDigital(key);}else if(INPUT_STATE==INPUT_1){left_op.pushDigital(key);}else if (INPUT_STATE==INPUT_OP){INPUT_STATE = INPUT_2;right_op.pushDigital(key);}else if (INPUT_STATE==INPUT_2) {right_op.pushDigital(key);}}if( key=='+' || key=='-' || key=='*' || key=='/' ){if(INPUT_STATE==INPUT_1)INPUT_STATE=INPUT_OP;else if(INPUT_STATE == SHOW){left_op.set(result);right_op.clear();INPUT_STATE=INPUT_OP;}// else if(INPUT_STATE)operator = key;}if(key=='='){result = get_result();INPUT_STATE = SHOW;}}double get_result(){switch(operator){case '+': return left_op.read_value() + right_op.read_value();case '-': return left_op.read_value() - right_op.read_value();case '*': return left_op.read_value() * right_op.read_value();case '/':if(right_op.read_value()==0) return Double.POSITIVE_INFINITY;else return left_op.read_value() / right_op.read_value();default:  return left_op.read_value();}}// TODO: you can modify this method to print any debug//information (It will be called by CalculatorCmd)void debugPrintStatus() {System.out.println("Expression = " + expression);}}






0 0
原创粉丝点击