Bootstrap

【iOS】仿写iOS计算器总结

思路解构

要学会写计算器,必须要学会用栈来实现四则运算,同时要实现复杂运算的前提,就增加括号、小数点情况的判断来处理 。

这里提供两篇博客参考:
四则运算
栈的四则运算(带小数点和括号)

然后根据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,改正坏习惯。

;