Java—应用栈结构实现计算器

我们在小学学数学的时候,老师总是强调:先乘除,后加减,从左算到右,先括号内后括号外,这个都大家都不陌生。但我们的计算机又怎么记住这些规则呢?答案是不行的,但是我们可以把我们的表达式表示成一种计算机可以识别的表达式,这就是要说的后缀(逆波兰)表达式

1.后缀表达式的计算

  • 例如:9+(3-1)3+10/2 这样的式子,用后缀表达式是这样的:9 3 1 - 3 + 10 2 / +

你可能会说:这式子是怎么得到的?不要着急,我们先分析计算机如何通过这个式子得到结果。我们前面说过一种数据结构叫做栈。我们现在就利用栈结构得出结果:

  • 遇到数字,将数字压栈
  • 遇到字符,将栈中最顶的两个元素弹栈,然后用该字符计算出结果(栈中第二个元素对栈顶元素操作),然后再将结果压栈,重复上述过程直到结束
  • 最终栈中的唯一一个元素就是结果,弹栈,结束计算
我们以上面的这个算式为例:

在java做大量计算逻辑怎么写_在java做大量计算逻辑怎么写

遇到了符号 “-“

在java做大量计算逻辑怎么写_后缀表达式_02

将结果压栈

在java做大量计算逻辑怎么写_后缀表达式_03

继续上述过程:

在java做大量计算逻辑怎么写_压栈_04

在java做大量计算逻辑怎么写_优先级_05

在java做大量计算逻辑怎么写_优先级_06

在java做大量计算逻辑怎么写_在java做大量计算逻辑怎么写_07

在java做大量计算逻辑怎么写_后缀表达式_08

以上就是在得到后缀表达式之后计算机如何对其进行处理从而的到结果,下面我们说一说如何得到这个后缀表达式

2.中缀转后缀

我们平时使用的算式就是中缀表达式,它与后缀表达式最根本的区别就是:带有括号,所以我们需要想办法把括号给去掉,并且不影响原本计算的顺序。下面是中缀转后缀的规则:

  • 从左到右遍历中缀表达式中的每个数字和符号,若是数字就输出,即称为后缀表达式的一部分
  • 若是符号,则判断其与栈顶符号的优先级,是右括号或优先级不高于栈顶符号(乘除优先加减)则栈顶元素依次弹栈并放入后缀表达式中,并将当前符号压栈,直到遍历结束,栈中元素依次弹栈加入后缀表达式中。

还是以 9+(3-1)*3+10/2 为例:

在java做大量计算逻辑怎么写_优先级_09

此时,栈中左右括号对应,将括号中的符号弹栈并加入到后缀表达式中

在java做大量计算逻辑怎么写_后缀表达式_10

继续压栈

在java做大量计算逻辑怎么写_在java做大量计算逻辑怎么写_11

这时,+要入栈,但是这时栈顶的 * 的优先级高于它,所以 “*” 弹栈,”+” 的优先级与+相等,所以也需要弹栈,这时栈空了,再把外面的 “+”压栈

在java做大量计算逻辑怎么写_后缀表达式_12

在java做大量计算逻辑怎么写_在java做大量计算逻辑怎么写_13

这时中缀表达式已经被遍历一遍了,我们只需要把栈内元素都弹出来,就得到了最终的后缀表达式,即 9 3 1 - 3 * + 10 2 / +

计算器的实现

根据上面的分析,我们知道了:要想求出一个中缀表达式的结果,我们需要先将其转为后缀表达式,再根据后缀表达式求出结果。两次用到了栈结构,这就是为什么我要说计算器是对栈结构的应用了。

下面是我用java实现的一个简易的计算器,能够进行整数,小数,负数,大数值的四则运算,并且可以分析出一些语法错误,下面是我的代码:

public class Calculator {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String middle = scan.nextLine();
        char[] mid = middle.toCharArray();
        mid = cutString(mid);
        List list = transferToBehind(mid);

        BigDecimal comeOut = calculator(list);
        System.out.println(comeOut);
    }

    public static BigDecimal calculator(List list){
        Deque<BigDecimal> deque = new ArrayDeque<>();

        Iterator it = list.iterator();
        while (it.hasNext()){
            Object o = it.next();
            if (o.getClass() == BigDecimal.class){
                deque.addFirst((BigDecimal)o);
            }else if (o.getClass() == Character.class){

                BigDecimal first = deque.removeFirst();
                BigDecimal second = deque.remove();
                BigDecimal out = null;
                if ((Character)o == '+'){
                    out = second.add(first);
                    deque.addFirst(out);
                }else if ((Character)o == '-'){
                    out = second.subtract(first);
                    deque.addFirst(out);
                }else if ((Character)o == '*'){
                    out = second.multiply(first);
                    deque.addFirst(out);
                }else if ((Character)o == '/'){
                    Double out2 = second.doubleValue()/first.doubleValue();
                    String out3 = out2.toString();
                    deque.addFirst(new BigDecimal(out3));
                }

            }
        }
        BigDecimal comeOut = deque.removeFirst();
        return comeOut;
    }


    public static List transferToBehind(char[] mid){
        Deque<Character> deque = new ArrayDeque<>();
        List list = new ArrayList();

        int len = mid.length;
        int i,k=0;

        for (i = 0; i < len; i++){
            if ( (judgeSymbol(mid[i]) && ((i > 0 && mid[i-1] != ')') || i == 0) ) || mid[i] == ')'
                    && !lastBracket(mid,i) && k != i){
                if (mid[i] == '-' && (i == 0 || judgeSymbol(mid[i-1]) || judgeBrackets(mid[i-1]))){
                        continue;
                }

                String number = new String(mid).substring(k, i);

                if (number.contains(")")){
                    while ( deque.peekFirst() != '('){
                        list.add(deque.removeFirst());
                    }
                    deque.removeFirst();
                    continue;
                }

                    BigDecimal num = new BigDecimal(number);
                    list.add(num);
                    if (judgeSymbol(mid[i]) )
                        k = i + 1;
                    else if (mid[i] == ')'){
                        k = i + 2;
                    }
            }

            if (judgeNumber(mid[i]) && lastNumber(mid,i)){
                String number = new String(mid).substring(i);
                list.add(new BigDecimal(number));
                break;
            }

            if (mid[i] == '('){
                k++;
            }

            if (judgeSymbol(mid[i]) || judgeBrackets(mid[i]) ){
                if (mid[i] == '*' || mid[i] == '/'){
                    if (!deque.isEmpty() && (deque.peekFirst() == '*' || deque.peekFirst() == '/')){
                        while (!deque.isEmpty() && deque.peekFirst() != '(' && deque.peekFirst() != '+'
                                && deque.peekFirst() != '-'){
                            list.add(deque.remove());
                        }
                    }
                    deque.addFirst(mid[i]);
                }else if (mid[i] == '+' || mid[i] == '-'){
                    if (deque.isEmpty()){
                        deque.addFirst(mid[i]);
                    }else if (judgeSymbol(deque.peekFirst())){
                        while ( !deque.isEmpty() && deque.peekFirst() != '('){
                            list.add(deque.removeFirst());
                        }
                        deque.addFirst(mid[i]);
                    }else {
                        deque.addFirst(mid[i]);
                    }
                }else if (mid[i] == '('){
                    deque.addFirst(mid[i]);
                }else if (mid[i] == ')'){
                    while ( deque.peekFirst() != '('){
                        list.add(deque.removeFirst());
                    }
                    deque.removeFirst();
                }
            }else {
                continue;
            }
        }
        int m = 1;
        while ( !deque.isEmpty() && (deque.peekFirst() == '(' || deque.peekFirst() == ')')){
            deque.removeFirst();
            System.out.println("有" + m++ + "个括号不匹配");
        }
        if (!deque.isEmpty()) {
            list.add(deque.removeFirst());
        }
        return list;
    }


    public static boolean judgeSymbol(char word){
        if (word == '+' || word == '-' || word == '*' || word == '/' ){
            return true;
        }
        return false;
    }
    public static boolean judgeBrackets(char word){
        if (word == '(' || word == ')'){
            return true;
        }
        return false;
    }

    public static boolean judgeNumber(char word){
        if ( (word >= '0' && word <= '9' )|| word == '.'){
            return true;
        }
        return false;
    }

    public static boolean lastNumber(char[] mid,int i){
        String str = new String(mid);
        String rest = str.substring(i+1);
        if (rest.contains("*") || rest.contains("/") || rest.contains("+") ||rest.contains("-") ||rest.contains("(")
                || rest.contains(")") ){
            return false;
        }
        return true;
    }

    public static boolean lastBracket(char[] mid, int i){
        if (mid[i-1] != ')' || mid[i-2] == ')'){
            return false;
        }
        String rest = new String(mid).substring(i);
        if (rest.contains("0") || rest.contains("1") ||rest.contains("2") || rest.contains("3") || rest.contains("4") ||
                rest.contains("5") ||rest.contains("6") || rest.contains("7") || rest.contains("8") || rest.contains("9")){
            return false;
        }
        return true;
    }

    public static boolean judgeIllegal(char[] mid,int i){
        String rest = new String(mid).substring(i);
        if (!rest.contains("+") && !rest.contains("-") && !rest.contains("*") && !rest.contains("/") &&
                !rest.contains("(") && !rest.contains(")") && !rest.contains("0") && !rest.contains("1") &&
                !rest.contains("2") && !rest.contains("3") && !rest.contains("4") && !rest.contains("5") &&
                !rest.contains("6") && !rest.contains("7") && !rest.contains("8") && !rest.contains("9")){
            return true;
        }
        return false;
    }

    public static char[] cutString(char[] mid){
        for (int i = 0; i < mid.length; i++){
            if (!judgeBrackets(mid[i]) && !judgeSymbol(mid[i]) && !judgeNumber(mid[i])){
                System.out.println("出现非法字符!已为你自动消除");
                if (i == 0){
                    mid = Arrays.copyOfRange(mid,1,mid.length);
                    i--;
                }else if (judgeIllegal(mid,i)){
                    mid = Arrays.copyOfRange(mid,0,i);
                    break;
                }else {
                    char[] beyond = Arrays.copyOfRange(mid,0,i);
                    char[] behind = Arrays.copyOfRange(mid,i+1,mid.length);
                    String str1 = new String(beyond);
                    String str2 = new String(behind);
                    String temp = str1 + str2;
                    mid = temp.toCharArray();
                    i--;
                }
            }
        }
        return mid;
    }
}

正常测试:

在java做大量计算逻辑怎么写_压栈_14

非正常测试:

在java做大量计算逻辑怎么写_在java做大量计算逻辑怎么写_15

暴力测试:

在java做大量计算逻辑怎么写_压栈_16

之所以用Java写而不用C语言写就是因为Java可以直接调用很多封装好的类和方法,这个程序用Java写了200行代码,如果用C语言写估计得600行以上。

至于四则运算太少,其他的运算也可以添加,就是要判断更多的符号优先级的问题。