最近几天,断断续续地在写,调试计算器
效果图(目前还没写完):
分析:
1。每按一次按钮,进行相应操作。
2。中缀表达式转换成后缀表达式,对之进行处理。
具体操作:
通过每一次按钮触发事件,并调用CalcExpression类对象进行相应的处理。
若本次按数字键,则对之前的操作符或者括号进行处理,并对数字进行连接操作;
若本次是操作符或括号操作,则判定之前若按的是数字键,进行数值加入后缀表达式操作,否则进行对相关的操作符或括号进行操作;
若本次是‘=’,则对前一次的输入进行判定作出相关操作,并把操作符或括号栈里的内容加入到后缀表达式。
以下是中缀表达式转后缀表达式的伪代码(copy至《Data Structures Outside In with Java》中文版)
expr ← input infix expressing string tok ← first token of expr while (tok is not empty) do if (tok is ...) then output tok if (tok is ...) then push tok on stack else if (tok is an operator) then if (stack is empty or top of stack is '(') then // 两条件顺序调整后 push tok on stack else if (tok has higher priority than stack top) then push tok on stack else pop stack and write popped value to output push tok on stack endif else if (tok is a closing parenthesis) then pop operators and write to output until a '(' is encountered pop '(' but don't write it to output endif tok ← next token of expr endwhile pop all operators and write to output
按我的理解翻译了下
// 输出 可理解为写入到后缀表达式中,不过写入每个数值时要做一些操作,不然每个数字都连在一起成一个数值了 expr ← 输入一个中缀表达式 tok ← 取expr的第一个记号 当 tok 不为空 则 如果 tok 是 0~9 或 '.' 那么 输出 tok 如果 tok 是 ’(' 那么 tok 入栈(包含 '+' '-' '*' '/' '(') 否则 如果 tok 是操作符( '+' '-' '*' '/') 那么 如果 栈为空 或者 栈顶元素 为 '(' 那么 tok 入栈 否则 如果 tok 的优先级高于栈顶元素 那么 tok 入栈 否则 取出栈顶元素,并输出 tok 入栈 否则 如果 tok 是 ')' 那么 取出栈中的操作符,并输出 直到遇到 '(' 取出 '(' 但不输出 tok ← expr 中的下一个记号 取出栈中所有操作符,并输出
文件结构:
CalcFrame.java import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JTextArea; /** * 计算器总框架 * @author Submarine * */ public class CalcFrame extends JFrame { private CalcExpression calcE; private GridBagConstraints gridBagCons; private GridBagLayout gridBagLayout; private JButton bBackspace, // ← bBracketLeft, // ( bBracketRight, // ) bClear, // C bPlus_Minus, // ± bAdd, // + bSub, // - bMult, // * bDiv, // / bSqrt, // √ bMod, // % bReci, // 1/n bEquation, // = bOne, // 1 bTwo, // 2 bThree, // 3 bFour, // 4 bFive, // 5 bSix, // 6 bSeven, // 7 bEight, // 8 bNine, // 9 bZero, // 0 bDot; // . private JMenu menuView, // 查看 menuEdit, // 编辑 menuHelp; // 帮助 private JMenuBar menuBar; private JMenuItem menuItemAbout; // 关于 private JPanel panel; private JTextArea textArea; private JButtonHandler handler = new JButtonHandler(); public CalcFrame(); private JButton MakeButton(String name, GridBagLayout gridBagLayout, GridBagConstraints gridBagCons); class JButtonHandler implements ActionListener { @Override public void actionPerformed(ActionEvent e); } }
CalcExpression.java /** * 计算器表达式处理 * @author Submarine * */ public class CalcExpression { private Stack<String> stackOper; // 存储中转后过程中的操作符 private LinkedList<String> postfixE; // 存储后缀表达式(操作符和数各自存在String对象中 private String strExpr; // 整个表达式 private String curNum; // 当前字符形式的数值 private String curOpOrBra; // 当前操作符或括号 private double ans; // 整个表达式的最终值 public CalcExpression(); /** * 追加数字 及'.' * @param s */ public void Append_0_9_dot(String s); /** * 用于按数字button或'.'button, 操作符button之时,当之前一个是操作符,则对前一个操作符作出相应操作 */ private void DealThePreOpOrBr(); /** * 追加左括号 * ((((1+1)+1))), 1*((1+1)*1), * @param s */ public void AppendBracketLeft(String s); /** * 追加右括号 * 1)=, 1)+1 * @param s */ public void AppendBracketRight(String s); /** * 当前表示数值的字符串追加字符 * @param s */ private void AppendCurNum(String s); /** * 追加等号 * @param s */ public void AppendEquation(String s); // 正常情况下,等号前只能出现数字或闭括号 /** * 总表达式增加一个字符 * @param s */ private void AppendExpr(String s); /** * 追加操作符 * 1+, )+ * @param s */ public void AppendOperator(String s); /** * * @return true if 之前是数字, or false */ private boolean CanPushNumToPostfixE(); /** * 计算后缀表达式 */ private void CalcThePostfixE(); /** * 清除 */ public void Clear(); /** * 获得表达式 * @return 表达式 */ public String GetE(); /** * 判断'.' * @param s * @return true if has '.', or false */ private boolean HasDot(String s); /** * 比较优先级 * @param curOp * @param staOp * @return true curOp has priority to staOp */ private boolean HasPriority(String curOp, String staOp); /** * 把double型的ans转成string型,并格式化 * @param n * @return */ private String MakeTheAnswer(double n); /** * 进行四种运算 * @param s * @param a * @param b * @return result if runtime successfully, or Double.MAX_VALUE */ private double Operate(int op, double a, double b); /** * 入栈,当前数值加入后缀表达式 */ private void PushToPostfixE(); /** * 入栈,操作符及括号加入后缀表达式 * @param op */ private void PushToPostfixE(String op); /** * 求操作符对应的数值 * @param s * @return 操作符对应的数值 */ private int ValueOfOp(String s); /** * 求操作符对应的数值 * @param s * @return the value of operator if defined, or 0 */ private int ValueOfOp(char s); }
优化1:
带括号的复杂操作,如5+(4-3)*2=;
解决方案:
经调试,发现操作符加入操作符及括号栈(stackOper)后当前操作符及括号变量(curOpOrBra,其实解释为前一个操作符或括号更恰当)没有置空。
优化2:
负数操作,如-1=;
解决方案:
通常负数出现在表达式最开始或者开括号之后,如 -1=, 1+(-1)=。在按'-'按钮时,判断下即可,把一个负数拆成 0-|负数| 的操作。
优化3:
每个数值最多包含一个小数点。
优化4:
精度增大;
解决方案:
操作数值运算时,用BigDecimal类。
优化5:
限制"()",'+', '-', '*', '/'的非法输入。
解决方案:
在输入时控制')'的输入次数不多于'('的输入次数,定义变量记录;
通过方法判定是否连续输入,若不是,完成常规操作,否则,只更替换原来操作符。
优化6:
界面采用流式布局,解决采用网包布局时组件间距不一致的问题。
解决方案:
通过setPreferredSize(new Dimension(WIDTH, HEIGHT))方法设置优先大小。