以下按位运算符的一些实际用例是什么?
- 和
- 异或
- 不
- 要么
- 左/右移位
以下按位运算符的一些实际用例是什么?
Answers:
位字段(标志)
它们是表示某些状态由若干“是或否”属性定义的内容的最有效方法。ACL是一个很好的例子;如果您假设有4个离散权限(读,写,执行,更改策略),则最好将其存储在1个字节中,而不是浪费4个。可以将它们映射为多种语言的枚举类型,以提高便利性。
通过端口/套接字进行的通信
始终涉及校验和,奇偶校验,停止位,流控制算法等,这通常取决于单个字节的逻辑值,而不是数字值,因为介质可能仅能够在1比特处传输一位。一个时间。
压缩,加密
这两者都严重依赖于按位算法。以deflate算法为例-一切都以位为单位,而不是以字节为单位。
有限状态机
我主要说的是嵌入某些硬件中的那种状态机,尽管它们也可以在软件中找到。这些组合在本质上-他们可能从字面上可以得到“编译”到一堆逻辑门,所以他们必须表现为AND
,OR
,NOT
,等。
图形
这里几乎没有足够的空间可以进入在图形编程中使用这些运算符的每个区域。 XOR
(或^
)在这里特别有趣,因为第二次应用相同的输入将撤消第一次输入。以前的GUI过去一直依赖于此来进行选择突出显示和其他叠加,以消除对昂贵的重绘的需求。它们在慢速图形协议(即远程桌面)中仍然有用。
这些只是我提出的头几个例子-这并不是一个详尽的清单。
奇怪吗?
(value & 0x1) > 0
它可以被二(偶数)整除吗?
(value & 0x1) == 0
这是一些处理作为个别位存储的标志的常见用法。
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)) {
}
我在为CMS实现安全模型时使用了按位操作。如果用户位于适当的组中,则可以访问这些页面。一个用户可能位于多个组中,因此我们需要检查用户组和页面组之间是否有交集。因此,我们为每个组分配了唯一的2的幂次标识符,例如:
Group A = 1 --> 00000001
Group B = 2 --> 00000010
Group C = 3 --> 00000100
我们将这些值或在一起,然后将值(作为单个int)存储在页面中。例如,如果A和B组可以访问页面,则我们将值3(二进制为00000011)存储为页面访问控制。以几乎相同的方式,我们与用户存储一个ORed组标识符的值,以表示它们所在的组。
因此,要检查给定的用户是否可以访问给定的页面,您只需要将这些值与在一起并检查该值是否为非零。这是非常快的,因为此检查是在一条指令中实现的,没有循环,也没有数据库往返。
低级编程是一个很好的例子。例如,您可能需要向存储器映射的寄存器写入特定的位,以使某些硬件执行您想要的操作:
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))
htons()
和htonl()
POSIX函数可以将主机()字节序中的a short
或a 交换为网络()字节顺序。long
h
n
htonl()
32位int
值吗?long
表示多种语言中的64位。
例如,我使用它们从打包的colorvalues中获取RGB(A)值。
(a & b) >> c
速度比a % d / e
(从表示ARGB的int提取单一颜色值的两种方法)快5倍以上。十亿次迭代分别为6.7s和35.2s。
%
它不是Modulus运算符,而是Remainder运算符。它们等于正值,但与负值不同。如果提供了适当的限制(例如传递a uint
代替int
),则两个示例的速度应该相同。
当我有一堆布尔标志时,我喜欢将它们全部存储在一个int中。
我使用逐位与将它们取出。例如:
int flags;
if (flags & 0x10) {
// Turn this feature on.
}
if (flags & 0x08) {
// Turn a second feature on.
}
等等
if (flags.feature_one_is_one) { // turn on feature }
。它符合ANSI C标准,因此可移植性不成问题。
&= AND:
屏蔽特定的位。
您正在定义应显示或不显示的特定位。0x0和x将清除字节中的所有位,而0xFF不会更改x。0x0F将显示低半字节中的位。
转换:
若要将较短的变量转换为具有位标识的较长变量,则必须调整这些位,因为int中的-1为0xFFFFFFFF,而long中的-1为0xFFFFFFFFFFFFFFFF。要保留身份,请在转换后应用掩码。
| = OR
设置位。如果已设置这些位,则将对其进行独立设置。许多数据结构(位域)具有可以独立设置的标志,例如IS_HSET = 0,IS_VSET = 1。要设置标志,请应用IS_HSET |。IS_VSET(在C和汇编语言中,这很方便阅读)
^ = XOR
查找相同或不同的位。
〜=不
翻转位。
可以证明,所有可能的本地位操作都可以通过这些操作来实现。因此,如果您愿意,可以仅通过位操作来实现ADD指令。
一些很棒的技巧:
http://www.ugcs.caltech.edu/~wnoise/base2.html
http://www.jjj.de/bitwizardry/bitwizardrypage.html
= ~
,而不是|=
OR。
& = AND
-为什么要清除所有位,为什么要获得字节的未修改版本,低位半字节该怎么办?
xor
它本身。我可以想出很多原因来提取较低的半字节。尤其是如果较低的半字节是数据结构的一部分,并且您想将其用作掩码或OR
与其他结构一起使用。
加密是所有按位操作。
这是一个以字节格式从位图图像中读取颜色的示例
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.
我希望这些小例子对您有所帮助。
在当今现代语言的抽象世界中,没有太多。文件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);
}
关于自定义实现,这是一个最近的示例:我创建了一个“消息中心”,用于从分布式应用程序的一个安装向另一个安装发送安全消息。基本上,它类似于电子邮件,具有“收件箱”,“发件箱”,“已发送”等,但它也保证了已读回执的传递,因此除了“收件箱”和“已发送”之外,还有其他子文件夹。这相当于我必须定义“收件箱中的”或“已发送文件夹中的”是什么。在发送的文件夹中,我需要知道已读和未读的内容。在未读内容中,我需要知道已收到什么和未收到什么。我使用此信息来构建动态的where子句,该子句过滤本地数据源并显示适当的信息。
这是枚举的组合方式:
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在收件箱中。
这是该方法的简化版本,该方法生成并返回定义当前选定文件夹视图的过滤器:
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;
}
非常简单,但是在抽象级别上却很整洁,通常不需要按位操作。
我很惊讶没有人选择互联网时代的明显答案。计算子网的有效网络地址。
通常,按位运算比执行乘法/除法更快。因此,如果您需要将变量x乘以9,那么您会x<<3 + x
比快几个周期x*9
。如果此代码位于ISR中,则可以节省响应时间。
同样,如果要将数组用作循环队列,则使用按位操作来处理环绕检查会更快(更优雅)。(您的数组大小应为2的幂)。例如:如果要插入/删除,可以使用tail = ((tail & MASK) + 1)
代替tail = ((tail +1) < size) ? tail+1 : 0
。
同样,如果您希望错误标志将多个错误代码保存在一起,则每个位可以保存一个单独的值。您可以将其与每个单独的错误代码进行“与”检查。在Unix错误代码中使用。
同样,n位位图可以是一个非常酷而紧凑的数据结构。如果要分配大小为n的资源池,我们可以使用n位来表示当前状态。
数字x
是2的幂吗?(例如,在计数器递增且仅对数次执行操作的算法中很有用)
(x & (x - 1)) == 0
整数的最高位是哪一位x
?(例如,可用于查找大于的2的最小幂x
)
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return x - (x >>> 1); // ">>>" is unsigned right shift
1
整数的最低位是哪一位x
?(帮助查找被2整除的次数。)
x & -x
x & -x
。
按位运算符对于循环长度为2的幂的数组很有用。正如许多人提到的那样,按位运算符非常有用,并用于Flags,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;
}
这两种方法的缺点是模量运算符很昂贵,因为它会在整数除法后寻找余数。和第一种方法运行的,如果在每次迭代语句。但是,使用按位运算符,如果数组的长度是2的幂,则可以像这样0 .. length - 1
使用&
(按位和)运算符轻松生成序列i & length
。因此,知道了这一点,上面的代码就变成了
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是11
,7是111
,15是1111
等等,您就会明白。现在,如果将&
任何数字与仅由二进制数字组成的数字进行比较会发生什么?假设我们这样做:
num & 7;
如果num
小于或等于7,则结果将是num
因为&
用1填充的每个位本身就是它。如果num
大于7,则在&
运行过程中,计算机将考虑7的前导零,在&
运行后,这些零当然会保留为零,仅尾部保留。就像在9 & 7
二进制的情况下一样
1001 & 0111
结果将是0001(十进制为1)并寻址数组中的第二个元素。
它在sql关系模型中也可以派上用场,假设您有下表:BlogEntry,BlogCategory
传统上,您可以使用BlogEntryCategory表在它们之间创建nn关系,或者当没有太多BlogCategory记录时,可以像使用带标记枚举一样使用BlogEntry中的一个值链接到多个BlogCategory记录,在大多数RDBMS中也有一个非常快速的运算符可以在“标记”列中进行选择...
当您只想更改微控制器输出的某些位,而要写入的寄存器是一个字节时,您可以执行以下操作(伪代码):
char newOut = OutRegister & 0b00011111 //clear 3 msb's
newOut = newOut | 0b10100000 //write '101' to the 3 msb's
OutRegister = newOut //Update Outputs
当然,许多微控制器允许您分别更改每个位。
如果您想以一定的2的幂来计算数量mod(%),则可以使用yourNumber & 2^N-1
,在这种情况下,它与相同yourNumber % 2^N
。
number % 16 = number & 15;
number % 128 = number & 127;
这可能仅是对模数运算的一种替代方法,因为它的模数运算非常大,为2 ^ N ...但是即使如此,在我对.NET 2.0的测试中,它对模数运算的速度提升还是可以忽略的。我怀疑现代编译器已经在执行这样的优化。有人对此有更多了解吗?
%
余数运算一样,它们对负数的处理也不同。但是,如果传递uint
到%
,则当第二个参数为2的已知幂时,C#编译器实际上将使用按位AND生成机器代码。
我的问题在这里有实际用途-
仅响应第一个WM_KEYDOWN通知?
当在Windows C api中使用WM_KEYDOWN消息时,位30指定先前的键状态。如果在发送消息之前按键已按下,则值为1;如果按键已按下,则值为0。
它们主要用于按位运算(惊奇)。这是在PHP代码库中找到的一些实际示例。
字符编码:
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时,它并没有全部点击一下如何使用它。(给出了现实生活中的例子)
x == 1
和y == 2
,则x || y
得出1,然后x | y
得出0。我也看不出为什么x^true
以!x
任何方式都优于。它输入更多,习惯用法更少,如果x
碰巧不是,bool
那是不可靠的。
x^true
是优于!x
是some->complicated().member->lookup ^= true;
还有一元运算符的无化合物指派的版本。
塔河内线性解决方案使用按位运算来解决问题。
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);
}
}
该解决方案的说明可以在这里找到