按位操作的实际应用
- 你用什么按位操作?
- 他们为什么这么得心应手?
- 有人可以请推荐一个非常简单的教程?
虽然每个人似乎都挂在标志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
那么就不可能知道A
和B
是什么,但如果你知道其中的一个,就可以计算出另一个。 - 操作员不会遇到像乘法/除法/加法/减法的任何溢出。
我见过使用这个操作符的一些技巧:
交换两个整数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
第一行FirstItem
为LastItem
。 那是另一个记忆。
在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
是有用的。
而且,虽然这个链接:
技术性很强,涵盖了一切。
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值。
这么多的事情
- 它们可以用于通过一个有限大小的variables将许多parameter passing给一个函数。
- 优点是低内存开销或低内存成本:因此提高了性能。
- 我不能当场写一篇教程,但是我确定他们在那里。
二进制sorting。 有一些问题,实现是使用一个除法运算符而不是一个移位运算符。 这导致BS在收集超过10,000,000的大小后失败
你会因为各种原因使用它们:
- 以有效的存储方式存储(和检查!)选项标志
- 如果进行计算编程,可能需要考虑通过使用按位运算而不是math运算符来优化您的一些操作(注意副作用)
- 格雷码 !
- 创build枚举值
我相信你能想到别人。
这就是说,有时候你需要问自己:记忆和性能提升是值得付出的努力。 写完这样的代码之后,让它rest一段时间再回来。 如果您为此感到困惑,请使用更易维护的代码进行重写。
另一方面,有时使用按位操作确实很有意义(想想密码学)。
更好的是,让其他人阅读,并广泛文件。
游戏!
回到过去,我用它代表了黑白棋的棋子。 它是8X8,所以我花了long
,比如,如果你想知道哪里是全部的东西 – 你们or
两个玩家。
如果你想要一个球员的所有可能的步骤,说到右边 – 你>>
球员的片段表示一个,并AND
对手的片断,以检查是否有现在普通的1(这意味着有一个对手件对)。 然后你继续这样做。 如果你回到自己的东西 – 不动。 如果你清楚一点 – 你可以移动到那里,并捕获所有的路上。
(这种技术被广泛使用,在包括国际象棋在内的多种棋盘游戏中)