第二章 计算器

计算器这个看似简单的应用,当你一旦着手去制作时,就会发现写出来的程序漏洞百出,那些在人类看来理所当然的逻辑,对于计算机来说,却是浑然不知,一定要将每个细节都照顾到,程序才能如你所愿地运行,否则就会出现一些莫名其妙的状况,

这里要讲解的是一个只有20个按键的简易计算器,实现了加减乘除的简单运算,以及清除、回退、求相反数等,如图2-1所示,更为复杂的运算,如求乘方、方根及三角函数的运算,可以利用开发工具中的数学函数,从现有的功能中衍生出来。

图2-1 计算器的外观

第一节 功能描述

一、符号及术语

  1. 前数:在计算过程中,居于运算符之前的那个数字;有三种情况可以生成前数:
    • 程序运行伊始,用户先输入数字,然后输入算符,此时该数字转变为前数;
    • 用户先后输入数字、算符、数字、等号后,计算结果被设定为前数;
    • 用户先后输入数字、算符、数字、算符后,其中的第二个算符具有等号的功能,将输入的两个数字和第一个算符进行运算,所得结果被设定为前数;
  2. 后数:在计算过程中,居于运算符之后的那个数字;在键入等号或第二个算符后,将运算结果设定为前数,并将后数设置为0;
  3. 算符:在本程序中特指 +、-、×、÷这四个运算符;
  4. 等号算符:用户先后输入数字、算符、数字、算符、数字、算符......,其中除了第一个输入的算符外,其他算符兼具等号的功能,我们称后面的算符为等号算符;
  5. C:英文CLEAR的缩写,用于清除计算过程中的全部信息;
  6. CE:英文CLEAR ENTRY的缩写,用于清除在算符之后输入的所有数字——后数;
  7. ←(回退):用于从尾部清除后数中的一个字符;
  8. ±(相反数):用于求相反数,如果后数不为0,则运算对后数生效,如果后数为0且前数不为0,则对前数生效;也可以理解为对屏幕上显示的数生效;

二、功能描述

  1. 常规操作:用户按顺序输入前数(屏幕将显示后数)、算符(屏幕不显示)、后数(屏幕显示后数)以及等号后,显示运算结果;
  2. 连续运算:用户先后输入数字、算符、数字、等号算符、数字、等号算符......,每次输入等号算符,显示运算结果,并将运算结果设置为前数,将后数设置为0;
  3. 重新开始:当完成一次运算(前数被设置为运算结果),此时如果用户不输入算符,而是直接输入数字,则清除此前的运算结果(相当于按键C的作用);
  4. 连续两次输入算符:如果用户输入算符之后没有输入数字,而是再次输入算符,则后面输入的算符有效(前面的算符被后面的覆盖了);
  5. 输入纯小数:用户有两种方法输入0.5:输入0.5或输入.5;
  6. 其他功能键的功能描述见上一个标题“符号与术语”。

第二节 用户界面设计

用户界面中用到了1个标签、20个按钮以及五个水平布局组件,其中20个按钮分别放置在五个水平布局组件中。水平布局组件及按钮的宽度和高度属性皆设置为“充满”。如图2-2所示。各组件的命名及属性设置见表2-1。

图2-2 计算器的用户界面设计

表2-1 组件的命名及属性设置

第三节 代码编写——实现常规操作

这个应用最麻烦的地方就是用户操作的不确定性,他可能随意地、想当然地按下某个键,就像使用一个实物计算器一样,因此我们要尽可能地在功能上接近于实物计算器,给用户一种良好的使用体验。但对于我们的编程过程来说,还是应该从实现最简单的功能入手,先实现常规操作,否则将会迷失在各种不确定之中。

一、输入数字

(1)设置三个全局变量:前数、后数、算符

如第一节所述,前数与后数是运算过程中的被操作数,算符是具体的运算类型。当程序运行伊始,前数与后数的初始值均为0,算符的初始值为空(“”);当用户输入第一串数字时,我们将这个数字保存在后数中;当用户点击算符键时,我们将算符之前输入的数字,即后数,保存在前数中,并设后数的值为0;

(2)创建点击数字过程

按照计算器的使用习惯,如果要输入数字123,会依次点击三个数字键,但三个数字的排列要用程序来处理,这里存在两种情况:

  • 当后数=0时,即,当用户输入第一个数字时,让后数直接等于输入的数字;
  • 当用户接着输入其他数字时,需要将后输入的数字与之前的数字进行拼接。

每输入一个数字,屏幕都会显示最新的输入结果,具体代码如图2-3所示。

图2-3 定义点击数字过程

过程中的参数“数”代表用户按下的具体数字。

(3)在按钮点击事件中调用点击数字过程

当用户点击数字键1时,在点击事件处理程序中调用点击数字过程,并将按钮显示文本作为参数,传递给该过程。如图2-4所示。

图2-4 在按钮点击事件中调用点击数字过程

以此类推,其他9个按钮的点击事件处理程序也将如法炮制。如图2-5所示。

图2-5 所有数字按钮的点击事件处理程序

二、点击算符

(1)定义点击算符过程

如图2-6所示,点击算符过程只有一行代码,即,设全局变量算符等于新近输入的运算符。

图2-6 定义点击算符过程

(2)在算符按钮点击事件中调用点击算符过程

图2-7 在算符按钮点击事件中调用点击算符过程

三、点击等号

(1)创建点击等号过程

当用户输入了前数、算符及后数之后,点击等号,此时需要对算符进行判断,依据不同的算符,执行不同的运算;在运算完成后,将所得结果保存在前数中,并显示在屏幕上,同时,设置全局变量算符为空,后数=0。具体代码如图2-8所示。

图2-8 定义点击等号过程

(2)在等号点击事件中调用点击等号过程

如图2-9所示,在等号点击事件中调用点击等号过程。

图2-9 调用点击等号过程

四、代码测试及说明

连接手机进行测试,按照设定的规范操作计算器,计算结果正确。

这里我们创建了3个过程——点击数字、点击算符及点击等号,这三个过程是本程序中仅有的三个过程,在接下来对程序的改进中,仅仅是对这三个过程进行完善,并不会再添加新的过程。

第四节 编写代码——实现连续运算

一、代码修改

按照第一节中对连续运算功能的描述,我们需要对点击算符过程进行修改,即,点击算符不仅要执行【前数=后数、后数=0、算符=具体算符】三项操作,还要兼具等号的功能,计算出此前输入项的运算结果,这里我们需要依据某个条件来判断此次输入的算符是算符,还是等号算符,这个条件就是全局变量算符的当前值:如果算符=“”,则此次输入的算符就仅仅是算符,如果算符≠空,则此次输入的算符是等号算符。修改过的代码如图2-10所示。

图2-10 修改点击算符过程以适应连续运算

这里要注意最后一行代码,原来该行代码在“如果...则”分支中,但在点击等号过程中执行了【算符=“”】的操作,因此需要重新将算符设置为本次输入的算符。

二、测试及代码修正

对代码进行测试,如果是连续输入数字、算符、数字、算符...,程序能够正确运行,但是,当中间输入等号之后,再输入算符、数字、等号后,计算结果则是错误的。

我们需要找到错误的原因。通过模拟程序执行过程、跟踪变量值的方法,可以帮我们找到原因。问题出在输入等号之后,此时: 前数=计算结果 后数=0 算符=“”

接下来输入算符,由于算符=“”,因此执行点击算符过程的“如果...则”分支,第一行代码为【前数=后数】,注意这时后数=0,因此前数并没有保留住原来的计算结果,而是被改写为0,因此后面的计算结果必然是错误的。我们为点击算符过程添加一个条件语句,来排除掉上述错误,代码如图2-11所示。

图2-11 当后数=0时,只改写算符的值

为了确保我们的限定条件不会给程序埋下隐患,我们分析了所有后数=0的情况,如表2-2所示,我们逐一对照,来判断限定条件是否会限制了合理的操作。

表2-2 后数=0的所有可能情况

  • 表中第1、2两条,当屏幕初始化或用户点击C之后,前数、后数均为0,此时点击算符键,再输入后数、等号(包括等号算符,下同),相当于做一次前数为零的运算,这在逻辑上是合理的,可以完成一次合理的运算;
  • 表中的第3条,当用户点击CE后,前数不变,后数为0,此时点击算符键,不改变前数,只改写算符值,此后再输入后数及等号,可以完成一次合理的运算;
  • 第4条,用户点击算符键后,后数为零,此时,如果用户再次点击算符键,不改变前数的值,只改变算符的值,相当于后输入的算符覆盖了前面输入的算符,这样,此后再输入后数及等号,可以完成一次合理的运算;
  • 第5条,用户输入等号后,前数为运算结果,后数为零,算符为空,此时用户点击算符键,不改变前数的值,只改写算符的值,这样,此后再输入后数及等号,可以完成一次合理的运算。

以上分析虽然显得有些罗嗦,但不失为一种保障程序完备性的方法。在人类思维及机器逻辑之间存在着一个鸿沟,缜密的思考与分析,是跨越这道鸿沟的唯一方法,这是计算器应用给我们留下的经验。

第五节 编写代码——实现小数输入

一、编写按钮点击程序

到现在为止,我们的程序还只能进行整数运算,下面我们编写小数点按钮的点击事件处理程序,来实现小数的输入,代码如图2-12所示。

图2-12 小数点按钮点击事件处理程序

在上述程序中,首先要对后数进行判断,查看其中是否已经有了小数点:如果后数中不包含小数点,则判断后数是否为0,如果为0,设后数为“0.”,否则直接在后数末尾添加小数点;如果后数中已经有了小数点,则程序不予响应。

二、代码测试及程序修正

测试过程是,屏幕初始化后,输入小数123.5,再输入加号,再输入小数0.5,此时,程序反应异常,预想中应该出现的0.5没有出现,屏幕上显示的是整数5。显然什么地方出现了错误。

错误出现在输入算符之后,再输入小数,与此项操作相关的有两个过程:点击算符和点击数字,我们先来检查点击数字过程。

在点击算符之后,全局变量的值发生变化:

前数=后数(或计算结果)
后数=0
算符=具体算符

此时点击0.5,看看点击数字过程如何处理:用户点击“0.5”中的5时,后数为“0.”,点击数字过程的参数值为5,此时过程执行“如果...则”分支,即,将5设置为后数,这样前面的“0.”就被覆盖了,于是就出现了测试过程中的问题。解决的方法是对后数的长度进行判断,如果是“0.”则长度为2,执行“如果...否则”分支,将5添加在“0.”之后,代码如图2-13所示。

图2-13 修改后的点击数字过程,当后数为“0.”时执行否则分支

再进行测试,程序运行正确。

第六节 编写代码——实现辅助功能

一、求相反数

按照第一节功能描述中的定义,按键±用于求相反数,但究竟是求前数的相反数,还是后数的呢?原则上讲,是求屏幕上正在显示的数的相反数,那么屏幕上有时会显示前数(如按等号或等号算符之后),更多时间是显示后数,这就需要为求相反数设定一个判定条件,来决定针对哪个数求相反数。我们以后数的值为判断依据,如果后数≠0,则运算对后数生效,如果后数=0且前数≠0,则对前数生效。代码如图2-14所示。

图2-14 将屏幕上显示的数字转变为其相反数

经过测试,程序运行正确。

二、删除末尾数字

按键←仅对后数有效,用于从后数的尾部删除一个数字。这个操作要用到“从字符串中截取特定长度子串”的功能,如图2-15所示。

图2-15 截取子串的代码块

当点击←键时,判断后数的长度:如果后数长度≥1,则从原字符串的首位开始截取长度为(后数长度-1)的子串。代码如图2-16所示。

图2-16 删除后数的末尾数字

经测试,正序运行正确。

三、清除后数

当用户点击CE按键时,会将已经输入的后数清除,或者说设置为0,并清空显示屏。代码如图2-17所示。

图2-17 点击CE时,清除后数

四、清除全部信息

当用户点击C按键时,清空所有已输入的信息及运算结果,代码如图2-18所示。

图2-18 清空全部信息

第七节 代码回顾

一、要素关系图

图2-19 整个程序中要素之间的关系图

在图2-19中,黄色块代表按钮的点击事件处理程序,蓝色块代表过程,橙色块代表全局变量,绿色块代表组件或组件的属性。这个程序中用到的组件个数虽然多,但种类很少。从图中可以看出,关联度最高的是全局变量后数,所有的按钮点击事件都与之相关联,因此要格外小心对它的操作。

二、关键环节的状态分析

这个程序在开发过程中,有两个操作是比较容易混乱的,一是开始输入数字,二是输入小数点。为了清楚操作前后变量及组件属性的状态变化,特制作了状态表格,表2-3描述了所有可能的、输入数字前后变量及标签属性的变化,这样做的目的是便于我们把握程序的走向。

表2-3 点击数字之前的所有可能状态

需要说明的是,这个程序并未经过严格的测试,其中难免存在一些错误,如果读者发现了错误,请自行修改自己的程序,也希望能够将错误反馈给笔者,以便于改进,多谢!