首页 > 编程语言 >使用Qt和Interpreter设计模式开发计算器(附源码)

使用Qt和Interpreter设计模式开发计算器(附源码)

时间:2023-01-01 15:31:20浏览次数:60  
标签:Digits strInstruction acosd 函数 源码 QString Qt 设计模式 AcosdFun


计算器软件其实有很多种,但是基本上都是模仿计算器,用鼠标点击按键来操作,这次我们反其道而行之,采用类似文本输入的操作方式。

功能

1.键盘输入算式,回车后计算结果。

2.根据当前输入的函数的一部分,自动找到备选函数。这时可以用上/下键选择需要的函数后,按空格键确定输入。在整个过程中一直可以表示函数的帮助信息。我们可以参考帮助信息,选择合适的函数。

3.支持三角函数,反三角函数,求和,平均值,乘方,开方,对数,当然还有包含嵌套的四则运算。

 

执行画面如下

 

技术要点:

除了操作界面以外,实际我们是要做这样一个解析器,就面临这一个如何描述我们所面的需求的问题。在这里我们使用EBNF的一种形式,它由W3C定义。我们可以在​​XML Path Language (XPath) 2.0 (Second Edition)​​中找到它的细节。

一下是我们在计算器中输入的表达式的描述。

 

[1]Expr::= AdditiveExpr 


[2] AdditiveExpr::=MultiplicativeExpr ( ("+" | "-") MultiplicativeExpr )*



[3]MultiplicativeExpr::= UnaryExpr ( ("*" | "/" | "%" ) UnaryExpr)*



[4]UnaryExpr::=("-" | "+")* PrimaryExpr



[5]PrimaryExpr::= NumericLiteral | ParenthesizedExpr | FunctionCall



[6]NumericLiteral::=IntegerLiteral | DecimalLiteral | DoubleLiteral



[7]ParenthesizedExpr::="(" Expr? ")"



[8]FunctionCall::=FunctionName "(" (Expr(","Expr)*)? ")"



[9]IntegerLiteral ::=Digits



[10]DecimalLiteral ::=("." Digits) | (Digits "." [0-9]*)



[11]DoubleLiteral::=(("." Digits) | (Digits ("." [0-9]*)?)) [eE] [+-]? Digits



[12]Digits ::=[0-9]+



[13] FunctionName=sinr

|sind

|cosr

|sind

|tanr

|tand

|asinr

|asind

|acosr

|acosd

|atanr

|atand

|power

|root

Token解析

解析算式也好,脚本也罢,在分析语法之前,一般都会引入词法分析的过程。简单说比如我们有如下算式

 

acosd(2)+cosd(30)*12+1.23E-21

为了处理简单,在真正计算之前都会先将输入分解成一个个的单词(Token)。比如上面的内容经过处理,如果能变成下面的形式,就容易处理了。

 

acosd,(,2,),+,cosd,(,30,),*,12+1.23E-21

这里,类似于acosd,cosd和(,),*之类的要素可以通过简单的字符串比较来解决。但是数字还有最后e指数就没有那么简单了。当然如果你认为这正好是展现编程基本功的机会的话,没有人拦着你,但是在本软件中我们采用了正则表达式来解决这个问题。

例如e指数可以如下定义:

 

"((\\.[0-9]+)|([0-9]+(\\.[0-9]*)?))[eE][+-]?[0-9]+"

对于一般形式的小数:

 

"(\\.[0-9]+)|([0-9]+\\.[0-9]*)"

整数就更简单了。

 

"[0-9]+"

这样一来我们就可以象处理+/-号一样处理其他要素了。可以很明显的看出,这里的内容和EBNF的内容很相似,也不知道是谁跟谁学的。

 

表达式解析

表达式解析的程序结果基本上就是参照EBNF的内容在加上一些归纳。主要功能有:

1.表达式解析通过buildExpr实现

2.表达式计算通过evaluate实现。

这里只提供类图,详细可以参照代码。

 

如何扩展自己的函数

计算器功能强大与否,主要是看函数功能是否强大。所以在设计的最初就把能够简单的扩展函数功能作为关键的设计目标之一。有没有做到呢?实际看一下代码应该最能说明问题。

先是头文件。


#ifndefACOSDFUN_H

ifndefACOSDFUN_H

#define ACOSDFUN_H
#include"calculatefunction.h"
class AcosdFun : public CalculateFunction
{
public:
AcosdFun();
virtual QString getName();
virtual QString getInstruction();
virtual bool execute(QList<double> paraList, double& result, QString& message);
};
#endif // ACOSDFUN_H

代码是16行,实际上需要动的,只有类名和防止重复引用的宏定义,算4行吧。

然后是实现部分。

#include"acosdfun.h"

include "functionmanager.h"
#include <math.h>
static FunctionManager::FunctionRegister acosRegister(new AcosdFun());
AcosdFun::AcosdFun()
{
}
QString AcosdFun::getName()
{
return "acosd";
}
QString AcosdFun::getInstruction()
{
QString strInstruction;
strInstruction += "acosd(x) -\r\n";
strInstruction += "Arccosine (inverse cosine) function. Result in degrees.\r\n";
strInstruction += "Argument type and attributes\r\n";
strInstruction += "x must be of type real. Its value must satisfy the inequality |X| <= 1.\r\n";
return strInstruction;
}
bool AcosdFun::execute(QList<double> paraList, double& result, QString& message)
{
if(paraList.count() != 1)
{
message = "Acosd:Invalid parameter count";
return false;
}
double para = paraList.first();
clearError();
result =acos(para) * 180 / M_PI;
return checkError(message);
}


这个类是角度单位的反余弦函数的实现。空行算在内也只有38行。

第6行可能比较费解,是用来登录函数的。有了这一行,就能在函数列表中看到自己定义的函数了。

如果是简单的函数的话,相信15分种可以搞定。

只要在工程文件加入两个文件就可以扩展函数功能,不能再简单了吧。

 





阅读最新文章请扫描下面二维码,关注公众号【面向对象思考】。


使用Qt和Interpreter设计模式开发计算器(附源码)_设计模式

标签:Digits,strInstruction,acosd,函数,源码,QString,Qt,设计模式,AcosdFun
From: https://blog.51cto.com/oothinking/5983029

相关文章

  • Android小游戏-九宫格,有源码的哦!
    为了学习Android,自己编了一个小游戏,就是9宫格。特点:1.自动分析可选数字。2.自动判断结果3.支持Undo功能(Command设计模式)4.支持自编题目。 功能简介主画面功能:1.点击项目选......
  • java中的LinkedList的add()源码解析
    一.介绍LinkedList类阐明LinkedList类的成员:其本质是双向链表,first指向链表的头部,last指向链表的尾部。二.介绍LinkedList静态内部类Node类阐明Nod......
  • Reflector 源码分析
    Reflector源码分析前面我们说了Informer通过对APIServer的资源对象执行List和Watch操作,把获取到的数据存储在本地的缓存中,其中实现这个的核心功能就是Reflecto......
  • WorkQueue 源码分析
    WorkQueue源码分析前面我们把Informer的整个流程完整分析了一遍,我们可以通过监听对象的变化,将资源对象写入到事件处理器的回调函数中,但是如果我们直接在回调函数中处理......
  • Shared Informer 源码分析
    SharedInformer源码分析我们分析了Indexer组件的实现,实际上最开始的时候我们在Informer示例中通过Informer的Lister获取的资源对象数据就来自于Indexer,当然除......
  • Qt音视频开发07-合并音视频文件
    一、前言之前已经把音视频分开存储了对应的文件,因为这个需求特别少,当然确实有部分用户是需要把音视频分开存储,但是毕竟是很少数,绝大部分的用户都是音视频合并到一个MP4文......
  • java中的ArrayList的add()源码解析
    使用无参构造器创建的ArrayList对象,示例代码如下:publicclassArrayListTest{publicstaticvoidmain(String[]args){Listlist=newArrayList();......
  • nacos注册中心单节点ap架构源码解析
    一、注册流程单nacos节点流程图如下:流程图可以知,Nacos注册流程包括客户端的服务注册、服务实例列表拉取、定时心跳任务;以及服务端的定时检查服务实例任务、服务实例更新......
  • drf快速使用 CBV源码分析 drf之APIView分析 drf之Request对象分析
    目录序列化和反序列化drf介绍和安装使用原生django写接口djangoDRF安装drf快速使用模型序列化类视图路由datagrip使用postman测试接口CBV源码分析drf之APIView分析View类......
  • Spring 事务源码(二):beanDefinition的准备-配置文件加载
    普通bean标签的beanDefinition的解析不再赘述,仅对事务相关的核心beanDefinition的获取做分析。一、BeanDefinition预览IOC容器刷新完成后,容器中的BeanDefi......