【问题标题】:When are bitwise operations appropriate什么时候适合按位运算
【发布时间】:2011-04-23 03:34:14
【问题描述】:

我知道什么是按位运算的基本前提(尽管希望得到“傻瓜”的解释);但是我不知道什么时候适合使用这种技术。

我的理解是older CPU architectures could perform bitwise operations faster then other operations,因此知道如何使用它们是有利的。鉴于情况不再如此;执行它们是否仍然合适?如果合适,出于什么目的和在什么条件下? (我对 C# 上下文特别感兴趣,但很高兴收到一般性答案)

【问题讨论】:

  • 你是指轮班之类的,还是所有的逻辑操作?
  • 无班次。我确实了解逻辑运算。

标签: c# algorithm bit-manipulation bit-shift


【解决方案1】:

按位运算是快速检查可能在变量上设置的标志的好方法。

以下示例突出了对Flag 枚举使用按位运算以及将位填充字段存储到数据库中的好处。然后可以轻松地检查 bitstuffed 字段以查看它是否包含来自 Flag 枚举的单个值或值的子集。

示例:

一个 User 数据库表,其中包含一个名为 Permissiontinyint 字段。该字段使用枚举创建的值填充,其值为 2^n。

[Flags]
public enum Permission : byte
{
    None = 0,
    ManageUsers = 1 << 0,
    CreateOrders = 1 << 1,
    PurchaseEquipment = 1 << 2,
    CancelOrders = 1 << 3,
}

除了用于在枚举中指定值的按位运算(在编译时完成)之外,您还可以使用枚举来检查数据库中的 Permission 字段是否包含任何可能值的子集。从数据库方面,您可以将值填充到单个字段中 - 无需为每个权限设置一列,并且在代码方面,您可以获得检查值的简单方法。

位填充示例(授予 ManageUsers 和 CreateOrders):

Permission userPermissions = Permission.ManageUsers | Permission.CreateOrders;

权限检查示例:

public bool HasPermissions(Permission userPermissions, Permission permissionsToCheckFor)
{
    return permissionsToCheckFor == Permission.None ? 
        false : 
        (userPermissions & permissionsToCheckFor) == permissionsToCheckFor;
}

【讨论】:

  • 这是一个很棒的例子。非常感谢你。 :-)
  • @Maxim-Gershkovich 如果您使用客户端在数据库中查找值,您不会立即看到 userPermissions 是什么,这不是一个缺点
  • 我同意@HCP。当有许多其他方法对此信息进行编码时,按位运算符会导致不必要的不​​可读代码。 1. 速度/尺寸优化是使用它的唯一真正原因,除非您在做嵌入式系统,否则我认为在当今大多数系统上需要这种加速的正当理由很少。对于大多数大型项目的速度而言,在可读性和调试能力之间进行权衡是非常值得的。
  • 根据MSDN:在以下情况下可能会显着节省大小: 1. 您希望将枚举用作非常频繁实例化的结构或类中的字段。 2. 您希望用户创建大型数组或枚举实例集合。 3. 你期望大量的枚举实例被序列化。
  • @m1m1k,给定这个例子,你会如何编码这个信息?您肯定不会为每个权限建议一个位列吗?此示例不适用于 speed/size 优化技术。它只是显示了一种管理权限的简单方法。这就对了。此外,您对 MSDN 的引用 引用了与 Flag 枚举完全无关的无关点!最后,按位运算与short circuit operations 一样是核心编程概念。这两个核心概念都易于理解和理解。
【解决方案2】:

问题不在于按位运算比整数运算快(尽管它们通常是这样),而在于它们是不同用于不同目的的运算。

从概念上讲,字节、short 和 int 是非常小的位数组,位运算符是布尔数组运算符。在当今的 C# 中,位运算符主要用于[Flags] 枚举和GetHashCode 的计算,但位数组的使用方式有很多。

【讨论】:

  • +1。我使用按位运算的实际示例 - Base64/Base32 编码/解码代码(数据被认为是一次编码 6 或 5 位的位数组)。更一般地,用于压缩/解压缩的代码通常使用按位操作将数据视为高位数组。
【解决方案3】:

你是对的,仅仅因为语言给你按位运算,你不应该仅仅因为你可以使用它们。我见过人们在处理简单的布尔值时使用按位运算符,但这不是他们的用途。

位运算符在处理数据片段未与字节边界对齐的数据结构时很有用。通常,这是在带宽(或一般内存占用)非常重要时完成的。我从事 RTP 视频流软件的工作,按位运算用于读取/构造 RTP 传输数据包以及读取视频编解码器流,这些流通常使用位而不是字节进行编码。

【讨论】:

    【解决方案4】:

    当空间非常宝贵时,它们在嵌入式控制情况下非常有用。例如,一个数据字节可以表示 8 个 i/o 位,并且可以使用掩码从 i/o 端口检索感兴趣的位(例如,如果 PIN0 = 1, PIN1 = 2, PIN2 = 4, PIN3 = 8 等等,那么:

    • 要获取PIN0 的值,我们可以说PORT0 &amp; PIN0 == 0
    • 要将PIN1设置为1,我们可以使用PORT0 |= PIN1
    • 要将PIN2 设置为0,我们可以说PORT0 &amp;= ~PIN2

    除了嵌入式控制,我现在很少看到这种方法被使用。在 C# 世界中,为每个感兴趣的值查看单独的布尔字段更为常见,因为开销对于现代硬件来说实际上并不是什么大问题(尽管您可能会遇到一两种情况,其中使用按位运算来保留许多这样的单个变量中的标志)。

    图形中还有一个有趣的应用。光标或边界框的一个巧妙技巧是通过将光标形状与下图进行异或来添加它们。再次执行相同的操作将得到原始图像。

    【讨论】:

      【解决方案5】:

      我发现自己不时使用位运算符的一种方法是使用二进制掩码生成给定字符串/数组的子集,如下所示:(对不起,c++ 代码)

      string s = "abcde";
      for(int i = 0; i < 1<<s.size(); i++) {
        string tmp;
        for(int j = 0; j < s.size(); j++) if(i & 1<<j) tmp.push_back(s[j]);
        cout<<tmp<<endl; 
      }
      

      您可能想查看这个富有洞察力的面向竞赛的编程教程:A bit of fun: fun with bits

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-09-13
        • 2010-10-08
        • 2012-06-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多