模拟计算器
- 问题描述
设计一个模拟计算器的程序,要求能对包含加、减、乘、除、求余、括号运算符及SQR和ABS函数的任意整型表达式进行求解。 - 设计要求
(1)要检查相关运算的限制条件,并对错误的条件产生报警。
(2)有用户图形界面。
数据结构课程设计,我选用的是Java语言来实现一个GUI计算器程序,图形界面使用Java Swing进行开发,实现了基本运算,以及绝对值、开方、平方,支持浮点数以及大数运算。 涉及到的知识点有将中缀表达式转换为后缀表达式并进行计算,同时能够检查表达式是否合法。
设计思路:
用户交互界面
表达式的合法判定
import java.util.Stack;
public class CheckExpression {
// 判断是否是合法算术表达式
public boolean isTrueExpression(String expression) {
// 如果一开始就是点(.)肯定就不是合法的表达式 合法的算术表达式含有数字、小数点、括号、普通运算符
if ("".equals(expression) || isOperator(expression.charAt(0)) || isOperator(expression.charAt(expression.length() - 1)) || isPoint(expression.charAt(0)) || isPoint(expression.charAt(expression.length() - 1))) {
return false;
} else {
// 控制括号成对
Stack<Character> stack = new Stack<>();
// 循环判断表达式的每一个字符是符合法
int i = 0;
int leftCount = 0;// 记录 ( 的数量
int rightCount = 0;// 记录 ) 的数量
while (i < expression.length()) {
char ch = expression.charAt(i);
// 如果此字符不是数字、普通运算符、特殊运算符、小数点、括号、空格(习惯分开 比如:2 + 4) 那么就是不合法
if (isNumber(ch) || isOperator(ch) || isSpecialOperator(ch) || isPoint(ch) || isBracket(ch) || ch == ' ') {
//如果此字符是空格 那么如果他的前一个或后一个是小数点、前后都是数字或者元素字符,那么这个表达式就是不合法,
// 不过由于本计算器是用户点击按钮进行输入,所以不会出现多出多余空格的错误
// 此字符前后可能有多个空格,调用方法得到此字符的前/后一个的有效字符
int pre = getPreIndexOfNotBlankSpace(expression, i);
int next = getNextIndexOfNotBlankSpace(expression, i);
if (ch == ' ') {
if (isPoint(expression.charAt(pre)) || isPoint(expression.charAt(next)) || (isNumber(expression.charAt(pre))) && isNumber(expression.charAt(next)) || (isOperator(expression.charAt(pre))) && isOperator(expression.charAt(next))) {
return false;
}
}
// 如果是左括号( 那么下一个有效字符必须不能是小数点或者*、/、)、%
if (ch == '(') {
leftCount++;
if (isPoint(expression.charAt(next)) || expression.charAt(next) == '*' || expression.charAt(next) == '/' || expression.charAt(next) == ')' || expression.charAt(next) == '%') {
return false;
}
// 入栈
stack.push(ch);
}
// 如果是) 此时栈空则说明不合法 否则弹出一个( )后面只可以是运算符或者)
if (ch == ')') {
rightCount++;
if (stack.empty() || (!isOperator(expression.charAt(next)) && expression.charAt(next) != ')')) {
return false;
} else {
stack.pop();
}
}
// 如果是除号 后一个数是0就不合法 除数不能是0
if (ch == '/') {
if (expression.charAt(next) == '0') {
return false;
}
}
// 如果是运算符,前一个或后一个不能是小数点、操作符
if (isOperator(ch) || isSpecialOperator(ch)) {
if (isPoint(expression.charAt(pre)) || isPoint(expression.charAt(next)) || expression.charAt(next) == ')' || isOperator(expression.charAt(next))) {
return false;
}
}
// 如果是小数点,前后都必须是数字
if (isPoint(ch)) {
if (!isNumber(expression.charAt(pre)) || !isNumber(expression.charAt(next))) {
return false;
}
}
i++;
} else {
return false;
}
}
// 如果字符都合法最后判断括号是否成对:即(数量 和 )括号数量是否相等
if (leftCount != rightCount) {
// 说明括号不是成对出现
return false;
}
}
return true;
}
// 获取后一个有效字符下标
public static int getPreIndexOfNotBlankSpace(String expression, int i) {
// 如果下表是最后一个字符下标那么就返回最后一个字符下标
if (i == 0) {
return 0;
} else if (i < 0 || i >= expression.length()) {
throw new RuntimeException("error");
} else {
i--;
while (expression.charAt(i) == ' ') {
i--;
}
return i;
}
}
// 获取前一个有效字符的下标
public static int getNextIndexOfNotBlankSpace(String expression, int i) {
// 如果下标为0 那么前一个有效字符就是0
if (i == expression.length() - 1) {
return expression.length() - 1;
} else if (i < 0 || i >= expression.length()) {
throw new RuntimeException("error");
} else {
i++;
while (expression.charAt(i) == ' ') {
i++;
}
return i;
}
}
// 判断这个字符是不是数字
public static boolean isNumber(char ch) {
if (ch >= '0' && ch <= '9') {
return true;
} else {
return false;
}
}
// 判断这个字符是不是普通运算符
public static boolean isOperator(char ch) {
if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '%') {
return true;
} else {
return false;
}
}
// 判断这个字符是不是普通运算符
public static boolean isSpecialOperator(char ch) {
if (ch == 'A' || ch == 'S' || ch == 'P') {
return true;
} else {
return false;
}
}
// 判断这个字符是不是小数点
public static boolean isPoint(char ch) {
return ch == '.';
}
// 判断这个字符是不是括号
public static boolean isBracket(char ch) {
return ch == '(' || ch == ')';
}
}
将中缀表达式转换为后缀表达式
//中缀表达式转后缀表达式
//比如输入为1+5*7,转为后缀表达式为157*+
private void Infix2Postfix() {
// 依次扫描表达式eq1
for(int i = 0;i < eq1.size();i++) {
if(prio.get(eq1.get(i)) != null) { //对运算符进行处理
String c = eq1.get(i);
if(stk.empty() || c.equals("(")) { //如果栈为空或者是左括号,直接入栈。为什么左括号也要直接入栈?考虑到括号嵌套问题,如6+((1+2)*3+4)*5
stk.push(c);
} else if(c.equals(")")) { //遇到右括号,将栈顶元素一直出栈直到遇到左括号为止,并将该左括号出栈(右括号不入栈)
while(!stk.peek().equals("(")) {
//只要栈顶元素不为左括号,就将该运算符出栈并添加到列表中
eq2.add(stk.pop());
}
stk.pop(); //将左括号出栈
} else if(prio.get(c) > prio.get(stk.peek())) {
//如果当前运算符优先级高于栈顶运算符优先级,直接入栈
stk.push(c);
} else { //当前运算符优先级低于或等于栈顶运算符优先级,分四种情况
while(!stk.empty() && !stk.peek().equals("(") && prio.get(stk.peek()) >= prio.get(c)) {
eq2.add(stk.pop());
}
// 之后再把当前运算符入栈
stk.push(c);
}
} else { //对参数数据进行处理,参数直接添加进列表
eq2.add(eq1.get(i));
}
}
// 按以上方法处理完所有的字符后,再将栈中所有运算符依次弹出,并加入后缀表达式
while(!stk.empty()){
eq2.add(stk.pop());
}
System.out.println("中缀表达式eq1: " + eq1);
System.out.println("后缀表达式eq2: " + eq2);
}
对后缀表达式进行计算
//对后缀表达式计算
private String postfixCalculate() {
BigDecimal p1,p2;
BigDecimal p3 = new BigDecimal("0");
for(int i = 0;i < eq2.size();i++){
if(prio.get(eq2.get(i)) == null){ //对参数进行处理
stk.push(eq2.get(i)); //参数直接入栈
} else { //对运算符进行处理
String c = eq2.get(i);
// 如果是S或者A运算需要单独处理
if (c.equals("A")) {
// 绝对值运算
p3 = new BigDecimal(stk.pop()).abs();
stk.push(p3.toString());
} else if (c.equals("S")) {
// 开方运算
p3 = new BigDecimal(Math.sqrt(Double.valueOf(stk.pop())));
stk.push(p3.toString());
} else if (c.equals("P")) {
// 平方运算
p3 = new BigDecimal(Math.pow(Double.valueOf(stk.pop()), 2.0));
stk.push(p3.toString());
} else {
p1 = new BigDecimal(stk.pop());
p2 = new BigDecimal(stk.pop());
switch(c){
case "+":p3 = p2.add(p1);break;
case "-":p3 = p2.subtract(p1);break;
case "*":p3 = p2.multiply(p1);break;
// 除法精确到小数点后第18位,并四舍五入
case "/": p3 = p2.divide(p1, RoundingMode.HALF_UP);break;
//case "/": p3 = new BigDecimal((p2.doubleValue()/p1.doubleValue()));
case "%":p3 = p2.remainder(p1);break;
default:p3=BigDecimal.ZERO;break;
}
stk.push(p3.toString());
}
}
}
return stk.pop();
}
**注:**以上只是部分代码,完整代码我上传到了gitee保管,calculator,点击即可访问。