前面我們已經了解了六大位操作符(&
|
~
^
<<
>>
)的用法(javascript 位運算),也整理了一些常用的位運算操作(常用位運算整理),本文我們繼續深入位運算,來了解下二進制的經典應用-標志位與掩碼。
位運算經常被用來創建、處理以及讀取標志位序列——一種類似二進制的變量。雖然可以使用變量代替標志位序列,但是這樣可以節省內存(1/32)。
例如有4個標志位:
- 標志位A: 我們有 orange
- 標志位B: 我們有 apple
- 標志位C: 我們有 banana
- 標志位D: 我們有 pear
標志位通過位序列DCBA來表示,當一個位置被置為1時,表示有該項,置為0時,表示沒有該項。例如一個變量flag=9,二進制表示為1001,就表示我們有D和A。
掩碼 (bitmask) 是一個通過與/或來讀取標志位的位序列。典型的定義每個標志位的原語掩碼如下:
var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000
新的掩碼可以在以上掩碼上使用邏輯運算創建。例如,掩碼 1011 可以通過 FLAG_A、FLAG_B 和 FLAG_D 邏輯或得到:
var mask = FLAG_A | FLAG_B | FLAG_D; // 0001 | 0010 | 1000 => 1011
某個特定的位可以通過與掩碼做邏輯與運算得到,通過與掩碼的與運算可以去掉無關的位,得到特定的位。例如,掩碼 0100 可以用來檢查標志位 C 是否被置位:(核心就是判斷某位上的數 參考常用位運算整理 下同)
// 如果我們有 banana
if (flags & FLAG_C) { // 0101 & 0100 => 0100 => true
// do stuff
}
一個有多個位被置位的掩碼表達任一/或者的含義。例如,以下兩個表達是等價的:
// 如果我們有 apple 或者 banana 至少一個
// (0101 & 0010) || (0101 & 0100) => 0000 || 0100 => true
if ((flags & FLAG_B) || (flags & FLAG_C)) {
// do stuff
}
var mask = FLAG_B | FLAG_C; // 0010 | 0100 => 0110
if (flags & mask) { // 0101 & 0110 => 0100 => true
// do stuff
}
可以通過與掩碼做或運算設置標志位,掩碼中為 1 的位可以設置對應的位。例如掩碼 1100 可用來設置位 C 和 D:(核心就是將某位變為1 )
// 我們有 banana 和 pear
var mask = FLAG_C | FLAG_D; // 0100 | 1000 => 1100
flags |= mask; // 0101 | 1100 => 1101
可以通過與掩碼做與運算清除標志位,掩碼中為 0 的位可以設置對應的位。掩碼可以通過對原語掩碼做非運算得到。例如,掩碼 1010 可以用來清除標志位 A 和 C :(核心就是將某位變為0)
// 我們沒有 orange 也沒有 banana
var mask = ~(FLAG_A | FLAG_C); // ~0101 => 1010
flags &= mask; // 1101 & 1010 => 1000
如上的掩碼同樣可以通過 ~FLAG_A & ~FLAG_C 得到(德摩根定律):
// 我們沒有 orange 也沒有 banana
var mask = ~FLAG_A & ~FLAG_C;
flags &= mask; // 1101 & 1010 => 1000
標志位可以使用異或運算切換。所有值為 1 的為可以切換對應的位。例如,掩碼 0110 可以用來切換標志位 B 和 C:(核心就是將某位取反)
// 如果我們以前沒有 apple ,那么我們現在有 apple
// 但是如果我們已經有了一個,那么現在沒有了
// 對 banana 也是相同的情況
var mask = FLAG_B | FLAG_C;
flags = flags ^ mask; // 1100 ^ 0110 => 1010
最后,所有標志位可以通過非運算翻轉:
// entering parallel universe...
flags = ~flags; // ~1010 => 0101