面向对象程序设计课第四次作业
经过多次的查找、参考和修改,终于大致完成了一个(可能)没有 bug 的版本。
初始
根据要求,新建了一个 Calculation 类用以计算。我参考的教程中提到了中缀表示法(Infix expression)、前缀表示法(Prefix expression)和后缀表示法(Postfix expression),这里我用中缀表示法来计算。这种算法的基本思想是一边将数字和运算符分别存储到两个栈,一边根据四则运算法则,判断各个运算符的优先级关系,根据优先级取出运算符和数字进行计算,再将结果压入栈,如此往复,数字栈剩下的最后一个数字即为结果。原教程中用了自己的方法区分运算符和数字,但上次作业使我们获得了已经区分好了的运算符和数字的队列,我们只需简单判断字符串长度是否为 1 就可以进行操作了。参考教程中的函数,注意把运算过程中的 int
类型数据改为 double
类型,而将队列中 string
类型的数字转换为 double
类型,题目中已经提到了使用 <sstream>
,在网上找一下相关的使用方法即可。
使用命令行调用程序之前似乎在哪里看到过,将原来的 int main()
改为 int main(int argc, char** argv)
即可,其中 argc
和 argv
是两个固定的参数,argc
表示参数的个数,argv
是一个数组,保存每个参数。因此,若有参数 -a
,只需判断 argv[1]
是否为 -a
,再进行相应操作。
大概完成后,1+1
、(3+4)*(34/3)
这类简单的问题都可以解决,但示例 -10000+20-3*(20+2)
会报错,橘子大神告诉我,表达式第一个字符为运算符负号,进行到此处时缺少一个数字进行计算,只需在待运算的数字栈中压入一个 0 即可解决。
难点
解决了 -10000+20-3*(20+2)
问题后,似乎较为复杂的表达式都可以处理了。直到我看到昭锡博客里的测试数据:-(12-(15+6/100-(14*8)))-96/3+36*(12+3-(14-12))
、-96-(12+3/2-63*15-6+(12-5))
,尝试输入后正常得出答案,但我随意在一个左括号后加一个负号,即例如:-96-(-12+3/2-63*15-6+(12-5))
,就无法计算了。经过排查,原因是 -(-
、*(-
这样的部分无法识别,因此若左括号后的第一个数是负数,不能输出一个负号和一个正数,而应将这个负号与数字合并。简单的解决思路是,当遇到左括号和一个负号,输出左括号,跳过负号,将接下来的数字变为相反数。但由于我原来的实现方式是,通过 Scan 类中的函数每次返回一个运算符或数字字符串,在 main 函数中组成这个队列,因此,当我输出左括号时,没办法保存一个用来标记接下来”跳过负号,将接下来的数字变为相反数”操作的 bool 值。所以,为了解决这个负数问题,我只好修改 main 函数,并重写整个 Scan 类中的函数,使它返回一个队列。这其中遇到的问题数不胜数,例如,临时存储字符串的变量在压入队列后需要置空;当前位置、前一个位置、接下来位置的纠葛;若遇到当前字符为负号,前一个字符为左括号的情况,则应该将 bool 值置反,而不是赋值为真,这样可以解决多重括号负号嵌套的情况,如:-(-(-3))
;# 应该在结束时 push 入队列,否则直接结束运算了;以及大量的逻辑/智商问题。
此外,在看到昭锡文章的更新后,我也注意到若 0 为除数应当报错。
解决
如上所说,我(艰苦地)重写了 Scan 类中的函数,以解决负数的问题,同时参考了网上的文章,利用 <float.h>
中的 _finite(double x)
函数判断结果是否无穷,若是则报错,另外,我顺便支持了 (1+2)(3/4)
这样括号中间省略乘号的格式。
Github 链接
https://github.com/ladit/object-oriented/tree/master/Calculator