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;
}