按位操作的实际应用

  1. 你用什么按位操作?
  2. 他们为什么这么得心应手?
  3. 有人可以请推荐一个非常简单的教程?

虽然每个人似乎都挂在标志usecase,这不是唯一的应用程序按位运算符(虽然可能是最常见的)。 另外C#是一个足够高的语言,其他技术可能很less被使用,但它仍然值得了解。 以下是我能想到的:


<<>>运算符可以快速乘以2的幂。当然,.NET JIT优化程序可能会为您(以及另一种语言的任何体面的编译器)执行此操作,但是如果您真的烦恼每一个微秒,你可能会写这个确定。

这些运算符的另一个常见用途是将两个16位整数填充到一个32位整数中。 喜欢:

 int Result = (shortIntA << 16 ) | shortIntB; 

这对于直接与Win32函数接口是很常见的,而Win32函数有时会因为遗留原因而使用这个技巧。

当然,当你想混淆没有经验的人时,这些操作员是有用的,比如当提供作业问题的答案时。 🙂

在任何真正的代码中,尽pipe使用乘法会比使用乘法更好,因为它具有更好的可读性,而且JIT会优化它,从而不会影响性能。


不less好奇的技巧处理^运算符(XOR)。 这实际上是一个非常强大的运算符,因为具有以下属性:

  • A^B == B^A
  • A^B^A == B
  • 如果你知道A^B那么就不可能知道AB是什么,但如果你知道其中的一个,就可以计算出另一个。
  • 操作员不会遇到像乘法/除法/加法/减法的任何溢出。

我见过使用这个操作符的一些技巧:

交换两个整数variables没有中间variables:

 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 

双链表,每个项目只有一个额外的variables。 这在C#中几乎没有用处,但是对于每个字节数的embedded式系统的低级编程来说,它可能会派上用场。

这个想法是,你跟踪第一个项目的指针; 最后一项的指针; 并为每个项目你跟踪pointer_to_previous ^ pointer_to_next 。 这样你可以从任何一端遍历列表,但是开销只是传统链表的一半。 以下是遍历的C ++代码:

 ItemStruct *CurrentItem = FirstItem, *PreviousItem=NULL; while ( CurrentItem != NULL ) { // Work with CurrentItem->Data ItemStruct *NextItem = CurrentItem ^ PreviousItem; PreviousItem = CurrentItem; CurrentItem = NextItem; } 

要从最后遍历,只需要将LastItem第一行FirstItemLastItem 。 那是另一个记忆。

在C#中定期使用^运算符的另一个地方是当我必须为我的types(这是一个复合types)计算一个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(); } } 

我在应用程序中使用按位运算符来确保安全性。 我将在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 } 

我不知道如何解决你认为的数独问题,但让我们假设它是。

想象一下,你想写一个数独求解器,或者甚至只是一个简单的程序,向你展示董事会,让你自己解决这个难题,但确保这些举动是合法的。

董事会本身很可能会由一个二维数组表示:

 uint [, ] theBoard = new uint[9, 9]; 

0表示单元格仍为空,范围[1u,9u]中的值为单板中的实际值。

现在想象一下,你想检查一下是否合法。 显然,你可以用几个循环来完成,但是掩码可以让你做更快的事情。 在一个简单的程序,只是确保遵守规则,没关系,但在解决它可以。

您可以维护位掩码数组,存储有关已插入每行,每列a和每个3×3框中的数字的信息。

 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#中,你可以这样计算bitpattern( value是一个uint ):

 uint bitpattern = 1u << (int)(value - 1u); 

在对应于位模式00000000 00000000 00000000 00000001 1u上面的行向左移动value - 1 。 如果,例如value == 5 ,你会得到

00000000 00000000 00000000 00010000

开始时,每行,每列和每个框的掩码为0 。 每次你在板子上放一些数字,你就更新掩码,所以设置了新值对应的位。

假设您在第3行插入值5(行和列从0开始编号)。 第3行的掩码存储在maskForNumbersSetInRow[3] 。 我们还假定在插入之前,第3行中已经有数字{1, 2, 4, 7, 9} maskForNumbersSetInRow[3] {1, 2, 4, 7, 9} 。掩码maskForNumbersSetInRow[3]的位模式如下所示:

 00000000 00000000 00000001 01001011 bits above correspond to:9 7 4 21 

目标是在该掩码中设置对应于值5的位。 你可以使用按位或运算符( | )来完成。 首先创build一个对应于值5的位模式

 uint bitpattern = 1u << 4; // 1u << (int)(value - 1u) 

然后你使用operator | 设置掩码中的位maskForNumbersSetInRow[3]

 maskForNumbersSetInRow[3] = maskForNumbersSetInRow[3] | bitpattern; 

或使用较短的forms

 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); } 

这里有几十个有趣的例子

代码是用C语言编写的,但是你可以很容易地将它调整到C#

他们可以用于不同应用程序的整个负载,这里是我以前在这里发布的问题,它使用按位运算:

按位与,按位包含OR问题,在Java中

对于其他例子,看看(说)标记的枚举。

在我的例子中,我使用按位运算将二进制数的范围从-128 … 127更改为0..255(将其从signed变为unsigned)。

这里的MSN文章 – >

http://msdn.microsoft.com/en-us/library/6a71f45d%28VS.71%29.aspx

是有用的。

而且,虽然这个链接:

http://weblogs.asp.net/alessandro/archive/2007/10/02/bitwise-operators-in-c-or-xor-and-amp-amp-not.aspx

技术性很强,涵盖了一切。

HTH

任何时候你有一个或多个项目的组合选项,然后按位通常是一个简单的修复。

一些例子包括安全位(等待Justin的样本..),计划日等。

我不得不说,最常见的用途之一是修改位域来压缩数据。 大多数程序试图通过数据包来节省成本。

使用位域的networking压缩示例

我在C#中使用它们的最常见的事情之一就是生成hashcode。 有一些相当好的散列方法使用它们。 例如,对于可以使用两个整数的X和Y的坐标类,

 public override int GetHashCode() { return x ^ ((y << 16) | y >> 16); } 

这很快就会产生一个数字,当由一个相等的对象产生的时候保证是相等的(假定相等意味着两个对象的X和Y参数是相同的),同时也不产生低价值对象的冲突模式(很可能是在大多数应用中最常见)。

另一个是结合标志枚举。 例如RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase 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; } 

当你需要时,哪些是有用的,但这往往不是很常见。

最后,你也可以使用它们来微观地优化math,比如<< 1而不是* 2但是我只是说它通常是一个坏主意,因为它隐藏了实际代码的意图,在性能上几乎没有什么节省,可以隐藏一些微妙的错误。

如果您需要与硬件沟通,则需要在某个时候使用。

提取像素值的RGB值。

这么多的事情

  1. 它们可以用于通过一个有限大小的variables将许多parameter passing给一个函数。
  2. 优点是低内存开销或低内存成本:因此提高了性能。
  3. 我不能当场写一篇教程,但是我确定他们在那里。

二进制sorting。 有一些问题,实现是使用一个除法运算符而不是一个移位运算符。 这导致BS在收集超过10,000,000的大小后失败

你会因为各种原因使用它们:

  • 以有效的存储方式存储(和检查!)选项标志
  • 如果进行计算编程,可能需要考虑通过使用按位运算而不是math运算符来优化您的一些操作(注意副作用)
  • 格雷码 !
  • 创build枚举值

我相信你能想到别人。

这就是说,有时候你需要问自己:记忆和性能提升是值得付出的努力。 写完这样的代码之后,让它rest一段时间再回来。 如果您为此感到困惑,请使用更易维护的代码进行重写。

另一方面,有时使用按位操作确实很有意义(想想密码学)。

更好的是,让其他人阅读,并广泛文件。

游戏!

回到过去,我用它代表了黑白棋的棋子。 它是8X8,所以我花了long ,比如,如果你想知道哪里是全部的东西 – 你们or两个玩家。
如果你想要一个球员的所有可能的步骤,说到右边 – 你>>球员的片段表示一个,并AND对手的片断,以检查是否有现在普通的1(这意味着有一个对手件对)。 然后你继续这样做。 如果你回到自己的东西 – 不动。 如果你清楚一点 – 你可以移动到那里,并捕获所有的路上。
(这种技术被广泛使用,在包括国际象棋在内的多种棋盘游戏中)