真实世界使用位运算符的情况
以下按位运算符的真实世界用例是什么?
- 和
- XOR
- 不
- 要么
-
位字段(标志)
它们是表示某些状态由几个“是或否”属性定义的最有效的方式。 ACL就是一个很好的例子。 如果你有4个独立的权限(读,写,执行,改变策略),最好把它存储在1个字节而不是浪费4个。为了方便起见,这些可以映射到多种语言的枚举types。 -
通过端口/sockets进行通信
总是涉及校验和,奇偶校验,停止位,stream量控制algorithm等,这些algorithm通常取决于单个字节的逻辑值而不是数值,因为介质一次只能传输一位数据。 -
压缩,encryption
这两者都严重依赖于按位algorithm。 看一个例子的deflatealgorithm – 一切都在位,而不是字节。 -
有限状态机
我主要讲的是embedded在硬件中的那种,尽pipe它们也可以在软件中find。 它们本质上是组合的 – 它们可能会被“编译”成一堆逻辑门,所以它们必须被expression为AND
,OR
,NOT
等。 -
graphics这里几乎没有足够的空间进入这些操作员在graphics编程中使用的每个区域。
XOR
(或^
)在这里特别有趣,因为第二次应用相同的input将撤消第一个。 较老的graphics用户界面曾经依赖于这个select突出显示和其他覆盖,以消除昂贵的重绘的需要。 它们在慢速graphics协议(即远程桌面)中仍然很有用。
这些只是我提出的前几个例子 – 这不是一个详尽的列表。
这是奇怪的吗?
(value & 0x1) > 0
可以被两个(偶数)整除吗?
(value & 0x1) == 0
低级编程就是一个很好的例子。 例如,你可能需要写一个特定的位到一个内存映射寄存器来使某个硬件达到你想要的状态:
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()
使用&
和|
来实现 运算符(在字节顺序 (字节顺序)不匹配networking顺序的机器上):
#define htons(a) ((((a) & 0xff00) >> 8) | \ (((a) & 0x00ff) << 8)) #define htonl(a) ((((a) & 0xff000000) >> 24) | \ (((a) & 0x00ff0000) >> 8) | \ (((a) & 0x0000ff00) << 8) | \ (((a) & 0x000000ff) << 24))
例如,我使用它们从打包色彩值中获取RGB(A)值。
以下是一些处理标记存储为单独位的常见习惯用法。
enum CDRIndicators { Local = 1 << 0, External = 1 << 1, CallerIDMissing = 1 << 2, Chargeable = 1 << 3 }; unsigned int flags = 0;
设置Chargeable标志:
flags |= Chargeable;
清除CallerIDMissing标志:
flags &= ~CallerIDMissing;
testing是否设置了CallerIDMissing和Chargeable:
if((flags & (CallerIDMissing | Chargeable )) == (CallerIDMissing | Chargeable)) { }
我已经使用按位操作来实现CMS的安全模型。 它有页面,如果用户在适当的组中,可以被用户访问。 用户可能在多个组中,所以我们需要检查用户组和页面组之间是否存在交集。 因此,我们为每个组分配了一个唯一的2的幂次标识符,例如:
Group A = 1 --> 00000001 Group B = 2 --> 00000010 Group C = 3 --> 00000100
我们或者将这些值合并在一起,并将该值(作为单个int)存储在页面中。 例如,如果一个页面可以被组A和B访问,我们将值3(二进制是00000011)作为页面访问控制。 同样,我们将ORed组标识符的值存储在一个用户中,以表示它们在哪个组中。
因此,要检查一个给定的用户是否可以访问给定的页面,只需要将这些值合并在一起,并检查该值是否为非零。 这是非常快的,因为这个检查是在一个指令中实现的,没有循环,没有数据库往返。
当我有一堆布尔标志,我喜欢把它们全部存储在一个int。
我使用按位“与”来取出它们。 例如:
int flags; if (flags & 0x10) { // Turn this feature on. } if (flags & 0x08) { // Turn a second feature on. }
等等
我刚刚在三分钟前使用了按位异或( ^
)来计算与PLC的串行通信的校验和…
你可以使用它们作为一个快速和肮脏的方式来哈希数据。
int a = 1230123; int b = 1234555; int c = 5865683; int hash = a ^ b ^ c;
encryption都是按位操作。
&= AND:
掩盖特定的位。
您正在定义应显示或不显示的特定位。 0x0&x将清除一个字节中的所有位,而0xFF不会改变x。 0x0F将显示下半字节中的位。
转换:
为了将更短的variables转换为具有位标识的更长的variables,必须调整位,因为int中的-1是0xFFFFFFFF,而long中的-1是0xFFFFFFFFFFFFFFFF。 要保留身份,请在转换后应用蒙版。
| = OR
设置位。 如果它们已经设置,这些位将被独立设置。 许多数据结构(比特字段)都有标志,如IS_HSET = 0,IS_VSET = 1,可以独立设置。 要设置标志,您应用IS_HSET | IS_VSET(在C和汇编中,这非常方便阅读)
^ = XOR
find相同或不同的位。
〜=不
翻转位。
可以看出, 所有可能的本地位操作都可以通过这些操作来实现。 所以如果你喜欢,你可以通过位操作来实现一个ADD指令。
一些精彩的黑客:
http://www.ugcs.caltech.edu/~wnoise/base2.html
http://www.jjj.de/bitwizardry/bitwizardrypage.html
Bitwise&用于屏蔽/提取某个字节的某个部分。
1个字节variables
01110010 &00001111 Bitmask of 0x0F to find out the lower nibble -------- 00000010
特别是移位运算符(<< >>)经常用于计算。
这是一个以字节格式从位图图像中读取颜色的例子
byte imagePixel = 0xCCDDEE; /* Image in RRGGBB format R=Red, G=Green, B=Blue */ //To only have red byte redColour = imagePixel & 0xFF0000; /*Bitmasking with AND operator */ //Now, we only want red colour redColour = (redColour >> 24) & 0xFF; /* This now returns a red colour between 0x00 and 0xFF.
我希望这个小例子有助于…
Base64编码就是一个例子。 Base64编码用于将二进制数据表示为用于通过电子邮件系统(和其他目的)发送的可打印字符。 Base64编码将一系列8位字节转换为6位字符查找索引。 位操作,移位,和,或者,不是对于实现Base64编码和解码所需的位操作是非常有用的。
这当然只是无数例子中的一个。
我惊讶没有人select互联网时代的明显答案。 计算子网的有效networking地址。
在现代语言的抽象世界中,并不是太多。 文件IO是一个容易想到的,虽然这是对已经实现的东西进行按位操作,并没有实现一些使用按位操作的东西。 不过,作为一个简单的例子,这段代码演示了如何在c#中移除一个文件的只读属性(以便它可以用于指定FileMode.Create的新FileStream):
//Hidden files posses some extra attibutes that make the FileStream throw an exception //even with FileMode.Create (if exists -> overwrite) so delete it and don't worry about it! if(File.Exists(targetName)) { FileAttributes attributes = File.GetAttributes(targetName); if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) File.SetAttributes(targetName, attributes & (~FileAttributes.ReadOnly)); File.Delete(targetName); }
就自定义实现而言,下面是一个最近的例子:我创build了一个“消息中心”,用于将安全消息从分布式应用程序的一个安装发送到另一个安装。 基本上,它类似于电子邮件,完整的收件箱,发件箱,发送等,但它也有保证传递阅读收据,所以除“收件箱”和“发送”之外还有其他子文件夹。 这是我要求一般定义什么是“在收件箱”或什么是“发送文件夹”中的要求。 在发送的文件夹中,我需要知道什么是已读,什么是未读。 什么是未读,我需要知道什么是收到的,什么是没有收到。 我使用这些信息来构build一个dynamicwhere子句,它过滤本地数据源并显示相应的信息。
下面是枚举如何被放在一起:
public enum MemoView :int { InboundMemos = 1, // 0000 0001 InboundMemosForMyOrders = 3, // 0000 0011 SentMemosAll = 16, // 0001 0000 SentMemosNotReceived = 48, // 0011 SentMemosReceivedNotRead = 80, // 0101 SentMemosRead = 144, // 1001 Outbox = 272, //0001 0001 0000 OutBoxErrors = 784 //0011 0001 0000 }
你看到这是什么? 通过与“收件箱”枚举值,InboundMemos(&),我知道InboundMemosForMyOrders是在收件箱。
以下是构build并返回为当前选定文件夹定义视图的filter的方法的简化版本:
private string GetFilterForView(MemoView view, DefaultableBoolean readOnly) { string filter = string.Empty; if((view & MemoView.InboundMemos) == MemoView.InboundMemos) { filter = "<inbox filter conditions>"; if((view & MemoView.InboundMemosForMyOrders) == MemoView.InboundMemosForMyOrders) { filter += "<my memo filter conditions>"; } } else if((view & MemoView.SentMemosAll) == MemoView.SentMemosAll) { //all sent items have originating system = to local filter = "<memos leaving current system>"; if((view & MemoView.Outbox) == MemoView.Outbox) { ... } else { //sent sub folders filter += "<all sent items>"; if((view & MemoView.SentMemosNotReceived) == MemoView.SentMemosNotReceived) { if((view & MemoView.SentMemosReceivedNotRead) == MemoView.SentMemosReceivedNotRead) { filter += "<not received and not read conditions>"; } else filter += "<received and not read conditions>"; } } } return filter; }
非常简单,但是在抽象层次上的简洁实现通常不需要按位操作。
似乎没有人提到定点math。
(是的,我老了,好吗?)
它也可以在SQL关系模型中得心应手,假设您有以下表格:BlogEntry,BlogCategory
传统上,您可以使用BlogEntryCategory表创build它们之间的nn关系,或者当没有那么多的BlogCategorylogging时,可以使用BlogEntry中的一个值链接到多个BlogCategorylogging,就像使用标记的枚举一样,在大多数RDBMS中也有一个非常快速的运营商select“标志”列…
数字x
是2的幂数吗? (例如,在计数器递增的algorithm中,以及仅采取对数次数的操作)
(x & (x - 1)) == 0
哪个是整数x
的最高位? (例如,这可以用来找出大于x
的2的最小幂)
x |= (x >> 1); x |= (x >> 2); x |= (x >> 4); x |= (x >> 8); x |= (x >> 16); return x - (x >>> 1); // ">>>" is unsigned right shift
哪一个是整数x
的最低位? (帮助查找被2整除的次数)
x & -x
通常按位运算比乘法/除法要快。 所以如果你需要乘以一个variablesx 9,你会做x<<3 + x
这将比x*9
快几个周期。 如果这个代码在ISR里面,你可以节省响应时间。
同样,如果你想使用一个数组作为循环队列,它会更快(更优雅)处理环绕检查位操作。 (你的数组大小应该是2的幂)。 例如:,你可以使用tail = ((tail & MASK) + 1)
而不是tail = ((tail +1) < size) ? tail+1 : 0
tail = ((tail +1) < size) ? tail+1 : 0
,如果你想插入/删除。
另外,如果你想要一个错误标志一起保存多个错误代码,每个位都可以保存一个单独的值。 你可以将它与每个单独的错误代码作为一个检查。 这在Unix错误代码中使用。
另外一个n位位图可以是一个非常酷和紧凑的数据结构。 如果你想分配一个大小为n的资源池,我们可以使用一个n位来表示当前状态。
我见过他们在基于angular色的访问控制系统中使用。
在这里我的问题有一个真正的世界使用 –
只响应第一个WM_KEYDOWN通知?
当在窗口中使用WM_KEYDOWN消息时,第30位指定了之前的键状态。 如果密钥在发送之前closures,则值为1;如果密钥已启动,则值为零
他们主要用于按位操作(惊喜)。 以下是在PHP代码库中find的一些真实世界的例子。
字符编码:
if (s <= 0 && (c & ~MBFL_WCSPLANE_MASK) == MBFL_WCSPLANE_KOI8R) {
数据结构:
ar_flags = other->ar_flags & ~SPL_ARRAY_INT_MASK;
数据库驱动:
dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READONLY);
编译器实现:
opline->extended_value = (opline->extended_value & ~ZEND_FETCH_CLASS_MASK) | ZEND_FETCH_CLASS_INTERFACE;
每当我第一次开始C编程,我理解真值表和所有这一切,但它并没有全部点击与如何实际使用它,直到我读这篇文章http://www.gamedev.net/reference/articles/article1563.asp (这给了真实生活的例子)
我不认为这是按位计算,但ruby的数组通过正常的整数位运算符定义集操作。 所以[1,2,4] & [1,2,3] # => [1,2]
。 类似地,对于a ^ b #=> set difference
和a | b #=> union
a | b #=> union
。
当你只想改变微控制器输出的某些位,但要写入的寄存器是一个字节时,你可以这样做(伪代码):
char newOut = OutRegister & 0b00011111 //clear 3 msb's newOut = newOut | 0b10100000 //write '101' to the 3 msb's OutRegister = newOut //Update Outputs
当然,许多微控制器都允许您单独更改每个位
如果你想计算你的数字mod(%)的某个2的幂,你可以使用你的yourNumber & 2^N-1
,在这种情况下,它和yourNumber % 2^N
是一样的。
number % 16 = number & 15; number % 128 = number & 127;
这可能是唯一有用的替代模数操作与非常大的红利是2 ^ N …但即使如此,它的速度提升超过模数操作是微不足道的在我的testing。 我怀疑现代编译器已经执行了这样的优化。 任何人都知道更多这个?
河内线性解决scheme使用按位运算来解决问题。
public static void linear(char start, char temp, char end, int discs) { int from,to; for (int i = 1; i < (1 << discs); i++) { from = (i & i-1) % 3; to = ((i | i-1) + 1) % 3; System.out.println(from+" => "+to); } }
这个解决scheme的解释可以在这里find
按位运算符对循环长度为2的数组非常有用。许多人提到,按位运算符非常有用,用于标志 , graphics , networking , encryption 。 不仅如此,而且速度非常快。 我个人最喜欢的用法是循环一个没有条件的数组 。 假设你有一个基于零索引的数组(例如第一个元素的索引是0),你需要无限循环它。 无限期地,我的意思是从第一个元素到最后一个返回到第一个。 实现这一点的一个方法是:
int[] arr = new int[8]; int i = 0; while (true) { print(arr[i]); i = i + 1; if (i >= arr.length) i = 0; }
这是最简单的方法,如果你想避免if语句,你可以使用如下的模数方法:
int[] arr = new int[8]; int i = 0; while (true) { print(arr[i]); i = i + 1; i = i % arr.length; }
这两种方法的缺点是模数运算是昂贵的,因为它在整数除法后寻找余数。 第一个方法在每次迭代中运行if语句。 使用按位运算符,但是如果数组的长度是2的幂,那么可以使用&
(按位和)运算符像这样的i & length
,轻松地生成像0 .. length - 1
这样的序列。 所以知道这一点,从上面的代码变成
int[] arr = new int[8]; int i = 0; while (true){ print(arr[i]); i = i + 1; i = i & (arr.length - 1); }
下面是它的工作原理。 在二进制格式中,2的幂减1的每个数字仅用1来表示。 例如二进制中的3是1111
是1111
是1111
等等,你就明白了。 现在,如果你&
任何数字相对于只包含二进制数的数字,会发生什么? 假设我们这样做:
num & 7;
如果num
小于或等于7,那么结果将是num
因为每个与1相交的位本身。 如果num
大于7,则在操作期间,计算机将考虑7的前导零,其在操作之后当然将保持为零,只有尾随部分将保留。 就像二进制中的9 & 7
,它看起来像
1001 & 0111
结果将是0001,它是十进制的1,并且是地址数组中的第二个元素。
我使用他们的多select选项,这种方式我只存储一个值而不是10或更多