软件园学生在线

  • {{ item.name }}
  • 2023试用期

登录与注册

【后端一】李朝阳

  • LiZhaoyang
  • 2023-10-24
  • 1

目录

1. 程序介绍

2. 代码

3. 学习历程


一、程序介绍

此次项目没有采用传统的两个输入框传数字的方式,而是模拟真实的计算器,采用输入一行表达式,计算出最终结果。实现所用的工具类为ArrayList,Stack和StringBuilder,前后端通讯方法为Get。整个项目的后端代码分为三大部分:

  1. 对输入的表达式进行处理

    • 该步骤的目的是把字符串切分成中缀表达式储存在ArrayList里

    • 使用StringBuilder保存表达式,以便对单个字符进行增删

    • 中文括号能自动转化成英文;非零数字开头即使有0,也能被parseDouble方法自动去掉

    • 加入了对非法字符的检查,仅允许数字,运算符和括号进入下一环节

    • 另创建了一个ArrayList变量,用于存储中缀表达式各个部分的变量类型,因为存进去的任何元素都会变成Object

  2. 中缀表达式转化成后缀表达式

    • 运用数据结构课上所学,把难处里的中缀表达式转化成易处理的后缀表达式。该步骤能把括号去掉,仅保留数字和运算符,因此也能检查括号不匹配问题。
    • 使用了栈的思想处理运算符和数字的顺序
    • 用biggerThan方法来比较运算符优先级,决定是否将栈里的运算符存进最终的后缀表达式里
  3. 后缀表达式的计算

    • 使用try-catch语句保护主体运算部分,一旦运算出错就终止程序。这可以解决运算符缺少或冗余的问题。
    • 没有对除以0进行检查,浏览器会自动把除以0的式子的结果变成Infinity
    • 没有对2147483647附近的溢出进行处理,因为数据类型是Double,可表示的范围够用,对Double判溢出的边界条件写起来也很麻烦
    • 后缀表达式的运算方法是:检测到运算符,就对运算符前面的两个数字进行运算,并从该表达式中移除这三个变量,直到把所有的变量都移出去。该方法是线性运算,没有调整运算顺序的可能,时间复杂度为O(n)。
  4. 食用方法

    • 前端只有一个输入框,输入形如5/(5*2+3)的式子,点击提交按钮就能输出结果,浏览器自动保留16位小数

二、我的代码

Class MainService :

package com.example.firstspringboot.service;

import java.util.ArrayList;
import java.util.Stack;
import org.springframework.stereotype.Service;

@Service
public class MainService {
    public Boolean biggerThan(Character chA, Character chB){
        if(chA == '(' || chB == '('){
            return false;
        } else if(chB == ')'){
            return true;
        } else if(chA == '+' || chA == '-'){
            switch(chB){
                case '+' :
                case '-' :
                    return true;
                case '*' :
                case '/' :
                    return false;
            }
        } else if(chA == '*' || chA == '/'){
            switch(chB){
                case '+' :
                case '-' :
                    return true;
                case '*' :
                case '/' :
                    return true;
            }
        }
        return false;
    }

    public Double answer(Double d1, Double d2, Character op){
        switch(op){
            case '+' : return d1+d2;
            case '-' : return d1-d2;
            case '*' : return d1*d2;
            case '/' : return d1/d2;
        }
        return 0.0;
    }
    public String calculator(String str){
        /*
        首先转化成中缀表达式,用ArrayList存储每个元素
         */
        ArrayList middleStr = new ArrayList(); //中缀表达式存储
        ArrayList<Integer> type = new ArrayList(); //type为0表示这个位置是数字,为1是运算符,为2是#,用于中缀转后缀判断数据类型
        StringBuilder tempItem = new StringBuilder(); //提取字符,转化为数字

        for(int i = 0 ; i < str.length() ; i++){ //逐个提取字符,遇到非数字或运算符则终止
            Character temp = str.charAt(i);
            if(temp == '('){ //中文括号调整成英文
                temp = '(';
            }else if(temp == ')'){
                temp = ')';
            }
            if(temp >= '0' && temp <= '9'){ //是数字字符就存下来
                tempItem.append(temp);
            } else if(temp == '+' || temp == '-' || temp == '*' || temp == '/' || temp == '(' || temp == ')'){
                if(tempItem.isEmpty()){ //如果数字暂存区没东西就直接把运算符保存下来
                    middleStr.add(temp);
                    type.add(1);
                } else{ //数字暂存区有东西要先把数字存进去,再存运算符
                    Double d = Double.parseDouble(tempItem.toString()); //形如05的数字也可以正常运算
                    middleStr.add(d);
                    type.add(0);
                    tempItem.delete(0,tempItem.length());
                    middleStr.add(temp);
                    type.add(1);
                }
            } else{
                return "输入的表达式格式不合法,里面有其他字符。" ;
            }
        }
        if(!tempItem.isEmpty()){
            Double d = Double.parseDouble(tempItem.toString());
            middleStr.add(d);
            type.add(0);
        }
        middleStr.add('#'); //转后缀表达式所用的间隔符
        type.add(2);

        /*
        中缀转后缀
         */
        Stack operatorStack = new Stack();
        operatorStack.push('#');
        ArrayList backStr = new ArrayList();
        for(int i = 0 ; i < middleStr.size() ; i++){
            if(type.get(i) == 0){ //数字
                backStr.add(middleStr.get(i));
            } else if(type.get(i) == 1){ //运算符
                if(middleStr.get(i).equals('(')){ //左括号
                    operatorStack.push(middleStr.get(i));
                } else if(middleStr.get(i).equals(')')){ //右括号
                    while(!operatorStack.peek().equals('(') && !operatorStack.peek().equals('#')){ //非左括号就弹出
                        backStr.add(operatorStack.pop());
                    }
                    if(operatorStack.peek().equals('(')){ //是左括号就消去
                        operatorStack.pop();
                    } else if(operatorStack.peek().equals('#')){ //没左括号就报错
                        return "输入的表达式格式不合法,右括号多了。";
                    }
                } else{
                    while(biggerThan((Character)operatorStack.peek(), (Character)middleStr.get(i))){ //栈顶大就弹出
                        backStr.add(operatorStack.pop());
                    }
                    operatorStack.push(middleStr.get(i));
                }
            }
        }
        while(!operatorStack.peek().equals('#')){ //清除栈中剩下的运算符,如果里面有左括号,说明输入格式不对
            if(!operatorStack.peek().equals('(')){
                backStr.add(operatorStack.pop());
            } else{
                return "输入的表达式格式不合法,左括号多了。";
            }

        }

        /*
        后缀表达式运算,中途卡住则报错表达式格式错误
         */
        int index = 0;
        Double d1;
        Double d2;
        Character op;
        while(backStr.size() > 1){
            try{
                Object current = backStr.get(index);
                if(current.equals('+') || current.equals('-') || current.equals('*') || current.equals('/')){
                    d1 = (Double)backStr.get(index-2);
                    d2 = (Double)backStr.get(index-1);
                    op = (Character)backStr.get(index);
                    Double ans = answer(d1,d2,op);
                    backStr.remove(index-2);
                    backStr.remove(index-2);
                    backStr.remove(index-2);
                    backStr.add(index-2,ans);
                    index = index-2;
                }
            } catch(Exception e){ //强转或进行运算时有一步出错都终止运算,因为肯定是输入格式不对
                return "输入的表达式格式不合法,运算符冗余或不足。";
            } finally{
                if(index >= backStr.size()){
                    index = 0;
                } else{
                    index++;
                }
            }
            if(backStr.size() == 2){ //最后只剩下两个元素,无法继续运算,也无法退出循环
                return "输入的表达式格式不合法,运算符少了。";
            }
        }

        return backStr.get(0).toString();
    }
}

Class Main :

package com.example.firstspringboot.controller;

import com.example.firstspringboot.service.MainService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/main")
public class Main{
    @Autowired
    MainService mainService;

    @RequestMapping(value = "calculator" , method = RequestMethod.GET)
    public String calculator(@RequestParam String str){
        return mainService.calculator(str);
    }
}

前端Html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <form action="http://localhost:8080/main/calculator" method="get">
        <input type="text" name="str" />
        <input type="submit" name="sub" />
    </form>
  </body>
</html>

三、学习历程

  • 防止注入攻击的办法:整个项目只有一个参数,一个输入框,&p2=4一点用也没有
  • 学会了使用StringBuilder类的各种方法来操作字符串,因为Java原本的String不支持修改内容。这种新形式类似C++的string。
  • ArrayList在不加泛型的时候,内容全都是Object类型,很多用于比较或运算的方法没法用,此时需要有充足的理由来强转类型,并保证这样做是不会出问题的。
  • Java的Stack只能看见栈顶元素,没法在不创建其他容器的条件下无损遍历
  • 善用try-catch能在特殊环境下得到极大的便利,比如后缀表达式运算时。运算进行不下去?一定是用户打错字了,这时候直接catch终止,让他重新输入。(因为我前面已经做了其他各种类型的数据检查,所以有理由中断程序)
LiZhaoyang
LiZhaoyang
© 2025 软件园学生在线
Theme by Wing