当前位置: 首页 > news >正文

【iOS】计算器的仿写

计算器

文章目录

  • 计算器
    • 前言
    • 简单的四则运算
    • UI界面
    • 事件的逻辑
    • 小结

前言

笔者应组内要求,简单实现了一个可以完成简单四则运算的计算器程序。UI界面则是通过最近学习的Masonry库来实现的,而简单的四则运算内容则是通过栈来实现一个简单的四则运算。

简单的四则运算

笔者这里四则运算的思路是一个中缀表达式转后缀表达式的方式,然后再通过后缀表达式来进行一个计算,然后得到一个结果。这里中缀表达式转后缀表达式的思路主要参考这篇博客《数据结构》:中缀表达式转后缀表达式 + 后缀表达式的计算

这里简单说明一下我们为什么在计算机中要将中缀表达式转换成后缀表达式,中缀表达式的顺序是混乱的(因为有括号和每个符号优先级的问题),而转化成后缀表达式的逻辑就会变得很简单,我们只用按照栈中的顺序来进行一个运算就可以了。

中缀表达式转后缀表达式的核心思想其实就是对于我们的运算符的顺序的控制,如果遇到右括号的话,我们要一直让符号栈一直出栈直到遇到左括号才停止。遇到操作符的话,我们只需要满足下面这个条件就可以了,栈为空或者是我们的当前的操作符的优先级大于栈顶元素的操作符时候,我们的操作符栈就可以停止出栈了,然后给当前读到的操作符入栈。

对于数字我们都是进行一个直接入栈。

这里给出一个C语言版本:

typedef struct Stack {char stk[80];int top;
}Stack;
int EmptyStack(Stack* stk) {if (stk->top == -1) {return 1;} else {return 0;}
}
char getTopStack(Stack* stk) {if (EmptyStack(stk)) {return -1;} else {return stk->stk[stk->top];}
}
int fullStack(Stack* stack) {if (stack->top == 80) {return 1;} else {return 0;}
}
void pushStack(Stack* stack, char a) {if (fullStack(stack)) {return;} else {stack->stk[++stack->top] = a;}
}
char popStack(Stack* stack) {if (EmptyStack(stack)) {return -1;} else {return stack->stk[stack->top--];}
}
int isDigit(char a) {int flag;switch (a) {case '0':flag = 1;break;case '1':flag = 1;break;case '2':flag = 1;break;case '3':flag = 1;break;case '4':flag = 1;break;case '5':flag = 1;break;case '6':flag = 1;break;case '7':flag = 1;break;case '8':flag = 1;break;case '9':flag = 1;break;default:flag = 0;break;}return flag;
}
char** changeStack(Stack* stk, int length, char* s, int* num1) {char** string = (char**)malloc(sizeof(char*) * 30);for (int i = 0; i < 30; i++) {string[i] = (char*)malloc(sizeof(char) * 10);}int num = 0;int tail = 0;for (int i = 0; i < length; i++) {if (s[i] == '(') {pushStack(stk, s[i]);} else if (s[i] == ')') {if (tail > 0) {string[num][tail] = '\0';num++;tail = 0;}while (!EmptyStack(stk) && getTopStack(stk) != '(') {string[num][0] = popStack(stk);string[num][1] = '\0';num++;}popStack(stk);} else if (isDigit(s[i]) || s[i] == '.') {string[num][tail++] = s[i];} else if (s[i] == '+' || s[i] == '-') {if (i == 0 || (i > 0 && !isDigit(s[i - 1]) && s[i - 1] != ')' && s[i] == '-')) {string[num][tail++] = s[i];} else {if (tail > 0) {string[num][tail] = '\0';num++;tail = 0;}while (!EmptyStack(stk) && (getTopStack(stk) == '*' || getTopStack(stk) == '/' || getTopStack(stk) == '+' || getTopStack(stk) == '-')) {string[num][0] = popStack(stk);string[num][1] = '\0';num++;}pushStack(stk, s[i]);}} else if (s[i] == '*' || s[i] == '/') {if (tail > 0) {string[num][tail] = '\0';num++;tail = 0;}while (!EmptyStack(stk) && (getTopStack(stk) == '*' || getTopStack(stk) == '/')) {string[num][0] = popStack(stk);string[num][1] = '\0';num++;}pushStack(stk, s[i]);}}if (tail > 0) {string[num][tail] = '\0';num++;}while (!EmptyStack(stk)) {string[num][0] = popStack(stk);string[num][1] = '\0';num++;}*num1 = num;return string;
}
int isNumber(char* token) {return strlen(token) > 1 || ('0' <= token[0] && token[0] <= '9');
}
double change(char* token) {double x = 0;double decimalFactor = 1.0;int index = -1;int flag = 1;if (token[0] == '-') {flag = -1;}for (int i = 0; i < strlen(token); i++) {if (token[i] == '-') {continue;}if (token[i] == '.') {index = i;} else {if (index == -1) {x = x * 10 + (token[i] - '0');} else {decimalFactor *= 0.1;x += (token[i] - '0') * decimalFactor;}}}printf("%lf\n", x * flag);return x * flag;
}
double evalRPN(char** tokens, int tokensSize) {int n = tokensSize;double stk[n];int top = 0;for (int i = 0; i < n; i++) {char* token = tokens[i];if (strlen(token) == 0) {continue;}if (isNumber(token)) {stk[top++] = change(token);} else {double num2 = stk[--top];double num1 = stk[--top];switch (token[0]) {case '+':stk[top++] = num1 + num2;break;case '-':stk[top++] = num1 - num2;break;case '*':stk[top++] = num1 * num2;break;case '/':stk[top++] = num1 / num2;break;}}}return stk[top - 1];
}

这里和上面简单的版本有一点区别,这里的还考虑到了一个负数的判别和一个小数点的时候对于我们的数字的一个读取特别判断,这里如果是数字或者是一个小数点我们都要继续进行一个读取。这里我对于负数的处理是将负号存储到我们对应的数字前面,因为一个数字如果是负数的话,那他的负号是链接在运算符后面的,或者链接在左括号后面的。所以通过一个特判,来分辨我们的普通符号减和一个负数的标志。

UI界面

在这里插入图片描述

UI界面采用了Masonry来布局,这个界面大致有两个部分组成一个是我们的textField,剩下的部分则是我们的按钮部分,这里布局我采用了一个for循环来不断创建我们的button,并且给这些button赋值对应的tag,这样方便我们对于具有不同button的进行一个划分。

UIView* preView = nil;
for (int i = 0; i < 19; i++) {UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];[self addSubview:button];//button.backgroundColor = UIColor.whiteColor;[button setTitle:ary[i] forState:UIControlStateNormal];[button setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];button.titleLabel.font = [UIFont systemFontOfSize:37];button.tag = 100 + i;if (i == 0) {[button mas_makeConstraints:^(MASConstraintMaker *make) {make.left.equalTo(self).offset(20);make.top.equalTo(self.textField.mas_bottom).offset(10);make.size.equalTo(@80);}];} else if (i % 4 == 0 && i != 16) {[button mas_makeConstraints:^(MASConstraintMaker *make) {make.left.equalTo(self).offset(20);make.top.equalTo(preView.mas_bottom).offset(10);make.size.equalTo(@80);}];} else if (i == 16) {[button mas_makeConstraints:^(MASConstraintMaker *make) {make.left.equalTo(self).offset(20);make.top.equalTo(preView.mas_bottom).offset(10);make.width.equalTo(@170);make.height.equalTo(@80);}];} else {[button mas_makeConstraints:^(MASConstraintMaker *make) {make.left.equalTo(preView.mas_right).offset(10);make.top.equalTo(preView);make.size.equalTo(@80);}];}button.layer.cornerRadius = 80 / 2;button.layer.masksToBounds = YES;preView = button;}for (UIView* subview in self.subviews) {if ([subview isKindOfClass:[UIButton class]]) {if (subview.tag < 103) {subview.backgroundColor = UIColor.lightGrayColor;} else if (subview.tag == 103 || subview.tag == 107 || subview.tag == 111 || subview.tag == 115 || subview.tag == 118) {subview.backgroundColor = UIColor.orangeColor;} else {subview.backgroundColor = UIColor.darkGrayColor;}}}

这部分代码是一个创建button的代码,然后根据button的不同tag来分配颜色以及设置对应的位置。

因为采用MVC架构,所以我这里将所有给button添加事件的函数都放在了ViewController中。

 for (UIView* subview in _myView.subviews) {if ([subview isKindOfClass:[UIButton class]]) {UIButton* myButton = (UIButton*)subview;if (subview.tag == 100) {[myButton addTarget:self action:@selector(empty) forControlEvents:UIControlEventTouchUpInside];} else if (subview.tag == 103 || subview.tag == 102 || subview.tag == 101 || subview.tag == 107 || subview.tag == 111 || subview.tag == 115 || subview.tag == 117) {[myButton addTarget:self action:@selector(pressopator:) forControlEvents:UIControlEventTouchUpInside];} else if (subview.tag == 118) {[myButton addTarget:self action:@selector(pressEqual:)forControlEvents:UIControlEventTouchUpInside];NSLog(@"12");} else {[myButton addTarget:self action:@selector(pressNum:) forControlEvents:UIControlEventTouchUpInside];}}}

这部分实现了一个给button添加事件函数。

这里可以注意一下textfieldadjustsFontSizeToFitWidth属性可以让他根据字符串长度来实现一个自适应字体的效果。

在这里插入图片描述

事件的逻辑

这里笔者对于输入运算符做了限制,同时也对我们输入的小数点和左右括号都做了限制。

比方说笔者在一开始只允许我们的负号输入和左括号允许输入,别的操作符被设置成无法键入符号的状态。

又或者是在输入数字的时候限制他只能输入一个小数点。

这部分的逻辑其实比较复杂,要考虑的内容也比较多。比方说判断数字的小数点个数是否符合要求或者是判断多个运算符重叠的情况。

在这里插入图片描述

这里我主要把这部分的判断分成了两部分,一个是通过一些全局变量来控制一些不合理的输入,另一个则是通过判断中缀表达式是否合理来然后返回一个error字符串。

这里我是通过一个dotFlag和numFlag来控制他一个数字只能输入一次小数点,从而限制输入。另一个部分就是我们开始我设置成只可以输入的符号只有负号。

小结

计算器的仿写比较困难的点在于我们需要考虑的问题比较多,以及对于字符串的处理需要注意一下。细节地方比较多。

相关文章:

  • 秦巴山区SHP格式矢量范围
  • OpenCV视频I/O(4)视频采集类VideoCapture之获取异常处理模式函数getExceptionMode()的使用
  • PHP的guzzlehttp/guzzle库在碰到各种异常时的场景
  • 【rust】 基于rust编写wasm,实现markdown转换为html文本
  • 智驭未来·电网负荷智控系统
  • <<迷雾>> 第 2 章 用电来表示数 示例电路
  • 700. 二叉搜索树中的搜索
  • 【JAVA】算法笔记
  • pkg升级导致xfce虚拟终端无法启动甚至xfce挂死无法登入@FreeBSD(未解决)
  • Linux安装JDK及配置环境变量超详细教程
  • C++那些你不得不知道的(2)
  • 3D Slicer医学图像全自动AI分割组合拳-MONAIAuto3DSeg扩展
  • QT--基础
  • C++编程:实现简单的高精度时间日志记录小程序
  • 【ChatGPT】Python 实现计算两线段的变换矩阵
  • [case10]使用RSQL实现端到端的动态查询
  • HTML-表单
  • in typeof instanceof ===这些运算符有什么作用
  • tweak 支持第三方库
  • 搭建gitbook 和 访问权限认证
  • 大数据与云计算学习:数据分析(二)
  • 让你的分享飞起来——极光推出社会化分享组件
  • nb
  • 整理一些计算机基础知识!
  • 正则表达式-基础知识Review
  • ###STL(标准模板库)
  • #C++ 智能指针 std::unique_ptr 、std::shared_ptr 和 std::weak_ptr
  • $ git push -u origin master 推送到远程库出错
  • $(selector).each()和$.each()的区别
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (八)Spring源码解析:Spring MVC
  • (初研) Sentence-embedding fine-tune notebook
  • (二)学习JVM —— 垃圾回收机制
  • (含答案)C++笔试题你可以答对多少?
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (剑指Offer)面试题34:丑数
  • (原創) 如何將struct塞進vector? (C/C++) (STL)
  • ***详解账号泄露:全球约1亿用户已泄露
  • .apk文件,IIS不支持下载解决
  • .net 调用php,php 调用.net com组件 --
  • .NET 设计一套高性能的弱事件机制
  • .NET连接数据库方式
  • /3GB和/USERVA开关
  • /dev下添加设备节点的方法步骤(通过device_create)
  • /ThinkPHP/Library/Think/Storage/Driver/File.class.php  LINE: 48
  • @property python知乎_Python3基础之:property
  • [100天算法】-实现 strStr()(day 52)
  • [20190401]关于semtimedop函数调用.txt
  • [ACL2022] Text Smoothing: 一种在文本分类任务上的数据增强方法
  • [BJDCTF2020]The mystery of ip1
  • [bug总结]: Feign调用GET请求找不到请求体实体类
  • [BUUCTF 2018]Online Tool
  • [bzoj 3124][sdoi 2013 省选] 直径
  • [BZOJ3211]:花神游历各国(小清新线段树)
  • [C++]C++基础知识概述