思路解构
要学会写计算器,必须要学会用栈来实现四则运算,同时要实现复杂运算的前提,就增加括号、小数点情况的判断来处理 。
这里提供两篇博客参考:
四则运算
栈的四则运算(带小数点和括号)
然后根据OC的特性,(Objective-C中没有栈这种数据结构)考虑用可变数组来直接进行对栈的模拟,操作更简洁。
当然,也可以直接用C来进行算法部分,OC的环境下运行C完全没有问题。
MVC
这里使用MVC设计模式来对计算器进行设计。
MVC的具体介绍可在这里了解:MVC模式基础
显然对于算法部分一定是放在Model中进行数据处理,计算器显示屏与计算器按钮的布局写在View中,当需要在viewController布局View时,View内的布局方法被调用。ViewController就负责转发请求,处理请求。
Masonry布局
Masonry安装以及基本使用
这次使用了Masonry框架来对计算器的各个按钮元件设置位置,更方便更快捷而且不会因为机型更迭而失效或出现乱位。
_roundButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[_roundButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_roundButton];
//设置按钮圆角
_roundButton.layer.cornerRadius = 45;
//边框宽度
_roundButton.layer.borderWidth = 1;
[_roundButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_roundButton.titleLabel.font = [UIFont systemFontOfSize:45];
[_roundButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@(self.frame.size.height/3 + 100 * (i-1) + 80));
make.left.equalTo(@(10 + (j-1) * 100));
make.height.equalTo(@90);
make.width.equalTo(@90);
实际效果:
设计时的问题解决
- 计算表达式:用UILabel去充当计算器显示屏,每次按下一个按钮就用传值从View传到Controller,处理传来的值并刷新Label,就可以实现向计算器不断输入数据.
刷新时使用下面方法:
- (void)reloadInputViews API_AVAILABLE(ios(3.2));
//具体使用:
[_mainView.numLabel reloadInputViews];
//_mianView.numLabel是需要刷新的label
- 计算结果处理:
计算结果如果是用double类型转换为字符串再放到label上显示,会出现多余0的情况,可以这么处理:
lastString = [NSMutableString stringWithFormat:@"%@", @(testString.floatValue)];
用floatValue属性对testString处理来得到lastString,注意使用这一操作时需要将数据转成字符串testString。
- 限制输入:对于计算器,人类所能写出来的算法只能计算正确无误的表达式,如果输入字符串的表达式不是符合数学上的规则,就会导致算法无法正常计算,程序崩溃,我们就可以用一些合适的属性作为标志,通过判断来限制输入(也可以通过判断在合适的时机将label.text置为error)。
下面列出了一部分我写计算器时所遵循的限制:
- 加减乘除:运算符号不能打头不能结尾,不能连续输入运算符,需要输入数字来保证运算符被分隔
- 小数点:不能打头不能结尾,不能连续输入小数点,不能跟随运算符或者括号出现
- 括号:不能左右括号数量不匹配,左括号左边不能连运算符号之外的字符,右括号右边不能连运算符号之外的字符。
代码
View
这是布局按钮的完整代码
//布局按钮
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <=4; j++) {
_roundButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[_roundButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_roundButton];
//设置按钮圆角
_roundButton.layer.cornerRadius = 45;
//边框宽度
_roundButton.layer.borderWidth = 1;
[_roundButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_roundButton.titleLabel.font = [UIFont systemFontOfSize:45];
[_roundButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@(self.frame.size.height/3 + 100 * (i-1) + 80));
make.left.equalTo(@(10 + (j-1) * 100));
make.height.equalTo(@90);
make.width.equalTo(@90);
}];
if (i == 1 && j != 4) {
_roundButton.backgroundColor = [UIColor colorWithRed:0.608 green:0.608 blue:0.608 alpha:1];
[_roundButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
if (j == 1) {
[_roundButton setTitle:@"AC" forState:UIControlStateNormal];
_roundButton.titleLabel.textColor = [UIColor blackColor];
}
if (j == 2) {
[_roundButton setTitle:@"(" forState:UIControlStateNormal];
_roundButton.titleLabel.textColor = [UIColor blackColor];
}
if (j == 3) {
[_roundButton setTitle:@")" forState:UIControlStateNormal];
_roundButton.titleLabel.textColor = [UIColor blackColor];
}
_roundButton.tag = 110 + j;
}
if (i == 2 && j != 4) {
_roundButton.backgroundColor = [UIColor darkGrayColor];
if (j == 1) {
[_roundButton setTitle:@"7" forState:UIControlStateNormal];
}
if (j == 2) {
[_roundButton setTitle:@"8" forState:UIControlStateNormal];
}
if (j == 3) {
[_roundButton setTitle:@"9" forState:UIControlStateNormal];
}
_roundButton.tag = 106 + j;
}
if (i == 3 && j != 4) {
_roundButton.backgroundColor = [UIColor darkGrayColor];
if (j == 1) {
[_roundButton setTitle:@"4" forState:UIControlStateNormal];
}
if (j == 2) {
[_roundButton setTitle:@"5" forState:UIControlStateNormal];
}
if (j == 3) {
[_roundButton setTitle:@"6" forState:UIControlStateNormal];
}
_roundButton.tag = 103 + j;
}
if (i == 4 && j != 4) {
_roundButton.backgroundColor = [UIColor darkGrayColor];
if (j == 1) {
[_roundButton setTitle:@"1" forState:UIControlStateNormal];
}
if (j == 2) {
[_roundButton setTitle:@"2" forState:UIControlStateNormal];
}
if (j == 3) {
[_roundButton setTitle:@"3" forState:UIControlStateNormal];
}
_roundButton.tag = 100 + j;
}
if (j == 4) {
_roundButton.backgroundColor = [UIColor orangeColor];
if (i == 1) {
[_roundButton setTitle:@"÷" forState:UIControlStateNormal];
_roundButton.tag = 114;
} else if (i == 2) {
[_roundButton setTitle:@"×" forState:UIControlStateNormal];
_roundButton.tag = 115;
} else if (i == 3) {
[_roundButton setTitle:@"-" forState:UIControlStateNormal];
_roundButton.tag = 116;
} else {
[_roundButton setTitle:@"+" forState:UIControlStateNormal];
_roundButton.tag = 117;
}
}
}
}
//最后一行 一个零 一个点 一个等号
for (int k = 0; k < 3; k++) {
if (k == 0) {
_roundButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[_roundButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_roundButton];
//设置按钮圆角
_roundButton.layer.cornerRadius = 45;
//边框宽度
_roundButton.layer.borderWidth = 1;
[_roundButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_roundButton.titleLabel.font = [UIFont systemFontOfSize:45];
[_roundButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@(self.frame.size.height/3 + 100 * 4 + 80));
make.left.equalTo(@(10 + 0 * 100));
make.height.equalTo(@90);
make.width.equalTo(@190);
}];
[_roundButton setTitle:@"0" forState:UIControlStateNormal];
_roundButton.backgroundColor = [UIColor darkGrayColor];
_roundButton.tag = 100;
} else {
_roundButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[_roundButton addTarget:self action:@selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_roundButton];
//设置按钮圆角
_roundButton.layer.cornerRadius = 45;
//边框宽度
_roundButton.layer.borderWidth = 1;
[_roundButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_roundButton.titleLabel.font = [UIFont systemFontOfSize:45];
[_roundButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@(self.frame.size.height/3 + 100 * 4 + 80));
make.left.equalTo(@(10 + (k+1) * 100));
make.height.equalTo(@90);
make.width.equalTo(@90);
}];
if (k == 1) {
[_roundButton setTitle:@"." forState:UIControlStateNormal];
_roundButton.backgroundColor = [UIColor darkGrayColor];
_roundButton.tag = 110;
}
if (k == 2) {
[_roundButton setTitle:@"=" forState:UIControlStateNormal];
_roundButton.backgroundColor = [UIColor orangeColor];
_roundButton.tag = 118;
}
}
}
Model
#import "CalculatorModel.h"
@implementation CalculatorModel
- (void) pushNum:(double)num {
if (_numArray.count == 50) {
NSLog(@"进栈失败");
return;
}
//NSLog(@"%f", num);
[_numArray addObject:[NSString stringWithFormat:@"%f", num]];
return;
}
- (void) pushChar: (char) c {
if (_strArray.count == 50) {
NSLog(@"进栈失败");
return;
}
//NSLog(@"%c", c);
[_strArray addObject:[NSString stringWithFormat:@"%c", c]];
return;
}
//字符串转数字
- (double) transNum: (NSString*) string {
//NSLog(@"%c", [string characterAtIndex:0]);
int minus = 0;
double result;
if ([string characterAtIndex:0] == '-') {
minus = 1;
}
if (minus == 0) {
int i = 0;
double x = 0;
while ([string characterAtIndex:i] >= '0' &&
[string characterAtIndex:i] <= '9' &&
i < string.length) {
x *= 10 ;
x += (double) [string characterAtIndex:i] - '0';
//NSLog(@"%f", x);
++i;
}
//存在小数点
if (i < string.length && [string characterAtIndex:i] == '.') {
double m = 0.1;
i++;
while (i < string.length) {
x += ((double) [string characterAtIndex:i] - '0') * m;
m *= 0.1;
++i;
}
}
result = x;
} else { //
int i = 1;
double x = 0;
while ([string characterAtIndex:i] >= '0' &&
[string characterAtIndex:i] <= '9' &&
i < string.length) {
x *= 10;
x += (double)[string characterAtIndex:i] - '0';
++i;
}
//存在小数点
if (i < string.length && [string characterAtIndex:i] == '.') {
double m = 0.1;
++i;
while (i < string.length) {
x += ((double)[string characterAtIndex:i] - '0') * m;
m *= 0.1;
++i;
}
}
result = x;
}
if (minus == 0) {
return result;
} else {
return -result;
}
}
- (double) popNum {
double temp = [self transNum:_numArray[_numArray.count - 1]];
[_numArray removeLastObject];
return temp;
}
- (char) popChar {
char temp = [_strArray[_strArray.count - 1] characterAtIndex:0];
[_strArray removeLastObject];
return temp;
}
//返回数字栈栈顶
- (double) topNum {
return [self transNum:_numArray[_numArray.count - 1]];
}
//返回符号栈栈顶
- (char) topChar {
return [_strArray[_strArray.count - 1] characterAtIndex:0];
}
//比较符号优先级
- (NSInteger) compare:(char) str {
if (str == '+' || str == '-') {
return 1;
} else if (str == '*' || str == '/') {
return 2;
} else {
return 0;
}
}
//计算函数
- (double) counter:(double) first :(double) second :(char) counterStr {
double ans = 0.0;
if (counterStr == '+') {
ans = first + second;
} else if (counterStr == '-') {
ans = second - first;
} else if (counterStr == '*') {
ans = first * second;
} else {
ans = second / first;
}
return ans;
}
- (NSInteger) emptyNum{
NSInteger cnt = _numArray.count;
[_numArray removeAllObjects];
return cnt;
}
- (NSInteger) emptyChar {
NSInteger cnt = _strArray.count;
[_strArray removeAllObjects];
return cnt;
}
@end
计算表达式:
- (void) calculation {
CalculatorModel* Model = [[CalculatorModel alloc]init];
Model.numArray = [[NSMutableArray alloc]init];
Model.strArray = [[NSMutableArray alloc]init];
Model.expression = [NSMutableString stringWithString:_mainView.numLabel.text];
NSMutableString* newString = [NSMutableString stringWithString:Model.expression];
[newString appendString:@"#"];
char* cStr = (char*)[newString UTF8String];
/* NSMutableArray* arrayStack = [[NSMutableArray alloc]init];
for (int i = 0; i < Model.expression.length; i++) {
if (cStr[i] == '+' || cStr[i] == '-') {
if ([arrayStack.lastObject isEqualToString:@"*"] ||
[arrayStack.lastObject isEqualToString:@"/"]) {
}
} else if (cStr[i] == '*' || cStr[i] == '/') {
} else if (cStr[i] == ')') {
} else {
}
}
*/
int index = 0;
double x = 0;
double numberFirst, numberSecond;
char testChar;
double lastResult = 0.0;
while (cStr[index] != '#') {
x = 0;
//判断是数字入数字栈
if (cStr[index] >= '0' && cStr[index] <= '9') {
while (cStr[index] >= '0' && cStr[index] <= '9') {
x = x * 10;
x = x + cStr[index] - '0';
index++;
}
//判断小数点
if (cStr[index] == '.') {
double t = 0.1;
index++;
while (cStr[index] >= '0' && cStr[index] <= '9') {
x += (t * (cStr[index] - '0'));
t *= 0.1;
index++;
}
}
//将得到的数字送进数字栈
[Model pushNum:x];
continue;
}
//判断是符号送进符号栈,送符号进栈时确保符号栈空
if (Model.strArray.count == 0 && (cStr[index] == '+' || cStr[index] == '-' || cStr[index] == '*' || cStr[index] == '/' || cStr[index] == '(' || cStr[index] == ')')) {
[Model pushChar:cStr[index]];
index++;
continue;
}
//左括号直接入栈
if (cStr[index] == '(') {
[Model pushChar:cStr[index]];
index++;
continue;
}
//右括号需要循环取出符号栈的符号,同时循环每次取两个数字栈的数字运算后,放回数字栈,直到从符号栈取出左括号
if (cStr[index] == ')') {
while ( [Model topChar] != '(') {
testChar = [Model popChar];
numberFirst = [Model popNum];
numberSecond = [Model popNum];
lastResult = [Model counter:numberFirst :numberSecond :testChar];
//计算结果入数字栈
[Model pushNum:lastResult];
}
//左括号出栈
[Model popChar];
index++;
continue;
}
//入符号栈
if ([Model compare:cStr[index]] <= [Model compare:[Model topChar]]) {
testChar = [Model popChar];
numberFirst = [Model popNum];
numberSecond = [Model popNum];
lastResult = [Model counter:numberFirst :numberSecond :testChar];
[Model pushNum:lastResult];
}
[Model pushChar:cStr[index]];
index++;
}
//若是符号栈还不为空
while (Model.strArray.count > 0) {
testChar = [Model popChar];
numberFirst = [Model popNum];
numberSecond = [Model popNum];
lastResult = [Model counter:numberFirst :numberSecond :testChar];
[Model pushNum:lastResult];
}
NSString* testString = [NSString stringWithFormat:@"%f", lastResult];
float k = (float) lastResult;
int n = (int) k;
NSMutableString* lastString;
if ((k - n) <= 0.001 && k >= n) {
lastString = [NSMutableString stringWithFormat:@"%d", n];
} else if((n-k) <= 0.001 && k < n) {
lastString = [NSMutableString stringWithFormat:@"%d", n];
} else {
if (k>=n) {
lastString = [NSMutableString stringWithFormat:@"%@", @(testString.floatValue)];
} else {
lastString = [NSMutableString stringWithFormat:@"%@", @(testString.floatValue)];
}
}
_mainView.numLabel.text = lastString;
[_mainView.numLabel reloadInputViews];
_ACSign = 0;
}
这次的计算器bug频出,究其原因是在写限制输入的时候没有构思好就写,导致后面bug改完一个,新的就又漏出来,必要处注释缺少。以后的项目会越来越难,必须认真对待,少写bug,改正坏习惯。