原文地址:简易的计算器(微信小程序)
开源地址:仿 ios 简单计算器的小程序(不包含科学部分)。以后还会有很多类似的。
目前在学习微信小程序开发,于是我就利用学习的栈做了一个简易的计算器。
目前还是用最简单粗暴的方式进行编写的,我会优化的,没有进行开发前的思考,一拍脑袋就做了。目前支持的运算有 + - * / % 四种,也是跟苹果手机上的计算器差不多,只不过没有科学计算的那部分。
我这里主要讲一下栈的部分,布局部分跟前端一样,觉得没什么说的,当然感兴趣的可以自己去 GitHub 上查看。
我使用的是两个栈,一个是负责存放数字,一个是负责存放运算符。
先看一下界面: 看一下结构:
// 存放运算符
operator = [];
// 存放数字
digit = [];
当拿到用户输入的字符串的时候,我会对字符串进行预处理,根据上面的图片我们可以知道像除号并不能直接使用,需要转成 / 才可以,所以我做了预处理:
// 对传入进来的字符串进行预处理,也就是消除字符串中的非运算符
// 因为有一些字符只是用于显示的,并不是计算使用的,假如
// 增加了开根号的运算,这个时候就需要将显示的根号换成方便处理的根号
// 当然我们这里没有根号,主要处理的就是运算符
pretreatmentStr(str = "") {
let tempStr = ""
for (let i = 0; i < str.length; ++ i) {
let ch = str.charAt(i)
if (ch === '−') {
tempStr += '-'
} else if (ch === '×') {
tempStr += '*'
} else if (ch === '÷') {
tempStr += '/'
} else {
tempStr += ch
}
}
return tempStr
}
处理完成的字符串就可以使用了,接下来我对字符串进行切割,切割成一个又一个的有效字符串,比如 32*1.2 ,变成 ["32","*","1.2"] 这样的格式,因为这样的才能方便下一步处理,当然了也可以都放到一个函数中处理,只不过我是分开的:
// 阅读字符串进而进行分类存放
readStr(str = "") {
let formatStr = [];
let i = 0;
while(i < str.length) {
switch (str.charAt(i)) {
case '0':case '1':
case '2':case '3':
case '4':case '5':
case '6':case '7':
case '8':case '9':
case '.':
let digit = this.readDigit(str, i)
i = i + digit.length
formatStr.push(digit)
break
case '+':case '-':
let sign = this.readAddOrSub(str, i)
i += sign.length
formatStr.push(sign)
break
case '*':case '/':case '%':
formatStr.push(str.charAt(i))
default:
++i
}
}
return formatStr;
}
如果是数字或者 . ,那么就看看后面的字符串,以便读取完成,然后保存到 formatStr 数组中,我们看一下 readDigit 函数:
// 处理优先级为 1 的情况
readDigit(str = "", startIndex = 0) {
let ch = str.charAt(startIndex)
if (!this.isDigit(ch) && ch != '.') {
return ""
}
return ch + this.readDigit(str, startIndex + 1)
}
很简单,我使用递归实现的,当然也可以不适用递归,递归的终止条件是不是数字或者 . 了,如果是数字或者 . 什么的,那么就直接添加到之前的数字串中。我这里没有处理像 5. 这样的字符串,因为这样的串,我不用管,只要能被 parseFloat 这个格式转换函数正确处理就没问题。
当遇到的运算符是 + 或 - 的时候,有两种情况,第一是运算符,第二是正负号,所以我的 readAddOrSub 函数就是处理这两种情况的:
// 读取正负号,加减运算符
readAddOrSub(str = "", startIndex = 0) {
let currentCh = str.charAt(startIndex)
// 判断前一个是不是数字,不是数字的话就代表是符号位,而不是运算符
if (!this.isDigit(str.charAt(startIndex - 1))) {
// 如果是非数字,那么需要加上刚才的符号位
return currentCh + this.readDigit(str, startIndex + 1)
}
return currentCh
}
我们发现对于简单的计算器来说只要前面的符号是不是数字,那么就代表这个符号是正负号,而不是运算符,如果用户输入错误怎么办,也就是 ++2 , 那么在最后计算的时候就会报错,自然也就提示了用户错误,当然也可以在用户输入的时候就让用户知道,这样的话,需要把逻辑前置,我在这就不处理这种情况,放到外面来处理。
然后我们再看怎么根据拿到的数组把对应字符放入栈中的,怎样保存运算正确的。
startCalc(str = "") {
// 预处理字符串并整理字符串成数组
let formatStr = this.readStr(this.pretreatmentStr(str));
formatStr.forEach((s) => {
// 当遇到运算符的时候
if (s === "+" || s === "-" || s === "*" || s === "/" || s === "%") {
// 根据运算规则处理
this.priority(s);
this.operator.push(s);
} else {
this.digit.push(parseFloat(s));
}
})
while (this.operator.length !== 0) {
let c = this.operator.pop();
let b = this.digit.pop();
let a = this.digit.pop();
this.digit.push(this.calc(a - 0, c, b - 0));
}
return this.digit.pop();
}
下面我们具体看一下 priority 函数:
// 需要递归看优先级,直到优先级当前低或者只剩下自己
priority(s = "") {
if (this.operator.length !== 0) {
// 读取栈中的符号位
let c = this.operator.pop();
// 得到对应的运算优先级
let cG = this.operationGrade(c);
let sG = this.operationGrade(s);
// 这里主要是体现两点,第一是优先级,第二是从左往右的规则
// 把满足规则的先进行计算
if (cG >= sG) {
// 由于栈的特性,所以先出来的操作数
// 后出来的才是被操作数
let b = this.digit.pop();
let a = this.digit.pop();
this.digit.push(this.calc(a - 0, c, b - 0));
// 继续跟栈中的下一个符号进行比较
this.priority(s);
} else {
this.operator.push(c);
}
}
}
在对栈进行操作的过程中,我们要实时根据运算规则:
- 由左而右计算;
- 先算× ÷,后算 + −(先乘除后加减)
第三条规则我们这里没有所以就不写出来了。我们看一下 startCalc 函数中的下面这段代码:
while (this.operator.length !== 0) {
let c = this.operator.pop();
let b = this.digit.pop();
let a = this.digit.pop();
this.digit.push(this.calc(a - 0, c, b - 0));
}
我们发现在这里面我们是没有处理优先级的,所以在前面我们需要处理优先级,我这里是在处理优先级的过程中顺便计算了。
其他的请转至 GitHub 看详情,希望大家看完支持一下,我发现没有支持靠自己真的很难坚持下去,自制力不行的人。
开源地址:仿 ios 简单计算器的小程序(不包含科学部分)。以后还会有很多类似的。