下面将着重讲解如何实现计算器,使其可以进行小数、负数的加减乘除计算,并显示结果给用户
处理用户输入
用户输入一长串的表达式,里面有数字,有加减乘除符号,有括号,我们应该把用户输入的表达式记录下来,并进行适当的划分,将数字和符号分开来,为后续的计算做准备
首先我们在为每个按钮都设置一个data-value自定义属性,从而在后台知道用户按了哪一个键
<view class="button_area" bindtap='clickButton'>
<view class="row">
<button class="row1-item button_item" data-value="7" hover-stay-time='120'>7</button>
<button class="row1-item button_item" data-value="8" hover-stay-time='120'>8</button>
<button class="row1-item button_item" data-value="9" hover-stay-time='120'>9</button>
<button class="row1-item button_item" data-value="/" hover-stay-time='120'>/</button>
<button class="row1-item button_item" data-value="back" hover-stay-time='120'>←</button>
</view>
<view class="row">
<button class="row1-item button_item" data-value="4" hover-stay-time='120'>4</button>
<button class="row1-item button_item" data-value="5" hover-stay-time='120'>5</button>
<button class="row1-item button_item" data-value="6" hover-stay-time='120'>6</button>
<button class="row1-item button_item" data-value="*" hover-stay-time='120'>*</button>
<button class="row1-item button_item" data-value="." hover-stay-time='120'>.</button>
</view>
<button class="button_item equal" data-value="=" hover-stay-time='120'>=</button>
<view class="row short">
<button class="row1-item button_item" data-value="1" hover-stay-time='120'>1</button>
<button class="row1-item button_item" data-value="2" hover-stay-time='120'>2</button>
<button class="row1-item button_item" data-value="3" hover-stay-time='120'>3</button>
<button class="row1-item button_item" data-value="-" hover-stay-time='120'>-</button>
</view>
<view class="row short">
<button class="row1-item button_item" data-value="0" hover-stay-time='120'>0</button>
<button class="row1-item button_item brace" data-value="(" hover-stay-time='120'>(</button>
<button class="row1-item button_item brace" data-value=")" hover-stay-time='120'>)</button>
<button class="row1-item button_item" data-value="+" hover-stay-time='120'>+</button>
</view>
</view>
在js文件中,我们使用两个数组来保存用户输入的表达式,一个用于显示给用户看,一个用于实际计算,你也可以只使用一个数组,看个人喜好。
data: {
expression:{
ForCustomer:[], //用于显示给用户,存储用户输入的数据
ForCompute:[] //用于进行计算,存储转换后的输入数据
},
value:0, //文本框的显示值
},
下面我们就开始处理用户输入,主要在clickButton事件程序中,主要思路就是把用户每个输入都依次存入到ForCustomer
中,而把数字和符号划分后的输入存入到ForCompute中。在进行表达式划分时,主要遵循以下原则:
- 将表达式中的数字和符号分开,例如表达式是
1*20-3+40
,那么ForCustomer
的值应该为['1','*','2','0','-','3','+','4','0']
,而ForCompute
的值应该为['1','*','20','-','3','+','40']
- 小数点应该放置在数字中,而不是作为单独的符号,例如表达式是
1.1+0.3
,那么ForCompute
的值应该为['1.1','+','0.3']
- 当减号前面是左括号或者没有任何数字和符号时,应将其视为负号,例如表达式是
-3-5+(-6)
,那么ForCompute
的值应该为['-3','-','5','+','(','-6',')']
- 0前面没有数字或符号时,应不许输入,防止出现
01+1
这样的表达式 - 当用户点击回退按钮时,应每次都弹出一个字符
下面就是clickButton的代码,对用户每次输入进行判定,执行相应的操作
clickButton: function(event){
var button_value = event.target.dataset.value,
show_value = '',
result_value = 'none',
expression_show = this.data.expression.ForCustomer,
expression_value = this.data.expression.ForCompute,
var length = expression_value.length,
show_length = expression_show.length,
zero_flag = true;
if (button_value){
switch (button_value) {
case 'back': //用户点击后退删除按钮
if (show_length >= 1){
expression_show.pop();
if (expression_value[length - 1].length > 1 && expression_value[length - 1] != 'ERROR' && expression_value[length - 1] != '0不能做除数'){
expression_value[length - 1] = expression_value[length - 1].substr(0, expression_value [length - 1].length -1);
}else{
expression_value.pop();
}
if (show_length == 1 ){
show_value = '0';
}
}
break;
case '=': //用户点击等于按钮,进行计算
if (length > 2){
result_value = this.calculate(expression_value);
if (result_value == 'zero'){
result_value = '0不能做除数';
}else if (isNaN(result_value)){ //若计算结果非数字,则出现错误
result_value = 'ERROR';
}
}
break;
default:
if (isNaN(button_value)){ //用户点击非数字按钮
if (button_value == '.' && !isNaN(expression_value[length - 1])) { //处理小数点问题
expression_value[length - 1] += button_value;
} else {
expression_value.push(button_value);
}
} else { //用户点击数字按钮
if (expression_value[length - 1] == '.' || ! isNaN(expression_value[length - 1])){ //处理小数点问题
expression_value[length - 1] += button_value;
} else if (expression_value[length - 1] == '-' && (length < 2 || expression_value[length - 2] == '(')) { //处理负号问题
expression_value[length - 1] += button_value;
} else if (button_value == '0' && length == 0){ //0不能作为表达式开头
zero_flag = false;
} else {
expression_value.push(button_value);
}
}
if (zero_flag){
expression_show.push(button_value);
}
break;
}
if (show_value !== '0'){ //将表达式数组转换为字符串
show_value = expression_show.join('');
}
if (result_value !== 'none'){ //当有计算结果时,清空输入的数据,并将计算结果显示
show_value = result_value;
expression_show.length = 0;
expression_value.length = 0;
expression_show.push(result_value);
expression_value.push(result_value);
}
if (show_value){ //显示结果
this.setData({
value: show_value
});
}
}
},
计算表达式
经过上述的输入处理,用户输完表达式后,我们就可以得到相应的表达式数组。例如用户输入-0.1+(3-0.5)*2
,此时ForCustomer
的值应该为['-','0','.','1','+','(','3','-','0','.','5',')','*','2']
,而ForCompute
的值应该为['-0.1','+','(','3','-','0.5',')','*','2']
。我们计算只需要ForCompute
的值。我们主要使用的是逆波兰算法,在此过程利用栈,先将中缀表达式转换为后缀表达式,再计算后缀表达式的值。该算法作为基本的算法,需要掌握,不清楚的自行百度。有一种更简单的方法,就是直接传入表达式字符串,使用eval()
函数计算表达式的值,但这样就太没有意思了。
将处理后的表达式数组传入calculate函数进行计算
calculate: function(expressions){
var transfer_stack = [], //用于中缀表达式转后缀表达式
temp_stack = [], //用于存储中缀表达式
calculate_stack = [], //用于计算中缀表达式
result = 0, //计算的结果
priority_table = { //符号优先级表
'*' : 2,
'/' : 2,
'+' : 1,
'-' : 1,
'(' : 0,
')' : 3
};
/*
将中缀表达式转换为后缀表达式
*/
for (var value in expressions){
if (!isNaN(expressions[value])){ //如果是数字,直接输出
temp_stack.push(expressions[value]);
} else {
if (expressions[value] == ')'){ //如果是右括号,将左括号之后的项目全部出栈
while(transfer_stack.length != 0 && transfer_stack[transfer_stack.length-1] != '('){
temp_stack.push(transfer_stack.pop());
}
transfer_stack.pop();
} else if (expressions[value] != '(' && priority_table[expressions[value]] <= priority_table[transfer_stack[transfer_stack.length-1]]) { //如果符号不是左括号,且符号优先级不大于栈顶项目,则依次出栈,直到项目全部出栈或者栈顶项目优先级小于当前符号
temp_stack.push(transfer_stack.pop());
while (priority_table[expressions[value]] <= priority_table[transfer_stack[transfer_stack.length - 1]]){
temp_stack.push(transfer_stack.pop());
}
transfer_stack.push(expressions[value]);
} else { //如果符号是左括号,或者优先级高于栈顶元素,则进栈
transfer_stack.push(expressions[value]);
}
}
}
while(transfer_stack.length > 0){ //将剩余的项目全部出栈
temp_stack.push(transfer_stack.pop());
}
/*
计算后缀表达式
*/
for (value in temp_stack){
if ( !isNaN(temp_stack[value])){ //如果是数字直接进栈
calculate_stack.push(temp_stack[value]);
}else{ //如果是符号,则将栈顶两元素出栈,计算后,再将结果入栈
var num_1 = Number(calculate_stack.pop()),
num_2 = Number(calculate_stack.pop());
switch (temp_stack[value]){
case '+':
result = num_1 + num_2;
break;
case '-':
result = num_2 - num_1;
break;
case '*':
result = num_2 * num_1;
break;
default:
if (num_1 == 0){
result = 'zero';
}else{
result = num_2 / num_1;
}
break;
}
calculate_stack.push(result);
}
if (isNaN(result)){ //如果出现错误,则退出循环
break;
}
}
result = calculate_stack.pop(); //取出最终计算结果
return result;
},
计算完成后,就返回result结果给clickButton函数,显示给用户,并把将存储表达式两个数组清空,将结果存入后,等待用户后续计算的输入,一次完整的计算过程就已经完成了。