位运算符的真实世界使用案例

280

以下是一些与实际应用有关的位运算符:

  • AND(&)
  • XOR(^)
  • NOT(~)
  • OR(|)
  • 左/右移位(<< / >>)

1
我还记得当我第一次了解它们时,似乎它们只用于低级编程...但自那以后,它们已经进入我的工具箱,我经常使用它们,包括在进行“高级”编程时。现在它们对我来说就像+和*一样。 - Oak
2
@Anon:在我看来,现实世界应该指的是除了位运算符最明显的低级编程之外的任何东西。 - Olivier Lalonde
位运算的实际应用 - phuclv
可以使用二进制异或运算在排列中找到缺失的数字:https://www.martinkysel.com/codility-permmissingelem-solution/ - Janac Meena
41个回答

271
  • 位域(标志)
    它们是表示由几个“是或否”属性定义状态的最有效方式。ACL是一个很好的例子;如果您有4个离散的权限(读取,写入,执行,更改策略),最好将其存储在1个字节中,而不是浪费4个字节。这些可以映射到许多语言中的枚举类型以获得更多方便。

  • 端口/套接字通信
    总是涉及校验和、奇偶校验位、停止位、流控算法等,在这种情况下通常依赖于单个字节的逻辑值而不是数值,因为介质可能只能一次传输一位。

  • 压缩、加密
    这两个都严重依赖于按位算法。以deflate算法为例-所有内容都是按位处理的,而不是按字节处理。

  • 有限状态机
    我主要讲的是一些嵌入式硬件中的有限状态机,虽然它们也可以在软件中找到。这些是组合性的——它们可能实际上被编译成一堆逻辑门,因此它们必须表示为ANDORNOT等。

  • 图形 这里几乎没有足够的空间来涉及这些运算符在图形编程中使用的每个领域。XOR(或^)在这里特别有趣,因为第二次应用相同的输入将撤消第一次。旧的GUI曾经依靠此进行选择突出显示和其他叠加,以消除昂贵的重新绘制的需要。它们在慢速图形协议(即远程桌面)中仍然有用。

这只是我想到的前几个例子 - 这远远不是一个详尽的列表。


嗨@Aaronaught,您与我们分享了非常好的知识。我对位运算符的实际应用很感兴趣。您介意与我们分享您的参考资料吗?这将对进一步了解它非常有帮助。 - Heena Hussain
按位运算对于可向量化计算有用吗? - Aaron Franke
为了扩展图形部分(哈哈),游戏开发者经常使用位运算符来优化性能,因为在物理引擎、3D引擎、场景光照等方面有很多数学运算。 - Alex

65

它是奇数吗?

(value & 0x1) > 0

它是否可以被二整除(偶数)?

(value & 0x1) == 0

3
根据你的语言值 & 0x1 > 0,可能被解析为 value & (0x1 > 0)。 - leeeroy
3
@leeeroy - 确实如此。加了一些括号。 - Seth
2
现代优化器将自动将类似于(value% 2) != 0的表达式转换为上述表达式。https://godbolt.org/z/mYEBH4 - Ferrarezi

40

我在一个CMS的安全模块中使用了位操作。该系统有许多页面,只有当用户在相应的用户组中时才能访问这些页面。一个用户可以属于多个用户组,因此我们需要检查用户组和页面组之间是否存在交集。为此,我们为每个用户组分配了唯一的2的幂次方标识符,例如:

Group A = 1 --> 00000001
Group B = 2 --> 00000010
Group C = 3 --> 00000100

我们将这些值进行逻辑或操作,并将该值(作为单个整数)与页面一起存储。例如,如果页面可以由A组和B组访问,则将值3(在二进制中为00000011)存储为页面访问控制。同样地,我们将OR后的组标识符的值与用户一起存储,以表示他们所在的组。

因此,要检查给定用户是否可以访问给定页面,只需将这些值进行逻辑与操作,并检查该值是否非零。这非常快速,因为此检查在单个指令中实现,没有循环,也没有数据库交互。


34

下面是一些常见的处理存储为单个比特的标志的习惯用语。

enum CDRIndicators {
  Local = 1 << 0,
  External = 1 << 1,
  CallerIDMissing = 1 << 2,
  Chargeable = 1 << 3
};

unsigned int flags = 0;

设置可计费标志:

flags |= Chargeable;

清除 CallerIDMissing 标志:

flags &= ~CallerIDMissing;

测试是否设置了CallerIDMissing和Chargeable:

if((flags & (CallerIDMissing | Chargeable )) == (CallerIDMissing | Chargeable)) {

}

28
低级编程是一个很好的例子。例如,您可能需要将特定位写入内存映射寄存器,以使某些硬件执行您想要的操作:
volatile uint32_t *register = (volatile uint32_t *)0x87000000;
uint32_t          value;
uint32_t          set_bit   = 0x00010000;
uint32_t          clear_bit = 0x00001000;

value = *register;            // get current value from the register
value = value & ~clear_bit;   // clear a bit
value = value | set_bit;      // set a bit
*register = value;            // write it back to the register

此外,htonl()htons()使用&|运算符实现(对于字节序与网络序不匹配的机器):
#define htons(a) ((((a) & 0xff00) >> 8) | \
                  (((a) & 0x00ff) << 8))

#define htonl(a) ((((a) & 0xff000000) >> 24) | \
                  (((a) & 0x00ff0000) >>  8) | \
                  (((a) & 0x0000ff00) <<  8) | \
                  (((a) & 0x000000ff) << 24))

10
不是每个人都会讲机器语言。你的第二个例子是在做什么? - ChaosPandion
8
htons()htonl() 是 POSIX 函数,用于将主机(h)字节序转换为网络(n)字节序,以实现对一个 shortlong 进行端序交换。 - Carl Norum
你可以使用类似的方法,在 O(logN) 操作中完成一个比特位翻转。 - Mike DeSimone
哈哈,当我第一次看到这个时,我还以为它是汇编语言! - David G
htonl() 不是用于 32 位 int 值吗?在许多语言中,long 表示 64 位。 - Aaron Franke

22

我使用它们从打包的颜色值中获取RGB(A)的值,例如。


而且它做得非常快! - Callum Rogers
在C#中,这确实是最佳解决方案之一,无论是从可读性还是速度上来看。 - CaptainCasey
6
在我的电脑上,(a & b) >> c 提取一个整数表示的 ARGB 值中的单个颜色值比 a % d / e 快了超过5倍。针对10亿次迭代分别耗时6.7秒和35.2秒。 - Benji XVI
@BenjiXVI,C#确实针对此进行了编译器优化。你观察到速度差异的原因是,在C#中,“%”不是模运算符,而是余数运算符。对于正值,它们是等效的,但对于负值则不同。如果您提供适当的限制(例如传递uint而不是int),那么这两个示例应该具有相同的速度。 - Aaron Franke
抱歉,我知道这已经过了很长时间。你能否请给出一个示例,展示如何使用它们获取每个单独的RGB值? - SpellTheif

17

当我有一堆布尔标志时,我喜欢将它们全部存储在一个整数中。

我使用按位与(bitwise-AND)来获取它们。例如:

int flags;
if (flags & 0x10) {
  // Turn this feature on.
}

if (flags & 0x08) {
  // Turn a second feature on.
}

等等。


30
希望这些在你实际代码中是常量,而不是魔法数字 :) - Earlz
1
在非低级别的世界中使用布尔标志的一个例子是使用各种GUI平台进行操作。例如,您可以使用my_button.Style |= STYLE_DISABLED将其关闭。 - MauriceL
2
我知道这个话题与编程语言无关,但是C语言提供了一种简单的方法来使用位域,所以你可以像这样使用if (flags.feature_one_is_one) { // turn on feature }。它在ANSI C标准中,因此可移植性不应该是一个问题。 - polandeer
1
希望能够解释一下这段代码的作用,为什么没有初始化标志(flags),"将它们全部存储在一个整数中"是什么意思,使用的符号是什么... - Angelo Oparah

16

& = AND:
屏蔽特定位。
您正在定义应该显示或不显示的特定位。 0x0 & x将清除字节中的所有位,而0xFF不会更改x。 0x0F将显示较低半字节中的位。

Conversion:
为了将较短的变量转换为具有位标识的较长变量,需要调整位,因为int中的-1是0xFFFFFFFF,而long中的-1是0xFFFFFFFFFFFFFFFF。为了保留标识,转换后应用掩码。

|=OR
设置位。 如果它们已经设置,则位将独立设置。 许多数据结构(位字段)具有像IS_HSET = 0,IS_VSET = 1这样的标志,可以独立设置。 要设置标志,请应用IS_HSET | IS_VSET(在C和汇编中非常方便)

^=XOR
查找相同或不同的位。

~= NOT
翻转位。

可以证明,所有可能的本地位操作都可以通过这些操作来实现。 因此,如果您愿意,可以仅通过位操作来实现ADD指令。

一些奇妙的技巧:

http://www.ugcs.caltech.edu/~wnoise/base2.html
http://www.jjj.de/bitwizardry/bitwizardrypage.html


以下是一个链接,提供了在AS3中使用位运算符进行超快速数学运算的绝佳示例(但它可能适用于大多数编程语言):http://lab.polygonal.de/2007/05/10/bitwise-gems-fast-integer-math/。 - heavilyinvolved
我认为“NOT”应该是= ~,而不是|=,后者是OR。 - Mike DeSimone
对于& = AND - 为什么我要清除所有位,为什么我想要获取未修改的字节版本,以及对低四位应该做什么? - confused00
1
@confused00 对的,更快/更简单的将结果置零的方法是将其与自身进行xor运算。我可以想到很多理由为什么你想要提取低四位。特别是如果这个低四位是数据结构的一部分,并且你想将其用作掩码或者与另一个结构体进行OR运算。 - James M. Lay

12

加密是由位运算组成的。


4
真的吗?加密实现通常会使用位运算,但加密算法通常是用数字术语而不是位表示来描述的。 - Constantin
2
除了实现算法,你还能做什么?我很好奇。 - recursive
2
@Constantin:例如,可以查看DES实现的描述:(http://en.wikipedia.org/wiki/Data_Encryption_Standard#The_Feistel_.28F.29_function)。 - Wayne Conrad
1
@recursive,如果你是在问我个人 - 我既不设计加密算法也不实现它们。但是有些人会做很多事情,比如分析它们的理论弱点。 - Constantin
@Constantin:看一下这个,这只是众多加密算法中的一个例子,展示了(部分)加密算法通常是如何描述的:http://en.wikipedia.org/wiki/Substitution_box - SyntaxT3rr0r
对于那些对(我认为是AES)感兴趣的人,可以查看以下易读的描述:http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html - RCIX

10

你可以将它们用作快速而简单的数据哈希方式。

int a = 1230123;
int b = 1234555;
int c = 5865683;
int hash = a ^ b ^ c;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接