背景:最近在学习小程序开发,刷到了一个教学视频做计算器。作者强调在微信小程序里面无法执行eval方法 。想用Function进行构造,还是不被执行。
我好奇的搜了下发现很多人都碰到这个问题,就想自己实现一下,但是现实非常打脸,想了一天多时间,也没找到突破口,最后就在网上找到了
zl_calculator_zl,借鉴了先进思想,也就有了这篇文章。
思路来源于:https://github.com/zhangluzhanglu/zl_calculator_zl
博主提供了一套支持()运算符的计算类并且在ReadMe文件中写了实现原理,本文也是用了此原理实现了加减乘除余运算。
主要思路:
1、记录用户的输入并把它显示在计算器中(其中要处理C键和退格键操作以及重复运算符输入的问题,另外要注意小数点和负数问题)
2、将记录的内容,转化为中缀表达式集合 比如:103+3.5*5-9/7 就转化为 【“103”、“+”、“3.5”、“*”、“5”、“-”、“9”、“/”、“7”】
3、再将中缀表达式集合改为后缀表达式集合 比如:【“103”、“+”、“3.5”、“*”、“5”、“-”、“9”、“/”、“7”】转化为【“103”、“3.5”、“5”、“*”,“+”,“9”、“7”,“/”、“-”】
用自己的话总结就是:
从左往右读取中缀表达式集合(需要另外一个集合S来存储读到的运算符)
a、碰到数字,直接加入后缀表达式集合P中
b、碰到运算符,判断S,S为空,直接存储当前运算符,
S不为空,则判断S中最近一次新加入的运算符和当前拿到的运算符优先级谁高 当前拿到的运算符等级高或优先级相等,则直接存入S集合
当前拿到的运算符登记低于S最近新加入的,则先把S集合中所有的运算符依次弹出,加入到后缀表达式集合P中,再清空S集合,将拿到的运算符存在S集合中
以下为参考文章中的原文描述:
-
自左向右读入中缀表达式
-
数字时,加入后缀表达式;
-
运算符:
- 若为 ‘(’,入栈
- 若为 ‘)’,则依次把栈中的的运算符加入后缀表达式中,直到出现’(’,从栈中删除’(’
- 若为除括号外的其他运算符, 当其优先级高于除’('以外的栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的运算符优先级高和优先级相等的运算符,直到一个比它优先级低的或者遇到了一个左括号为止,然后将其自身压入栈中(先出后入)。
-
-
当扫描的中缀表达式结束时,栈中的的所有运算符出栈;
4、计算表达式
建立一个栈W 。从左到右读表达式,如果读到操作数就将它压入栈W中,如果读到n元运算符(即需要参数个数为n的运算符)则取出由栈顶向下的n项按操作数运算,再将运算的结果代替原栈顶的n项,压入栈W中 。如果后缀表达式未读完,则重复上面过程,最后输出栈顶的数值则为结束。
app.json的配置代码
calc2:{ str:'', //临时字符串 strList:[], //中缀表达式存储(队列先进先出) strListP:[], //后缀表达式(队列先进先出) list:[], //存放运算符的堆栈 (先进后出) count:[], //计算表达式堆栈(先进后出) flag:0 //表示字符串最后一位是否是运算符号位 }
wxml代码
<!--pages/calc-v2/calc-v2.wxml--> <view class="calc"> <view class="content"> <text>{{express}}</text> <text>{{result}}</text> </view> <view class="btn"> <view class="btn-view"> <text bindtap="click" data-con="c" class="btn-view-text-color">c</text> <text bindtap="click" data-con="÷" class="btn-view-text-color">÷</text> <text bindtap="click" data-con="×" class="btn-view-text-color">×</text> <text bindtap="click" data-con="←" class="btn-view-text-color">←</text> </view> <view class="btn-view"> <text bindtap="click" data-con="7">7</text> <text bindtap="click" data-con="8">8</text> <text bindtap="click" data-con="9">9</text> <text bindtap="click" data-con="-" class="btn-view-text-color">-</text> </view> <view class="btn-view"> <text bindtap="click" data-con="4">4</text> <text bindtap="click" data-con="5">5</text> <text bindtap="click" data-con="6">6</text> <text bindtap="click" data-con="+" class="btn-view-text-color">+</text> </view> <view class="bottom"> <view class="left"> <view class="btn-view-123"> <text bindtap="click" data-con="1">1</text> <text bindtap="click" data-con="2">2</text> <text bindtap="click" data-con="3">3</text> </view> <view class="btn-view-123"> <text bindtap="click" data-con="%">%</text> <text bindtap="click" data-con="0">0</text> <text bindtap="click" data-con=".">.</text> </view> </view> <view class="right"> <view bindtap="result" data-con="=" class="denghao"> <text>=</text> </view> </view> </view> </view> </view>
wxss代码
/* pages/calc-v2/calc-v2.wxss */ .calc{margin: 20rpx;} .content{ border: 1px #b4b5b6 solid; width: 710rpx;height: 150rpx; text-align: right; display: flex; flex-direction: column; /*元素的排列方向为垂直*/ font-size: 20px; font-weight:400;} .content text { height: 70rpx; margin-top: 10rpx; margin-right: 20rpx;} .btn { text-align: center; width: 100%; height: 874rpx; font-weight:400;font-size: 20px;} .btn-view{ display: flex; justify-content:space-between; height: 175rpx; } .btn-view-123{ height: 175rpx; text-align: left; display: flex; } .btn-view-123 text{ width: 175rpx; height: 175rpx; border: 1px #b4b5b6 solid; border-radius: 5rpx; line-height: 175rpx; text-align: center; } .btn-view text{ width: 175rpx; height: 175rpx; border: 1px #b4b5b6 solid; border-radius: 5rpx; line-height: 175rpx; text-align: center; } .btn-view-text-color{ color: #169fe6; } .bottom{ display: flex; } .denghao{ border: 1px #b4b5b6 solid; text-align: center; line-height: 350rpx; height: 350rpx; width: 175rpx; background: #169fe6; } .denghao text{ color: white; height: 350rpx; width: 175rpx; line-height: 175rpx; }
js代码
// pages/calc/calc.js const app = getApp() Page({ /** * 页面的初始数据 */ data: { express: '', //第一行的表达式 result: '' //第二行的结果 }, /** * 用户点击右上角分享 */ onShareAppMessage: function() { }, //给所有text或view绑定此事件,同时增加对应的自定义属性值 click(e) { //console.log(e.target.dataset.con) let input = e.target.dataset.con //获取每次输入的内容 if (input == "c") { this.handleClear(); } else if (input == "←") { this.handleDelete(); } else { //调用处理字符串 this.handleInfo(input); } }, //处理本地用户的输入操作 handleInfo(input) { if (app.calc2.str.length == 0) { //第一次点击 if (input == "-" || this.checkShuZi(input)) { //为减号 this.addStr(input); } else { return; } } else { if (app.calc2.flag == 1) { //说明最后一位是符号 if (this.checkFuHao(input)) { app.calc2.str = app.calc2.str.substring(0, app.calc2.str.length - 1); //去掉最后一位符号,添加最新的符号进去 this.addStr(input); } else { this.addStr(input); } } else { this.addStr(input); } } }, //客户点击等号了 result() { //每次点击等号重新把列表给空 app.calc2.strList.length = 0; app.calc2.strListP.length = 0; app.calc2.list.length = 0; app.calc2.count.length = 0; //将表达式变成中缀表达式队列 this.expressToStrList(this.data.express); console.log(app.calc2.strList); //将中缀表达式集合赋值给临时变量 let tempList = app.calc2.strList; this.expressToStrListP(tempList); console.log(app.calc2.strListP); //最终的计算 let tempP = app.calc2.strListP for (let m in tempP){ if (this.checkFuHao2(tempP[m])) {//不含点号的符号方法判断 let m1 = app.calc2.count[0]; //取出第一个数据 app.calc2.count.shift(); //取出后删除该数据 let m2 = app.calc2.count[0]; app.calc2.count.shift(); // console.log('m1是' +m1); // console.log('m2是' + m2); // console.log('运算符是' + tempP[m]); // console.log('计算结果是:' + this.countDetail(m2, tempP[m], m1)); app.calc2.count.unshift(this.countDetail(m2, tempP[m], m1)); //将计算结果放到count中 }else{ app.calc2.count.unshift(tempP[m]) //将数字压进去 } } console.log('最终的计算结果是' + app.calc2.count[0]); this.setData({ result: app.calc2.count[0] }); }, //实际具体计算 countDetail(e1, e2, e3) { let result = 0.0; console.log(e2); try { if (e2 == "×") { result = parseFloat(e1) * parseFloat(e3); } else if (e2 == "÷") { result = parseFloat(e1) / parseFloat(e3); } else if (e2 == "%") { result = parseFloat(e1) % parseFloat(e3); } else if (e2 == "+") { result = parseFloat(e1) + parseFloat(e3); } else { result = parseFloat(e1) - parseFloat(e3); } } catch (error) { } return result; }, //将中缀表达式集合转变为后缀表达式集合 expressToStrListP(tempList){ for (let item in tempList) { if (this.checkFuHao2(tempList[item])) { //不含点号的符号方法判断 if (app.calc2.list.length == 0) { app.calc2.list.unshift(tempList[item]); //直接添加添加运算符 } else { if (this.checkFuHaoDX(app.calc2.list[0], tempList[item])) { for (let x in app.calc2.list) { app.calc2.strListP.push(app.calc2.list[x]); //将运算符都放到listP中 } app.calc2.list.length = 0; //循环完把list置空 app.calc2.list.unshift(tempList[item]);//加新元素进去 } else { app.calc2.list.unshift(tempList[item]); //直接添加添加运算符 } } } else { app.calc2.strListP.push(tempList[item]); //数字直接加到后缀集合中 } } //循环完有可能最后一个是数字了,取到的不是字符,那么运算符里剩余的还的加到listP中 if (app.calc2.list.length > 0) { for (let x in app.calc2.list) { app.calc2.strListP.push(app.calc2.list[x]); //将运算符都放到listP中 } app.calc2.list.length = 0; //循环完把list置空 } }, //判断两个运算符的优先级(m1是list集合中最后加进去的那个元素比较将要进来的元素,如果m1比m2大,弹出list集合到listp中,再把m2加到list中,否则直接将m2加入list) checkFuHaoDX(m1, m2) { if ((m1 == "%" || m1 == "×" || m1 == "÷") && (m2 == "-" || m2 == "+")) { return true; } else { return false; } }, //将字符串表达式变成中缀队列 expressToStrList(express) { let temp = ''; //定义临时变量 //将表达式改为中缀队列 for (let i = 0; i < express.length; i++) { if (i == 0 && express[i] == "-") { temp = temp + express[i]; } else { if (this.checkShuZi2(express[i])) { //如果i是数字 temp = temp + express[i]; } else { if (temp.length > 0) { if (express[i] == ".") { temp = temp + express[i]; } else { app.calc2.strList.push(parseFloat(temp)); temp = ''; app.calc2.strList.push(express[i]); } } else { temp = temp + express[i]; } } } } //循环到最后再看temp中有没有数字了,如果有加进来 if (temp.length > 0 && this.checkShuZi(temp.substring(temp.length - 1))) { app.calc2.strList.push(parseFloat(temp)); temp = ''; } }, //处理客户输入清除键 handleClear() { app.calc2.str = ''; app.calc2.strList.length = 0; app.calc2.strListP.length = 0; app.calc2.list.length = 0; app.calc2.count.length = 0; app.calc2.minusFlag = 0; this.setData({ express: '', result: '' }); }, //处理客户输入回退键 handleDelete() { let str = app.calc2.str; if (str.length > 0) { str = str.substring(0, str.length - 1); app.calc2.str = str; this.setData({ express: str, }); } else { return; } }, //判断是否是运算符(含点号,用在组织表达式时 .不能重复输入) checkFuHao(input) { if (input == "-" || input == "+" || input == "÷" || input == "%" || input == "×" || input == ".") { return true; } else { return false; } }, //判断是否是运算符(不含点号) checkFuHao2(input) { if (input == "-" || input == "+" || input == "÷" || input == "%" || input == "×") { return true; } else { return false; } }, //判断是否是数字 checkShuZi(input) { if (input == "0" || input == "1" || input == "2" || input == "3" || input == "4" || input == "5" || input == "6" || input == "7" || input == "8" || input == "9") { return true; } else { return false; } }, //判断是否是数字(包含.号,用在表达式转中缀方法中) checkShuZi2(input) { if (input == "0" || input == "1" || input == "2" || input == "3" || input == "4" || input == "5" || input == "6" || input == "7" || input == "8" || input == "9" || input == ".") { return true; } else { return false; } }, //给字符串添加新字符(直接追加 在判断是否要改变变量flag) addStr(input) { app.calc2.str = app.calc2.str + input; if (this.checkFuHao(input)) { app.calc2.flag = 1; //设置标记位位1 } else { app.calc2.flag = 0; } this.setData({ express: app.calc2.str }); } })