构建一个支持多种数值系统的C++计算器

在这篇文章中,将探讨如何使用C++构建一个功能齐全的计算器应用程序。这个计算器将支持不同的数值系统、括号以及科学计算器上可用的函数。

去年的一个不眠之夜,自问:计算器是如何工作的?那时正在为Jump’n’Run框架开发一个基于栈的小型脚本引擎。完成后,思考是否可以使用栈来实现计算器。第二天,在网上搜索计算器的工作原理,但没有找到很好的解释(实际上直到今天都不知道方法是否是通用方法,或者是否有更好或更简单的方法)。因此,决定尝试一下。在对计算器一无所知的情况下,简单地开始分析计算器的行为。

问题

最感兴趣的问题是,如果不知道整个表达式,那么计算一个项的规则是什么。让给展示这个问题:

要计算这个表达式:

6*3+2*2+(2+3)

要计算这个表达式,可以使用这种方式:

  1. 0: 6*3+2*2+(2+3)
  2. 1: 18+2*2+(2+3)
  3. 2: 18+4+(2+3)
  4. 3: 18+4+5
  5. 4: 27

可以这样做的一个原因是,从一开始就知道整个表达式。比一个只输入了表达式到n位置的计算器处于更好的情况。尽管如此,计算器在不知道整个表达式的情况下就开始计算。这是如何工作的?这就是想问的问题。

分析

开始分析计算器(TI-30Xa)的行为,并发现了一些要点:

在这一点上,只关心这些操作:+, -, *, /, (, )。

在输入数字时,显示的数字并不真正表现得像一个数字。它更像是一个字符串而不是一个数字。

按下数字键不会引起任何计算。

按下操作符键有时会触发计算。

每次按下操作符键后都可能发生计算。

现在让更仔细地看看这些要点。

当开始输入一个数字时,显示的数字表现得像一个字符串。让尝试在计算器上输入1024。开始按下#1, #0, #2和#5。这里发生了什么?这些数字被连接起来了。嗯,想打错了一个数字。显示屏显示1025而不是1024。没问题,使用退格键,显示屏显示102。数字5被切断了。现在按下#4,看到1024。

想明白所说的“表现得像一个字符串”是什么意思。

关键是只要显示屏上的数字没有用于计算,它就是一个字符串。

没什么好说的,试试就知道了。可以输入任何想要的数字,计算器永远不会计算任何东西。

写下一些表达式并在计算器上输入它们。每当看到任何计算时,在笔记中相应的位置标记一个“|”。

例子:

6*2+|3+|4*2=| 1024/2*|(2+2)|-|1+|2=| 7+2*4*|0,5=|

看到了什么?#(永远不会触发计算。目前,不知道这个键实际上做了什么。

每当进行了计算,按下了以下键之一:#+, #-, #*, #/, #), #=。

从这个角度来看,将它们全部称为操作符。对于这种不精确性,感到抱歉,因为从数学角度来看,#(和#)并不是操作符。

目前没有看到任何规则,哪个操作符在哪个位置会触发计算。这比说每当按下#*就进行计算要复杂得多。看看第一个表达式“...4*2=”,这里它没有触发计算,而在最后一个表达式“7+2*4*”中,最后一个确实触发了计算。但到目前为止,有以下规则:

  • 如果按下以下键之一:#0, #1, #2, #3, #4, #5, #6, #7, #8, #9, #-或#,,则操作显示屏上的数字。
  • 如果按下以下键之一:#+, #-, #*, #/或#),则尝试计算。

计算系统

通过试错开发了这个系统。开始了一个简单的想法,将数字和操作符推到它们的栈上,然后尝试计算表达式。如果看到这个系统失败了,就会找出它为什么不工作,并进行更改。过了一会儿,喝了几杯茶之后,它变得越来越好。最后它起作用了。

这就是它:

首先,每个操作符都有一个优先级:

  • / *    3
  • -    2
  • +    1

接下来需要两个栈,一个用于存储数字,一个用于存储操作符。

此外,需要一个变量来存储最后输入的操作符的优先级(让称之为pol)。

最后,一个要计算的表达式:6+2*3-(2+3)=

要计算这个表达式,必须使用这些工具,并结合以下规则。

  1. 如果按下数字键,以相应的方式操作显示屏上的数字。
    • 如果按下操作符,执行以下操作:
      1. 将显示屏上的数字推到数字上。
      2. 尝试计算。
      3. 将按下的操作符推到操作符上。
      4. 将pol设置为按下的操作符的优先级。
    • 如果按下#(,
      1. 将pol设置为0。
      2. 将显示屏上的数字设置为0。
      3. 将按下的操作符推到操作符上。
    • 如果按下#),
      1. 尝试计算。
      2. 如果操作符栈上最上面的操作符是“(”,则将其移除。
  2. 尝试计算
    • 这是整个系统最有趣的部分。计算本身相当简单,它使用以下规则:
      1. 从数字栈上取两个最上面的数字。
      2. 从操作符栈上取最上面的操作符。
      3. 进行计算,并将结果推到数字栈上。

源代码可能如下所示:

var op: String = _opstack.pop().toString(); var p2: Number = Number(_numberStack.pop()); var p1: Number = Number(_numberStack.pop()); switch (op) { case "/": _numberStack.push(p1 / p2); break; case "*": _numberStack.push(p1 * p2); break; case "-": _numberStack.push(p1 - p2); break; case "+": _numberStack.push(p1 + p2); break; }

看看p1和p2。p2是第一个输入的数字,现在是栈上最上面的数字。知道10-2 != 2-10。

认为已经得到了所有需要的东西了吗?不,还有一个最后一点。如果进行计算,不必进行一次计算,必须进行尽可能多的计算。这是关注pol的地方。开始计算,如果当前按下的操作符的优先级小于或等于操作符栈上最上面的操作符的优先级,并重复计算,只要pol(最后输入的操作符的优先级)高于或等于操作符栈上最上面的操作符的优先级。

源代码可能如下所示:

while ( getPriority(nop) <= getPriority(_opstack[_opstack.length - 1]) && _pol >= getPriority(_opstack[_opstack.length - 1]) && _opstack[_opstack.length - 1] != "(" ) { ... calculation }

就是这样!这是计算器的非常基本的系统。

当然,还有更多要说的,例如:

  • 如果输入一个具有正确语法的表达式,这是有效的。但是如果是这样的一个: 3*2+((2-3)+4=
  • 如果计算结果显示,不能通过按下数字键或逗号键来操纵它。

感兴趣的点

可能想知道为什么写了这篇文章。去年用Actionscript在Flash中写了一个计算器。但目前正在学习C++和wxWidgets,为此,需要一个小项目来实现。这就是重写这个计算器的原因。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485