这其实是属于前辈级程序员们玩剩下的东西,去写win32的程序,调用win32 api的时候,经常会碰到属性状态的组合,比如这个例子:
int DisplayResourceNAMessageBox()
{
int msgboxID = MessageBox(
NULL,
(LPCWSTR)L"Resource not available\nDo you want to try again?",
(LPCWSTR)L"Account Details",
MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2
);
switch (msgboxID)
{
case IDCANCEL:
// TODO: add code
break;
case IDTRYAGAIN:
// TODO: add code
break;
case IDCONTINUE:
// TODO: add code
break;
}
return msgboxID;
}
MessageBox
这个函数的第四个参数,用于定义窗体的显示形式,可以是多种形式的组合,就像上面的例子,传入的就是MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2
这种形式的好处就在于,多个属性组合时,不需要为函数定义一大堆的入参,通过巧妙的属性值选取,配合二进制位操作,无论多少个属性,都可以通过一个参数完成。
假如某函数可接受4种状态的组合:a,b,c,d
我们定义四个常量:
const STATUS_A = 0b1 // 二进制 1
const STATUS_B = 0b10 // 二进制 10
const STATUS_C = 0b100 // 二进制 100
const STATUS_D = 0b1000 // 二进制 1000
为什么这么定义? 因为二进制的或运算(|)
有如下特征:
0b1000 | 0b10 // ==> 0b1010
0b10 | 0b101 // ==> 0b111
...
二进制位右对齐后,两个同位置的值有一个为1,则结果为1,否则结果为0。
对于我们的例子:
let status = STATUS_B | STATUS_D // 得到 status 为二进制 1010
很明显STATUS_B
对应的第二位与STATUS_D
对应的第四位被置为了1,而另两位依然是0
人眼是很容易看明白哪位为0,哪位为1,程序有什么快速的方式知道呢?
答案,依然是位运算,不过这次用与运算(&)
,特征如下:
0b101 & 0b11 // ==> 0b101
0b111 & 0b10 // ==> 0b110
...
二进制位右对齐后,两个同位置的值同时为1,则结果为1,其中一项不为1,则结果为0。
利用这个特性,当我们需要知道某状态是否被设置时,只需要入参的值与该状态的值做与运算
就好了,因为该状态的值对应位的值肯定为1,所以就有:
let status = STATUS_B | STATUS_D // 得到 status 为二进制 1010
console.log(status & STATUS_A) // 输出0
console.log(status & STATUS_B) // 输出十进制2 对应二进制 10
console.log(status & STATUS_C) // 输出0
console.log(status & STATUS_D) // 输出十进制8 对应二进制 1000
聪明的你,应该已经知道答案了吧。
完整示例:
// 采用parseInt中转一下,直接使用二进制定义更直观
const STATUS_A = 0b1
const STATUS_B = 0b10
const STATUS_C = 0b100
const STATUS_D = 0b1000
function statusHandle(statuMask){
if(statuMask & STATUS_A){
console.log('状态A被设置')
}
if(statuMask & STATUS_B){
console.log('状态B被设置')
}
if(statuMask & STATUS_C){
console.log('状态C被设置')
}
if(statuMask & STATUS_D){
console.log('状态D被设置')
}
}
/* test */
statusHandle(STATUS_C)
// 状态C被设置
statusHandle(STATUS_A | STATUS_D)
// 状态A被设置
// 状态D被设置