簡單的位運(yùn)算

一曼玩、先從常用的交換兩個變量的值說起渣刷。

一般情況下,交換變量值都是如下的方法:

int sum = a;

a = b;

b = sum;

有時會用這樣的方法:

a = a + b;

b = a - b;

a = a - b;

通過位運(yùn)算算法,則可以不用添加任何其他變量節(jié)省內(nèi)存開銷坡脐,并且不用進(jìn)行加減運(yùn)算:

a ^= b;

b = a ^ b;

a = a ^ b;

上面的代碼和第二種方式的實(shí)現(xiàn)思路類似奥务,都是將a和b合并成單個變量物独,再分別消除變量中的a和b的值(^運(yùn)算會對相同二進(jìn)制位的值置0,意味著b^b的結(jié)果等于0)汗洒。

在普遍使用高級語言開發(fā)的大環(huán)境下议纯,位運(yùn)算的實(shí)現(xiàn)更多的被封裝起來,因此大多數(shù)開發(fā)者在項目開發(fā)中不見得會使用這一機(jī)制溢谤。位運(yùn)算的時鐘周期為一瞻凤,同加減運(yùn)算一樣憨攒。位運(yùn)算是直接操作計算機(jī)中的二進(jìn)制數(shù)據(jù)位的數(shù)據(jù)。

在iOS中基本所有的位運(yùn)算都通過枚舉聲明傳值的方式將位運(yùn)算的實(shí)現(xiàn)細(xì)節(jié)隱藏了起來:

typedef NS_OPTIONS(NSUInteger, UIRectEdge) {

UIRectEdgeNone? = 0,

UIRectEdgeTop? ? = 1 << 0,

UIRectEdgeLeft? = 1 << 1,

UIRectEdgeBottom = 1 << 2,

UIRectEdgeRight? = 1 << 3,

UIRectEdgeAll? ? = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight

} NS_ENUM_AVAILABLE_IOS(7_0);

位運(yùn)算是一種極為高效乃至可以說最為高效的計算方式阀参,雖然現(xiàn)代程序開發(fā)中編譯器已經(jīng)為我們做了大量的優(yōu)化肝集,但是合理的使用位運(yùn)算可以提高代碼的可讀性以及執(zhí)行效率。

二蛛壳、基礎(chǔ)計算

在了解怎么使用位運(yùn)算之前杏瞻,筆者簡單說一下CPU處理計算的過程。如果你對CPU的計算方式有所了解衙荐,可以跳過這一節(jié)捞挥。

當(dāng)代碼int sum = 11 + 79被執(zhí)行的時候,計算機(jī)直接將兩個數(shù)的二進(jìn)制位進(jìn)行相加和進(jìn)位操作:

11:? 0 0 0 0 1 0 1 1

79:? 0 1 0 0 1 1 1 1

————————————————————

90:? 0 1 0 1 1 0 1 0

通常來說CPU執(zhí)行兩個數(shù)相加操作所花費(fèi)的時間被我們稱作一個時鐘周期忧吟,而2.0GHz頻率的CPU表示可以在一秒執(zhí)行運(yùn)算2.0*1024*1024*1024個時鐘周期砌函。相較于加法運(yùn)算,下面看一下11*2溜族、11*4的二進(jìn)制結(jié)果:

11:? 0 0 0 0 1 0 1 1? *? 2

————————————————————

22:? 0 0 0 1 0 1 1 0

11:? 0 0 0 0 1 0 1 1? *? 4

————————————————————

44:? 0 0 1 0 1 1 0 0

簡單來說讹俊,不難發(fā)現(xiàn)當(dāng)某個數(shù)乘以2的N次冪的時候,結(jié)果等同于將這個數(shù)的二進(jìn)制位置向左移動N位煌抒,在代碼中我們使用num << N表示將num的二進(jìn)制數(shù)據(jù)左移N個位置仍劈,其效果等同于下面這段代碼:

for (int idx = 0; idx < N; idx++) {

num *= 2;

}

假如相乘的兩個數(shù)都不是2的N次冪,這時候編譯器會將其中某個值分解成多個2的N次冪相加的結(jié)果進(jìn)行運(yùn)算寡壮。比如37 * 69贩疙,這時候CPU會將37分解成32+4+1,然后換算成(69<<5) + (69<<2) + (69<<0)的方式計算出結(jié)果诬像。因此屋群,計算兩個數(shù)相乘通常需要十個左右的時鐘周期。 同理坏挠,代碼num >> N的作用等效于:

for (int idx = 0; idx < N; idx++) {

num /= 2;

}

但是兩個數(shù)相除花費(fèi)的時鐘周期要比乘法還要多得多芍躏,其大部分消耗在將數(shù)值分解成多個2的N次冪上。除此之外降狠,浮點(diǎn)數(shù)涉及到的計算更為復(fù)雜对竣,這里也簡單聊聊浮點(diǎn)數(shù)的準(zhǔn)確度問題。拿float類型來說榜配,總共使用了32bit的存儲空間否纬,其中第一位表示正負(fù),2~13位表示整數(shù)部分的值蛋褥,14~32位之中分別存儲了小數(shù)位以及科學(xué)計數(shù)的標(biāo)識值(這里可能并不那么準(zhǔn)確临燃,主要是為了給讀者一個大概的介紹)。由于小數(shù)位的二進(jìn)制數(shù)據(jù)依舊保持2的N次冪特性,假如下面的二進(jìn)制屬于小數(shù)位:

1 0 1 1 1 0 0 1

那么這部分小數(shù)位的值等于:1/2 + 1/4 + 1/8 + 1/16 + 1/128 = 0.9453125膜廊。因此乏沸,當(dāng)你把一個沒有任何規(guī)律的小數(shù)例如3.1415926535898存入計算機(jī)的時候,小數(shù)點(diǎn)后面會被拆解成很多的2的N次冪進(jìn)行保存爪瓜。由于小數(shù)位總是有限的蹬跃,因此當(dāng)分解的N超出這些位數(shù)時導(dǎo)致存儲不下,就會出現(xiàn)精度偏差铆铆。另一方面蝶缀,這樣的分解計算勢必要消耗大量的時鐘周期,這也是大量的浮點(diǎn)數(shù)運(yùn)算(cell動態(tài)計算)容易引發(fā)卡頓的原因薄货。所以翁都,當(dāng)小數(shù)位過多時,改用字符串存儲是一個更優(yōu)的選擇菲驴。

三荐吵、位運(yùn)算符


使用的運(yùn)算符包括下面:

含義運(yùn)算符

左移?

右移?

按位或︳

按位并&

按位取反~

按位異或^

& 操作

0 0 1 0 1 1 1 0 ? ? ? ? ? ? ? 46 ??

1 0 0 1 1 1 0 1 1 ? ? ? ? ? ?57

?———————————————

?0 0 0 0 1 1 0 0 ? ? ? ? ? ? ?12

| ?操作

0 0 1 0 1 1 1 0? ? ? ? ? ? ? 46

1 0 0 1 1 1 0 1 ? ? ? ? ? ? 157

?———————————————?

1 0 1 1 1 1 1 1 ? ? ? ? ? ? ?191

~ 操作

0 0 1 0 1 1 1 0 ? ? ? ? ? ? ? ? 46?

———————————————?

1 1 0 1 0 0 0 1 ? ? ? ? ? ? ? ?225

^ 操作

0 0 1 0 1 1 1 0 ? ? ? ? ? ? ? ? 46?

1 0 0 1 1 1 0 1 ? ? ? ? ? ? ? 157?

———————————————?

1 0 1 1 0 0 1 1 ? ? ? ? ? ? ? 179


四、位運(yùn)算應(yīng)用

蘋果在類對象的結(jié)構(gòu)中使用了位運(yùn)算這一設(shè)計:每個對象都有一個整型類型的標(biāo)識符flags赊瞬,其中多個不同的位表示了是否存在弱引用、是否被初始化等信息贼涩,對于這些存儲的數(shù)據(jù)通過&巧涧、|等運(yùn)算符獲取出來映皆。這些在runtime源碼中都能看到血公,下面是一段偽代碼(參數(shù)請勿對號入座)

#define IS_TAGGED_POINTER (1 << 12);

#define HAS_WEAK_REFERENCE (1 << 13);

inline void objc_object::free() {

if (this->flags | HAS_WEAK_REFERENCE) {

///? set all weak reference point to nil

}

}

inline int objc_object::retainCount() {

if (this.flags | IS_TAGGED_POINTER) {

return (int)INT_MAX;

} else {

return this->retainCount;

}

}

......

借鑒蘋果的運(yùn)算操作,可以聲明一個應(yīng)用常用權(quán)限的枚舉塌计,來獲取我們的應(yīng)用權(quán)限:

typedef NS_ENUM(NSInteger, LXDAuthorizationType)

{

LXDAuthorizationTypeNone = 0,

LXDAuthorizationTypePush = 1 << 0,? ///<? ? 推送授權(quán)

LXDAuthorizationTypeLocation = 1 << 1,? ///<? ? 定位授權(quán)

LXDAuthorizationTypeCamera = 1 << 2,? ? ///<? ? 相機(jī)授權(quán)

LXDAuthorizationTypePhoto = 1 << 3,? ? ///<? ? 相冊授權(quán)

LXDAuthorizationTypeAudio = 1 << 4,? ///<? ? 麥克風(fēng)授權(quán)

LXDAuthorizationTypeContacts = 1 << 5,? ///<? ? 通訊錄授權(quán)

};

通過聲明一個全局的權(quán)限變量來保存不同的授權(quán)信息袒哥。當(dāng)應(yīng)用擁有對應(yīng)的授權(quán)時缩筛,通過|操作符保證對應(yīng)的二進(jìn)制位的值被修改成1。否則對對應(yīng)授權(quán)枚舉進(jìn)行~取反后再&操作消除二進(jìn)制位的授權(quán)表達(dá)堡称。為了完成這些工作瞎抛,建立一個工具類來獲取以及更新授權(quán)的狀態(tài):

/*!

*@brief獲取應(yīng)用授權(quán)信息工具,最低使用版本:iOS8.0

*/

NS_CLASS_AVAILABLE_IOS(8_0)@interfaceLXDAuthObtainTool :NSObject

///獲取當(dāng)前應(yīng)用權(quán)限

+ (LXDAuthorizationType)obtainAuthorization;

///更新應(yīng)用權(quán)限

+ (void)updateAuthorization;

@end

#pragma mark -LXDAuthObtainTool.m

staticLXDAuthorizationType kAuthorization;

@implementationLXDAuthObtainTool

+ (void)initialize

{

kAuthorization = LXDAuthorizationTypeNone;

[selfupdateAuthorization];

}

///獲取當(dāng)前應(yīng)用權(quán)限

+ (LXDAuthorizationType)obtainAuthorization

{

returnkAuthorization;

}

///更新應(yīng)用權(quán)限

+ (void)updateAuthorization

{

///推送

if([UIApplicationsharedApplication].currentUserNotificationSettings.types==UIUserNotificationTypeNone) {

kAuthorization &= (~LXDAuthorizationTypePush);

}else{

kAuthorization |= LXDAuthorizationTypePush;

}

///定位

if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse) {

kAuthorization |= LXDAuthorizationTypeLocation;

}else{

kAuthorization &= (~LXDAuthorizationTypeLocation);

}

///相機(jī)

if([AVCaptureDevice authorizationStatusForMediaType: AVMediaTypeVideo] == AVAuthorizationStatusAuthorized) {

kAuthorization |= LXDAuthorizationTypeCamera;

}else{

kAuthorization &= (~LXDAuthorizationTypeCamera);

}

///相冊

if([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {

kAuthorization |= LXDAuthorizationTypePhoto;

}else{

kAuthorization &= (~LXDAuthorizationTypePhoto);

}

///麥克風(fēng)

[[AVAudioSessionsharedInstance]requestRecordPermission: ^(BOOLgranted) {

if(granted) {

kAuthorization |= LXDAuthorizationTypeAudio;

}else{

kAuthorization &= (~LXDAuthorizationTypeAudio);

}

}];

///通訊錄

if([UIDevicecurrentDevice].systemVersion.doubleValue>=9) {

if([CNContactStore authorizationStatusForEntityType: CNEntityTypeContacts] == CNAuthorizationStatusAuthorized) {

kAuthorization |= LXDAuthorizationTypeContacts;

}else{

kAuthorization &= (~LXDAuthorizationTypeContacts);

}

}else{

if(ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {

kAuthorization |= LXDAuthorizationTypeContacts;

}else{

kAuthorization &= (~LXDAuthorizationTypeContacts);

}

}

}

@end

在我們需要使用某些授權(quán)的時候却紧,例如打開相冊時桐臊,直接使用&運(yùn)算符判斷權(quán)限即可:

- (void)openCamera {

LXDAuthorizationType type = [LXDAuthObtainTool obtainAuthorization];

if (type & LXDAuthorizationTypeCamera) {

///? open camera

} else {

/// alert

}

}

在數(shù)據(jù)存儲的方面位運(yùn)算擁有著占用內(nèi)存少,高效率的優(yōu)點(diǎn)晓殊,當(dāng)然位運(yùn)算能做的不僅僅是這些断凶,比如筆者項目有這樣的一個需求:用戶登錄成功之后在首頁界面請求服務(wù)器下載所有金額相關(guān)的數(shù)據(jù)。這個需求最大的問題是:

AFN2.3+版本的請求庫不支持同步請求巫俺,當(dāng)需要多個請求任務(wù)一次性執(zhí)行時认烁,判斷請求任務(wù)完成是很麻煩的一件事情。

由于NSInteger擁有8個字節(jié)64位的二進(jìn)制位,因此筆者將每一個二進(jìn)制位用來表示單個任務(wù)請求的完成狀態(tài)却嗡。已知登陸后需要同步數(shù)據(jù)的接口為N(<64)個舶沛,因此可以聲明一個全部請求任務(wù)完成后的狀態(tài)變量:

NSInteger complete = 0;

for (int idx = 0; idx < N; idx++) {

complete |= (1 << idx);

}

然后使用一個標(biāo)志變量flags用來記錄當(dāng)前任務(wù)請求的完成情況,每一個數(shù)據(jù)同步的任務(wù)完成之后對應(yīng)的二進(jìn)制位就置為1:

__block NSInteger flags = 0;

NSArray * urls = @[......];

NSArray * params = @[......];

for (NSInteger idx = 0; idx < urls.count; idx++) {

NSString * url = urls[idx];

NSDictionary * param = params[idx];

[LXDDataSyncTool syncWithUrl: url params: param complete: ^{

flags |= (1 << idx);

if ( (flags ^ complete) == 0 ) {

[self completeDataSync];

}

}];

}

參考鏈接:iOS開發(fā)之位運(yùn)算

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稽穆,一起剝皮案震驚了整個濱河市冠王,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舌镶,老刑警劉巖柱彻,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異餐胀,居然都是意外死亡哟楷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門否灾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卖擅,“玉大人,你說我怎么就攤上這事墨技〕徒祝” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵扣汪,是天一觀的道長断楷。 經(jīng)常有香客問我,道長崭别,這世上最難降的妖魔是什么冬筒? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮茅主,結(jié)果婚禮上舞痰,老公的妹妹穿的比我還像新娘。我一直安慰自己诀姚,他們只是感情好响牛,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著学搜,像睡著了一般娃善。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瑞佩,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天聚磺,我揣著相機(jī)與錄音,去河邊找鬼炬丸。 笑死瘫寝,一個胖子當(dāng)著我的面吹牛蜒蕾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播焕阿,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼咪啡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了暮屡?” 一聲冷哼從身側(cè)響起撤摸,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褒纲,沒想到半個月后准夷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莺掠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年衫嵌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彻秆。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡楔绞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唇兑,到底是詐尸還是另有隱情酒朵,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布扎附,位于F島的核電站耻讽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏帕棉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一饼记、第九天 我趴在偏房一處隱蔽的房頂上張望香伴。 院中可真熱鬧,春花似錦具则、人聲如沸即纲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽低斋。三九已至,卻和暖如春匪凡,著一層夾襖步出監(jiān)牢的瞬間膊畴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工病游, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唇跨,地道東北人稠通。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像买猖,于是被迫代替她去往敵國和親改橘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗玉控。 張土汪:刷leetcod...
    土汪閱讀 12,724評論 0 33
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法飞主,類相關(guān)的語法,內(nèi)部類的語法高诺,繼承相關(guān)的語法碌识,異常的語法,線程的語...
    子非魚_t_閱讀 31,587評論 18 399
  • 方法一 方法二 方法三 方法四添加單元測試懒叛,在單元測試方法中添加代碼丸冕,按Command+U運(yùn)行測試,控制臺會打印出...
    ThaiLanKing閱讀 1,259評論 0 0
  • 拈一抹和煦陽光融入靈魂薛窥。將心扉打開胖烛,讓生命明媚日日似春。 在這溫暖璀璨的陽光里诅迷,您可以回到天真無邪爛漫可愛童年的佩番,...
    徐淑英柔情婉淑女薔薇閱讀 425評論 1 3
  • 去了青島之后,蚊子見面后的當(dāng)天就發(fā)了一條說說罢杉,然后青島的幾個朋友就紛紛看見了趟畏。 他發(fā)來消息問我什么時候來的青島,什...
    大熊貓的小竹林閱讀 281評論 0 0