罪恶克星 发表于 2024-11-29 07:30:37

君子生非异也,善假于物也!—— 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]
查看完整版本: 君子生非异也,善假于物也!—— HarmonyOS 计算器实现