用枚举变量作标帜位的初学者手册
一次我在Visual C++论坛闲逛时(又一次),我不得不面对这样的事实,很多初学者对按位操作,通常是二进制,缺乏常识性了解。在我敲疼了手指给无知的人写一个长长的答案之后,很明显,我不得不通过这篇文章与社区分享这一易混淆的知识。 如果你想要对C/C++位操作知识有更深入的理解,你可以去读PJArends写的非常全面的文章《位操作介绍》。你也可以进行复杂但有效的位分析(bit hacking),阅读这篇文章:Sean Eron Anderson 写的Bit Twiddling Hacks。 我尽我所能的全部方式来呈现我能够用位操作、标帜的事和其他所有的二进制有关的东西。
事实 我们发现最常用位操作的情况之一是当一个库提供了一个枚举变量集合并且函数使用DWORD作为标帜位容器。我们以一个enum为例,如下定义:enum { STYLE1 = 1, STYLE2 = 2, STYLE3 = 4, STYLE4 = 8, STYLE5 = 16, STYLE6 = 32, STYLE7 = 64, STYLE8 = 128}; 就像我们看到的,这些常量都是2的n次幂,为了理解为什么如此选择这些常量,我们必须研究二进制的表示 1 -> 0b 00000000 00000000 00000000 00000001 2 -> 0b 00000000 00000000 00000000 00000010 4 -> 0b 00000000 00000000 00000000 00000100 8 -> 0b 00000000 00000000 00000000 00001000 16 -> 0b 00000000 00000000 00000000 00010000 32 -> 0b 00000000 00000000 00000000 00100000 64 -> 0b 00000000 00000000 00000000 01000000 128 -> 0b 00000000 00000000 00000000 10000000注意到所有的这些值,每次只有一位被置位,所有的其他的位都等于0。现在你看到了这样的一大好处就是每一位都被作代表一个功能的标帜位(这里每一位代表一种类型)。为了避免使用像标帜位一样多的布尔类型变量,现在我们来想像一种能够混合这些标帜位到一个变量中的方法。考虑下面的例子:0b 00000000 00000000 00000000 00100101类型1、3和6被置位
操作
现在我们面对一个问题。C++不是直接处理二进制的,我们不得不使用按位操作来代替。我们知道有三种原子按位操作符,按优先级升序排列:或(|),和 (&) 非 (~)。它们的行为如下: x y | x y & x ~ -------- -------- ------ 0 0 0 0 0 0 0 1 0 1 1 0 1 0 1 0 1 0 1 1 0 0 1 1 1 1 1 1
知道这些,我们现在可以用这些操作来构造上面讲过的混合标帜位。在STYLE1 | STYLE3 | STYLE6这条语句例子中,我们像这样来“或”这些常量。 0b 00000000 00000000 00000000 00000001 <- STYLE1 0b 00000000 00000000 00000000 00000100 <- STYLE3 0b 00000000 00000000 00000000 00100000 <- STYLE6----------------------------------------------- 0b 00000000 00000000 00000000 00100101 <- STYLE1 | STYLE3 | STYLE6 我们看到按位操作“或”非常像“加”(+)操作。但如果你希望用+代替|,那么你要非常小心。原因很简单,“加”1+1结果是0(有一个进位)。如果所有的常量都是严格的不同,那么不会有问题 ,但是当处理二进制使用其他的操作而不是按位操作是个坏习惯,我将不深入讨论。
DWORD按位操作
这样的混合标帜一般采用DWORD类型。但是,这不是必须条款,我们也能够用任何整型(char、short、int、long...)来实现。一个DWORD是一个无符号32位整型(就像这篇文章中的二进制表示的那些)。让我们想像这样一种情况,当我们在函数中有一个DWORD,在参数中传递另一个。调用函数怎么知道哪一位被置1了呢?这简单,往下看! 比如说我们想要知道在参数中被传递的DWORD中是否SYTLE8位被置1。我们必须有一个我们能购与参量求逻辑和的掩码。实践中,掩码与我们测试的常量是一样的,因此不需要额
外的代码来创建掩码:
DWORD parameter -> 0b 00000000 00000000 00000000 00100101 STYLE8 mask -> 0b 00000000 00000000 00000000 10000000 ---------------------------------------- Bitwise AND -> 0b 00000000 00000000 00000000 00000000 <- 0x00000000 DWORD parameter -> 0b 00000000 00000000 00000000 10100101 STYLE8 mask -> 0b 00000000 00000000 00000000 10000000 ---------------------------------------- Bitwise AND -> 0b 00000000 00000000 00000000 10000000 <- STYLE8
如果“和”操作返回0,那么这个位将被置1,否则,返回掩码自身。
好的,现在,实践中下面是你经常可以看到的void SetStyles(DWORD dwStyles) { if ((STYLE1 & dwStyles) == STYLE1) { //Apply style 1 } else if ((STYLE2 & dwStyles) == STYLE2) { //Apply style 2 } else if ((STYLE3 & dwStyles) == STYLE3) { //Apply style 3 } //etc...}
我还没有讲第三个位操作“否”(~)。它通常用在你已经有了一个位集,有一些位被置1而另一些没有,并且你不会清除其中的任何一个,下面的样例代码展示了怎样实现这些功能 void RemoveStyle5 (DWORD& dwStyles) { if ((STYLE5 & dwStyles) == STYLE5) { dwStyles &= ~STYLE5; }}
我尚未提及另一个位操作“异或”(^), 最后提及它的唯一原因是它不是原子的位操作,也就是说我们不能够同过前面的提到的位操作符来重构它的行为。#define XOR(a,b) (((a) & ~(b)) | ((b) & ~(a)))
不管怎样,这种操作很容易切换一个位。void SwitchStyle5 (DWORD& dwStyles) { dwStyles ^= STYLE5;}
链接
下面的链接是一些补充位操作的知识,如果你想要深入了解按位操作的知识An introduction to bitwise operators by PJ Arends. Bit Twiddling Hacks by Sean Eron Anderson.
结论
好的,如果你读到了这,我希望我帮助了你,如果你发现这里的错误,那么不要犹豫指出我的错误,我因此而能很快改正。
原文:http://www.codeproject.com/useritems/Binary_Guide.asp
译者:这篇文章我翻译的不是很准确,但基本意思都表达出来了。希望大家指正其中的错误forwhoever@gmail.com
