iOS DevelopmentiOS Developer安卓开发

关于位运算看这个就够了

2018-01-12  本文已影响347人  茉莉儿

1:背景

在现代计算机中所有的数据都是以二进制的形式存储在设备中。即0、1两种状态,计算机对二进制数据进行的运算(+、-、*、/)都是叫位运算,即将符号位共同参与运算的运算。

我们每一种语言最终都会通过编译器转换成机器语言来执行,所以直接使用底层的语言就不需要便编译器的转换工作从而得到更高的执行效率,当然可读性可能会降低,这也是为什么汇编在大部分情况下有更快的速度。项目中合理的运用位运算能提高我们代码的执行效率。

在iOS系统中位运算多见于枚举中,其他地方很少见,因为位运算是底层的计算机语言,而在iOS开发中不管是Objective—C还是Swift都属于高级的编程语言,大量的位运算都被苹果封装了起来,我们只关心调用的接口不用关心内部的实现。

typedef NS_OPTIONS(NSUInteger, NSLayoutFormatOptions) {
    NSLayoutFormatAlignAllLeft = (1 << NSLayoutAttributeLeft),
    NSLayoutFormatAlignAllRight = (1 << NSLayoutAttributeRight),
    NSLayoutFormatAlignAllTop = (1 << NSLayoutAttributeTop),
    NSLayoutFormatAlignAllBottom = (1 << NSLayoutAttributeBottom),
    NSLayoutFormatAlignAllLeading = (1 << NSLayoutAttributeLeading),
    NSLayoutFormatAlignAllTrailing = (1 << NSLayoutAttributeTrailing),
.
.
.
.
    }

10:计算机计算原理

加法和乘法

举一个简单的例子来看下CPU是如何进行计算的,比如这行代码

int a = 35;
int b = 47;
int c = a + b;

计算两个数的和,因为在计算机中都是以二进制来进行运算,所以上面我们所给的int变量会在机器内部先转换为二进制在进行相加

35:  0 0 1 0 0 0 1 1
47:  0 0 1 0 1 1 1 1
————————————————————
82:  0 1 0 1 0 0 1 0

再来看下乘法,执行如下的代码

int a = 3;
int b = 2;
int c = a * b;

3:  0 0 0 0 0 0 1 1  *  2
————————————————————
6:  0 0 0 0 0 1 1 0

*********************************************

int a = 3;
int b = 4;
int c = a * b;

3:  0 0 0 0 0 0 1 1  *  4
————————————————————
12:  0 0 0 0 1 1 0 0

*********************************************

int a = 3;
int b = 8;
int c = a * b;

3:  0 0 0 0 0 0 1 1  *  8
————————————————————
24:  0 0 0 1 1 0 0 0

通过以上运算可以看出当用a乘b,且如果b满足2^N的时候 就相当于把a的二进制数据向左移动N位,放到代码中 我们可以这样来写 a << N,所以上面3 * 2、3 * 4、3 * 8其实是可以写成3<<1、3<<2、3<<3,运算结果都是一样的。

那假如相乘的两个数都不满足2N怎么办呢?其实这个时候编译器会将其中一个数拆分成多个满足2N的数相加的情况,打个比方

int a = 15;             int a = 15
int b = 13;      =>     int b = (4 + 8 + 1)
int c = a * b;          int c = a * b   

最后其实执行相乘运算就会变成这样 15 * 4 + 15 * 8 + 15 * 1,按照上文说的移位来转换为位运算就会变成15 << 2 + 15 << 3 + 15 << 0

减法和除法

减法也是与加法同理只不过计算机内减法操作就是加上一个数的负数形式,且在操作系统中都是以补码的形式进行操作(因为正数的源码补码反码都与本身相同)。首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减. 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了.

除法的话其实和乘法原理相同,不过乘法是左移而除法是右移,但是除法的计算量要比乘法大得多,其大部分的消耗都在拆分数值,和处理小数的步骤上,所以如果我们在进行生成变量的时候如果遇到多位的小数我们尽量把他换成string的形式,这也是为什么浮点运算会消耗大量的时钟周期(操作系统中每进行一个移位或者加法运算的过程所消耗的时间就是一个时钟周期,3.0GHz频率的CPU可以在一秒执行运算3.010241024*1024个时钟周期)

11:位运算符

使用的运算符包括下面:

含义 运算符 例子
左移 << 0011 => 0110
右移 >> 0110 => 0011
按位或 0011
------- => 1011
1011
按位与 & 0011
------- => 0011
1011
按位取反 ~ 0011 => 1100
按位异或 (相同为零不同为一) ^ 0011
------- => 1000
1011

100:颜色转换

背景

上面说了iOS中经常见到的位运算的地方是在枚举中,那么颜色转换应该是除了枚举之外第二比较常用位运算的场景。打个比方设计师再给我们出设计稿的时候通常会在设计稿上按照16进制的样子给我们标色值。但是iOS中的UIColor并不支持使用十六进制的数据来初始化。所以我们需要将十六进制的色值转换为UIColor。

原理分析

UIColor中通常是用传入RGB的数值来初始化,而且每个颜色的取值范围是十进制下的0~255,而设计同学又给的是十六进制数据,所以在操作系统中需要把这两种进制的数据统一成二进制来进行计算,这就用到了位运算。这里用一个十六进制的色值来举例子比如0xffa131我们要转换就要先理解其组成

通常来讲十六进制的颜色是按照上面的RGB的顺序排列的,但是并不固定,有时候可能会在其中加A(Alpha)值,具体情况按照设计为准,本文以通用情况举例。

综上,我们只需把对应位的值转换为10进制然后/255.0f就可得到RGB色彩值,从而转换为UIColor

转换代码

先列出代码,后续解析

- (UIColor *)colorWithHex:(long)hexColor alpha:(float)opacity
{
    //将传入的十六进制颜色0xffa131 转换为UIColor
    
    float red = ((hexColor & 0xFF0000) >> 16)/255.0f;
    float green = ((hexColor & 0xFF00) >> 8)/255.0f;
    float blue = (hexColor & 0xFF)/255.0f;
    return [UIColor colorWithRed:red green:green blue:blue alpha:opacity];
}

大概原理可以看出将RGB每个值都解析出来然后变成UIColor,先拿第一步转换红色值来说,我们按照运算顺序一步步来讲(默认将参数代入,用0xffa131代替hexColor)

101:枚举

关于枚举中使用位运算我们之前也讲过,下面我们自己来写一个枚举(伪代码)

typedef NS_OPTIONS(NSUInteger, TestOptions) {
     TestOptionOne     =    1 << 0, (000001)
    
     TestOptionTwo     =    1 << 1, (000010)
    
     TestOptionThree   =    1 << 2, (000100)
    
     TestOptionFour    =    1 << 3, (001000)

     TestOptionFive    =    1 << 4, (010000)

     TestOptionSix     =    1 << 5, (100000)
.
.
.
.

110:加密

在iOS中我们可以利用异或来进行加解密,异或的特性如下

A ^ B = C => C ^ A = B => C ^ B = A 

上文我们可以把A认为是需要加密的数据,B认为是密钥 C是加密后的数据
比如:

#include <stdio.h>
main()
{
   char a[]="MyPassword";        /*要加密的密码*/
   char b[]="cryptographic";     /*密钥*/
   int i;
   /*加密代码*/
   for(i=0;a[i]!='\0';i++)
a[i]=a[i]^b[i];
   printf("You Password encrypted: %s\n",a);
   /*解密代码*/
   for(i=0;a[i]!='\0';i++)
a[i]=a[i]^b[i];
   printf("You Password: %s\n",a);
  
}

111:其他应用

void exchange(int x , int y) 
{ 
    x ^= y; 
    y ^= x; 
    x ^= y; 
} 
void test(int x)
{
    if (x&1) {
        printf("奇数");
    } else {
        printf("偶数");
    }
}

原理很简单,因为二进制是满二进一,一旦超过1就会变0并进一位,这时候和00001做&操作一定会为0,反之不为零。这样写效率会更高。

int average(int x, int y) 
{    
    return (x&y)+((x^y)>>1); 
} 

1000:总结

其实位运算的应用远远不止这些,在算法方面适当的使用还是很有帮助的。

上一篇 下一篇

猜你喜欢

热点阅读