君子生非异也,善假于物也!—— HarmonyOS 计算器实现 ...

打印 上一主题 下一主题

主题 805|帖子 805|积分 2415

        前言

        风流要是贤公子,白晰仍为美少年。隐墨同学恋爱了,她爱上了隔壁班的少年,恰同学少年,风华正茂,指点山河,挥斥方遒。隐墨同学彻底的坠入了爱河,一发不可收拾,每天空想着和少年檫出不一样的火花,来一场特殊的经历。让两个人的生存交轨,可谓衣带渐宽终不悔,为伊消得人干瘪啊!!
        正巧,少年家电脑上的计算器不能正常利用了,你眼睛一亮,本相只有一个,那就是机会,你快速在书架上找到《HarmonyOS》大喊:如意如意,按我心意

        一: 需要办理的问题


        1. 小数计算的精度丢失

        有计算机根本基础的都清楚:由于数字都是双精度浮点数,在计算机中是二进制存储数据的,因此小数和非安全整数(凌驾整数的安全范围[-Math.pow(2, 53),Math.pow(2, 53)]的数据)在计算过程中会存在精度丢失的情况。
        小数运算时:“0.2 + 2.22 = 2.4200000000000004”
   

  ​
  
        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组件
   

  ​
                  代码如下
  1. @Styles function KeyBoxStyleOther(){
  2.   .width(60)
  3.   .backgroundColor('#ffe0dddd')
  4.   .height(60)
  5.   .borderRadius(8)
  6. }
  7. @Styles function KeyBoxStyle(){
  8.   .width(60)
  9.   .backgroundColor(Color.White)
  10.   .height(60)
  11.   .borderRadius(8)
  12. }
  13. const nums:string[] = ['7', '8', '9', '4', '5', '6', '1', '2', '3', '0', '.']
  14. @Entry
  15. @Component
  16. struct Index {
  17.   columnsTemplate: string = '1fr 1fr 1fr 1fr'
  18.   @State rowsTemp: string = '1fr 1fr 1fr 1fr 1fr'
  19.   @State rows: number = 5
  20.   @State expression: string = ''
  21.   @State result: string = '0'
  22.   @State @Watch('aboutToAppear') isShowMore: boolean = false //用于判断是否要更多的的展示
  23.   @State expressionFontSize: number = 40
  24.   @State expressionColor: string = '#000000'
  25.   @State resultFontSize: number = 30
  26.   @State resultColor: string = '#000000'
  27.   aboutToAppear(): void {
  28.     this.JudgeIsMore(this.isShowMore)
  29.   }
  30.   JudgeIsMore(judge: boolean) {
  31.     if (judge == true) {
  32.       this.rowsTemp = '1fr 1fr 1fr 1fr 1fr 1fr'
  33.       this.rows = 6
  34.     } else {
  35.       this.rowsTemp = '1fr 1fr 1fr 1fr 1fr'
  36.       this.rows = 5
  37.     }
  38.   }
  39.   build() {
  40.     Column() {
  41.       Row() {
  42.         Image($r('app.media.three_dot'))
  43.           .width(50)
  44.           .height(50)
  45.           .margin(20)
  46.           .onClick(() => {
  47.             this.isShowMore = !this.isShowMore
  48.           })
  49.       }
  50.       .width('100%')
  51.       .justifyContent(FlexAlign.End)
  52.       .alignItems(VerticalAlign.Top) //位于顶端
  53.       Column() {
  54.         Row() {
  55.           TextInput({ text: this.result })
  56.             .fontSize(lengthSize(this.result))
  57.             .fontColor(this.resultColor)
  58.             .fontWeight(700)
  59.             .textAlign(TextAlign.End)
  60.             .backgroundColor(Color.White)
  61.             .padding({ bottom: 20 })
  62.             .animation({ duration: 500 })
  63.         }
  64.         .padding({ right: 15 })
  65.         .justifyContent(FlexAlign.End)
  66.         .width('100%')
  67.         Divider().width('90%').color(Color.Black).height(20)
  68.         Row() {
  69.           TextInput({ text: this.expression })
  70.             .textAlign(TextAlign.End)
  71.             .backgroundColor(Color.White)
  72.             .fontSize(lengthSize(this.expression))
  73.             .fontColor(this.expressionColor)
  74.             .animation({ duration: 500 })
  75.         }
  76.         .padding({ top: 5, bottom: 5 })
  77.         .justifyContent(FlexAlign.End)
  78.         .width('100%')
  79.         .height(70)
  80.         Grid() {
  81.           //是否展开,根据按钮的不同有不同的情况
  82.           if (this.isShowMore) {
  83.             GridItem() {
  84.               Image($r('app.media.rightbucket'))
  85.                 .width(30)
  86.             }.KeyBoxStyleOther()
  87.             .onClick(() => {
  88.               this.expression += '('
  89.             })
  90.             GridItem() {
  91.               Image($r('app.media.leftbucket'))
  92.                 .width(30)
  93.             }.KeyBoxStyleOther()
  94.             .onClick(() => {
  95.               this.expression += ')'
  96.             })
  97.             GridItem() {
  98.               Image($r('app.media.percent'))
  99.                 .width(30)
  100.             }.KeyBoxStyleOther()
  101.             .onClick(() => {
  102.               this.expression += '!'
  103.             })
  104.             GridItem() {
  105.               Image($r('app.media.mi'))
  106.                 .width(30)
  107.             }.KeyBoxStyleOther()
  108.             .onClick(() => {
  109.               this.expression += '^'
  110.             })
  111.           }
  112.           //原有键的布局
  113.           GridItem() {
  114.             Image($r('app.media.C'))
  115.               .width(30)
  116.           }.KeyBoxStyleOther()
  117.           .onClick(() => { //对于数据的全部清除
  118.             this.expression = ''
  119.             this.result = '0'
  120.           })
  121.           GridItem() {
  122.             Image($r('app.media.gf_obelus'))
  123.               .width(30)
  124.           }.KeyBoxStyleOther()
  125.           .onClick(() => {
  126.             this.expression=SymbolRecep(this.expression,'/')
  127.           })
  128.           GridItem() {
  129.             Image($r('app.media.mul'))
  130.               .width(30)
  131.           }.KeyBoxStyleOther()
  132.           .onClick(() => {
  133.             this.expressionFontSize
  134.             this.expression=SymbolRecep(this.expression,'*')
  135.           })
  136.           GridItem() {
  137.             Image($r('app.media.delete_left'))
  138.               .width(40)
  139.           }.KeyBoxStyleOther()
  140.           .onClick(() => { //删除最后一位数据
  141.             this.expression = this.expression.slice(0, this.expression.length - 1)
  142.             this.result =''
  143.           })
  144.           GridItem() {
  145.             Image($r('app.media.jiecheng'))
  146.               .width(30)
  147.           }
  148.           .onClick(() => {
  149.             this.expression=SymbolRecep(this.expression,'%')
  150.           })
  151.           .KeyBoxStyleOther()
  152.           .rowStart(this.rows - 1)
  153.           .rowEnd(this.rows - 1)
  154.           .columnStart(0)
  155.           .columnEnd(0)
  156.           GridItem() {
  157.             Image($r('app.media.minus'))
  158.               .width(30)
  159.           }
  160.           .onClick(() => {
  161.             this.expression=SymbolRecep(this.expression,'-')
  162.           })
  163.           .KeyBoxStyleOther()
  164.           .rowStart(this.rows - 4)
  165.           .rowEnd(this.rows - 4)
  166.           .columnStart(3)
  167.           .columnEnd(3)
  168.           GridItem() {
  169.             Image($r('app.media.plus'))
  170.               .width(30)
  171.           }.KeyBoxStyleOther()
  172.           .onClick(() => {
  173.             this.expression=SymbolRecep(this.expression,'+')
  174.           })
  175.           .rowStart(this.rows - 3)
  176.           .columnStart(3)
  177.           GridItem() {
  178.             Text('=')
  179.               .fontColor(Color.White)
  180.               .fontSize(30)
  181.               .fontWeight(FontWeight.Bold)
  182.           }
  183.           .onClick(() => {
  184.             this.expressionFontSize=lengthSize(this.expression)
  185.             this.resultFontSize=lengthSize(this.result)
  186.             this.result=total(this.expression).toString()
  187.             this.expression=''
  188.           })
  189.           .KeyBoxStyleOther()
  190.           .rowStart(this.rows - 2)
  191.           .rowEnd(this.rows - 1)
  192.           .columnStart(3)
  193.           .height(130)
  194.           .columnEnd(3)
  195.           .backgroundColor('#ffff6b14')
  196.           //循环加载数字键盘
  197.           ForEach(nums, (item: string) => {
  198.             GridItem() {
  199.               Text(item)
  200.                 .fontSize(20)
  201.                 .fontWeight(700)
  202.             }
  203.             .onClick(() => {
  204.               if(item=='.')
  205.                 this.expression=SymbolRecep(this.expression,'.')
  206.               else
  207.                 this.expression+=item
  208.             })
  209.             .KeyBoxStyle()
  210.           })
  211.         }
  212.         .rowsTemplate(this.rowsTemp)
  213.         .columnsTemplate(this.columnsTemplate)
  214.         .rowsGap(20)
  215.         .columnsGap(8)
  216.         .width('100%')
  217.         .height(this.isShowMore == false ? '55%' : '65%')
  218.         .animation({ duration: 300, curve: Curve.FastOutSlowIn, delay: 5 })
  219.         .backgroundColor("#ffeeebed")
  220.         .padding({
  221.           bottom: 20,
  222.           top: 20,
  223.           left: 8,
  224.           right: 8
  225.         })
  226.       }
  227.       .layoutWeight(1)
  228.       .justifyContent(FlexAlign.End)
  229.     }
  230.   }
  231. }
复制代码
    举行了相应的扩展


        2. 表达式转化和异常处置惩罚

        对于数据的处置惩罚代码如下
  1. function total(expression: string):number {
  2.   //检查是否含有这些符号
  3.   if('+-x/%^'.includes((expression[expression.length-1])))
  4.     return NaN
  5.   //expression 为 .3 时,正则表达式中的 \d+ 期望至少有一个数字开头,因此 .3 会被分为两个部分:"." 和 "3"。
  6.   //然而,由于正则表达式没有独立的模式来捕捉孤立的点,它仅匹配了数字 "3",因此 tokens 数组只包含 "3"。
  7.   // 使用正则划分出 整数或小数和符号的数组,修改正则表达式以支持负数
  8.   let tokens: string[] = expression.match(/(\d+(\.\d+)?|\+|-|\*|\/|\%|\^|\(|\)|!)/g) || [];
  9.   //用于检查输入的符号
  10.   for(let i=0;i<tokens.length;i++) {
  11.     console.log(tokens[i].toString())
  12.   }
  13.   let output: string[] = []
  14.   let operations: string[] = []
  15.   // 增加前一个token,用来判断!前是否有数字,以及判断-是否为负号
  16.   let prevToken:string = 'NaN';
  17.   for (let token of tokens) {
  18.       // 是数字直接入栈(包括负数)
  19.       if (!isNaN(Number(token))) {
  20.         output.push(token)
  21.       } else if (token == '(') {
  22.         operations.push(token)
  23.       } else if (token == '!') {
  24.         if (isNaN(Number(prevToken)))  //判断前面有没有数字
  25.           return NaN
  26.         else
  27.           operations.push(token)
  28.       } else if (token == '-' && (prevToken == 'NaN' || prevToken == '(' || '+-*/%^'.includes(prevToken))) {
  29.         // 处理负号的情况,用乘号进行处理
  30.         output.push('-1')
  31.         operations.push('*')
  32.       } else {
  33.         //栈顶优先级大于当前优先级,符号栈顶出栈
  34.         while (operations.length > 0 && getPrecedence(operations[operations.length - 1]) >= getPrecedence(token)) {
  35.           let op = operations.pop()
  36.           if (op === '(')
  37.             break
  38.           if (op) {
  39.             output.push(op)
  40.           }
  41.         }
  42.         if (token != ')')
  43.           operations.push(token)
  44.       }
  45.       prevToken = token;
  46.     }
  47.     while (operations.length > 0) {
  48.       const op = operations.pop()
  49.       if (op !== undefined&&op!='(')
  50.         output.push(op)
  51.     }
  52.   console.log('以下为后缀表达式中的表示')
  53.   for(let i=0;i<output.length;i++) {
  54.     console.log(output[i])
  55.   }
  56.     //定义一个数组栈,用来计算最终结果
  57.     let nums: number[] = []
  58.     for (let value of output) {
  59.       if (!isNaN(Number(value))) {
  60.         nums.push(Number(value))
  61.       } else if (value == '!') {
  62.         let num1 = nums.pop()
  63.         if (num1 !== undefined)
  64.           nums.push(factorial(num1))
  65.       } else {
  66.         //左侧为空就是右侧,要不然是左侧
  67.         let num1 = nums.pop() ?? 0
  68.         let num2 = nums.pop() ?? 0
  69.         switch (value) {
  70.           case '+':
  71.             nums.push(num2 + num1)
  72.             break
  73.           case '-':
  74.             if(num2==0) {
  75.               nums.push(num1)
  76.             }
  77.             else{
  78.               nums.push(num2 - num1)
  79.             }
  80.             break
  81.           case '/':
  82.             if (num1 !== 0)
  83.               if(num2==0) {
  84.                 nums.push(num1)
  85.               }
  86.               else{
  87.                 nums.push(num2 / num1)
  88.               }
  89.             else
  90.               return NaN  // 除数为零
  91.             break
  92.           case '*':
  93.             num1*=100000000000
  94.             num2*=100000000000
  95.             if(num2==0) {
  96.               nums.push(num1/100000000000)
  97.             }
  98.             else{
  99.               nums.push(num2/100000000000 * num1 /100000000000)
  100.             }
  101.             break
  102.           case '%':
  103.             if(num2==0) {
  104.               nums.push(num1)
  105.             }
  106.             else{
  107.               nums.push(num2 % num1)
  108.             }
  109.             break
  110.           case '^':
  111.             nums.push(Math.pow(num2, num1))
  112.             break
  113.         }
  114.       }
  115.     }
  116.     return nums[0] ?? NaN
  117. }
  118. //计算出等级
  119. function getPrecedence (op:string):number {
  120.   switch(op){
  121.     case '!':
  122.       return 6
  123.     case '^':
  124.       return 4
  125.     case '%':
  126.       return 3
  127.     case '*':
  128.     case '/':
  129.       return 2
  130.     case '+':
  131.     case '-':
  132.       return 1
  133.     case '(':
  134.       case ')':
  135.       return 0
  136.     default :
  137.       return -1
  138.   }
  139. }
  140. //计算阶乘
  141. function factorial(num: number): number {
  142.   let result = 1;
  143.   for (let i = 2; i <= num; i++) {
  144.     result *= i;
  145.   }
  146.   return result;
  147. }
复制代码
        现在让我们开始分析哈:
                2.1 正则表达式

  1. let tokens: string[] = expression.match(/(\d+(\.\d+)?|\+|-|\*|\/|\%|\^|\(|\)|!)/g) || [];
复制代码
        这是一串正则表达式,能起到辨认和分离每一个字符的作用。
        正则表达式的界说和作用:正则表达式是一种文本模式,它使用特定的字符序列来形貌、匹配和处置惩罚字符串中的字符组合。正则表达式广泛应用于编程语言和文本处置惩罚工具中,用于执行诸如搜刮、更换、验证和提取等操纵。通过正则表达式,可以快速地对大量文本数据举行复杂的模式匹配和操纵。
        小提醒:假如看不懂的话可以可以通过设置日志的方式找BUG!!!!!!
        举例对于输入 2*(-3*2)-6,以下为token数组中所存放的数据
 

                2.2 后缀表达式转换

        我上述所讲比较的粗糙,具体可以参考  后缀表达式转换
        压入栈的时候同时处置惩罚多括号的问题
  1. while (operations.length > 0) {
  2.       const op = operations.pop()
  3.       if (op !== undefined&&op!='(')
  4.         output.push(op)
  5.     }
复制代码
        通过日志可以看出被转化后的表达式

        计算机就可以成功的举行辨认和处置惩罚了表达式了

                2.3 精度丢失 

        小数运算时:“0.2 + 2.22 = 2.4200000000000004”,当前示例的办理方法是将小数扩展到整数举行计算,计算完成之后再将效果缩小,计算过程为“(0.2 * 100 + 2.22 * 100) / 100 = 2.42”。
  1. case '*':
  2.             num1*=100000000000
  3.             num2*=100000000000
  4.             if(num2==0) {
  5.               nums.push(num1/100000000000)
  6.             }
  7.             else{
  8.               nums.push(num2/100000000000 * num1 /100000000000)
  9.             }
  10.             break
复制代码
              通过测试发现成功的办理  

        
        2.4 结构美化和限制问题

  1. //保证运算符只出现一次
  2. function SymbolRecep(expression: string,chara:string): string {
  3.   if (expression[expression.length-1] == '+' || expression[expression.length-1] == '-' ||
  4.     expression[expression.length-1] == 'x' || expression[expression.length-1] == '/' ||
  5.     expression[expression.length-1] == '%' || expression[expression.length-1] == '.') {
  6.     return expression.slice(0, expression.length - 1) + chara
  7.   } else
  8.     return expression + chara
  9. }
复制代码
        假如输入的数字过长,就可以改变字体的巨细,更加得当
  1. function lengthSize(expression: string): number {
  2.   if(expression.length<12)
  3.     return 40
  4.   else if(expression.length<20)
  5.     return 25
  6.   else
  7.     return 20
  8. }
复制代码
         效果图如下



        后言 

        隐墨累了。她的计算器成功写好了,但是终究入不了少年的眼!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

罪恶克星

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表