数学知识:八则运算(加减乘除、开方、幂、取反、取余)。
编程知识:界面设计、八则运算方法、简单异常处理(输入非数字、除数为0等)。
本节课,我们来设计并开发一个常用的数学工具——电子计算器,并使其达到真实可用的效果,因此,我们不仅要实现整数的运算,还需要考虑其他更精确的数据类型(比如小数、负数)。
界面的设计:可以参考实物计算器,主要由一个显示屏和一系列排列整齐的操作键组成。为了简单起见,我们用一个计算器的图片作为背景,用一个文本框(TextBlock)表示显示屏,用一系列按钮(Button)来表示按键。至于如何将这些在背景图上排列整齐,有多种方式,这里我们用最方便的表格法(Grid)来布局这些控件,具体步骤为:
添加一个表格控件(Grid)在背景图上
根据所需的按钮个数计算需要的行和列
在表格上添加这些行和列,并调整宽度
将控件拖动到表格的单元格里完成布局
这些都可以用鼠标轻松完成,设计过程如图1所示:
图(1)
程序设计:我们先对计算器的按键进行分类:
一元运算符:取反、开方
二元运算符:加减乘除、幂、取余
功能键:计算(=)、清除、退格(←)
数字键:0-9,小数点
相同分类的键具有相同或者相似的功能,这里我们采用程序设计初期经常会使用到的流程图来表示具体的计算逻辑,反复推演流程图的正确性,然后再开始编写程序。由于二元运算符需要两个操作数,但是显示屏始终只会显示一个数,因此我们需要在后台添加两个对象来存储这两个操作数:
last:上一个操作数
curr:当前显示的数
(比如,依次按下1、+、2、3四个按键后,last变为1,curr变为23)
流程图设计如图(2)所示:
图(2)
下面的代码对应流程图里的功能模块【执行运算】:
//一元运算,操作数为d
double Cal(char op, double d){
switch (op){
case '±': return -d;//取反
case '√': return Math.Sqrt(d);//开方
default:
throw "非法运算符:".Fill(op);
}
}
//二元运算,操作数为d1、d2
double Cal(double d1, char op, double d2){
switch (op){
case '+': return d1 + d2;
case '-': return d1 - d2;
case '×': return d1 * d2;
case '÷': return d1 / d2;
case '%': return d1 % d2;//取余
case '^': return Math.Pow(d1, d2);//幂
default:
throw "非法运算符:".Fill(op);
}
}
上述代码并不完善,因为很明显计算的时候可能会出错,比如当开方的操作数为负的时候,或者除数为0的时候(这就是流程图里面提到的异常情况处理)。还有一种特殊情况就是取反的操作数为0的时候,因此,我们在程序里面需要处理这些异常或者特殊情况。我们以最后一种情况为例,当用户按下取反按键(±)以后,会进行取反运算,假如当前显示的数字是1,取反后会变成-1,当前的数字是-1,取反后会变成1。但是如果当前数字是0,则取反后则不会有变化,造成的直接后果就是让用户感觉按键坏了。
如何在这种情况下让用户体验更好呢?那就是如果当前数是0,按下取反后变成-0,这样用户可以继续输入如-0.12这样的操作数,否则用户只能先输入0.12然后再进行取反,体验就差了很多。处理这种特殊情况的代码如下:
//取反【±】
if (btn == btnRev && (curr.In("", "-")){
//操作数为0时正负切换特殊处理
SetCurr(curr =="-"? "" : "-");
return;
}
接下来,就是按照流程图处理人机交互的逻辑了,为了使界面的按钮和后台的运算符、操作数一一对应,我们可以从命名上对按钮进行规范,这样我们就可以在代码实现中通过对按钮的分组使用,来达到操作运算符的目的,同时也使得代码更加简洁规范,如:
//2个一元运算符
OneVarOps = { btnSR, btnRev };
//6个二元运算符
TwoVarOps = { btnAdd, btnSub, btnMul, btnDiv, btnRem, btnPow };
//所有8个运算符
Ops = new List();
Ops.AddRange(OneVarOps);
Ops.AddRange(TwoVarOps);
//10个数字,1个小数点
Numbers = { btn0, btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9,btnDot };
//所有按钮
var btns = { btnClear, btnBack, btnEqual, btnDot };
btns.AddRange(OneVarOps);
btns.AddRange(TwoVarOps);
btns.AddRange(Numbers);
//为所有按钮添加点击事件
foreach (var btn in btns){
btn.Click += (s, e) => { ButtonClick(s); };
}
在按钮事件中,将按钮当作具体的对象来进行各自的处理:
//按钮事件
void ButtonClick(Button btn){
//一元运算符
if (btn.In(OneVarOps)){//...}
//二元运算符
if (btn.In(TwoVarOps)){//...}
//数字【0-9】
if (btn.In(Numbers)) {//...}
//...
至此,计算器的核心代码设计已经完成,不过还有些稍微复杂冗长的代码需要编写,比如如何将输入的一个个字符组成数字、如何避免输入多个小数点,等等,由于这部分代码较冗长不再列出,大家可以参考具体的程序源文件。
Demo运行效果如图3:
图(3)
【课后练习与思考】
计算器的功能相对简单,因为操作数是一次一次的输入的,输入完就立即计算。假如输入的不仅仅是数字,而是允许将操作符一并输入,然后求表达式,这样的程序应该如何设计呢?比如用户输入:1+2+3x5=。
领取专属 10元无门槛券
私享最新 技术干货