【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来控制他一个数字只能输入一次小数点,从而限制输入。另一个部分就是我们开始我设置成只可以输入的符号只有负号。

小结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/437420.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QSqlDatabase在多线程中的使用

Qt中多线程使用数据库_qt数据库管理类支持多数据库,多线程-CSDN博客 1. 代码&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include <QSqlDatabase> #include <QSqlQuery> #include <QSqlError>…

【SpringBoot详细教程】-08-MybatisPlus详细教程以及SpringBoot整合Mybatis-plus【持续更新】

目录 🌲 MyBatis Plus 简介 🌾入门案例 🌾 MP 简介 🌲 MP 的CRUD 🌾 新增 🌾 删除 🌾 修改在进行 🌾 根据ID查询 🌾 查询所有 🌲 分页功能 🌾 设置分页参数 🌾 设置分页拦截器 🌲 优化启动 🌾 取消mbatisPlusBanner 🌾 取消Sprin…

L0-Linux-关卡材料提交

SSH全称Secure Shell&#xff0c;中文翻译为安全外壳&#xff0c;它是一种网络安全协议&#xff0c;通过加密和认证机制实现安全的访问和文件传输等业务。SSH 协议通过对网络数据进行加密和验证&#xff0c;在不安全的网络环境中提供了安全的网络服务。 SSH 是&#xff08;C/S…

大学学校用电安全远程监测预警系统

1.概述&#xff1a; 该系统是基于移动互联网、云计算技术&#xff0c;通过物联网传感终端&#xff0c;将办公建筑、学校、医院、工厂、体育场馆、宾馆、福利院等人员密集场所的电气安全数据&#xff0c;实时传输至安全用申管理服务器&#xff0c;为用户提供不间断的数据跟踪&a…

Linux shell编程学习笔记84:tee命令——显示保存两不误

0 引言 在前面的学习笔记中&#xff0c;我们经常使用echo命令和输出重定向来生成脚本文件或演示文件&#xff0c;其实Linux提供了一个可以从标准输入读取数据&#xff0c;并输出成文件的命令——tee。 1 tee命令 的帮助信息、功能、命令格式、选项和参数说明 1.1 tee命令 的…

基于Hive和Hadoop的用电量分析系统

本项目是一个基于大数据技术的用电量分析系统&#xff0c;旨在为用户提供全面的电力消耗信息和深入的用电量分析。系统采用 Hadoop 平台进行大规模数据存储和处理&#xff0c;利用 MapReduce 进行数据分析和处理&#xff0c;通过 Sqoop 实现数据的导入导出&#xff0c;以 Spark…

Windows 11 安装配置 Git 教程

目录 Git Windows 11 环境安装配置 Git Git Git是一个开源的分布式版本控制系统&#xff0c;由Linus Torvalds创建&#xff0c;用于有效、高速地处理从小到大的项目版本管理。Git是目前世界上最流行的版本控制系统&#xff0c;广泛应用于软件开发中。 以下是Git的一些关键特…

【2024最新】华为HCIE认证考试流程

HCIE是华为认证体系中最高级别的ICT技术认证&#xff0c;表示通过认证的人具有ICT领域专业知识和丰富实践经验。 HCIE认证方向&#xff1a;最高认证级别HCIE的技术方向有13个 下面以HCIE-Datacom为例给大家介绍一下&#xff1a; HCIE-Datacom认证考试流程&#xff1a; 1.笔试…

查找与排序-快速排序

排序算法可以分为内部排序和外部排序&#xff0c;内部排序是数据记录在内存中进行排序&#xff0c;而外部排序是因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外存。常见的内部排序算法有&#xff1a;插入排序、希尔排序、选择排序…

【Linux系统编程】第二十六弹---彻底掌握文件I/O:C/C++文件接口与Linux系统调用实践

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、回顾C语言文件接口 1.1、以写的方式打开文件 1.2、以追加的方式打开文件 2、初步理解文件 2.1、C文件接口 3、进一步理…

element-ui 通过按钮式触发日期选择器

element ui 写在前面1. 自定义的日期时间组件CustomDatePicker.vue2. 页面效果总结写在最后 写在前面 需求&#xff1a;elementui中日期时间选择器&#xff0c;目前只能通过点击input输入框触发日期选择器&#xff0c;我希望能通过其他方式触发日期选择器同时把input输入框去掉…

【经典机器学习算法】谱聚类算法及其实现(python)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;深度学习_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. 前…

模版and初识vector

一、引言 在C语言中&#xff0c;不论是数组&#xff0c;还是结构体定义的数组&#xff0c;功能都比较欠缺&#xff0c;不是单纯的添加几个变量就能够解决的。缺少增删查改的功能&#xff0c;为了解决这个问题&#xff0c;C决定填上C语言这个坑&#xff0c;但是填过坑的人都知道…

线性DP之最长上升/下降子序列

分析过程&#xff1a; 代码&#xff1a; #include<bits/stdc.h> #include<unordered_map> #include<unordered_set> using namespace std; #define int long long //可能会超时 #define PII pair<int,int> const int INF 0x3f3f3f3f, mod 99824435…

认知杂谈73《成年人的修炼:勇敢前行,积极向上》

内容摘要&#xff1a; 成长是成年人的必修课&#xff0c;它要求我们不断学习、面对挑战、做出选择、调整行动。成长的必要性在于适应社会、实现自我价值。实现成长的策略包括自我掌舵、自救、为结果负责、保持积极心态。 追求艺术或商业目标、自己解决问题、承担责任、换个角度…

Android 车载虚拟化底层技术-显示虚拟化(双card)总览

系列文章请扫关注公众号&#xff01; 本文主要包括部分&#xff1a; 显示虚拟化场景DRM架构 2.1 DRM简介&#xff08;Direct Rendering Manager&#xff09; 2.2 高通SDM驱动 Multiple-drm-cards方案 3.1 介绍 3.2 Qcom驱动框架解析 3.3 高通及MTK平台支持情况 3.4 方案…

针对考研的C语言学习(定制化快速掌握重点2)

1.C语言中字符与字符串的比较方法 在C语言中&#xff0c;单字符可以用进行比较也可以用 > , < ,但是字符串却不能用直接比较&#xff0c;需要用strcmp函数。 strcmp 函数的原型定义在 <string.h> 头文件中&#xff0c;其定义如下&#xff1a; int strcmp(const …

基于深度学习的乳腺癌分类识别与诊断系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 乳腺癌是全球最常见的癌症之一&#xff0c;早期诊断对于治疗效果至关重要。近年来&#xff0c;深度学习技术在医学图像分析领域取得了显著进展&#xff0c;能够从大量的医学影像数据中自动学习和提…

TiDB 在线打标签实现副本调度应用实践

作者&#xff1a; 数据源的TiDB学习之路 原文来源&#xff1a; https://tidb.net/blog/4e14596a 案例背景 某原有系统为虚拟机环境部署&#xff0c;整体性能不满足预期。为提升集群整体性能&#xff0c;计划分阶段采购物理机&#xff0c;并以扩缩容的方式逐渐把物理机添加到…

uniapp踩坑 tabbar页面数据刷新了但视图没有更新

问题描述&#xff1a; 有个uni-data-checkbox组件&#xff0c;两个选项&#xff1a;选项1和选项2&#xff08;对应的value值分别为1和2&#xff09;&#xff0c;v-model绑定属性名为value 两个tabbar页面&#xff1a;tab1&#xff0c;tab2。 tab1页面有个逻辑是在onShow中刷新v…