设计目标:借用栈的特性(后进后出),可将先进入的数据进行暂时存储,在得到运算符后进行运算。
考虑因素:因为四则运算中既包含一级运算也包含二级运算(优先级高),所以需对这两种情况进行不同的区分,此外对于出现小括号时压栈和出栈的情况也要进行考虑。
主要思路:
由于栈的特性,我们可以在拿到运算符时进行对前一个运算符进行判断,借此来完成数值的运算。
1)、建立两个栈结构、一个存储运算符(栈A)、一个存储数值(栈B)
2)、针对六种情况进行不同的区分( 数值、(+,-)、(*,/)、=、(、) )
对于整个运算式(String类型),进行逐个字符的提取,加以判断
①对于数值(包含小数点):由于提取到的是字符类型(1位),所以首先应该在判断出数值时应该循环提取,这时可用ASCAII码进行判断,对判断出的字符类型进行转换,拼接。因为小数点的存在,就需要设立一个标志来标记小数点的出现,依此来完成小数部分的拼接。随后,压入栈B。
②对于一级运算符(+、-):一级运算符级别较低,出现时,对栈A栈顶进行判断。若不为空且不是‘(’,栈A出栈得到运算符,栈B出栈可得到数值(2个),进行运算的出结果压入栈B。此处需注意2个数值的出栈顺序,会影响到运算结果。
③对于二级运算符(*、/):运算级别高于一级运算符,出现时,对栈A栈顶进行判断。若不为空、‘(’、+、-时,即可进行运算,运算过程同②。
④对于‘(’:直接压入栈中。
⑤对于‘)’:由于左括号和右括号是相互匹配的,在出现时,需对栈A进行遍历运算,直到得出‘(’时,停止运算。
⑥对于‘=’:‘=’号的出现意味着运算结束,此时需清空栈A,将栈B栈顶元素进行返回。
PS:此处要提醒重要的一点,‘-’ 号的出现会影响一级运算符运算结果的正确性
如:3-4*5+6+7 运算到“3-20+6”时,此时会输入一个‘+’号并且运算前一个‘+’号,但20的前面有一个‘-’号,若直接运算就得到“3-26+”,很明显是错误的,正确结果应为“3-14+”。所以在进行一级运算时,若前一符号为‘-’,则应将该计算的‘+’变‘-’,‘-’变‘+’。
设计方案:
①定义一个栈的结构,使用数组来完成数据的存储,需实现的方法:栈的初始化、压栈函数、出栈函数、返回栈顶数据(并不出栈,用以判断)。
②设计一个总的运算函数,形参为运算式,对运算式进行切割,针对不同的情况进行不同的处理。
③由于在很多情况中都会进行出栈运算,所以可将该运算进行封装,减少代码的重复性,提高可用性。
具体实现:
①栈结构(由于存储数据类型的不同、此处我定义为object数组,也可以使用范型)
//定义栈
private Object[] objStack;
//定义栈长
private int Max_num;
//定义栈顶指针
private int top;
//初始化
public StoreStack() {
Max_num = 20; //定义能够存储20个数
objStack = new Object[Max_num+1]; //top指针占一个位置
top = 0;
}
//是否满
private boolean fullStack() {
if(this.top == this.Max_num) {
return true;
}
return false;
}
//是否空
public boolean emptyStack() {
if(this.top == 0) {
return true;
}
return false;
}
//入栈函数
public void push(Object obj) {
//判断是否栈满
if(fullStack()) {
System.out.println("栈满,无法添加");
}else {
objStack[top++] = obj;
}
}
//出栈函数
public Object popStack() {
//判断是否栈空
if(emptyStack()) {
//若空,返回null
return null;
}
return objStack[--top];
}
//查看栈顶元素
public Object catStackTop() {
if(top == 0) {
return null;
}else {
return objStack[top-1]; //栈顶指针比数据高一位
}
}
②主运算函数
//存储数值
private StoreStack numStack;
//存储运算符
private StoreStack synStack;
//初始化
public Calculation() {
this.numStack = new StoreStack();
this.synStack = new StoreStack();
}
public double valueCalculate(String expression) {
for(int loc = 0; loc < expression.length(); loc++) {
//从运算式中取值
char c = expression.charAt(loc);
//①若该值为数字,再次取值,直至不为数字,然后将值拼起来,压入栈
if(48<=c&&c<=57 || c == '.') {
int locate = 0; //判断是小数点前还是小数点后,且判断小数点后第几位
Double target = 0D;
while(48<=c&&c<=57 || c == '.') {
if(c == '.') {
locate = 1;
c = expression.charAt(++loc);
continue;
}
int a = c - 48;
if(locate == 0) {
target = a + target*10; //转化为double类型
}else {
target += (double)a/Math.pow(10, locate);
//小数点位数加一
locate++;
}
//取下一位
c = expression.charAt(++loc);
}
this.numStack.push(target); //入栈
}
//②若为')',入栈
if(c == '(') {
this.synStack.push(c);
//③若为'+'或'-'
}else if(c == '+' || c == '-') {
if(!synStack.emptyStack() && !String.valueOf(synStack.catStackTop()).equals("(")) {
//当运算符栈顶有值且不为括号时,进行运算
this.twoCalculate();
}
//运算符入栈
synStack.push(c);
//④若为'*'或'/'
}else if(c == '*' || c == '/'){
if(String.valueOf(synStack.catStackTop()).equals("(")
|| synStack.emptyStack()
|| String.valueOf(synStack.catStackTop()).equals("+")
|| String.valueOf(synStack.catStackTop()).equals("-")) {
//对于 +、-、空、(不做运算
}else {
//对于 *、/ 调用运算器
this.twoCalculate();
}
//运算符入栈
synStack.push(c);
//⑤若为')'
}else if(c == ')') {
//查看栈顶运算符,循环计算括号中数值
while(!String.valueOf(synStack.catStackTop()).equals("(")) {
//调用运算器
this.twoCalculate();
}
//取出括号'('
synStack.popStack();
//⑥a若为'='
}else if(c == '=') {
//清空运算符,输出
while(!synStack.emptyStack()) {
this.twoCalculate();
}
}
}
③封装的运算函数
private void twoCalculate() {
//运算符和数字出栈
String sign = String.valueOf(synStack.popStack());
double a = Double.parseDouble(String.valueOf(numStack.popStack()));
double b = Double.parseDouble(String.valueOf(numStack.popStack()));
//为*、/时
if(sign.equals("*")) {
numStack.push(b*a);
}else if(sign.equals("/")) {
int mid = (int)(b/a*100);
//对于除法、取小数点后两位
numStack.push((double)mid/100);
//为+、-时
}else if(String.valueOf(synStack.catStackTop()).equals("-")) {
//若前一符号为负数、会影响加减运算,需判断
if(sign.equals("+")) {
numStack.push(b-a);
}else {
numStack.push(b+a);
}
}else{
if(sign.equals("+")) {
numStack.push(b+a);
}else {
numStack.push(b-a);
}
}
}
运算结果:
对于一级运算‘-’号影响的测试:
对于双括号,小数点前两位和后两位测试: