基于量子框架的科学计算器的设计与实现
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
基于量子框架的科学计算器的设计与实现
前言
与程序相反,气泡和前头的好处在于它们绝对不会崩溃。
――Bertrand Meyer
有过Windows平台程序开发经历人,对Win32消息机制一定不会陌生。图形用户界面的成功是非常引人注目的。对话框、按钮等流行控件已被广大用户所接受。在多年的发展中,以Visual Studio主导的图形开发模式已经非常完善。在这些IDE的帮助下,图形用户界面简单又容易,用户通过向导添加控件以及处理例程,多数情况下,这种方法可以工作得很好。事件驱动的特点是,事件能在任何时间以任何顺序出现,用户必须要随时处理它们。
然而,大多数界面响应不是幂等1(idempotent)。程序员通常使用一些变量来记录当前状态,以便正确处理响应。变量的过多引入将使程序变得混乱而且难以调试。一种解决办法是不使用事件驱动,改用状态驱动。量子框架便是给程序员实现状态驱动的绝佳工具。
为什么要使用量子框架
Ian Horrocks的《状态图构建图形用户界面》一书中,作者给出一个由微软编写的简单计算器的示例程序,使用的语言是VB,采用的方法无疑是消息驱动。然而这个程序却存在许多问题。如输入1,/,-,=,2,=,程序立即崩溃(图1),又如输入2,×,CE,2,=,这时CE不会起作用。VB计算器的处理负数时经常出问题,因为‘-’既可以表示负号,也可以表示减号,微软也是采用加入一个状态变量来区分,不过实事上在处理复杂输入时,这种方法不大管用。我有个朋友为了降低难度,放弃‘-’的多义性,直接用一个按键‘+/-’来处理负号,尽管如此,他的程序还是有多种bug。
图(1)图(2)
考虑实现一个有多种功能的科学计算器(图2),其难度比刚才的计算器大很多倍,如果用原来的方法,不和要多少个才行,而且程序一定会变得非常混乱,bug也会很多。如果用量子框架来解决,整个编程过程非常清楚。当把状态图画出后,甚至能用工具自动生成2!
①如果一个运算对于在范围内的所有的一个数多次进行该运算所得的结果和进行一次该运算所得的结果是一样的,那么我们就称该运算是幂等的。
②比如rhapsody
开始设计
在应用量子框架时,一个重要且必须的工作便是画出状态转换图。就像大型软件设计之初要画出UML图一样。不同之处是,UML强调模块之间合作,而状态转换强调流程,而且状态转换图更详细。正是因为提供了详细的信息,CASE工具才能自动地生成代码。不过每个CASE工具厂商在表示格式和命名方式都有所不同。量子框架是用C++实现的,它没有图形化的CASE工具,使用时只须要包含相应的头文件即可。实事上,量子框架最初是为嵌入式操作系统而设计的,因为是用C++编写的,所以可移植性非常好,x86平台已经移植过来。
我所设计的科学计算器计算功能有:+,-,×,÷,%,π,sin,sin-1,cos,cos-1,tan,tan-1,ln,e x,log,10x,n!,y x,y-x,exp,1/x,x2;支持十进制、二进制,八进制,十六进制。按键的布局依照KK-305b科学计算器。这里我省略了一些键,因为统计功能我没有去实现。
科学计算器虽然功能众多,但是其本质特点在于对一个数或两个数的计算。不过对其详细分析后,计算器中参与的计算有三个数:计算数1,计算数2和结果。结果也是可以当作计算数,如在算出结果后(按‘=’),再次按‘=’,那么会再计算一次,如5,+,7,=,显示12,按‘=’,显示19。
虽然量子框架是以状态图为基础的,但是状态转换仍然是由消息驱动的。在我的计算器计算器中,存在的消息类型有
⑴.数字消息(IDC_0, IDC_NUM,IDC_POINT):数字键是一种特殊的键,主要用于数字输入,有必要分清0和其它键,因为多个零出现在前面时只显示一个。
⑵.一元操作符消息(IDC_UOPER):大多数数学函数都是一元操作,如sin,HEX等。
⑶.二元操作符消息(IDC_BOPER):+,-,×,÷。
⑷.一元扩展操作符消息(IDC_EUOPER):,y x,y-x,exp。
⑸.特殊消息:C,CE,=,2ndf。
程序的主状态Calc,它可以处理一些公共消息,如IDC_CANCLE。当发生错误时(溢出和除0),只有此消息才能消除冻结状态。当程序初始化时,默认是自动转换到ready状态,等待第一次输入。
ready状态是resault状态的上级,它能处理的消息有IDC_CE,IDC_UOPER,IDC_OPER 和数字消息。resault状态只处理IDC_EQUALS,计算连等操作。
当ready收到数字消息时,便转换到moperand1,这是操作数1的主操作数,主操作数在eval计算中被使用;辅操作数exoperand是在有IDC_EUOPER时才有,在退出operand1时,辅操作数参与euoper计算,结果放在主操作数中。所有操作数都能处理IDC_UOPER,buoper根据UOPER消息的keyId来识别类型。
opEntered是BOPER消息处理中心,事实上所有IDC_BOPER的目的地都是opEntered。在这个状态中有一个监视器myBOperand。
operand2与operand1不同之处在于,前者能处理IDC_EQUALS,然后转换到resalut状态,这一过程完成一次计算循环。
图(3):状态转换图详细设计
头文件Calc.h
/////////////////////////////////////////////////////////////////// //
// 仿真科学计算器
// 程序作者:冯星(annidy@)
// 版权所有2009。未经作者同意,严禁用于商业用途!!!
/////////////////////////////////////////////////////////////////// //
#ifndef calc_h
#define calc_h
#include
#include "qf_win32.h"
#define BUFSIZE 20
enum RADIX{DEC, BIN, HEX, OTC};
struct CalcEvt : public QEvent {
int keyId; // ID of the key depressed
};
class Calc : public QHsm { // calculator state machine
public:
Calc() : QHsm((QPseudoState)initial){
myResault = 0;
myOperand1 = 0;
myEOperand1 = 0;
myOperand2 = 0;
myEOperand2 = 0;
myBOperator = 0;
myUOperator = 0;
myEUOperator = 0;
myRadix = DEC;
err = false;
is2ndf = false;
dispRadix();
};
static Calc *instance(); // Singleton accessor method private:
void initial(QEvent const *e);
QSTATE calc(QEvent const *e);
QSTATE ready(QEvent const *e);
QSTATE result(QEvent const *e);
QSTATE operand1(QEvent const *e);
QSTATE zero1(QEvent const *e);
QSTATE int1(QEvent const *e);
QSTATE frac1(QEvent const *e);
QSTATE opEntered(QEvent const *e);
QSTATE operand2(QEvent const *e);
QSTATE zero2(QEvent const *e);
QSTATE int2(QEvent const *e);
QSTATE frac2(QEvent const *e);
QSTATE final(QEvent const *e);
QSTATE exoperand1(QEvent const *e);
QSTATE ezero1(QEvent const *e);
QSTATE eint1(QEvent const *e);
QSTATE efrac1(QEvent const *e);
QSTATE exoperand2(QEvent const *e);
QSTATE ezero2(QEvent const *e);
QSTATE eint2(QEvent const *e);
QSTATE efrac2(QEvent const *e);
QSTATE moperand1(QEvent const *e);
QSTATE moperand2(QEvent const *e);
void clear();
void insert(int keyId);
void eval();
void dispState(char const *s);
void dispRadix();
void disp(double i); //多进制显示
double readnum(); //多进制读数
void buoper(double& i);
void euoper(double& i, double& e);
//operand中有一个myOperand,还有一个exOperand
bool checknum(int keyid); //检查数字按键的合法性
bool pointgrant(); //小数点允许private:
HWND myHwnd; // the calculator window handle
BOOL isHandled;