【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];
}
这里和上面简单的版本有一点区别,这里的还考虑到了一个负数的判别和一个小数点的时候对于我们的数字的一个读取特别判断,这里如果是数字或者是一个小数点我们都要继续进行一个读取。这里我对于负数的处理是将负号存储到我们对应的数字前面,因为一个数字如果是负数的话,那他的负号是链接在运算符后面的,或者链接在左括号后面的。所以通过一个特判,来分辨我们的普通符号减和一个负数的标志。
但是在OC中给出了一个类NSDecimalNumber
这个类可以实现一个比较精确的加减乘除,下面给出我们使用这个类来实现计算的过程
- (NSDecimalNumber*) evalRPN {NSInteger n = self.ary.count;NSLog(@"%@", self.ary);NSMutableArray* stack = [NSMutableArray array];//int top = 0;for (int i = 0; i < n; i++) {NSString* token = self.ary[i];if (token.length == 0) {continue;}if ([self isNumber:token]) {[stack addObject: [self change:token]];} else {NSDecimalNumber* num2 = [stack lastObject];[stack removeLastObject];NSDecimalNumber* num1 = [stack lastObject];[stack removeLastObject];if ([token isEqualToString:@"+"]) {[stack addObject:[num1 decimalNumberByAdding:num2]];} else if ([token isEqualToString:@"-"]) {[stack addObject:[num1 decimalNumberBySubtracting:num2]];} else if ([token isEqualToString:@"×"]) {[stack addObject:[num1 decimalNumberByMultiplyingBy:num2]];} else if ([token isEqualToString:@"÷"]) {[stack addObject:[num1 decimalNumberByDividingBy:num2]];}}}if (stack.count > 1) {return nil;} else {return [stack lastObject];}
}
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添加事件函数。
这里可以注意一下textfield
的adjustsFontSizeToFitWidth
属性可以让他根据字符串长度来实现一个自适应字体的效果。
事件的逻辑
这里笔者对于输入运算符做了限制,同时也对我们输入的小数点和左右括号都做了限制。
比方说笔者在一开始只允许我们的负号输入和左括号允许输入,别的操作符被设置成无法键入符号的状态。
又或者是在输入数字的时候限制他只能输入一个小数点。
这部分的逻辑其实比较复杂,要考虑的内容也比较多。比方说判断数字的小数点个数是否符合要求或者是判断多个运算符重叠的情况。
这里我主要把这部分的判断分成了两部分,一个是通过一些全局变量来控制一些不合理的输入,另一个则是通过判断中缀表达式是否合理来然后返回一个error字符串。
这里我是通过一个dotFlag和numFlag来控制他一个数字只能输入一次小数点,从而限制输入。另一个部分就是我们开始我设置成只可以输入的符号只有负号。
小结
计算器的仿写比较困难的点在于我们需要考虑的问题比较多,以及对于字符串的处理需要注意一下。细节地方比较多。
相关文章:

【iOS】计算器的仿写
计算器 文章目录 计算器前言简单的四则运算UI界面事件的逻辑小结 前言 笔者应组内要求,简单实现了一个可以完成简单四则运算的计算器程序。UI界面则是通过最近学习的Masonry库来实现的,而简单的四则运算内容则是通过栈来实现一个简单的四则运算。 简单…...
报错 libgomp.so.1, needed by vendor/llama.cpp/ggml/src/libggml.so, not found
在安装 xinference时报错 安装命令 pip install "xinference[all]" 报错内容 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 3.7 MB/s eta 0:00:00 INFO: pip is looking at multiple versions of multiprocess t…...
wsl(3) -- USB使用
1. 简介 WSL1中可以直接使用Windows的串口,其对应关系就是COMx对应WSL的/dev/ttySx,例如COM2对应WSL的/dev/ttyS2。WSL2是不支持USB设备的,但可以通过usbipd-win程序将windows上的usb设备映射到wsl2中,参考微软官方文档连接 USB …...

从原理到代码:如何通过 FGSM 生成对抗样本并进行攻击
从原理到代码:如何通过 FGSM 生成对抗样本并进行攻击 简介 在机器学习领域,深度神经网络的强大表现令人印象深刻,尤其是在图像分类等任务上。然而,随着对深度学习的深入研究,研究人员发现了神经网络的一个脆弱性&…...

从零开始学习OMNeT++系列第一弹——OMNeT++的介绍与安装
最近由于由于工作上的需求,接了一个网络仿真的任务。于是开始调研各个仿真平台,然后根据目前的需求和网络上公开资料的多少,决定使用omnet这个网络仿真平台。现在也是刚开始学习,所以决定记录一下从零开始的这个学习过程。因为虽然…...

Cluster Explanation via Polyhedral Descriptions
通过多面体描述进行聚类解释 本文关注聚类描述问题,即在给定数据集及其聚类划分的情况下,解释这些聚类的任务。我们提出了一种新的聚类解释方法,通过在每个聚类周围构建一个多面体,同时最小化最终多面体的复杂性或用于描述的特征…...
爬虫设计思考之一
爬虫设计思考之一 经常做爬虫的人对于技术比较的执着,尤其是本身从事的擅长的技术领域,从而容易忽视与之相近或者相似的技术。因此我建议大家在遇到此类问题的时候,可以采用对比分析的方式来理解。 本次的思考是基于国内最大的中文搜索引擎百…...
解决centos 删除文件后但空间没有释放
一、问题描述:磁盘空间不足,清理完垃圾日志以后磁盘空间还是没有释放 查看磁盘空间 [rootxwj-qt-65-44 ~]# df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 1.9G 0 1.9G 0% /dev tmpfs 1.9G 0 1.9G …...
微软SCCM:企业级系统管理的核心工具
目录 摘要 1. 引言 2. SCCM的基本概念 2.1 什么是SCCM? 2.2 SCCM的历史 3. SCCM的架构 3.1 中心服务器 3.2 数据库 3.3 管理点(Management Point) 3.4 分发点(Distribution Point) 3.5 客户端代理 3.6 报告服务 4. SCCM的核心功能 4.1 软件部署与管理 4.2 操…...

RTSP作为客户端 推流 拉流的过程分析
之前写过一个 rtsp server 作为服务端的简单demo 这次分析下 rtsp作为客户端 推流和拉流时候的过 A.作为客户端拉流 TCP方式 1.Client发送OPTIONS方法 Server回应告诉支持的方法 2.Client发送DESCRIPE方法 这里是从海康摄像机拉流并且设置了用户名密码 Server回复未认证 3.客…...

【MySQL 07】内置函数
目录 1.日期函数 日期函数使用场景: 2.字符串函数 字符串函数使用场景: 3.数学函数 4.控制流函数 1.日期函数 函数示例: 1.在日期的基础上加日期 在该日期下,加上10天。 2.在日期的基础上减去时间 在该日期下减去2天 3.计算两…...

《深度学习》OpenCV 背景建模 原理及案例解析
目录 一、背景建模 1、什么是背景建模 2、背景建模的方法 1)帧差法(backgroundSubtractor) 2)基于K近邻的背景/前景分割算法BackgroundSubtractorKNN 3)基于高斯混合的背景/前景分割算法BackgroundSubtractorMOG2 3、步骤 1)初…...
机器学习(1):机器学习的概念
1. 机器学习的定义和相关概念 机器学习之父 Arthur Samuel 对机器学习的定义是:在没有明确设置的情况下,使计算机具有学习能力的研究领域。 国际机器学习大会的创始人之一 Tom Mitchell 对机器学习的定义是:计算机程序从经验 E 中学习&#…...

0. Pixel3 在Ubuntu22下Android12源码拉取 + 编译
0. Pixel3 在Ubuntu22下Android12源码拉取 编译 原文地址: http://www.androidcrack.com/index.php/archives/3/ 1. 前言 这是一个非常悲伤的故事, 因为一个意外, 不小心把之前镜像的源码搞坏了. 也没做版本管理,恢复不了了. 那么只能说是重新做一次. 再者以前的镜像太老旧…...
ip经过多个服务器转发会网速变慢吗
会的,IP经过多个服务器转发时,网速通常会变慢,主要原因包括: 增加的延迟: 每经过一个服务器,数据包就需要额外的时间进行处理和转发。这种处理时间和网络延迟会累积,导致整体延迟增加。 带宽限制…...
mongodb通过mongoimport导入JSON文件数据
目录 一、概念 二、mongoimport导入工具 三、导入命令 一、概念 MongoDB是一个流行的开源文档数据库,它支持JSON格式的文档,非常适合存储和处理大量的非结构化数据。在实际应用中,我们经常需要将大量的数据批量导入到MongoDB中。mongoimpo…...

【Qt】控件概述 (1)
控件概述 1. QWidget核心属性1.1核心属性概述1.2 enable1.3 geometry——窗口坐标1.4 window frame的影响1.4 windowTitle——窗口标题1.5 windowIcon——窗口图标1.6 windowOpacity——透明度设置1.7 cursor——光标设置1.8 font——字体设置1.9 toolTip——鼠标悬停提示设置1…...

ping基本使用详解
在网络中ping是一个十分强大的TCP/IP工具。它的作用主要为: 用来检测网络的连通情况和分析网络速度根据域名得到服务器 IP根据 ping 返回的 TTL 值来判断对方所使用的操作系统及数据包经过路由器数量。我们通常会用它来直接 ping ip 地址,来测试网络的连…...

Win10之解决:设置静态IP后,为什么自动获取动态IP问题(七十八)
简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【…...

【AI论文精读1】针对知识密集型NLP任务的检索增强生成(RAG原始论文)
目录 一、简介一句话简介作者、引用数、时间论文地址开源代码地址 二、摘要三、引言四、整体架构(用一个例子来阐明)场景例子:核心点: 五、方法 (架构各部分详解)5.1 模型1. RAG-Sequence Model2. RAG-Toke…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...