======================================= 数据结构
计算器 abacus 的下载地址:http://www.oschina.net/code/snippet_736932_13725 dom
若是你有关于 abacus 的问题或者建议,请发邮件至 zhoucosin@163.com。谢谢。 函数
======================================= ui
本节介绍一些问题以及如何设计计算器以解决这些问题。 .net
程序的目标: 命令行
表达式在本质上就是一个由运算符、运算数、标点符号这些表达式元素组成的序列,因此问题的关键在于解释这些序列的数学意义。 设计
首先须要从字符串形式的表达式中提取各个表达式元素(运算符、运算数、标点符号:主要是括号和逗号),并将这些表达式元素依次保存到一个线性的数据结构中去,这称为词法分析。在词法分析以前,应该进行一些预处理,好比检查括号是否匹配。而在词法分析以后也应有一些后处理,好比对标识符做进一步处理,词法分析器只能识别出标识符,它并不知道一个标识符究竟是一个函数的名字仍是一个符号常量,须要作相应的更换。 3d
通过词法分析以后,咱们就获得了一个由表达式元素所组成的序列了,而表达式元素都是有相应的数学意义的,好比运算符有优先级,须要的运算数个数,以及最重要的计算函数指针这些属性,而运算数最主要的属性就是它所对应的数值了。好比表达式 1-2*(sin(pi/3))^2 在通过词法分析之后将被分割成以下的表达式元素序列: 指针
1 - 2 * ( sin ( pi / 3 ) ) ^ 2 code
其中的各个部件都已经有了数学意义(这须要相应的表达式部件类的实现),好比运算符 * 具备属性: 字符串体:“*”,优先级:2, 所需运算数个数:2, 计算函数指针:(一个完成乘法运算的函数)。而最后的运算数2具备属性:字符串体:“2”,数值:2.
这里有一个很是重要的设计,就是把数学函数当成运算符处理。这样作的理由是,函数运算符跟普通的四则混合运算符都能对给定的若干个实数,计算出一个结果来,这说明它们在本质上是一致的,你能够把加法 2+3 写成函数调用的形式 +(2,3),也能够把函数调用 pow(2,3) 按照二元运算符的惯例放在两个运算数之间: 2 pow 3,想必人们对于 2^3 这样的指数写法习觉得常,那么这个 2 pow 3 不也是一脉相承的么?而对于一元函数也是同样,一元运算符负号放在运算符以前,那么 sin(3) 也能够写成 sin 3 的形式。总之,把函数运算符跟普通运算符统一处理,这是 abacus 在设计上的一大特点。
接下来就要调整表达式的结构,传统的数学表达式实际上是很不规范的,历史上加减乘除这些二元运算符最先发明,很天然的就把它们放在两个运算符之间了,但一样的形式对于非二元运算符就不适用了。又如,做为一元运算符的负号是放在运算数以前的,而阶乘符号倒是放在运算数以后的,而函数运算符却采用了另一种写法: opt(a1,a2,.....)。因而可知传统的数学表达式虽然对人来讲是易于理解的,但却不利于计算机进行处理,在此咱们须要用一种统一的观点来看待全部的运算符,即运算符的本质是一个映射,能对给定的若个数(输入)产生一个肯定的结果(输出),有了这一点认识,咱们就但愿用一种统一的形式来刻画表达式。
咱们采用“运算符前置”的形式来规范数学表达式的结构,它把任何一个表达式放在一对小括号内部,而小括号内的第一个对象就是运算符,剩余的对象均是参与运算的运算数,例如 2+3 的规范形式为 (+ 2 3),而 sin(pi) 的规范式为 (sin pi),而且表达式能够任意嵌套,好比 5*(2-3) 的规范形式为 (* 5 (- 2 3)),做为一个更复杂的例子,一元二次方程x^2-3x+2=0的求根公式(正根)将表示为 (/ (+ (- (-3)) (sqrt (- ((^ (- 3) 2)) (* (* 4 1) 2)))) (* 2 1)) 。虽然这样的表达式对于人来说是一种痛苦,但因为其格式的规范性,计算机是乐于处理这样的结构的。
由传统形式向规范式转换的过程咱们就称之为语法分析,若是有语法错误,在这个过程当中将会被发现。在语法分析以前也应该会有一些预处理,例如在词法分析阶段并不能区分负号与减号,这须要在语法分析以前进行一点“有语法倾向”的检查纠正,而后根据括号的层次提高运算符的优先级。
表达式在通过语法分析以后,就已经被转换为规范式了,这个时候进行计算就至关简单了,只要采用递归的方式,在表达式中查找主运算符(即优先级最低的运算符),而后检查剩余的各个运算对象,若是某个运算对象自己也是一个表达式,即含有子表达式,则先计算子表达式的值,最后计算整个表达式的值。 1-2*(sin(pi/3))^2 的计算过程以下:
转换为规范式: (- 1 (* 2 (^ (sin (/ pi 3)) 2)))
计算过程:
Step 1: (- 1 (* 2 (^ (sin (/ pi 3)) 2)))
Step2: (- 1 (* 2 (^ (sin 1.047197551) 2)))
Step3: (- 1 (* 2 (^ 0.8660256282 2)))
Step4: (- 1 (* 2 0.7500010327))
Step5: (- 1 1.500002065)
Step6: -0.500002065
这样就完成了计算过程。在此处能够看到这个“规范式”带来的一个最大的好处:把语法分析跟递归计算分离开来了,若是不转换成这种规范式而使用传统的数学表达式,那么因为表达式的各类形式(分一元普通运算符,二元普通运算符,函数运算符.....),就只能在语法分析的过程当中根据相应的运算符选择合适的形式来进行计算了。而如今引入了这种规范式,在计算过程当中根本不用去关心这个运算符是什么类型的运算符。这说明,运算符前置表达式的引进,是 abacus 的另外一个很是好的设计。
至此大致设计基本上就完成了。这里有一个用脑图描述的计算器 abacus 的实现,一看就懂的:
http://www.mindomo.com/mindmap/abacus-5b6804e0f3fa466f96bc198c9b5d63d3
zhcosin
2012-10-29