计算器
- 前言
- 四则运算
- View层
- Masonry布局
- 非法输入的逻辑
前言
前两周进行了计算器的仿写,运用了刚学习的Masonry布局以及MVC框架,同时学习了简单的四则运算,今天撰写博客对计算器的仿写进行一个总结,整理归纳期间遇到的问题和收获。
四则运算
这里我先讲中缀表达式转化为后缀表达式,转化的思路是将数字和小数点直接压入后缀表达式的字符串之中,将符号先压入符号栈,而后根据优先级的顺序来将符号取出来放入后缀表达式之中,当碰到括号时,左括号正常入栈,而右括号则将栈内元素出栈进入后缀表达式之中直到栈空或者栈顶元素为左括号,但是左括号也要出栈,但是不进入后缀表达式之中。
推荐可以看看《数据结构》:中缀表达式转后缀表达式 + 后缀表达式的计算这篇博客,讲解的很详细。
后缀表达式的计算我使用OC中自带的NSDecimalNumber
类进行运算。
NSDecimalNumber
是 Objective-C 中用于表示和处理高精度十进制数的类。它是 Foundation 框架的一部分,专门设计用于需要精确计算的场景,比如财务应用和科学计算。
代码展示:
static int n = 0;typedef struct stack {char stack[1000];int top;
}Stack;static char nBolan[1000];Stack* CreatStack(void) {Stack* stk = (Stack*) malloc(sizeof(Stack));stk->top = -1;return stk;
}void Push(Stack* obj, char val) {obj->stack[++obj->top] = val;
}void pop(Stack* obj) {if(obj->top != -1) {obj->top--;}
}char GetTopStack(Stack* obj) {if(obj->top!=-1) {return obj->stack[obj->top];}return 0;
}//将内容压入后缀表达式void zhuanhuan(char a, Stack* obj) {if(a == '+' || a == '-') {if(obj->stack[obj->top] == '-') {while(obj->stack[obj->top] != '(' && obj->top != -1) {nBolan[n++] = GetTopStack(obj);pop(obj);}Push(obj, a);nBolan[n++] = ',';} else if( obj->stack[obj->top] == '(') {Push(obj, a);nBolan[n++] = ',';} else if(obj->stack[obj->top] == '*' || obj->stack[obj->top] == '/') {while(obj->stack[obj->top] != '(' && obj->top != -1) {nBolan[n++] = GetTopStack(obj);pop(obj);}//将数字和小数点压入后缀表达式Push(obj, a);nBolan[n++] = ',';//说明是在乘除一个负数} else {Push(obj, a);nBolan[n++] = ',';//说明前一个是数字}} else if(a == '*' || a == '/') {if (obj->stack[obj->top] == '/') {nBolan[n++] = GetTopStack(obj);pop(obj);Push(obj, a);nBolan[n++] = ',';} else if (obj->stack[obj->top] == '(') {Push(obj, a);nBolan[n++] = ',';} else {Push(obj, a);nBolan[n++] = ',';}} else if (a == ')') {while(obj->stack[obj->top] != '(') {nBolan[n++] = GetTopStack(obj);pop(obj);}pop(obj);} else if (a == '(') {Push(obj, a);} else if (a != '=' && a != ')' && a != '(') {nBolan[n++] = a;}if (a == '=') {while(obj->top >= 0) {nBolan[n++] = GetTopStack(obj);pop(obj);}}printf("%s\n", nBolan);}double evalRPN(char* tokens, int tokenSize) {NSMutableArray* stack = [NSMutableArray array];for(int i = 0; i < tokenSize; i++) {if (tokens[i] == ',' || tokens[i] == '(' || tokens[i] == ')') {continue;}if (tokens[i] == '+') {NSDecimalNumber* num2 = [stack lastObject];NSLog(@"%@", num2);[stack removeLastObject];NSDecimalNumber* num1 = [stack lastObject];NSLog(@"%@", num1);[stack removeLastObject];[stack addObject:[num1 decimalNumberByAdding:num2]];NSLog(@"%@", [stack lastObject]);continue;}if (tokens[i] == '-') {NSDecimalNumber* num2 = [stack lastObject];[stack removeLastObject];NSDecimalNumber* num1 = [stack lastObject];[stack removeLastObject];[stack addObject:[num1 decimalNumberBySubtracting:num2]];continue;}if (tokens[i] == '*') {NSDecimalNumber* num2 = [stack lastObject];[stack removeLastObject];NSDecimalNumber* num1 = [stack lastObject];[stack removeLastObject];[stack addObject:[num1 decimalNumberByMultiplyingBy:num2]];continue;}if (tokens[i] == '/') {NSDecimalNumber* num2 = [stack lastObject];[stack removeLastObject];double a = [num2 doubleValue];if(a==0){n = 0;break;}NSDecimalNumber* num1 = [stack lastObject];[stack removeLastObject];[stack addObject:[num1 decimalNumberByDividingBy:num2]];continue;} else {NSMutableString* str = [[NSMutableString alloc] init];while((tokens[i] >='0'&& tokens[i]<='9')||tokens[i]=='.') {[str appendFormat:@"%c", tokens[i]];i++;}i--;NSDecimalNumber* num = [NSDecimalNumber decimalNumberWithString:str];[stack addObject:num];NSLog(@"%@", num);}}NSLog(@"jieshu");NSDecimalNumber* num3=[stack lastObject];NSLog(@"%@", num3);double a = [num3 doubleValue];NSLog(@"%f", a);return a;
}-(double) jisuan:(NSString*) str
{Stack* obj = CreatStack();n = 0;for (int i = 0; i < [str length]; i++) {//负数的转化if ((i == 0 && [str characterAtIndex:0] == '-') || ([str characterAtIndex:i] == '-' && ([str characterAtIndex:i-1] == '+' || [str characterAtIndex:i-1] == '-' || [str characterAtIndex:i-1] == '*' || [str characterAtIndex:i-1] == '/' || [str characterAtIndex:i-1] == '('))) {zhuanhuan('(', obj);zhuanhuan('0', obj);zhuanhuan('-', obj);//使用添加括号的方式,将负数变换为一个括号内进行0-整数的方式i++;while (([str characterAtIndex:i] >= '0' && [str characterAtIndex:i] <= '9') || [str characterAtIndex:i] == '.') {zhuanhuan([str characterAtIndex:i], obj);i++;}zhuanhuan(')', obj);i--;continue;}zhuanhuan([str characterAtIndex:i], obj);}nBolan[n++] = '\0';NSLog(@"%s", nBolan);int Size = (int)strlen(nBolan);double b = evalRPN(nBolan, Size);NSLog(@"%f",b);return b;
}
在实际使用的时候,我发现对于括号内的运算,由于运算时会直接跳过后缀表达式中存在的括号,所以右侧括号不写也可以正常进行运算,相当于自动补全括号一样。
View层
Masonry布局
在View层中构建计算器的页面,这里我使用了Masonry自动布局进行布局,由于按钮的排列是有规律的,故而使用Masonry进行布局会更加简便,下面展示部分布置按钮的代码。
for (int i = 0; i < 4; i++) {for (int j = 0; j < 5; j++) {if(i == 0 && j == 4){self.btn = [UIButton buttonWithType:UIButtonTypeCustom];self.btn.tag = 105;[self addSubview:self.btn];[self.btn mas_makeConstraints:^(MASConstraintMaker *make) {make.left.mas_equalTo(15);make.top.mas_equalTo(720);make.height.mas_equalTo(80);make.width.mas_equalTo(170);}];self.btn.layer.cornerRadius = 40;self.btn.layer.masksToBounds = YES;[self.btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];[self.btn setTitle:@"0" forState:UIControlStateNormal];[self.btn setBackgroundColor:[UIColor darkGrayColor]];self.btn.titleLabel.font = [UIFont systemFontOfSize:36];[self.btn addTarget:self action:@selector(press:) forControlEvents:UIControlEventTouchUpInside];continue;}if(i == 1 && j == 4)continue;self.btn = [UIButton buttonWithType:UIButtonTypeCustom];self.btn.tag = 101 + i * 5 + j;[self addSubview:self.btn];[self.btn mas_makeConstraints:^(MASConstraintMaker *make) {make.left.mas_equalTo(15 + 90 * i);make.top.mas_equalTo(360 + 90 * j);make.width.and.height.mas_equalTo(80);}];self.btn.layer.cornerRadius = 40;self.btn.layer.masksToBounds = YES;[self.btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];if(j == 0 && i < 3) {[self.btn setTitle:[NSString stringWithFormat:@"%@",arr[i*5+j]] forState:UIControlStateNormal];[self.btn setBackgroundColor:[UIColor grayColor]];} else if(i < 3 && j < 5) {[self.btn setTitle:[NSString stringWithFormat:@"%@",arr[i*5+j]] forState:UIControlStateNormal];[self.btn setBackgroundColor:[UIColor darkGrayColor]];} else {[self.btn setTitle:[NSString stringWithFormat:@"%@",arr[i*5+j]] forState:UIControlStateNormal];[self.btn setBackgroundColor:[UIColor orangeColor]];}self.btn.titleLabel.font = [UIFont systemFontOfSize:36];[self.btn addTarget:self action:@selector(press:) forControlEvents:UIControlEventTouchUpInside];}}
上述代码是我对于按钮的排布,我们要注意的是,由于我们要在Controller层对于按钮响应事件进行处理,所以我们需要传值将按钮传递给Controller层,这里我使用的是通知传值,也可以使用协议传值
-(void) press:(UIButton*) btn
{NSDictionary *dict =[NSDictionary dictionaryWithObject:btn forKey:@"btn"];[[NSNotificationCenter defaultCenter] postNotificationName:@"anniu" object:nil userInfo:dict];
}
这里我使用了UITextField的adjustsFontSizeToFitWidth
属性,使用它可以让文本输入框的文字自适应大小。
self.label.adjustsFontSizeToFitWidth = YES;self.label.font = [UIFont systemFontOfSize:72.0];//设置刚开始显示的字体大小self.label.minimumFontSize = 36.0;//设置最小的字体大小
非法输入的逻辑
笔者对于非法输入的逻辑是禁止非法输入被成功输入,添加到字符串当中去,即只能输入一个合法的式子去进行运算,所以在输入的时候,通过了大量的BOOL类型以及计数的原则去禁止非法的输入出现,下面通过代码结合来讲解。
由于我是限制输入,所以我使用多个全局变量来达到我的目的。下面展示三个比较具有代表的限制输入的例子,来展示数字,小数点,符号的限制输入。
case 114://对于数字的限制输入
{if(chuyiling) {NSUInteger a = [self.model.strNum length];[self.model.strNum deleteCharactersInRange:NSMakeRange(0, a)];ling = NO;chuyiling =NO;}//这个情况是限制在出现除以零报错后点击任意的按钮,均清空字符串内容if(ling) {[self.model.strNum deleteCharactersInRange:NSMakeRange(self.model.strNum.length-1, 1)];ling = NO;}//这个情况是当出现小数点前有一个零,为了避免出现03的情况,直接删除0,变成3这样才是合理的if(kuohaowai) {if(xiaoshu == 1) xiaoshu++;jia = NO;jian = NO;cheng = NO;chu = NO;[self.model.strNum appendFormat:@"3"];}//重置符号,同时可以使用小数点break;
}
case 115:
{if(chuyiling) {NSUInteger a = [self.model.strNum length];[self.model.strNum deleteCharactersInRange:NSMakeRange(0, a)];ling = NO;chuyiling =NO;chu = YES;jia = YES;jian = NO;cheng = YES;kuohaowai = YES;ling = NO;xiaoshu = 1;}if(xiaoshu == 2) {[self.model.strNum appendFormat:@"."];jia = YES;jian = YES;cheng = YES;chu = YES;xiaoshu = 0;ling = NO;}//限制当xiaoshu==2时才可以输入小数点,即小数点之前一定是一个数字break;
}
case 116:
{if(chuyiling) {NSUInteger a = [self.model.strNum length];[self.model.strNum deleteCharactersInRange:NSMakeRange(0, a)];ling = NO;chuyiling =NO;chu = YES;jia = YES;jian = NO;cheng = YES;kuohaowai = YES;ling = NO;xiaoshu = 1;}if(!chu){[self.model.strNum appendFormat:@"/"];chu = YES;jia = YES;jian = NO;cheng = YES;kuohaowai = YES;ling = NO;xiaoshu = 1;}//重置小数点计数为1,满足了小数点后有了符号才能出现小数点的条件,重置符号条件,避免出现多个符号。break;
}