什么是位运算符?

135

我是一个纯属出于兴趣编写代码的人,没有在学术或职业环境中深入研究过它,所以这些位运算符之类的东西真的让我无从下手。

我正在阅读一篇关于JavaScript的文章,其中提到了该语言支持按位运算。我一直在看到这种操作被提及,我试图阅读了解更多关于它是什么的信息,但似乎完全不理解。那么它们是什么?提供具体的例子会很好! :D

还有几个问题 - 按位运算的一些实际应用是什么?你何时会使用它们?


2
有进一步的问题,您可能希望添加一个新的 SO 问题并引用此问题。这样您可能会得到更好的答案集。 - Greg Hewgill
9个回答

186
自从没有人提到这些为什么有用的话题:
当我处理标志时,我经常使用位运算。例如,如果您想将一系列标志传递给操作(比如,启用读写模式的File.Open()),可以将它们作为单个值传递。这可以通过在位集合(byte、short、int或long)中为每个可能的标志分配自己的位来实现。例如:
 Read: 00000001
Write: 00000010

因此,如果您想要通过读写,您需要通过 (READ | WRITE) 的方式来进行合并

00000011

然后在另一端可以进行解密,如下所示:

if ((flag & Read) != 0) { //...

这个程序可以检查

00000011 &
00000001

该函数返回

00000001

这不是 0,因此标志确实指定为 READ。

您可以使用异或运算来切换各种位。当使用标志来指定方向输入(上、下、左、右)时,我会使用这个技巧。例如,如果精灵正在水平移动,并且我想让它转身:

     Up: 00000001
   Down: 00000010
   Left: 00000100
  Right: 00001000
Current: 00000100

我只是将当前值与 (LEFT | RIGHT) 进行异或运算,这将关闭 LEFT 并打开 RIGHT。

比特移位在多种情况下都非常有用。

x << y

等同于

x * 2y

如果你需要快速乘以2的幂次方,但要注意将1位移动到最高位可能会使这个数变成负数(除非是无符号数)。在处理不同大小数据时也很有用。比如从四个字节中读取一个整数:

int val = (A << 24) | (B << 16) | (C << 8) | D;

假设 A 是最高位字节,D 是最低位字节。则结果如下所示:
A = 01000000
B = 00000101
C = 00101011
D = 11100011
val = 01000000 00000101 00101011 11100011

颜色通常使用以下方式存储(最高有效字节可能被忽略或作为 Alpha 使用):

A = 255 = 11111111
R = 21 = 00010101
G = 255 = 11111111
B = 0 = 00000000
Color = 11111111 00010101 11111111 00000000

要再次查找这些值,只需将位向右移,直到它在底部,然后掩码保留剩余的高阶位:
Int Alpha = Color >> 24
Int Red = Color >> 16 & 0xFF
Int Green = Color >> 8 & 0xFF
Int Blue = Color & 0xFF

0xFF 等同于 11111111。 因此,对于红色,您将执行以下操作:

Color >> 16 = (filled in 00000000 00000000)11111111 00010101  (removed 11111111 00000000)
00000000 00000000 11111111 00010101 &
00000000 00000000 00000000 11111111 =
00000000 00000000 00000000 00010101 (The original value)

x<<n,所以n必须是2的幂次方形式? - Ahmed C
还要记住: 异或运算符是一种可逆操作 (a^b)^b==a - undefined

31

值得注意的是,其他回答中列出的单比特真值表仅适用于一次处理一个或两个输入比特的情况。当您使用整数时会发生什么,例如:

int x = 5 & 6;
答案在每个输入的二进制展开中:

答案在每个输入的二进制展开中:

  5 = 0 0 0 0 0 1 0 1
& 6 = 0 0 0 0 0 1 1 0
---------------------
      0 0 0 0 0 1 0 0

每列中的每一对位通过“AND”函数运行,以给出底部行上相应的输出位。因此,上述表达式的答案是4。在本例中,CPU并行执行了8个单独的“AND”操作,每个操作对应一列。

我提到这一点是因为我仍然记得多年前学到这一点时的那种恍然大悟的感觉。


哇,现在这个东西有意义多了。它听起来比实际上复杂得多。谢谢。由于有很多好的答案可供选择,我不确定该选择哪一个作为正确答案,而且我也无法点赞,所以还是谢谢吧。 - click
一直在使用这个,但是不知道。谢谢。 - user12582392

28

按位运算符是一种逐位操作的运算符。

AND仅在其两个输入都为1时才返回1。

OR如果其中一个或多个输入为1,则返回1。

XOR仅在其两个输入中只有一个为1时才返回1。

NOT仅在其输入为0时才返回1。

这些可以最好地描述为真值表。输入可能性在顶部和左侧,结果位是两个输入交汇处显示的四个(对于仅具有一个输入的NOT,仅有两个)值之一。

AND|0 1      OR|0 1
---+----    ---+----
  0|0 0       0|0 1
  1|0 1       1|1 1

XOR|0 1     NOT|0 1
---+----    ---+---
  0|0 1        |1 0
  1|1 0

一个例子是如果您只想要一个整数的低4位,您可以使用与运算符和15(二进制1111)来实现:

    203: 1100 1011
AND  15: 0000 1111
------------------
 IS  11: 0000 1011

18

以下是位运算操作符,在 JavaScript 中都得到支持:

  • op1 & op2 -- AND 运算符比较两个二进制位,如果两个二进制位均为 1,则返回 1;否则返回 0。

  • op1 | op2 -- OR 运算符比较两个二进制位,如果两个二进制位不全为 0,则返回 1;否则返回 0。

  • op1 ^ op2 -- EXCLUSIVE-OR 运算符比较两个二进制位,如果两个二进制位其中一个为 1,则返回 1;否则返回 0。如果两个二进制位均为 0 或 1,则返回 0。

  • ~op1 -- COMPLEMENT 运算符用于取反操作数的所有二进制位。

  • op1 << op2 -- SHIFT LEFT 运算符将二进制位向左移动,去掉最左边的位,并将最右边的位赋值为 0。每次向左移动相当于将 op1 乘以 2。

  • op1 >> op2 -- SHIFT RIGHT 运算符将二进制位向右移动,去掉最右边的位,并将最左边的位赋值为 0。每次向右移动相当于将 op1 除以 2。保留最左边的符号位。

  • op1 >>> op2 -- SHIFT RIGHT - ZERO FILL 运算符将二进制位向右移动,去掉最右边的位,并将最左边的位赋值为 0。每次向右移动相当于将 op1 除以 2。舍弃最左边的符号位。


如果比特数是互补的 - 什么? - Andrey Tyukin
1
@AndreyTyukin,如果其中一个是1,另一个是0,则两个二进制位互补。 - Jeff Hillman
@JeffHillman 根据您在评论中的描述,1和1不是“互补”的。那么我不清楚为什么 1 | 1 得到 1 而不是 0,以及 | 应该如何与 ^ 不同。几天前我不得不将此Q/A用作重复目标,并希望经过10年后,我们可以为这类问题拥有一个更清晰的规范重复项。 - Andrey Tyukin

4
在数字计算机编程中,按位操作针对一个或多个位模式或二进制数的各个位进行操作。这是一种快速、原始的操作,由处理器直接支持,并用于操纵值以进行比较和计算。
以下是可用的操作:
- 按位与 - 按位或 - 按位取反 - 按位异或 - 等等
列表项
    AND|0 1        OR|0 1 
    ---+----      ---+---- 
      0|0 0         0|0 1 
      1|0 1         1|1 1 

   XOR|0 1        NOT|0 1 
   ---+----       ---+--- 
     0|0 1           |1 0 
     1|1 0

Eg.

    203: 1100 1011
AND  15: 0000 1111
------------------
  =  11: 0000 1011

位运算符的用途

  • 左移和右移运算符分别相当于乘以 x * 2y 和除以 x * 2y

例如:

int main()
{
     int x = 19;
     printf ("x << 1 = %d\n" , x <<1);
     printf ("x >> 1 = %d\n", x >>1);
     return 0;
}
// Output: 38 9
  • & 运算符可用于快速检查数字是奇数还是偶数

例如:

int main()
{
    int x = 19;
    (x & 1)? printf("Odd"): printf("Even");
    return 0;
 }
// Output: Odd
  • 不使用if else语句快速找到x和y的最小值

例如:

int min(int x, int y)
{
    return y ^ ((x ^ y) & - (x < y))
}
  • 十进制转二进制转换

例如:

#include <stdio.h>
int main ()
{
    int n , c , k ;
    printf("Enter an integer in decimal number system\n " ) ;
    scanf( "%d" , & n );
    printf("%d in binary number
    system is: \n " , n ) ;
    for ( c = 31; c >= 0 ; c -- )
    {
         k = n >> c ;
         if ( k & 1 )
              printf("1" ) ;
         else
              printf("0" ) ;
      }
      printf(" \n " );
      return 0 ;
}
  • XOR门加密是一种流行的技术,由于其复杂性和程序员的罕见使用。
  • 从技术面试的角度来看,按位异或运算符是最有用的运算符。

按位移位仅适用于正数。

此外,按位逻辑有广泛的应用。


"兼容性和稀有性..." - Jonathan Cross
左移和右移运算符分别等同于乘以x * 2的y次方 和 除以x * 2的y次方. 是的!https://muyiy.cn/question/program/102.html - xgqfrms
我的解决方案 https://repl.it/@xgqfrms/bitwise-operator-and-left-shift-and-right-shift - xgqfrms

4
为了更好地解释,这与所讨论的值的二进制表示有很大关系。
例如(以十进制为例): x = 8 y = 1
转换成二进制后: x = 1000 y = 0001
从这里开始,您可以执行“and”或“or”等计算操作;在这种情况下: x | y = 1000 0001 | ------ 1001
或者...在十进制中是9
希望这可以帮助您。

| 是一个或操作吗? - Si8
由于某种原因,这对我来说是最有意义的。但我仍然不确定 x | y = 1000 0001 | 这一部分。 - samayo

3
当提到“按位运算”时,有时需要澄清它不是“逻辑”运算符。
例如在JavaScript中,按位运算符将其操作数视为32位(零和一)的序列; 同时,逻辑运算符通常与布尔(逻辑)值一起使用,但也可以使用非布尔类型。
以expr1 && expr2为例。
返回expr1(如果可以转换为false);否则返回expr2。因此,当与布尔值一起使用时,&&如果两个操作数都为true,则返回true;否则,返回false。
a = "Cat" && "Dog"     // t && t returns Dog
a = 2 && 4     // t && t returns 4

正如其他人指出的那样,2&4是按位与运算,因此它将返回0。

您可以复制以下内容到test.html或其他地方进行测试:

<html>
<body>
<script>
    alert("\"Cat\" && \"Dog\" = " + ("Cat" && "Dog") + "\n"
        + "2 && 4 = " + (2 && 4) + "\n"
        + "2 & 4 = " + (2 & 4));
</script>

0

也许这样想会有所帮助。这就是AND(&)的工作原理:

它基本上是在问这两个数字都是1吗?因此,如果你有两个数字5和3,它们将被转换为二进制,计算机将会思考:

         5: 00000101
         3: 00000011

都是一:00000001 0代表假,1代表真

因此,5和3的AND运算结果为1。OR(|)运算符执行相同的操作,但只需要其中一个数字为1即可输出1,而不是两个数字都为1。


-5

我一直听说JavaScript位运算符很慢。我为我的最新博客文章做了一些测试,发现它们在几个测试中比算术运算符快40%到80%。也许它们曾经很慢,但在现代浏览器中,我喜欢它们。

我的代码中有一个情况,由于这个原因会更快,更容易阅读。我会继续留意其他情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接