当前位置: 首页 > 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];
}

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

但是在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添加事件函数。

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

在这里插入图片描述

事件的逻辑

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

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

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

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

在这里插入图片描述

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

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

小结

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

相关文章:

【iOS】计算器的仿写

计算器 文章目录 计算器前言简单的四则运算UI界面事件的逻辑小结 前言 笔者应组内要求&#xff0c;简单实现了一个可以完成简单四则运算的计算器程序。UI界面则是通过最近学习的Masonry库来实现的&#xff0c;而简单的四则运算内容则是通过栈来实现一个简单的四则运算。 简单…...

报错 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的串口&#xff0c;其对应关系就是COMx对应WSL的/dev/ttySx&#xff0c;例如COM2对应WSL的/dev/ttyS2。WSL2是不支持USB设备的&#xff0c;但可以通过usbipd-win程序将windows上的usb设备映射到wsl2中&#xff0c;参考微软官方文档连接 USB …...

从原理到代码:如何通过 FGSM 生成对抗样本并进行攻击

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

从零开始学习OMNeT++系列第一弹——OMNeT++的介绍与安装

最近由于由于工作上的需求&#xff0c;接了一个网络仿真的任务。于是开始调研各个仿真平台&#xff0c;然后根据目前的需求和网络上公开资料的多少&#xff0c;决定使用omnet这个网络仿真平台。现在也是刚开始学习&#xff0c;所以决定记录一下从零开始的这个学习过程。因为虽然…...

Cluster Explanation via Polyhedral Descriptions

通过多面体描述进行聚类解释 本文关注聚类描述问题&#xff0c;即在给定数据集及其聚类划分的情况下&#xff0c;解释这些聚类的任务。我们提出了一种新的聚类解释方法&#xff0c;通过在每个聚类周围构建一个多面体&#xff0c;同时最小化最终多面体的复杂性或用于描述的特征…...

爬虫设计思考之一

爬虫设计思考之一 经常做爬虫的人对于技术比较的执着&#xff0c;尤其是本身从事的擅长的技术领域&#xff0c;从而容易忽视与之相近或者相似的技术。因此我建议大家在遇到此类问题的时候&#xff0c;可以采用对比分析的方式来理解。 本次的思考是基于国内最大的中文搜索引擎百…...

解决centos 删除文件后但空间没有释放

一、问题描述&#xff1a;磁盘空间不足&#xff0c;清理完垃圾日志以后磁盘空间还是没有释放 查看磁盘空间 [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.日期函数 日期函数使用场景&#xff1a; 2.字符串函数 字符串函数使用场景&#xff1a; 3.数学函数 4.控制流函数 1.日期函数 函数示例&#xff1a; 1.在日期的基础上加日期 在该日期下&#xff0c;加上10天。 2.在日期的基础上减去时间 在该日期下减去2天 3.计算两…...

《深度学习》OpenCV 背景建模 原理及案例解析

目录 一、背景建模 1、什么是背景建模 2、背景建模的方法 1&#xff09;帧差法(backgroundSubtractor) 2&#xff09;基于K近邻的背景/前景分割算法BackgroundSubtractorKNN 3&#xff09;基于高斯混合的背景/前景分割算法BackgroundSubtractorMOG2 3、步骤 1&#xff09;初…...

机器学习(1):机器学习的概念

1. 机器学习的定义和相关概念 机器学习之父 Arthur Samuel 对机器学习的定义是&#xff1a;在没有明确设置的情况下&#xff0c;使计算机具有学习能力的研究领域。 国际机器学习大会的创始人之一 Tom Mitchell 对机器学习的定义是&#xff1a;计算机程序从经验 E 中学习&#…...

0. Pixel3 在Ubuntu22下Android12源码拉取 + 编译

0. Pixel3 在Ubuntu22下Android12源码拉取 编译 原文地址: http://www.androidcrack.com/index.php/archives/3/ 1. 前言 这是一个非常悲伤的故事, 因为一个意外, 不小心把之前镜像的源码搞坏了. 也没做版本管理,恢复不了了. 那么只能说是重新做一次. 再者以前的镜像太老旧…...

ip经过多个服务器转发会网速变慢吗

会的&#xff0c;IP经过多个服务器转发时&#xff0c;网速通常会变慢&#xff0c;主要原因包括&#xff1a; 增加的延迟&#xff1a; 每经过一个服务器&#xff0c;数据包就需要额外的时间进行处理和转发。这种处理时间和网络延迟会累积&#xff0c;导致整体延迟增加。 带宽限制…...

mongodb通过mongoimport导入JSON文件数据

目录 一、概念 二、mongoimport导入工具 三、导入命令 一、概念 MongoDB是一个流行的开源文档数据库&#xff0c;它支持JSON格式的文档&#xff0c;非常适合存储和处理大量的非结构化数据。在实际应用中&#xff0c;我们经常需要将大量的数据批量导入到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工具。它的作用主要为&#xff1a; 用来检测网络的连通情况和分析网络速度根据域名得到服务器 IP根据 ping 返回的 TTL 值来判断对方所使用的操作系统及数据包经过路由器数量。我们通常会用它来直接 ping ip 地址&#xff0c;来测试网络的连…...

Win10之解决:设置静态IP后,为什么自动获取动态IP问题(七十八)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…...

【AI论文精读1】针对知识密集型NLP任务的检索增强生成(RAG原始论文)

目录 一、简介一句话简介作者、引用数、时间论文地址开源代码地址 二、摘要三、引言四、整体架构&#xff08;用一个例子来阐明&#xff09;场景例子&#xff1a;核心点&#xff1a; 五、方法 &#xff08;架构各部分详解&#xff09;5.1 模型1. RAG-Sequence Model2. RAG-Toke…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器

拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件&#xff1a; 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区...