这几天java课上老师要我们实现一个计算器。由于刚开始学习java,其中界面显示部分的代码老师已经准备好了,并且整个程序是采用MVC(Model–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 calculator
String getExpression() {
return expression;
}
// TODO: modify the method to handle the key press event
void 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 application
class 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 interface
import 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 application
import 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 events
import 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;
else
result = 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 );
}
}
/* 操作数用类来实现,统一各种运算
--> 与数字有关的所有符号:【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";
else
expression = String.valueOf(key);
}
else
expression +=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";
else
expression = String.valueOf(key);
}
else
expression += 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 calculator
String 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 event
void 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);
}
}