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

iOS仿写 —— 计算器

在计算器的仿写中,本人首次实际使用MVC构架和Mansory自动布局。

安装Mansory

首先安装cocoapods,然后在终端找到项目文件夹,执行 pod init

随后修改文件,将内容改为:

platform :ios, '12.0'

target '计算器77' do    
pod 'LookinServer', :configurations => ['Debug']
pod 'Masonry'
end

作者同时安装了Lookin方便调试,随后在终端执行: pod install。

如果下载有问题,请给终端挂一个梯子,因为下载源是国外的。

就此安装完成。

框架

M:我的Model负责执行运算逻辑

V:我的View负责实现一个基本的计算器页面

C:负责联系主页面和Model,同时负责各种判断各种非法运算

页面:

我实现的页面如图。

我使用两层for循环,外层循环负责便历5行,内层循环负责遍历每一行的四个按钮

for (int i = 0; i < 5; i++) {UIView* rowView = [[UIView alloc] init];[self addSubview:rowView];[rowView mas_makeConstraints:^(MASConstraintMaker *make) {if (lastRow) {make.top.equalTo(lastRow.mas_bottom).offset(btnSpacing);} else {make.top.equalTo(_topLabel.mas_bottom).offset(40);}make.left.right.equalTo(self).inset(20);//左右20make.height.mas_equalTo(btnHeight);}];lastRow = rowView;UIView* lastButton = nil;int buttonsInRow = 4;if (i == 4) buttonsInRow = 3; // 第5行只有3个for (int j = 0; j < buttonsInRow; j++) {NSInteger index = i * 4 + j;if (i == 4 && j >= 1) {index = i * 4 + j + 1;}NSString* title = _array[index];UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];button.tag = 100 + index;[button setTitle:title forState:UIControlStateNormal];[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];button.layer.cornerRadius = btnHeight / 2;button.titleLabel.font = [UIFont systemFontOfSize:42];button.clipsToBounds = YES;[button addTarget:self action:@selector(return:) forControlEvents:UIControlEventTouchUpInside];if (i == 0) {button.backgroundColor = [UIColor colorWithWhite:0.6 alpha:1];} else if (j == 3 && i != 4) {button.backgroundColor = [UIColor colorWithRed:236/255.0 green:146/255.0 blue:47/255.0 alpha:1];} else {button.backgroundColor = [UIColor colorWithWhite:0.3 alpha:1];}[rowView addSubview:button];[button mas_makeConstraints:^(MASConstraintMaker *make) {make.top.bottom.equalTo(rowView);if (i == 4) {if (j == 0) {// "0" 按钮占两倍宽度make.left.equalTo(rowView);make.width.equalTo(rowView).multipliedBy(0.5).offset(-btnSpacing/2);} else {// 其他按钮正常宽度make.left.equalTo(lastButton.mas_right).offset(btnSpacing);make.width.equalTo(rowView).multipliedBy(0.25).offset(-btnSpacing*0.75);}} else {// 其他行正常处理if (lastButton) {make.left.equalTo(lastButton.mas_right).offset(btnSpacing);} else {make.left.equalTo(rowView);}make.top.bottom.equalTo(rowView);make.width.equalTo(@(btnHeight)); // 宽高相等,圆形}}];lastButton = button;}}

Controller:

这部分属于重中之重,尤其在于各种非法输入的防止。

我分别设置了几个属性,来辅助进行判定

 self.pointFlag = NO;self.operatorFlag = NO;self.equalFlag = NO;self.left = 0;self.right = 0;

每次在页面的点击,都会调用我的pressChange函数:

- (void)pressChange:(UIButton *)button {NSString *title = button.currentTitle;NSLog(@"按钮被点击: %@", title);if (self.equalFlag) {if ([title isEqualToString:@"="]) {// 第二次按等号什么都不做return;} else if ([self isOperator:title]) {// 允许继续输入self.equalFlag = NO;} else {// 输入的是数字,表示开始新一轮运算[self.calArray removeAllObjects];self.equalFlag = NO;}}// "AC"if ([title isEqualToString:@"AC"]) {self.calView.topLabel.text = @"0";[self.calArray removeAllObjects];self.pointFlag = NO;self.operatorFlag = NO;self.equalFlag = NO;self.left = 0;self.right = 0;return;}// 等号else if ([title isEqualToString:@"="]) {// 括号未配对if (self.left != self.right) {NSLog(@"当前左括号数量 left = %ld,右括号数量 right = %ld", (long)self.left, (long)self.right);self.calView.topLabel.text = @"ERROR brackets not match!";return;}// 表达式为空if (self.calArray.count == 0) {self.calView.topLabel.text = @"0";return;}NSString *last = [self.calArray lastObject];// 表达式不能以运算符或左括号结尾if ([self isOperator:last] || [last isEqualToString:@"("]) {self.calView.topLabel.text = @"ERROR,NO Calculator or (";return;}NSString *expression = [self.calArray componentsJoinedByString:@""];expression = [expression stringByReplacingOccurrencesOfString:@"×" withString:@"*"];expression = [expression stringByReplacingOccurrencesOfString:@"÷" withString:@"/"];NSLog(@"计算表达式:%@", expression);NSLog(@"当前左括号数量 left = %ld,右括号数量 right = %ld", (long)self.left, (long)self.right);NSString *result = [self.model calculate:expression];self.calView.topLabel.text = result;// 清空[self.calArray removeAllObjects];[self.calArray addObject:result];self.equalFlag = YES;self.pointFlag = NO;self.operatorFlag = NO;self.left = 0;self.right = 0;return;}// 小数点处理else if ([title isEqualToString:@"."]) {NSString *last = [self.calArray lastObject];// 当前没有输入任何内容if (!last) {self.pointFlag = YES;[self.calArray addObject:@"0."];}// 如果当前是数字,并且当前数字中还没有小数点else if (!self.pointFlag && ![self isOperator:last] && ![last isEqualToString:@"("] && ![last isEqualToString:@")"]) {self.pointFlag = YES;NSString *newStr = [last stringByAppendingString:@"."];[self.calArray removeLastObject];[self.calArray addObject:newStr];}// 当前是操作符或括号或等其他情况,自动补 0.else if (!self.pointFlag && ![last isEqualToString:@")"]) {self.pointFlag = YES;[self.calArray addObject:@"0."];}self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];return;}else if ([title isEqualToString:@"("]) {NSString* last = [self.calArray lastObject];if (last && ![self isOperator:last] && ![last isEqualToString:@"("]) {return;}self.left++;[self.calArray addObject:title];self.operatorFlag = NO;self.pointFlag = NO;self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];return;}else if ([title isEqualToString:@")"]) {if (self.left > self.right) {NSString* last = [self.calArray lastObject];if ([self isOperator:last] || [last isEqualToString:@"("]) {return;}self.right++;[self.calArray addObject:title];self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];}return;}// 运算符else if ([self isOperator:title]) {NSString *last = [self.calArray lastObject];// kongif (!last) {if ([title isEqualToString:@"-"]) {// 允许表达式起始为负号[self.calArray addObject:title];self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];}return;}// 处理上一个字符是运算符if ([self isOperator:last]) {// 其他运算符替换上一个运算符[self.calArray removeLastObject];[self.calArray addObject:title];self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];return;}// 处理上一个字符是左括号的情况if ([last isEqualToString:@"("]) {if ([title isEqualToString:@"-"]) {// 允许在左括号后添加负号[self.calArray addObject:title];self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];}return;}// 正常添加运算符[self.calArray addObject:title];self.pointFlag = NO;self.operatorFlag = YES;self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];} else {// 数字处理NSString *last = [self.calArray lastObject];if (!last || [self isOperator:last] || [last isEqualToString:@"("] || [last isEqualToString:@")"]) {[self.calArray addObject:title];} else {NSString *newStr = [last stringByAppendingString:title];[self.calArray removeLastObject];[self.calArray addObject:newStr];}self.operatorFlag = NO;self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];}
}- (BOOL)isOperator:(NSString *)str {return [@[@"+", @"-", @"*", @"/"] containsObject:str];
}- (BOOL)isValidExpression:(NSString *)expression {return ![expression containsString:@"++"] &&![expression containsString:@"--"] &&![expression containsString:@"**"] &&![expression containsString:@"//"] &&![expression containsString:@"××"] &&![expression containsString:@"÷÷"];
}@end

在程序中,我已经给出了一些注释,相信大家也能看懂。

一些特殊情况的处理

我设置了运算符号的替换,以避免多个连续运算符。

我有两个变量,记录(和)的数量,在按下等号时,如果两个数量不同,会报错。

同时,我还添加了一些逻辑:例如:表达式不能以运算符或左括号结尾;如果上一个是操作符或括号等其他情况,自动补0;如果上一位是(,不能添加)等等,从输入上禁止非法运算的输入。

Model

我的Model中并没有使用常规的后缀转后缀。

我使用了两个栈,一个符号栈和一个数字栈

- (void)applyTopOperator {//栈里没有两个元素if (_operandStack.count < 2 || _operatorStack.count == 0) return;unichar op = [_operatorStack.lastObject characterAtIndex:0];[_operatorStack removeLastObject];//两个运算的数字double b = [[_operandStack lastObject] doubleValue];[_operandStack removeLastObject];double a = [[_operandStack lastObject] doubleValue];[_operandStack removeLastObject];double result = [self applyOperator:op to:a and:b];[_operandStack addObject:@(result)];
}

- (NSString *)calculate:(NSString *)expression {[_operandStack removeAllObjects];[_operatorStack removeAllObjects];expression = [expression stringByReplacingOccurrencesOfString:@"×" withString:@"*"];expression = [expression stringByReplacingOccurrencesOfString:@"÷" withString:@"/"];// 添加结束符,我也没想清楚有嘛用expression = [expression stringByAppendingString:@" "];NSInteger length = expression.length;NSInteger i = 0;//负BOOL isNegative = NO;BOOL expectOperand = YES;while (i < length) {unichar c = [expression characterAtIndex:i];if (c == ' ') {i++;continue;}// 负数,有负号且不准备被操作if (c == '-' && expectOperand) {isNegative = YES;i++;continue;}// 处理数字if ([self isDigit:c]) {NSInteger start = i;//循环手机数字while (i < length && [self isDigit:[expression characterAtIndex:i]]) {i++;}NSString *numStr = [expression substringWithRange:NSMakeRange(start, i - start)];double value = [numStr doubleValue];//类型转换//fushu处理if (isNegative) {value = -value;isNegative = NO;}[_operandStack addObject:@(value)];expectOperand = NO;continue;}// 处理左括号if (c == '(') {[_operatorStack addObject:[NSString stringWithCharacters:&c length:1]];//操作栈expectOperand = YES; // 可能跟着负数i++;continue;}// 处理右括号if (c == ')') {//一直运算知道遇到左括号while (_operatorStack.count > 0) {unichar top = [_operatorStack.lastObject characterAtIndex:0];//括号内算完了if (top == '(') {[_operatorStack removeLastObject]; // 弹出左括号break;}//做一次运算[self applyTopOperator];}expectOperand = NO;i++;continue;}// 处理运算符if ([self isOperator:c]) {// 处理运算符优先级while (_operatorStack.count > 0) {unichar top = [_operatorStack.lastObject characterAtIndex:0];// 遇到左括号停止if (top == '(') {break;}// 比较优先级if ([self precedenceForOperator:top] >= [self precedenceForOperator:c]) {[self applyTopOperator];} else {break;}}[_operatorStack addObject:[NSString stringWithCharacters:&c length:1]];expectOperand = YES; // 运算符后可能跟着负数i++;continue;}i++;}// 处理栈中剩余的操作符while (_operatorStack.count > 0) {[self applyTopOperator];//函数}// 获取结果if (_operandStack.count == 1) {double result = [[_operandStack lastObject] doubleValue];if (isnan(result) || isinf(result)) {return @"错误"; // 这里替代异常处理,直接返回错误提示,或者可以用@catch那个东西,以后详细学习}return [self formatResult:result];}return @"ERROR";
}

最后,还要加一个结果处理


//结果处理
- (NSString *)formatResult:(double)value {//检查是否为非数字/无穷大,学到了if (isnan(value) || isinf(value)) {return @"ERROR";}// 整数if (fmod(value, 1) == 0) {  //返回value / 1的余数return [NSString stringWithFormat:@"%.0f", value];}NSString *result = [NSString stringWithFormat:@"%.6f", value];// 删除小数点后多余的0NSRange dotRange = [result rangeOfString:@"."];if (dotRange.location != NSNotFound) {  //从后往前// 遍历后缀,找到从后往前第一个不是0的位置NSUInteger i = result.length;while (i > dotRange.location + 1 && [result characterAtIndex:i - 1] == '0') {i--;}result = [result substringToIndex:i];// 如果最后是小数点,也去掉if ([result hasSuffix:@"."]) {result = [result substringToIndex:result.length - 1];}}return result;
}

http://www.xdnf.cn/news/16676.html

相关文章:

  • Python 程序设计讲义(28):字符串的用法——格式化字符串
  • [leetcode] 组合总和
  • 冒泡排序算法
  • Java中什么是类加载?类加载的过程?
  • bash变量名不能有连字符
  • 【Redis实现基础的分布式锁及Lua脚本说明】
  • 爬虫逆向之瑞数五案例:某某医学院(补环境,联调)
  • Makefile 快速入门指南
  • 嵌入式第十四课!!!指针在字符数组的应用与数组指针
  • JavaWeb 入门:CSS 基础与实战详解(Java 开发者视角)
  • DataParallel (DP) DistributedDataParallel (DDP)
  • JavaWeb学习打卡18(JDBC案例详解)
  • [leetcode] 电话号码的排列组合
  • CSRF漏洞原理
  • CentOS7 安装和配置教程
  • USRP X410 X440 5G及未来通信技术的非地面网络(NTN)
  • Matplotlib(三)- 图表辅助元素
  • 经典算法题解析:从思路到实现,掌握核心编程思维
  • 天学网面试总结 —— 前端开发岗
  • Go 语言-->指针
  • 【2025/07/28】GitHub 今日热门项目
  • 【服务器知识】nginx配置ipv6支持
  • 大模型的开发应用(十九):AIGC基础
  • 【Spring WebFlux】 三、响应式流规范与实战
  • Java 笔记 serialVersionUID
  • ADB+Python控制(有线/无线) Scrcpy+按键映射(推荐)
  • 服务器查日志太慢,试试grep组合拳
  • 时序数据库选型指南:工业大数据场景下基于Apache IoTDB技术价值与实践路径
  • 5 分钟上手 Firecrawl
  • 【办公类-109-01】20250728托小班新生挂牌(学号姓名)