今天花了大概四个小时时间,用栈(stack)实现了“任意表达式的值计算”的问题。
C++ 比 C 好的一点就是,C++ 的STL定义了大量的数据类型和算法,相比于 C 更加视觉化。
实现这个的基本思路很简单:分成两部分完成。两个主要函数:
string shorten(string m) 把 string m 由中缀式变为右缀式,double calculate(string s) 计算右缀式的表达式值。
数字写入右缀式m很简单,不过别忘了用个符号把数字分开。我选择的是$。
比较难的地方是shorten函数,分析怎样把 string m 变为右缀式。可能的计算有这么几种:+,-,*,/,()。于是我通过列表的方式来分析:
+|- *|/ ( 空
+|- m+=c m+=c push push
*|/ push m+=c push push
( push push push push
) m+=c m+=c m+=c XXXX (XXXX表示不可能发生的情况)
竖行是操作符,横行是对应每一个竖行可能遇到的情况,“空”代表栈为空。push表示入栈,m+=c表示元素出栈,写入表达式(右缀式)m。
其实列表的原则挺简单的:
1. 把“(”看做开辟一次新的栈(从“(”开始算,忽视之前的元素,直到“)”被取消);
2. 元素a遇到相同或更高优先级的,栈顶元素c出栈,给右缀表达式m;再次对比;直到遇到更高优先级的,元素a入栈;
3. “)”出现的时候,之前所有元素依次出栈,写入m,直到“(”;
4. 在数字都加入右缀表达式之后,栈中所有元素依次出栈,写入m,直到栈为空。
关键就是 2、3条,我花了好久才总结出来的规律。顺便说下,列表法真的是很好的分析问题的方式,不重不漏还简练。
然后进行右缀表达式的计算,这个比较简单:数字依次入栈,遇到操作符后对栈顶的两个元素进行顺序操作(倒数第二个 +-*/ 倒数第一个),然后把这两个元素换成新的结果。如此循环即可。
原题目要求用int即可,但用int必然会造成除法的不精确。所以考虑用double类型,并且在读取的时候可以支持小数点。记得是有直接转化的函数,但忘了,所以自己写啦,命名为 str2double(string s)。有个dec变量决定计算整数部分还是小数部分,详细可参见函数。
这次实践这个程序还有个小收获,是在我无数次的debug之中:可以在某些关键步骤(比较容易发现bug的地方)加上输出语句,这样可以判断程序是否运行过了这些关键地方。
还有,顺便提一下,“模块化”的思想真的很重要,不然在进行一些小改动时,那种“牵一发而动全身”的感觉很焦虑、很凌乱。