关键词:C# .NET 计算器 词法分析 语法分析 表达式计算 ConExpress Calculator
写在前面的话
其实很早之前就想写点关于这个的话题了,可总觉得其中的逻辑有点乱,不敢贸然动手。前一阵看了设计模式的书,心血来潮把代码优化了一遍,灵活性更强,可读性更强。目前对设计模式也只是初步了解,完全理解乃至灵活应用还需时日,但好处是不言而喻的,以后也要更努力的学习和实践。关于文中的错误和缺陷,还请大家指正。
关于“我的计算器”
因为不是计算机专业毕业的,一开始一点头绪都没有。感觉类似Excel那种输入公式然后动态计算的功能,但对其中的原理完全不理解。上网查了一下资料,感觉是编译原理方面的。找了本编译原理的电子书粗略看了前几章,然后又看了一下别人的代码,就着手写代码了。
一段时间后,虽然功能实现了,但写完之后总觉得设计思想有点模糊,却不知道问题在哪里,就一直搁着。前一阵看了一本设计模式的书(《大话设计模式》程杰 清华大学出版社),行文风格和小说类似,不知不觉就看完了。虽然并不能完全理解,但心中已经有一点设计模式的概念了。接着看另一本设计模式的书(《软件设计精要与模式》 张逸 电子工业出版社),同时开始对代码进行修改。
经过几天的努力,完成了一个独立的小程序。对比修改前后的代码,之前是2个类,一个用来存储数据,一个用来实现所有的分析与计算,功能太多太杂,不符合单一职责原则。一旦要添加或者修改功能,好多地方需要修改,又违背了开发封闭原则。修改之后虽然类的数量达到40多个,但整体概念清晰多了,要添加或者修改,涉及的地方也少。
功能需求
1.实现基本的数学运算和比较,如+-*/><=
2.对字符串进行操作和比较,如mid,left,right
3.实现if的判断功能
4.可以用括号实现多层嵌套
最开始的需求就是这些,现在根据我自己的修改,可以实现更多功能,而且也可以很灵活的添加功能。之前的分析过程就不再详细叙述了,讲一下现在的分析过程。我对编译原理不怎么了解,虽然看了几章教材,但只是知道一点皮毛,这里就不用其中的概念和原理。
先分析一下现实中的计算过程:
以5*3+12/4为例,我们拿到这个表达式之后会得到如下分析:
1.乘法的优先级高后面的加法,先算乘法,乘号两边的数分别是5和3,执行乘法5*3=15
2.加法的优先级没有后面的除法优先级高,先算除法,除号两边的数是12和4,执行除法12/4=3
3.乘除法做完之后再做加法,这时候加法并不需要知道两边到底怎么计算的,只需要知道两边计算后的值就可以了,乘法的值是15,除法的值是3,执行加法15+3=18
在这个表达式后面再加一个步骤:5*3+12/4-8,这时候只不过多了一步操作
4.做完加法后做减法,减号也不需要知道两边怎么计算的,向两边请求值,得到加法的值是18,另一边是一个数字8,不需要计算就直接给它,执行减法18-8=10
再稍微改变一下:5*3+12/4-8/2,计算过程就是
4.做完加法准备做减法,但后面的除法优先级高,先做除法,除号两边的数是8和2,执行除法得到4
5.然后执行减法,减法左边是执行加法后的值18,右边是执行除法后的值4,执行减法18-4=14
到此会发现,其实可以把每一个操作符理解成一个数值存储单元,并且该单元可以根据它两边的数值进行一定的计算得到自身的值。它只关心两边的值,而不需要关心值是通过什么算法得来的。这样就有了二叉树的味道,一个结点下面可以有两个子结点。另外,不管表达式多复杂,最终都只有一个结果,这个结果就是最后一个操作符对它两边的值进行计算得到的,而最后一个节点就可以理解成根节点。下面,就把表达式按照树的形状分解:

这里减法就是根节点,它向下请求值并进行计算。而它的下级也是采用同样的方法向下请求值,请求到值后就执行自身的计算,把计算结果存储在自己的存储单元里,然后让上一级调用自身的值。最后由根节点计算其下级的值得到整个表达式的值。最终计算完的树结构如下:
在“我的计算器”中就是采用这种方法对表达式进行分析和计算的,如图:
至于如何将表达式分析成树形结构,以及如何调用下级节点求值,在以后的小节中详细介绍。今天就先写到这里吧,以后再慢慢写。