Java 简单控制台计算器 递归处理括号问题 多位数字问题*
1.在讲主要代码前我们先看几个判断函数,以免一会懵圈。
- isOpt函数是用来判断字符是否是操作符,即+、-、*、/;
- isKuohao函数用来判断字符是否是括号,主要是判断是不是左括号,用不到判断有括号,原因一会就知道了。
- isNums函数是判断字符是否是数字或者小数点的,那么怎么处理多位的数字呢?且慢慢向下看。
这三个函数很简单啊,我就不多说了。
private static boolean isOpt(char ex) {return ex=='+'||ex == '-'||ex == '*'||ex=='/';}
private static boolean isKuohao(char ex) { return ex=='('||ex==')';}
private static boolean isNums(char ex) {return ex>='0'&&ex<='9'||ex=='.';}
- getPrio(char)函数是用来判断+ - * / 四个运算符的优先级的,很好理解!
private static int getPrio(Character peek) {
switch (peek){
case '+','-':return 1;
case '*','/':return 3;
default: return 0;
}
}
2.下面代码为查找表达式字符数组中某一左括号所匹配的右括号的位置
/**
* 我们这里用一个flag位用以辅助,初始化为1
* 从left+1的位置开始遍历ex数组,遇到左括号 flag+1,遇到右括号 flag-1
* 当flag减为0时,代表找到了匹配的右括号
* 退出for循环条件是flag==0 或者下标i==ex.length(这种情况除非表达式是错误的,不然肯定有右括号)
* @param ex 表达式字符数组
* @param left 左括号的下标
* @return 返回匹配上的右括号的下标
*/
private static int searchMatchRightPosition(char[] ex, int left) {
int flag=1;
int i=left;
for (i = left+1;i<ex.length&&flag>0;i++){
if (ex[i]=='('){
flag++;
}
if (ex[i]==')'){
flag--;
}
}
return i-1;//因为退出循环判断条件在for循环中,判断时i++了,所以返回时要-1;
}
- 我们这里用一个flag位用以辅助,初始化为1 *
- 从left+1的位置开始遍历ex数组,遇到左括号 flag+1,遇到右括号flag-1
- 当flag减为0时,代表找到了匹配的右括号
- 退出for循环条件是flag0或者下标iex.length(这种情况除非表达式是错误的,不然肯定有右括号)
- @param ex 表达式字符数组 *
- @param left 左括号的下标
- @return 返回匹配上的右括号的下标
3。计算代码块 通过两个操作数和一个运算符作为参数, 注意’-‘和’/'两种操作num2和num1两个数前后顺序不能反。
private static double calc(double num1, double num2, char op) {
switch (op){
case '+':return num1+num2;
case '-':return num2-num1;
case '*':return num2*num1;
case '/':return num2/num1;
default: throw new RuntimeException("操作符异常");
}
}
4.下面是计算器主代码块,参数是表达式字符串
private static double calculator(@NotNull String substring) {
char[] ex = substring.toCharArray();//将字符串转为字符数组
int len = substring.length();//表达式的长度 用于判断何时扫描完整个表达式
int index = 0;//维护一个下标 用于扫描字符数组时向后移动
Stack<Double> nums = new Stack<>();//存储数字的栈 你也可以自己实现一个栈,我就为了少上点代码,就用现成的了
Stack<Character> ops = new Stack<>();//存储操作符的栈
while(true){//
if (index==len)//当下标出界后就break退出while
break;
if (isOpt(ex[index])){//如果字符是操作符
if (ops.isEmpty()){//如果操作符栈为空,就直接将操作符入栈
ops.push(ex[index]);
}else if (getPrio(ex[index])>getPrio(ops.peek())){
/*如果指向的操作符比栈顶的操作符的优先级大,
则将指向的操作符入栈*/
ops.push(ex[index]);
}else if (getPrio(ex[index])<=getPrio(ops.peek())){
/*
如果指向的操作符的优先级小于或者等于栈顶操作符的优先级,
则从数字栈中出栈两个数,从操作符栈中出栈一个操作符,
调用calc函数计算结果并将结果压入栈,指向的操作符入栈
*/
double num1 = nums.pop();
double num2 = nums.pop();
char op = ops.pop();
nums.push(calc(num1,num2,op));
ops.push(ex[index]);
}
}else if (isNums(ex[index])){
/*
如果指向的字符是数字,则声明一个String变量来存储数字
维护一个变量n 表示数字的位数,即数字的长度
在ex数组有效的长度内,循环判断下一位是否是数字,是的话就添加到s
index向后移动n-1,因为每次一个大的循环完之后都会有index++
数字找完后就压入栈
*/
String s=String.valueOf(ex[index]);
int n=1;//数字的位数;
while(index+n<len&&isNums(ex[index+n])){//获取多位数字的数字
s+=ex[index+(n++)];
}
index+=(n-1);
nums.push(Double.parseDouble(s));
}else if(isKuohao(ex[index])){
//有括号则使用递归求解括号内的值,并将数组下表指针指向右括号的位置
//每次递归
//将括号视为一个独立体,遇到则递归,计算括号内的表达式
int right = searchMatchRightPosition(ex,index);
char []newex = new char[right-index-1];//将括号内的部分复制到新的数组中, 不包括两边的括号
System.arraycopy(ex,index+1,newex,0,right-index-1);
double temp_res = calculator(String.valueOf(newex));
//将计算结果压入栈
nums.push(temp_res);
//每次将index指向右括号的位置,同样,因为下面有index++
index=right;
}
index++;
}
//循环退出后,只是表达式扫面完毕,实际栈中可能还有操作符存在,此时需要继续计算
//运行到操作符栈为空时,数字栈中剩下唯一的数就是整个表达式的计算结果了
while(!ops.isEmpty()){
//最后的计算不用考虑扫描,直接数字栈出两个数,操作符栈出一个操作符,运算,结果入栈,循环
double num1 = nums.pop();
double num2 = nums.pop();
char op = ops.pop();
double r = calc(num1,num2,op);
nums.push(r);
}
//最后将结果出栈即可
return nums.pop();
}
最后,在把main函数放上去。
目前所有代码都放在这了,复制可运行。
public static void main(String[] args) {
while(true){
System.out.println("输入表达示");
String s=new Scanner(System.in).next();
if (s.endsWith("=")){
double res = calculator(s.substring(0,s.length()-1));
if (Math.ceil(res)==res){
System.out.println((int)res);
}else
System.out.println(res);
}
if (!s.endsWith("=")){
double res = calculator(s);
if (Math.ceil(res)==res){
System.out.println((int)res);
}else
System.out.println(res);
}
}
}
总结:第一次写这个小计算器时没有想到左右括号递归处理,使得代码写的杂乱无章,第二次写的时候把各个功能明确分开,互不干扰,使得扫描表达式的循环中的每一次判断都更加直观,条理性更强。遇到左右括号采用递归的方式省去了转后缀入栈出栈一堆多余的判断条件,在最大程度上降低了数据的耦合性。
希望对有疑惑的同学能够有所帮助!~~