君子生非异也,善假于物也!—— HarmonyOS 计算器实现
前言风流要是贤公子,白晰仍为美少年。隐墨同学恋爱了,她爱上了隔壁班的少年,恰同学少年,风华正茂,指点山河,挥斥方遒。隐墨同学彻底的坠入了爱河,一发不可收拾,每天空想着和少年檫出不一样的火花,来一场特殊的经历。让两个人的生存交轨,可谓衣带渐宽终不悔,为伊消得人干瘪啊!!
正巧,少年家电脑上的计算器不能正常利用了,你眼睛一亮,本相只有一个,那就是机会,你快速在书架上找到《HarmonyOS》大喊:如意如意,按我心意
一: 需要办理的问题
1. 小数计算的精度丢失
有计算机根本基础的都清楚:由于数字都是双精度浮点数,在计算机中是二进制存储数据的,因此小数和非安全整数(凌驾整数的安全范围[-Math.pow(2, 53),Math.pow(2, 53)]的数据)在计算过程中会存在精度丢失的情况。
小数运算时:“0.2 + 2.22 = 2.4200000000000004”
https://i-blog.csdnimg.cn/direct/f1cec88732fe438da88b12a558420452.png
2. 中缀表达式转为后缀表达式
隐墨同学提问道,作甚要变革呢,为什么要变,怎样变!
逆波兰表达式又叫做后缀表达式。逆波兰表示法是波兰逻辑学家J・卢卡西维兹于1929年首先提出的一种表达式的表示方法 。厥后,人们就把用这种表示法写出的表达式称作“逆波兰表达式”。逆波兰表达式把运算量写在前面,把算符写在后面。
中缀表达式:就是我们平时用的表达式,好比 1+((2+3)*4)-5这种表达式
“1+((2+3)*4)-5”转换为后缀表达式是”1 2 3 + 4 * + 5 -”,可以发现后缀表达式里没有括号
明显的可以察觉到没有括号,由于计算机并不会去先计算括号里面的,大概根据优先级的去计算你给定的字符串(你看似输入的是表达式,但是计算机是按字符串举行处置惩罚的)
规则:
1. 遇到数字就直接输出到后缀表达式中,遇到操纵符就判定其优先级,并将其压入栈中。
2. 假如栈顶元素的优先级大于即是当前操纵符,则先将栈顶元素弹出并输出到后缀表达式中,再将当前操纵符压入栈中。
3. 假如遇到了左括号,则直接将其压入栈中,假如遇到了右括号,则弹出栈中的元素,直到遇到了左括号为止,并将这些元素输出到后缀表达式中。
4. 将栈中剩余的元素依次弹出,并输出到后缀表达式中。
3. 对于异常信息的处置惩罚
仅限于隐墨同学所发现的,有其他情况可以增补呦!!
1. 参考本技艺机上的计算器,机会发现,当表达式的最后一位为符号位时,你在点击例外一个符号他会自动的举行代替。
2. 对于输入 .3 的效果为 3 大概 ((2)+2 = 4
二:团体的思绪而且代码的实现
1. UI结构(Grid)
不难发现,整个计算器的结构就为网格结构(Grid)
Grid组件为网格容器,其中容器内各条目对应一个GridItem组件
https://i-blog.csdnimg.cn/direct/04f69f1280cb47569f61e0bf4d46bfc3.png
代码如下
@Styles function KeyBoxStyleOther(){
.width(60)
.backgroundColor('#ffe0dddd')
.height(60)
.borderRadius(8)
}
@Styles function KeyBoxStyle(){
.width(60)
.backgroundColor(Color.White)
.height(60)
.borderRadius(8)
}
const nums:string[] = ['7', '8', '9', '4', '5', '6', '1', '2', '3', '0', '.']
@Entry
@Component
struct Index {
columnsTemplate: string = '1fr 1fr 1fr 1fr'
@State rowsTemp: string = '1fr 1fr 1fr 1fr 1fr'
@State rows: number = 5
@State expression: string = ''
@State result: string = '0'
@State @Watch('aboutToAppear') isShowMore: boolean = false //用于判断是否要更多的的展示
@State expressionFontSize: number = 40
@State expressionColor: string = '#000000'
@State resultFontSize: number = 30
@State resultColor: string = '#000000'
aboutToAppear(): void {
this.JudgeIsMore(this.isShowMore)
}
JudgeIsMore(judge: boolean) {
if (judge == true) {
this.rowsTemp = '1fr 1fr 1fr 1fr 1fr 1fr'
this.rows = 6
} else {
this.rowsTemp = '1fr 1fr 1fr 1fr 1fr'
this.rows = 5
}
}
build() {
Column() {
Row() {
Image($r('app.media.three_dot'))
.width(50)
.height(50)
.margin(20)
.onClick(() => {
this.isShowMore = !this.isShowMore
})
}
.width('100%')
.justifyContent(FlexAlign.End)
.alignItems(VerticalAlign.Top) //位于顶端
Column() {
Row() {
TextInput({ text: this.result })
.fontSize(lengthSize(this.result))
.fontColor(this.resultColor)
.fontWeight(700)
.textAlign(TextAlign.End)
.backgroundColor(Color.White)
.padding({ bottom: 20 })
.animation({ duration: 500 })
}
.padding({ right: 15 })
.justifyContent(FlexAlign.End)
.width('100%')
Divider().width('90%').color(Color.Black).height(20)
Row() {
TextInput({ text: this.expression })
.textAlign(TextAlign.End)
.backgroundColor(Color.White)
.fontSize(lengthSize(this.expression))
.fontColor(this.expressionColor)
.animation({ duration: 500 })
}
.padding({ top: 5, bottom: 5 })
.justifyContent(FlexAlign.End)
.width('100%')
.height(70)
Grid() {
//是否展开,根据按钮的不同有不同的情况
if (this.isShowMore) {
GridItem() {
Image($r('app.media.rightbucket'))
.width(30)
}.KeyBoxStyleOther()
.onClick(() => {
this.expression += '('
})
GridItem() {
Image($r('app.media.leftbucket'))
.width(30)
}.KeyBoxStyleOther()
.onClick(() => {
this.expression += ')'
})
GridItem() {
Image($r('app.media.percent'))
.width(30)
}.KeyBoxStyleOther()
.onClick(() => {
this.expression += '!'
})
GridItem() {
Image($r('app.media.mi'))
.width(30)
}.KeyBoxStyleOther()
.onClick(() => {
this.expression += '^'
})
}
//原有键的布局
GridItem() {
Image($r('app.media.C'))
.width(30)
}.KeyBoxStyleOther()
.onClick(() => { //对于数据的全部清除
this.expression = ''
this.result = '0'
})
GridItem() {
Image($r('app.media.gf_obelus'))
.width(30)
}.KeyBoxStyleOther()
.onClick(() => {
this.expression=SymbolRecep(this.expression,'/')
})
GridItem() {
Image($r('app.media.mul'))
.width(30)
}.KeyBoxStyleOther()
.onClick(() => {
this.expressionFontSize
this.expression=SymbolRecep(this.expression,'*')
})
GridItem() {
Image($r('app.media.delete_left'))
.width(40)
}.KeyBoxStyleOther()
.onClick(() => { //删除最后一位数据
this.expression = this.expression.slice(0, this.expression.length - 1)
this.result =''
})
GridItem() {
Image($r('app.media.jiecheng'))
.width(30)
}
.onClick(() => {
this.expression=SymbolRecep(this.expression,'%')
})
.KeyBoxStyleOther()
.rowStart(this.rows - 1)
.rowEnd(this.rows - 1)
.columnStart(0)
.columnEnd(0)
GridItem() {
Image($r('app.media.minus'))
.width(30)
}
.onClick(() => {
this.expression=SymbolRecep(this.expression,'-')
})
.KeyBoxStyleOther()
.rowStart(this.rows - 4)
.rowEnd(this.rows - 4)
.columnStart(3)
.columnEnd(3)
GridItem() {
Image($r('app.media.plus'))
.width(30)
}.KeyBoxStyleOther()
.onClick(() => {
this.expression=SymbolRecep(this.expression,'+')
})
.rowStart(this.rows - 3)
.columnStart(3)
GridItem() {
Text('=')
.fontColor(Color.White)
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
.onClick(() => {
this.expressionFontSize=lengthSize(this.expression)
this.resultFontSize=lengthSize(this.result)
this.result=total(this.expression).toString()
this.expression=''
})
.KeyBoxStyleOther()
.rowStart(this.rows - 2)
.rowEnd(this.rows - 1)
.columnStart(3)
.height(130)
.columnEnd(3)
.backgroundColor('#ffff6b14')
//循环加载数字键盘
ForEach(nums, (item: string) => {
GridItem() {
Text(item)
.fontSize(20)
.fontWeight(700)
}
.onClick(() => {
if(item=='.')
this.expression=SymbolRecep(this.expression,'.')
else
this.expression+=item
})
.KeyBoxStyle()
})
}
.rowsTemplate(this.rowsTemp)
.columnsTemplate(this.columnsTemplate)
.rowsGap(20)
.columnsGap(8)
.width('100%')
.height(this.isShowMore == false ? '55%' : '65%')
.animation({ duration: 300, curve: Curve.FastOutSlowIn, delay: 5 })
.backgroundColor("#ffeeebed")
.padding({
bottom: 20,
top: 20,
left: 8,
right: 8
})
}
.layoutWeight(1)
.justifyContent(FlexAlign.End)
}
}
} 举行了相应的扩展
https://i-blog.csdnimg.cn/direct/f9dd1ea5b11a446e8d6026a172889967.pnghttps://i-blog.csdnimg.cn/direct/a490d223f01049a385cb298539e24f5c.png
2. 表达式转化和异常处置惩罚
对于数据的处置惩罚代码如下
function total(expression: string):number {
//检查是否含有这些符号
if('+-x/%^'.includes((expression)))
return NaN
//expression 为 .3 时,正则表达式中的 \d+ 期望至少有一个数字开头,因此 .3 会被分为两个部分:"." 和 "3"。
//然而,由于正则表达式没有独立的模式来捕捉孤立的点,它仅匹配了数字 "3",因此 tokens 数组只包含 "3"。
// 使用正则划分出 整数或小数和符号的数组,修改正则表达式以支持负数
let tokens: string[] = expression.match(/(\d+(\.\d+)?|\+|-|\*|\/|\%|\^|\(|\)|!)/g) || [];
//用于检查输入的符号
for(let i=0;i<tokens.length;i++) {
console.log(tokens.toString())
}
let output: string[] = []
let operations: string[] = []
// 增加前一个token,用来判断!前是否有数字,以及判断-是否为负号
let prevToken:string = 'NaN';
for (let token of tokens) {
// 是数字直接入栈(包括负数)
if (!isNaN(Number(token))) {
output.push(token)
} else if (token == '(') {
operations.push(token)
} else if (token == '!') {
if (isNaN(Number(prevToken)))//判断前面有没有数字
return NaN
else
operations.push(token)
} else if (token == '-' && (prevToken == 'NaN' || prevToken == '(' || '+-*/%^'.includes(prevToken))) {
// 处理负号的情况,用乘号进行处理
output.push('-1')
operations.push('*')
} else {
//栈顶优先级大于当前优先级,符号栈顶出栈
while (operations.length > 0 && getPrecedence(operations) >= getPrecedence(token)) {
let op = operations.pop()
if (op === '(')
break
if (op) {
output.push(op)
}
}
if (token != ')')
operations.push(token)
}
prevToken = token;
}
while (operations.length > 0) {
const op = operations.pop()
if (op !== undefined&&op!='(')
output.push(op)
}
console.log('以下为后缀表达式中的表示')
for(let i=0;i<output.length;i++) {
console.log(output)
}
//定义一个数组栈,用来计算最终结果
let nums: number[] = []
for (let value of output) {
if (!isNaN(Number(value))) {
nums.push(Number(value))
} else if (value == '!') {
let num1 = nums.pop()
if (num1 !== undefined)
nums.push(factorial(num1))
} else {
//左侧为空就是右侧,要不然是左侧
let num1 = nums.pop() ?? 0
let num2 = nums.pop() ?? 0
switch (value) {
case '+':
nums.push(num2 + num1)
break
case '-':
if(num2==0) {
nums.push(num1)
}
else{
nums.push(num2 - num1)
}
break
case '/':
if (num1 !== 0)
if(num2==0) {
nums.push(num1)
}
else{
nums.push(num2 / num1)
}
else
return NaN// 除数为零
break
case '*':
num1*=100000000000
num2*=100000000000
if(num2==0) {
nums.push(num1/100000000000)
}
else{
nums.push(num2/100000000000 * num1 /100000000000)
}
break
case '%':
if(num2==0) {
nums.push(num1)
}
else{
nums.push(num2 % num1)
}
break
case '^':
nums.push(Math.pow(num2, num1))
break
}
}
}
return nums ?? NaN
}
//计算出等级
function getPrecedence (op:string):number {
switch(op){
case '!':
return 6
case '^':
return 4
case '%':
return 3
case '*':
case '/':
return 2
case '+':
case '-':
return 1
case '(':
case ')':
return 0
default :
return -1
}
}
//计算阶乘
function factorial(num: number): number {
let result = 1;
for (let i = 2; i <= num; i++) {
result *= i;
}
return result;
} 现在让我们开始分析哈:
2.1 正则表达式
let tokens: string[] = expression.match(/(\d+(\.\d+)?|\+|-|\*|\/|\%|\^|\(|\)|!)/g) || []; 这是一串正则表达式,能起到辨认和分离每一个字符的作用。
正则表达式的界说和作用:正则表达式是一种文本模式,它使用特定的字符序列来形貌、匹配和处置惩罚字符串中的字符组合。正则表达式广泛应用于编程语言和文本处置惩罚工具中,用于执行诸如搜刮、更换、验证和提取等操纵。通过正则表达式,可以快速地对大量文本数据举行复杂的模式匹配和操纵。
小提醒:假如看不懂的话可以可以通过设置日志的方式找BUG!!!!!!
举例对于输入 2*(-3*2)-6,以下为token数组中所存放的数据
https://i-blog.csdnimg.cn/direct/09f203779c2c419198bf247c4ee513a3.png
2.2 后缀表达式转换
我上述所讲比较的粗糙,具体可以参考 后缀表达式转换
压入栈的时候同时处置惩罚多括号的问题
while (operations.length > 0) {
const op = operations.pop()
if (op !== undefined&&op!='(')
output.push(op)
} 通过日志可以看出被转化后的表达式
https://i-blog.csdnimg.cn/direct/80fd774a02f7401eb81d3d7f81fdce5e.png
计算机就可以成功的举行辨认和处置惩罚了表达式了
2.3 精度丢失
小数运算时:“0.2 + 2.22 = 2.4200000000000004”,当前示例的办理方法是将小数扩展到整数举行计算,计算完成之后再将效果缩小,计算过程为“(0.2 * 100 + 2.22 * 100) / 100 = 2.42”。
case '*':
num1*=100000000000
num2*=100000000000
if(num2==0) {
nums.push(num1/100000000000)
}
else{
nums.push(num2/100000000000 * num1 /100000000000)
}
break 通过测试发现成功的办理
https://i-blog.csdnimg.cn/direct/baf36c421f5b42bfbaf91cb7da8f7139.png
2.4 结构美化和限制问题
//保证运算符只出现一次
function SymbolRecep(expression: string,chara:string): string {
if (expression == '+' || expression == '-' ||
expression == 'x' || expression == '/' ||
expression == '%' || expression == '.') {
return expression.slice(0, expression.length - 1) + chara
} else
return expression + chara
}
假如输入的数字过长,就可以改变字体的巨细,更加得当
function lengthSize(expression: string): number {
if(expression.length<12)
return 40
else if(expression.length<20)
return 25
else
return 20
} 效果图如下
https://i-blog.csdnimg.cn/direct/1cb774f5d38443ebbf9a68c9b83cdb6e.png
后言
隐墨累了。她的计算器成功写好了,但是终究入不了少年的眼!!!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]