答案 0 :(得分:76)
虽然每个人似乎都迷上了标志用例,但这并不是按位运算符的唯一应用(虽然可能是最常见的)。此外,C#是一种足够高级别的语言,其他技术可能很少使用,但仍然值得了解它们。这是我能想到的:
<<
和>>
运算符可以快速乘以2的幂。当然,.NET JIT优化器可能会为您(以及其他语言的任何体面编译器)执行此操作,但如果你真的每微秒都在烦恼,你可能会写这个以确定。
这些运算符的另一个常见用途是将两个16位整数填充为一个32位整数。像:
int Result = (shortIntA << 16 ) | shortIntB;
这对于与Win32函数的直接接口很常见,Win32函数有时会出于遗留原因使用此技巧。
当然,当你想让没有经验的人混淆时,这些算子很有用,就像提供家庭作业问题的答案一样。 :)
在任何实际代码中,尽管使用乘法会更好,但是因为它具有更好的可读性,并且JIT将其优化为shl
和shr
指令,所以没有绩效惩罚。
与^
运算符(XOR)处理相当多的好奇技巧。由于以下属性,这实际上是一个非常强大的运算符:
A^B == B^A
A^B^A == B
A^B
,则无法确定A
和B
是什么,但如果您知道其中之一,则可以计算另一个。我使用此运算符看到了几个技巧:
交换两个没有中间变量的整数变量:
A = A^B // A is now XOR of A and B
B = A^B // B is now the original A
A = A^B // A is now the original B
双重链接列表,每个项目只有一个额外变量。这在C#中几乎没用,但它可能会对每个字节都很重要的嵌入式系统的低级编程有用。
这个想法是你跟踪第一个项目的指针;最后一项的指针;并且对于您跟踪pointer_to_previous ^ pointer_to_next
的每个项目。这样您就可以从任一端遍历列表,但开销只是传统链表的一半。这是遍历的C ++代码:
ItemStruct *CurrentItem = FirstItem, *PreviousItem=NULL;
while ( CurrentItem != NULL )
{
// Work with CurrentItem->Data
ItemStruct *NextItem = CurrentItem->XorPointers ^ PreviousItem;
PreviousItem = CurrentItem;
CurrentItem = NextItem;
}
要从最后遍历,您只需将第一行从FirstItem
更改为LastItem
即可。那是另一个记忆保存。
我在C#中定期使用^
运算符的另一个地方是我必须为我的类型计算一个复合类型的HashCode。像:
class Person
{
string FirstName;
string LastName;
int Age;
public int override GetHashCode()
{
return (FirstName == null ? 0 : FirstName.GetHashCode()) ^
(LastName == null ? 0 : LastName.GetHashCode()) ^
Age.GetHashCode();
}
}
答案 1 :(得分:66)
我在应用程序中使用按位运算符来提高安全性。我将在Enum中存储不同的级别:
[Flags]
public enum SecurityLevel
{
User = 1, // 0001
SuperUser = 2, // 0010
QuestionAdmin = 4, // 0100
AnswerAdmin = 8 // 1000
}
然后为用户分配他们的级别:
// Set User Permissions to 1010
//
// 0010
// | 1000
// ----
// 1010
User.Permissions = SecurityLevel.SuperUser | SecurityLevel.AnswerAdmin;
然后检查正在执行的操作中的权限:
// Check if the user has the required permission group
//
// 1010
// & 1000
// ----
// 1000
if( (User.Permissions & SecurityLevel.AnswerAdmin) == SecurityLevel.AnswerAdmin )
{
// Allowed
}
答案 2 :(得分:16)
我不知道解决你认为的数独是多么实际,但我们假设它是。
想象一下,你想要写一个数独求解器,甚至只是一个简单的程序,它会向你显示板子并让你自己解决这个难题,但确保这些动作是合法的。
董事会本身很可能由二维数组表示,如:
uint [, ] theBoard = new uint[9, 9];
值0
表示单元格仍为空,范围[1u,9u]中的值是板中的实际值。
现在想象一下你想检查某些举动是否合法。显然你可以通过几个循环来完成它,但是bitmasks可以让你更快地完成任务。在一个只能确保遵守规则的简单程序中,它并不重要,但在求解器中它可以。
您可以维护位掩码数组,这些数组存储有关已插入每行,每列a和每个3x3框的数字的信息。
uint [] maskForNumbersSetInRow = new uint[9];
uint [] maskForNumbersSetInCol = new uint[9];
uint [, ] maskForNumbersSetInBox = new uint[3, 3];
从数字到位模式的映射,其中一位对应于该数字集,非常简单
1 -> 00000000 00000000 00000000 00000001
2 -> 00000000 00000000 00000000 00000010
3 -> 00000000 00000000 00000000 00000100
...
9 -> 00000000 00000000 00000001 00000000
在C#中,您可以通过这种方式计算位模式(value
是uint
):
uint bitpattern = 1u << (int)(value - 1u);
在对应于bitpattern 1u
的上方00000000 00000000 00000000 00000001
行中,value - 1
向左移动。例如,如果value == 5
,则获得
00000000 00000000 00000000 00010000
一开始,每行,列和框的掩码为0
。每次在板上放一些数字时,都会更新掩码,因此设置了与新值对应的位。
假设您在第3行插入值5(行和列从0开始编号)。第3行的掩码存储在maskForNumbersSetInRow[3]
中。我们还假设在插入之前,第3行中已经有数字{1, 2, 4, 7, 9}
。掩码maskForNumbersSetInRow[3]
中的位模式如下所示:
00000000 00000000 00000001 01001011
bits above correspond to:9 7 4 21
目标是在此掩码中设置与值5对应的位。您可以使用按位或运算符(|
)来执行此操作。首先,创建一个与值5
uint bitpattern = 1u << 4; // 1u << (int)(value - 1u)
然后使用operator |
设置掩码maskForNumbersSetInRow[3]
maskForNumbersSetInRow[3] = maskForNumbersSetInRow[3] | bitpattern;
或使用更短的表格
maskForNumbersSetInRow[3] |= bitpattern;
00000000 00000000 00000001 01001011
|
00000000 00000000 00000000 00010000
=
00000000 00000000 00000001 01011011
现在,您的蒙版表示此行(第3行)中有值{1, 2, 4, 5, 7, 9}
。
如果要检查,如果行中有某个值,则可以使用operator &
检查掩码中是否设置了相应的位。如果该运算符的结果应用于掩码,并且对应于该值的位模式不为零,则该值已在该行中。如果结果为0,则该值不在行中。
例如,如果要检查值3是否在行中,可以这样做:
uint bitpattern = 1u << 2; // 1u << (int)(value - 1u)
bool value3IsInRow = ((maskForNumbersSetInRow[3] & bitpattern) != 0);
00000000 00000000 00000001 01001011 // the mask
|
00000000 00000000 00000000 00000100 // bitpattern for the value 3
=
00000000 00000000 00000000 00000000 // the result is 0. value 3 is not in the row.
以下是在电路板中设置新值,保持适当的位屏最新以及检查移动是否合法的方法。
public void insertNewValue(int row, int col, uint value)
{
if(!isMoveLegal(row, col, value))
throw ...
theBoard[row, col] = value;
uint bitpattern = 1u << (int)(value - 1u);
maskForNumbersSetInRow[row] |= bitpattern;
maskForNumbersSetInCol[col] |= bitpattern;
int boxRowNumber = row / 3;
int boxColNumber = col / 3;
maskForNumbersSetInBox[boxRowNumber, boxColNumber] |= bitpattern;
}
拥有面具,您可以检查移动是否合法:
public bool isMoveLegal(int row, int col, uint value)
{
uint bitpattern = 1u << (int)(value - 1u);
int boxRowNumber = row / 3;
int boxColNumber = col / 3;
uint combinedMask = maskForNumbersSetInRow[row] | maskForNumbersSetInCol[col]
| maskForNumbersSetInBox[boxRowNumber, boxColNumber];
return ((theBoard[row, col] == 0) && ((combinedMask & bitpattern) == 0u);
}
答案 3 :(得分:4)
Dozens of bit twiddling examples here
代码在C中,但您可以轻松地将其调整为C#
答案 4 :(得分:3)
如果你需要与硬件进行通信,你需要在某些时候使用bit twiddling。
提取像素值的RGB值。
这么多东西
答案 5 :(得分:2)
它们可以用于不同应用程序的整个负载,这里是我之前发布的一个问题,它使用按位运算:
Bitwise AND, Bitwise Inclusive OR question, in Java
对于其他示例,请查看(例如)标记的枚举。
在我的示例中,我使用按位运算将二进制数的范围从-128 ... 127更改为0..255(将其表示从有符号更改为无符号)。
这里的MSN文章 - &gt;
http://msdn.microsoft.com/en-us/library/6a71f45d%28VS.71%29.aspx
很有用。
而且,虽然这个链接:
非常技术性,它涵盖了一切。
HTH
答案 6 :(得分:2)
任何时候你有一个或多个项目组合的选项,然后按位通常是一个简单的解决方案。
一些例子包括安全位(等待Justin的样本......),安排日期等等。
答案 7 :(得分:2)
我不得不说最常见的用途之一是修改位域来压缩数据。你经常在试图使用数据包的程序中看到这一点。
的示例答案 8 :(得分:2)
我在C#中使用它们的最常见的事情之一是生成哈希码。有一些相当不错的散列方法可以使用它们。例如。对于一个带有X和Y的坐标类,我可能会使用它们:
public override int GetHashCode()
{
return x ^ ((y << 16) | y >> 16);
}
这会快速生成一个在由相等对象生成时保证相等的数字(假设相等意味着两个对象中的X和Y参数都相同),同时也不会为低值对象生成冲突模式(可能在大多数应用中最常见。)
另一种是组合标志枚举。例如。 RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase
当您对.NET这样的框架进行编码时,通常不需要进行一些低级操作(例如,在C#中,我不需要编写代码将UTF-8转换为UTF-16,它就在那里对我来说,在框架中),但当然有人必须编写该代码。
有一些比特麻烦的技术,比如四舍五入到最接近的二进制数(例如向上舍入1010到10000):
unchecked
{
--x;
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return ++x;
}
当你需要它们时它们很有用,但这种情况往往不常见。
最后,您还可以使用它们来微观优化数学,例如<< 1
而不是* 2
,但我只是说它通常是一个坏主意,因为它隐藏了真实代码的意图,几乎没有保存任何性能,并且可以隐藏一些微妙的错误。
答案 9 :(得分:1)
答案 10 :(得分:1)
二进制排序。存在实现使用除法运算符而不是位移运算符的问题。这导致BS在收集到大小超过10,000,000
之后失败答案 11 :(得分:1)
您将出于各种原因使用它们:
我相信你能想到别人。
话虽这么说,有时你需要问问自己:记忆和性能的提升值得付出努力。在编写了那种代码后,让它休息一段时间然后再回过头来。如果您对此感到困难,请使用更易于维护的代码重写。
另一方面,有时使用按位操作(想想密码术)确实很有意义。
更好的是,让其他人阅读,并广泛记录。
答案 12 :(得分:1)
游戏!
在这些日子里,我用它来代表一个黑白棋手的作品。它是8X8,所以它花了我long
类型,而且,例如,如果你想知道所有棋子在哪里 - 你or
两个玩家的棋子。
如果你想要一个玩家的所有可能的步骤,请向右说 - 你>>
玩家的棋子代表一个,AND
它与对手的棋子一起检查现在是否有共同的1(这意味着你的右边有一个对手)。然后你继续这样做。如果你回到自己的作品 - 没有动作。如果你得到一个清楚的位 - 你可以移动到那里,并在途中捕获所有的碎片
(这种技术广泛用于许多类型的棋盘游戏,包括国际象棋)