Runtime系列:位运算在OC中的取值和赋值【01】

简介: 本片文章介绍将数据(如:Bool类型的数据)存储到二进制位中,包括实现逻辑、代码示例以及各自的优缺点分析。

一、实现逻辑


我们可以用简单的Bool类型,来实现数据在二进制中的取值和赋值:


1、取值

掩码 + 位与(&)运算。


/** 
 *
 *   0000 0111
 * & 0000 0010
 *-------------
 *   0000 0010
 *  
 *  二进制转Bool类型需要两次取反(!!)
 */


2、赋值

2.1、当设置值为1时

掩码 + 位或(|)运算。


/**
 *   0000 0101
 * | 0000 0010
 *-------------
 *   0000 0111
 */


2.2、当设置值为0时

掩码取反(~) + 再进行位与(&)运算。


/**
 *   0000 0111
 * & 1111 1101
 *-------------
 *   0000 0101
 */


知道了位运算的基本规则,下面用几个例子,来了解一下位运算在OC中的应用:


二、位运算符赋值


这里我们就用高富帅(tall、rich、handsome)的取值和赋值方法,作为举例。


2.1、代码示例

Person.h


#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
// 高
- (BOOL)tall;
// 富
- (BOOL)rich;
// 帅
- (BOOL)handsome;
@end


Person.m


// 1、10进制位赋值
//#define kTallMask 1
//#define kRichMask 2
//#define kHandsomeMask 4
// 2、2进制位赋值,0b:表示2进制位
//#define kTallMask 0b00000001
//#define kRichMask 0b00000010
//#define kHandsomeMask 0b00000100
// 3、运算符赋值
#define kTallMask (1<<0)
#define kRichMask (1<<1)
#define kHandsomeMask (1<<2)
@interface Person(){
  // char:长度一个字节(8个二进制位)
    char _tallRichHandsome;// 0b 0000 0000
}
@end
@implementation Person
- (void)setTall:(BOOL)tall {
    if (tall) {
      // 掩码,位或(|)运算
        _tallRichHandsome |= kTallMask;
    } else {
      //掩码取反,进行位与(&)运算
        _tallRichHandsome &= ~kTallMask;
    }
}
- (void)setRich:(BOOL)rich {
    if (rich) {
        _tallRichHandsome |= kRichMask;
    } else {
        _tallRichHandsome &= ~kRichMask;
    }
}
- (void)setHandsome:(BOOL)handsome {
    if (handsome) {
        _tallRichHandsome |= kHandsomeMask;
    } else {
        _tallRichHandsome &= ~kHandsomeMask;
    }
}
- (BOOL)tall {
  // 掩码,位与(&)运算:两次取反(!!)返回Bool类型
    return !!(_tallRichHandsome & kTallMask);
}
- (BOOL)rich {
    return !!(_tallRichHandsome & kRichMask);
}
- (BOOL)handsome {
    return !!(_tallRichHandsome & kHandsomeMask);
}
@end


main.m


#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Leader *person = [[Leader alloc] init];
        person.tall = YES;
        person.rich = YES;
        person.handsome = YES;
        NSLog(@"-------tall:%hhd",person.tall);
        NSLog(@"-------rich:%hhd",person.rich);
        NSLog(@"-------handsome:%hhd",person.handsome);
    }
    return 0;
}


2.2、结果分析

1、mask标记:表示掩码,一般用来位运算的。

2、掩码(mask)赋值有多种方式:


1、可以用10进制位赋值的。

2、用2进制位赋值的。

3、位移运算符赋值的,这里会用到左移运算符(<<)。

其中位移运算符赋值是最简单直观的,推荐使用这种方式。


3、掩码 + 位与(&)运算转Bool类型,需要两次取反(!!)。


三、结构体+位域实现


3.1、代码示例

#import <Foundation/Foundation.h>
@interface Student : NSObject
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)tall;
- (BOOL)rich;
- (BOOL)handsome;
@end


#import "Student.h"
@interface Student(){
    struct {
        char tall : 1;
        char rich : 1;
        char handsome : 1;
    }_tallRichHandsome;
}
@end
@implementation Student
- (instancetype)init {
    if (self = [super init]) {
    }
    return self;
}
- (void)setTall:(BOOL)tall {
    _tallRichHandsome.tall = tall;
}
- (void)setRich:(BOOL)rich {
    _tallRichHandsome.rich = rich;
}
- (void)setHandsome:(BOOL)handsome {
    _tallRichHandsome.handsome = handsome;
}
- (BOOL)tall {
    return !!_tallRichHandsome.tall;
}
- (BOOL)rich {
    return !!_tallRichHandsome.rich;
}
- (BOOL)handsome {
    return !!_tallRichHandsome.handsome;
}


3.2、分析

当取值的时候,如果用如下方式实现:


- (BOOL)handsome {
    return _tallRichHandsome.handsome;
}

发现,取值时打印出来为-1,实际二进制值为(0b1111 1111),这不是我们想要的结果,怎么处理?


/**
* tallRichHandsome.tall = 0b1                一个二进制位
*  BOOL                 = 0b0000 0000        一个字节为8个二进制位 
* ----------------------------------------------------------
*                   结果 = 0b1111 1111 = 255(-1)
* 分析:当一个二进制位赋值给一个8个二进制位时,前面不足的拿最左边值(1)的填补。
*/


那么这个问题这么处理呢?


1、取值时,因为拿到的是bool类型,可以两个取反获取到想要的值(如上面👆的例子)。


第一次取反,只要不等于0,返回的都是false,二次取反,得到我们需要的值true。


2、根据位于临近填补的特点,设置占位为两位(如:0b01),得到的就是:0b0000 0001


四、结构体+位域优化


根据上面的问题处理,这里只要修改如下部分代码:


@interface Student(){
    struct {// 结构体位域长度设置为2
        char tall : 2;
        char rich : 2;
        char handsome : 2;
    }_tallRichHandsome;
}
@end
// 取值不在需要二次取反
- (BOOL)tall {
    return _tallRichHandsome.tall;
}


缺点:

结构体位域存储方式,变量在结构体中的位置,直接决定了在二进制中的存储位置。如代码有增删改操作,容易定位不准确,导致出错。


五、共用体(推荐)


这是OC内部底层实现的逻辑。


5.1、代码示例

#import <Foundation/Foundation.h>
@interface Leader : NSObject
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)tall;
- (BOOL)rich;
- (BOOL)handsome;
@end


#import "Leader.h"
#define kTallMask (1<<0)
#define kRichMask (1<<1)
#define kHandsomeMask (1<<2)
@interface Leader(){
    union {// 共用体:大家都共用一个字节
        char bits;//位
        struct {// 结构体仅是提高代码可读性,没有实际应用
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        };
    }_tallRichHandsome;
}
@end
@implementation Leader
- (void)setTall:(BOOL)tall {
    if (tall) {
        _tallRichHandsome.bits |= kTallMask;
    } else {
        _tallRichHandsome.bits &= ~kTallMask;
    }
}
- (void)setRich:(BOOL)rich {
    if (rich) {
        _tallRichHandsome.bits |= kRichMask;
    } else {
        _tallRichHandsome.bits &= ~kRichMask;
    }
}
- (void)setHandsome:(BOOL)handsome {
    if (handsome) {
        _tallRichHandsome.bits |= kHandsomeMask;
    } else {
        _tallRichHandsome.bits &= ~kHandsomeMask;
    }
}
- (BOOL)tall {
    return !!(_tallRichHandsome.bits & kTallMask);
}
- (BOOL)rich {
    return !!(_tallRichHandsome.bits & kRichMask);
}
- (BOOL)handsome {
    return !!(_tallRichHandsome.bits & kHandsomeMask);
}
@end


5.2、分析

这是位运算与结构体的位域结合方式,利用各自的优势。

那有什么优势呢?


1、位运算:定位精准,提高运算效率。

2、结构体的位域:提高代码的可读性。


六、OC中的应用


1、ISA_MASK

1、在arm64架构以前,isa指针就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。

2、从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域存储了更多信息。需要 &ISA_MASK 才能得到Class、Meta-Class对象的内存地址。


#   define ISA_MASK        0x0000000ffffffff8ULL

2、enum枚举

在OC中最常见的位运算是枚举设置


// 枚举设施孩子
typedef enum {
    ZMOptionsNone = 0,
    ZMOptionsOne = 1<<0,// 0b0001
    ZMOptionsTwo = 1<<1,// 0b0010
    ZMOptionsThree = 1<<2,// 0b0100
    ZMOptionsFour = 1<<3,// 0b1000
}ZMOptions;
// 取值
- (void)setOptions:(ZMOptions)options {
    if (options&ZMOptionsOne) {
        NSLog(@"ZMOptionsOne");
    }
    if (options&ZMOptionsTwo) {
        NSLog(@"ZMOptionsTwo");
    }
    if (options&ZMOptionsThree) {
        NSLog(@"ZMOptionsThree");
    }
    if (options&ZMOptionsFour) {
        NSLog(@"ZMOptionsFour");
    }
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // 赋值
    [self setOptions:ZMOptionsOne|ZMOptionsFour];
}
相关文章
|
4月前
|
安全 Go C语言
Go常量的定义和使用const,const特性“隐式重复前一个表达式”,以及iota枚举常量的使用
这篇文章介绍了Go语言中使用`const`定义常量的方法,包括常量的特性“隐式重复前一个表达式”,以及如何使用`iota`实现枚举常量的功能。
|
7月前
|
安全 Java
比较java枚举成员使用equal还是==
比较java枚举成员使用equal还是==
139 0
|
存储 Java C语言
JavaSE ---01 数据类型与运算符
JavaSE ---01 数据类型与运算符
42 0
|
Java
JAVA赋值和赋值扩展运算符
JAVA赋值和赋值扩展运算符
65 0
|
Java 数据安全/隐私保护
java实现请编一程序,用赋初值的方法使cl、c2、c3、c4、c5五个变量的值分别为,’C’、’h’、’i’、’n’、’a’,使c1、c2、c3、c4、c5变为’G’、’l’、’m’、’r’、’e
java实现请编一程序,用赋初值的方法使cl、c2、c3、c4、c5五个变量的值分别为,’C’、’h’、’i’、’n’、’a’,使c1、c2、c3、c4、c5变为’G’、’l’、’m’、’r’、’e
Go 编程 | 连载 03 - 布尔类型和数值类型
Go 编程 | 连载 03 - 布尔类型和数值类型
Go 编程 | 连载 03 - 布尔类型和数值类型
|
Go
go的方法值和方法表达式
go的方法值和方法表达式
109 0
|
Java
编写Java程序_定义两个方法,实现奇数偶数的判断,并计算和(有参数有返回值方法)
编写Java程序_定义两个方法,实现奇数偶数的判断,并计算和(有参数有返回值方法)
249 0
编写Java程序_定义两个方法,实现奇数偶数的判断,并计算和(有参数有返回值方法)
宏#define的三种基本定义方式:固定值,表达式,运算符。
宏#define的三种基本定义方式:固定值,表达式,运算符。
 宏#define的三种基本定义方式:固定值,表达式,运算符。

热门文章

最新文章