按位运算符在C中的应用及其效率?

时间:2013-06-19 00:55:58

标签: c bitwise-operators masking

我是bitwise运算符的新手。

我理解逻辑函数如何工作以获得最终结果。例如,当您按位AND两个数字时,最终结果将是这两个数字的AND1 & 0 = 0; 1 & 1 = 1; 0 & 0 = 0) 。与ORXORNOT相同。

我不明白的是他们的申请。我试着到处寻找,其中大多数只是解释按位操作是如何工作的。在所有按位运算符中,我只理解移位运算符的应用(乘法和除法)。我也遇到了掩蔽。我理解屏蔽是使用按位AND完成的,但究竟是什么目的以及我在何处以及如何使用它?

你能详细说明我如何使用遮蔽吗? ORXOR是否有类似用途?

6 个答案:

答案 0 :(得分:16)

按位运算符的低级用例是执行基数2数学运算。有一个众所周知的技巧来测试一个数字是2的幂:

if ((x & (x - 1)) == 0) {
    printf("%d is a power of 2\n", x);
}

但是,它也可以提供更高级别的功能:设置操作。您可以将一组位集合视为一组。为了解释,让一个字节中的每个位代表8个不同的项目,比如我们太阳系中的行星(冥王星不再被认为是行星,所以8就足够了!):

#define Mercury (1 << 0)
#define Venus   (1 << 1)
#define Earth   (1 << 2)
#define Mars    (1 << 3)
#define Jupiter (1 << 4)
#define Saturn  (1 << 5)
#define Uranus  (1 << 6)
#define Neptune (1 << 7)

然后,我们可以形成一组行星(子集),就像使用|

一样
unsigned char Giants = (Jupiter|Saturn|Uranus|Neptune);
unsigned char Visited = (Venus|Earth|Mars);
unsigned char BeyondTheBelt = (Jupiter|Saturn|Uranus|Neptune);
unsigned char All = (Mercury|Venus|Earth|Mars|Jupiter|Saturn|Uranus|Neptune);

现在,您可以使用&来测试两个集合是否有交集:

if (Visited & Giants) {
    puts("we might be giants");
}

^操作通常用于查看两个集合之间的不同(集合的并集减去它们的交集):

if (Giants ^ BeyondTheBelt) {
    puts("there are non-giants out there");
}

因此,将|视为联合,将&视为交集,将^视为联合减去交集。

一旦你接受了表示集合的位的想法,那么按位运算自然会帮助操作这些集合。

答案 1 :(得分:3)

按位AND的一个应用是检查是否在一个字节中设置了一个位。这在网络通信中很有用,其中协议报头尝试将尽可能多的信息打包到最小区域中以尽可能减少开销。

例如,IPv4标头利用第6个字节的前3位来判断给定的IP数据包是否可以被分段,如果是,则是否期望跟随给定数据包的更多片段。如果这些字段的大小为整数(1字节),则每个IP数据包将比必要的大21位。这每天都会通过互联网转化为大量不必要的数据。

要检索这3位,可以在位掩码旁边使用按位AND来确定它们是否已设置。

char mymask = 0x80;
if(mymask & (ipheader + 48) == mymask)
  //the second bit of the 6th byte of the ip header is set 

答案 2 :(得分:3)

小集,如前所述。你可以快速完成大量的操作,交集和联合以及(对称)差异显然是微不足道的,但是例如你也可以有效:

  1. 使用x & -x
  2. 获取集合中的最低项目
  3. 使用x & (x - 1)
  4. 删除集中的最低项目
  5. 添加小于最小当前项目的所有项目
  6. 添加高于最小当前项目的所有项目
  7. 计算他们的基数(虽然算法非常重要)
  8. 以某种方式置换集合,即更改项目的索引(并非所有排列都同样有效)
  9. 计算按字典顺序排列的下一组包含尽可能多的项目(Gosper's Hack)
  10. 1和2及其变体可用于在小图上构建有效的图算法,例如参见“计算机编程艺术4A”中的算法R.

    按位运算的其他应用包括但不限于

    • 位板,在许多棋盘游戏中很重要。没有位置的国际象棋就像没有圣诞老人的圣诞节。它不仅是一个节省空间的表示,你可以直接用位板进行非平凡的计算(参见Hyperbola Quintessence)
    • 横向堆积,以及它们在寻找最近共同祖先和计算范围最小查询中的应用。
    • 高效循环检测(Gosper's Loop Detection,见于HAKMEM)
    • 向Z曲线地址添加偏移而不解构和重构它们(参见Tesseral Arithmetic)

    这些用途更强大,但也更先进,更罕见,更具体。然而,他们表明,按位操作不仅仅是旧的低级别日子遗留下来的可爱玩具。

答案 3 :(得分:1)

示例1

如果您有10个“一起工作”的布尔值,您可以大大简化您的代码。

int B1 = 0x01;
int B2 = 0x02;
int B10 = 0x0A;

int someValue = get_a_value_from_somewhere();

if (someValue & (B1 + B10)) {
    // B1 and B10 are set
}

示例2

与硬件接口。硬件上的地址可能需要位级访问来控制接口。例如缓冲区或状态字节上的溢出位,可以告诉您8种不同的状态。使用位掩码,您可以获得所需的实际信息。

if (register & 0x80) {
    // top bit in the byte is set which may have special meaning.
}

这实际上只是示例1的一个特例。

答案 4 :(得分:1)

按位运算符在资源有限的系统中特别有用,因为每个位都可以编码布尔值。使用许多字符标记是浪费的,因为每个字符占用一个字节的空间(当它们每个可以存储8个标记时)。

通常微控制器的IO端口具有C接口,其中每个位控制8个端口中的1个。如果没有按位运算符,这些将很难控制。

关于掩蔽,通常使用&amp; amp; amp;和|:

x & 0x0F //ensures the 4 high bits are 0
x | 0x0F //ensures the 4 low bits are 1

答案 5 :(得分:0)

在微控制器应用程序中,您可以使用按位在端口之间切换。在下图中,如果我们想要在关闭其余部分的同时打开单个端口,则可以使用以下代码。

void main() 
{
     unsigned char ON = 1;
     TRISB=0;
     PORTB=0;
     while(1){
        PORTB = ON;
        delay_ms(200);
        ON = ON << 1;
        if(ON == 0) ON=1;
     }
}

enter image description here