關於位運算看這個就夠了

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:總結

其實位運算的應用遠遠不止這些,在算法方面適當的使用仍是頗有幫助的。參考文章文章

相關文章
相關標籤/搜索