关于位运算看这个就够了

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:计算机计算原理

####加法和乘法bash

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

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

复制代码

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

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 * 二、3 * 四、3 * 8实际上是能够写成3<<一、3<<二、3<<3,运算结果都是同样的。spa

那假如相乘的两个数都不知足2^N怎么办呢?其实这个时候编译器会将其中一个数拆分红多个知足2^N的数相加的状况,打个比方操作系统

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
------- => 1011
1011
按位取反 ~ 0011 => 1100
按位异或 (相同为零不一样为一) ^ 0011
------- => 1000
1011

100:颜色转换

背景

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

原理分析

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

  • 0x或者0X:十六进制的标识符,表示这个后面是个十六进制的数值,对数值自己没有任何意义

  • ff 颜色中的R值,转换为二进制为 1111 1111

  • a1 颜色中的G值,转换为二进制为 1010 0001

  • 31 颜色中的B值,转换为二进制为 0011 0001

  • 上述色彩值转换为二进制后为1111 1111 1010 0001 0011 0001(每一位十六进制的对应4位二进制,若是位数不够记得高位补零)

一般来说十六进制的颜色是按照上面的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)

  • 0xffa131 & 0xFF0000

    咱们知道红色值是前两位也就是ff,因此这一步咱们既然要取出红色值就要把其余位所有置零来排除干扰,这步操做即是如此,在计算机系统内是二进制来实现的,即:
    1111 1111 1010 0001 0011 0001
    ------------------------------------------- => & => 1111 1111 0000 0000 0000
    1111 1111 0000 0000 0000 0000
    这部操做作完后能够看出将除了R值以外的G值B值所有置零了,可是离最终结果还差点,由于0xFF是1111 1111,而咱们的结果后面多出了16个0,因此便有了第二步操做

  • >> 16

    将上一步获得的结果右移16位即获得0000 0000 0000 0000 1111 1111高位的零能够忽略,这也是最终的结果

  • / 255.0f

    这一步应该都知道UIColor中传入的数值范围在0~1,因此咱们要作下转换

  • 后续的G值和B值都是同样的,只是你们注意位数就能够了,值得注意的是两个二进制数进行位运算必定保证两个数的位数相同,位数不够的那个数高位要用0补齐

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)
.
.
.
.

复制代码
  • 解析 上面的枚举我后面用括号代表了位移后对应的二进制的值。这样写枚举的好处是我能够对其中选项多选好比TestOptionOne | TestOptionTwo (000001 | 000010 => 000011) 或者有其余的自定义组合。

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:其余应用

  • 记得iOS总有一道面试题在不使用第三个变量的状况下交换两个变量的值,这里用到异或的上面加解密中的特性。我有x、y两个个变量,作以下位运算操做
void exchange(int x , int y) 
{ 
    x ^= y; 
    y ^= x; 
    x ^= y; 
} 
复制代码
  • 判断一个数的奇偶性,其实咱们能够用**%2**来判断,代码量不高,可是以前讲过,除法运算的时钟周期很是多,因此代码虽然很少并不表明效率高,咱们能够用以下运算来完成:
void test(int x)
{
    if (x&1) {
        printf("奇数");
    } else {
        printf("偶数");
    }
}
复制代码

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

  • 计算两个数的平均值,一般咱们都是(x+y)/2,先不考虑效率问题,这样还会引发一个其余的问题,那就是x+y的值颇有可能溢出大于INT_MAX,因此咱们采用位运算的办法来解决便可:
int average(int x, int y) 
{    
    return (x&y)+((x^y)>>1); 
} 
复制代码

1000:总结

其实位运算的应用远远不止这些,在算法方面适当的使用仍是颇有帮助的。参考文章文章

相关文章
相关标签/搜索