前段时间我们制作了一个表达式求值的程序,
我们赋予他基本框架与功能:【实例】表达式求值——利用stack实现五种运算符的运算 | 《课程设计》笔记 (i-nmb.cn)
以及更新了一目运算符的支持:【实例更新】表达式求值——加入新的单目运算符 | 《课程设计》笔记 (i-nmb.cn)
问题引入
我们在经过这两个步骤之后,我们可以基本实现 表达式的求值操作 ,但是L 2 8 =
没有括号就显得十分奇怪,并且我们在输入其他字符时报错方法还可以加以优化,让程序能够定位错误。
所以目前我们需要解决的问题有两种,即:
1.加入括号进行运算
2.优化报错方法
一、加入括号
首先我们要进行加入括号的运算方法
问题思考
1.在前面的单目运算符以及双目运算符中,我们需要进行数的运算,那么加入括号,操作数又有什么变化?
2.括号的特点是什么?他的左右运算符有没有什么特点?
3.插入括号后,具体的算法(或者说 运算流程 )是怎么样子的?
4.如何“优雅地”添加括号并且不让程序做很大改动?
这些问题我们需要思考与解决,以下为思考解决的部分过程
加入括号,操作数又有什么变化?
无论是单目运算符或者是双目运算符,其本质为运算符,是处理数的一种符号,只是运算过程中在处理 操作数的数目 的多少而划分。
但是括号不是数学中的运算符号,他并没有处理操作数,没有改变操作数的数目和数值,他只是单纯的改变运算符之间的优先级、只是运算顺序的辅助符号。添加括号时 在栈顶的操作数 就在栈顶,没有任何变化。
括号的特点是什么?
1.因为括号不是数学中的运算符号,括号最直接的特点就是,加入左括号时,不改变在左括号(
之前的其他符号的运算顺序,并且在右括号)
之前,都正常运算(即括号内的正常运算),左括号需要等待右括号才进行出栈
2.由1可以推断,无论左括号前面是什么,直接进栈;左括号后面的符号只要不是右括号,左括号始终不出栈,右括号始终不进栈。
具体的算法(或者说 运算流程 )
比如3*(2+4)=
首先等号入栈[history.top()
]
第一步,3
入栈 operands
第二步, * 与history.top()
(=)优先级比较, * 入栈。
第三步,由上述问题,无论左括号前面是什么,直接进栈,所以(
入栈
第四步,2
入栈
第五步,左括号与+进行优先级比较,由上述括号的特点可知:左括号后面的符号只要不是右括号,左括号始终不出栈,所以开始计算
第六步,输入4入栈,两次取出栈顶的元素(4和2)并进行+运算,得数为6,压入 operands
栈顶,弹出 + 号
第七步,输入右括号,与栈顶history.top()
的左括号匹配,所以弹出左括号,并且右括号始终不进栈。
第八步,输入等号,等号优先级最小,先运算现在栈顶history.top()
的 * 号,连续从栈顶取出两个元素相乘,得数18,弹出 * 号
第九步,此时栈顶[history.top()
]为“=”,与输入的等号相等,程序结束,输出operands.top()
【此时为得数】
如何“优雅地”添加括号?
根据以上括号的特点及运算流程
我们可以知道:
1.无论左括号前面是什么,直接进栈;
2.左括号后面的符号只要不是右括号,左括号始终不出栈,并且将符号压入栈
3.当等到右括号,则开始计算【等到右括号,“()”内的数计算完毕】
4.当等到右括号,则弹出左括号
换成伪代码的形式
将以上四点转换成伪代码的形式:
if(op=='('){push(op)}
top=='('&&op!=')':push(op)
if(op==')'):calc
top=='('&&op==')':pop('(')
将伪代码转换为现实
以上的伪代码只是我们的“梦想”,要让其成为现实的道路并不容易。
因为我们之前的程序大体以及框架已经确定,不能重新打乱和改变。
那么我们如何“优雅地”添加括号并且不让程序做很大改动?
答案是把括号作为一种特殊的“运算符”加入map
我们需要用以下特性:
1.无论左括号前面是什么,直接进栈
2.只要不是右括号,左括号始终不出栈
3.右括号始终不进栈
得出以下结论
1.(左括号的右优先级要非常大)
2.(左括号的左优先级要非常小)
3.(右括号的右优先级非常小)
转为代码
1 2 3 4 5 6 7
| std::map<oper, prv_num_t> p_num={ ………… {'(',{99,1}}, {')',{0,99}}, ………… }
|
我们重新审查代码,使用以上代码,可以实现以下目标:
1.无论左括号前面是什么,直接进栈;if(op=='('){push(op)}
2.左括号后面的符号只要不是右括号,左括号始终不出栈,并且将符号压入栈 top=='('&&op!=')':push(op)
3.当等到右括号,则开始计算 if(op==')'):calc
【等到右括号,“()”内的数计算完毕】
但是还有最后一点没有实现
即:4.当等到右括号,则弹出左括号top=='('&&op==')':pop('(')
此时我们必须修改源代码,在之前的判断等号与哨兵相等之前(if(prior(history.top(),op)==0) break;
)根据伪代码if(top=='('&&op==')'):pop('(')
加入以下代码,
1 2 3 4
| if(op==")"){ history.pop(); continue; }
|
因为左括号的左优先数==右括号的右优先数,如果经过if(prior(history.top(),op)==0) break;
那就跳出循环,运算就出错了,所以我们应该在此之前加入代码即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| while(1) { scanf(" %c",&op); if(op==',') continue;
if(isdigit(op)) { ungetc(op, stdin); scanf("%f",&x); operands.push(x); continue; } while(prior(history.top(),op)<0){ operands.push(calc()); history.pop(); } + if(op==')'){ + history.pop(); + continue; + } if(prior(history.top(),op)==0) break; history.push(op); }
|
此时我们基本实现了加入括号的问题了
二、优化报错方法
解决了上面的问题,在输入右括号而不输入左括号,或者输入其他不识别的运算符时,报错代码来自
![报错代码来自calc函数]()
这样不利于我们定位出错的位置,所以我们需要优化程序:优化报错方法
若我们输入其他错误字符时,若需要报错,则需要正在通过map函数查找字符对应的优先数的函数中int prior(oper op1, oper op2)
进行
在其中加入以下代码
1 2 3 4 5 6 7 8 9 10 11 12
| int prior(oper op1, oper op2) { + if(p_num.find(op1)==p_num.end()||p_num[op1].left==0){ + printf("错误的运算符:%c\n【程序即将终止】",op1); + exit (-1); + } + if(p_num.find(op2)==p_num.end()||p_num[op1].right==0){ + printf("错误的运算符:%c\n【程序即将终止】",op2); + exit (-1); + } return p_num[op1].left - p_num[op2].right; }
|
若我们在没有左括号的情况下输入右括号,报错,则需要在字符
压入栈之前判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| operand solve()
{ operand x, y; oper op; history.push('='); while(1) { scanf(" %c",&op); if(op==',') continue;
if(isdigit(op)) { ungetc(op, stdin); scanf("%f",&x); operands.push(x); continue; } while(prior(history.top(),op)<0){ operands.push(calc()); history.pop(); } + if(op==')'){ + if(history.top()!='('){ + printf("请检测:有没有成对括号\n截止目前,计算"); + break; + } history.pop(); continue; } if(prior(history.top(),op)==0) break; history.push(op); } return operands.top(); }
|
三、问题解决完毕
目前我们需要解决的问题有两种,即:
1.加入括号进行运算
2.优化报错方法
而这两种在上面已经基本上解决了,至此完结~(撒花)
下面附上更改后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| #include<stdio.h> #include<stack> #include<map> #include<math.h> typedef char oper; typedef float operand; std::stack<oper> history; std::stack<operand> operands;
struct prv_num_t{char left,right;}; std::map<oper, prv_num_t> p_num={ {'+',{81,82}}, {'-',{81,82}}, {'*',{41,42}}, {'/',{41,42}}, {'=', {100,100}}, {'^',{32,31}}, {'L',{22,21}}, {'S',{22,21}}, {'C',{22,21}}, {'(',{99,1}}, {')',{0,99}}, };
int prior(oper op1, oper op2) { if(p_num.find(op1)==p_num.end()||p_num[op1].left==0){ printf("错误的运算符:%c\n【程序即将终止】",op1); exit (-1); } if(p_num.find(op2)==p_num.end()||p_num[op1].right==0){ printf("错误的运算符:%c\n【程序即将终止】",op2); exit (-1); } return p_num[op1].left - p_num[op2].right; } #include<ctype.h> #include<string.h> operand calc(){ operand x, y;
if(strchr("+-*/^L",history.top())){ y = operands.top(); operands.pop(); }
x = operands.top(); operands.pop(); switch(history.top()){ case '+': return x+y; case '-': return x-y; case '*': return x*y; case '/': return x/y; case '^': return pow(x,y); case 'L': return log(y)/log(x); case 'S': return sin(y); case 'C': return cos(y); } printf("Err"); return -1; } operand solve()
{ operand x, y; oper op; history.push('='); while(1) { scanf(" %c",&op); if(op==',') continue;
if(isdigit(op)) { ungetc(op, stdin); scanf("%f",&x); operands.push(x); continue; } while(prior(history.top(),op)<0){ operands.push(calc()); history.pop(); } if(op==')'){ if(history.top()!='('){ printf("请检测:有没有成对括号\n截止目前,计算"); break; } history.pop(); continue; } if(prior(history.top(),op)==0) break; history.push(op); } return operands.top(); }
int main() {
printf("%g\n", solve()); return 0; }
|