- 您对位运算使用了什么?
- 他们为什么这么方便?
- 有人可以推荐一个非常简单的教程吗?
Answers:
尽管每个人似乎都对标志用例感兴趣,但这并不是按位运算符的唯一应用(尽管可能是最常见的)。另外,C#是一种足够高级的语言,可能很少使用其他技术,但是仍然值得了解它们。这是我能想到的:
在<<
与>>
运营商可以迅速地乘的2场的力量,.NET JIT优化可能会为你做这个(和其他语言以及任何像样的编译器),但如果你每微秒真的心乱了,你也许可以写这个来确定。
这些运算符的另一个常用用法是将两个16位整数填充为一个32位整数。喜欢:
int Result = (shortIntA << 16 ) | shortIntB;
直接与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();
}
}
我在应用程序中使用按位运算符来确保安全性。我将在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和每个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);
在上面1u
对应于位模式的行中00000000 00000000 00000000 00000001
左移value - 1
。例如value == 5
,如果
00000000 00000000 00000000 00010000
开始时,每行,每列和每一个框的掩码为0
。每次在板上放置一些数字时,都会更新掩码,因此会设置对应于新值的位。
假设您在第3行中插入值5(行和列从0开始编号)。第3行的遮罩存储在中maskForNumbersSetInRow[3]
。我们还假设在插入之前{1, 2, 4, 7, 9}
第3行中已经有数字。掩码中的位模式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
现在,您的掩码指示{1, 2, 4, 5, 7, 9}
该行(第3行)中有值。
如果要检查,如果行中有一些值,则可以使用 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#
它们可以用于不同应用程序的全部负载,这是我之前在此处发布的一个问题,它使用按位运算:
对于其他示例,请查看(说)标记的枚举。
在我的示例中,我使用按位运算将二进制数的范围从-128 ... 127更改为0..255(将其表示形式从有符号更改为无符号)。
此处的MSN文章->
http://msdn.microsoft.com/zh-CN/library/6a71f45d%28VS.71%29.aspx
是有用的。
并且,尽管此链接:
是非常技术性的,它涵盖了所有内容。
高温超导
我在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
但是我仅是说这通常是一个坏主意,因为它隐藏了真实代码的意图,几乎没有节省任何性能,并且可以隐藏一些细微的错误。 。