C#中的两个左尖括号 "<<" 表示什么?

43

基本上,标题中所提出的问题。我正在查看MVC 2源代码:

[Flags]
public enum HttpVerbs {
    Get = 1 << 0,
    Post = 1 << 1,
    Put = 1 << 2,
    Delete = 1 << 3,
    Head = 1 << 4
}

我只是好奇双左尖括号<<有什么作用。


如果问题涉及“>>”,那么可能的答案必须包括C#通用编程部分。 - CoffeDeveloper
15个回答

130

当你写作时

1 << n

您将比特组合 000000001 左移 n 次,从而将 n 放入2的指数中:

您将比特组合 000000001左移n次,从而将n放入2的指数中:

2^n

所以

1 << 10

真的是这样

1024

如果有一个包含5个项目的列表,那么您的for循环将循环32次。


8
是的。位移运算只能使用基数为2(劣势),但比 Math.Pow() 更快(优势),后者更加灵活,可以使用浮点数作为基数和指数。位移运算可以成为一条单独的机器码指令。 - pid
29
哈哈,是的。还有你的雇主再也放不了你了:你的代码跑得飞快,其他开发者都看不懂。 - Robert Fricke
7
我觉得很难相信性能会如此重要,以至于一次使用 Math.Pow 计算循环次数就不再更有意义。这样你就不用担心让开发人员遇到转移混乱的问题了。 - Gusdor
6
@Plutor 不知道是可以的(这就是语言规范的用途)。如果发现"难以理解"、"难以阅读"、"其他开发者无法理解"等问题,那么这表明您不愿意学习除了 +、-、*、/ 之外的任何东西,或者完全不熟悉整数及其表示方法。变通的方法Math.pow(n,2)非常丑陋。例如,在Java中,这将涉及到从double进行转换。 - Ingo
26
我不确定为什么每个人都把这看作一种“优化”。对我来说,这是表达二的幂次方的自然习惯用语,我永远不会考虑以另一种方式编写它。使用库函数执行二的幂计算使其更难阅读(可读性差的前置表示法,而不是value operator value这种更易读的表示法)。说真的,你真的认为Convert.ToInt32(Math.Pow(2,value))1<<value更易读了吗?现在操作符的含义已经被解释了。 - Jules
显示剩余24条评论

82

它被称为 左移位 运算符。请查看文档

左移位运算符会使第一个操作数的比特模式向左移动由第二个操作数指定的比特位数。移位操作所空出的比特位将填充为零。这是一种逻辑移位,而不是移位和旋转操作。

下面是一个演示 左移位 运算符的简单例子:

for (int i = 0; i < 10; i++)
{
    var shiftedValue = 1 << i;
    Console.WriteLine(" 1 << {0} = {1} \t Binary: {2}",i,shiftedValue,Convert.ToString(shiftedValue,2).PadLeft(10,'0'));
}

//Output:

// 1 << 0 = 1      Binary: 0000000001
// 1 << 1 = 2      Binary: 0000000010
// 1 << 2 = 4      Binary: 0000000100
// 1 << 3 = 8      Binary: 0000001000
// 1 << 4 = 16     Binary: 0000010000
// 1 << 5 = 32     Binary: 0000100000
// 1 << 6 = 64     Binary: 0001000000
// 1 << 7 = 128    Binary: 0010000000
// 1 << 8 = 256    Binary: 0100000000
// 1 << 9 = 512    Binary: 1000000000

将一个二进制位向左移动相当于乘以2。事实上,移位操作比标准的乘法运算更快。让我们来看一个例子来证明这个事实:

假设我们有两种方法:

static void ShiftBits(long number,int count)
{
    long value = number;
    for (int i = 0; i < count; i+=128)
    {
          for (int j = 1; j < 65; j++)
          {
              value = value << j;
          }
          for (int j = 1; j < 65; j++)
          {
               value = value >> j;
          }
    }
}

static void MultipleAndDivide(long number, int count)
{
      long value = number;
      for (int i = 0; i < count; i += 128)
      {
            for (int j = 1; j < 65; j++)
            {
                value = value * (2 * j);
            }
            for (int j = 1; j < 65; j++)
            {
                value = value / (2 * j);
            }
      }
}

我们希望以这种方式测试它们:

ShiftBits(1, 10000000);
ShiftBits(1, 100000000);
ShiftBits(1, 1000000000);
...
MultipleAndDivide(1, 10000000);
MultipleAndDivide(1, 100000000);
MultipleAndDivide(1, 1000000000);
...

以下是结果:

Bit manipulation 10.000.000 times: 58 milliseconds
Bit manipulation 100.000.000 times: 375 milliseconds
Bit manipulation 1.000.000.000 times: 4073 milliseconds

Multiplication and Division 10.000.000 times: 81 milliseconds
Multiplication and Division 100.000.000 times: 824 milliseconds
Multiplication and Division 1.000.000.000 times: 8224 milliseconds

在密码学中,我们倾向于使用位旋转而不是位移。虽然有些地方会使用位移,但它们并不像位旋转那样常见。 - Reid
这个说法太笼统了,说实话我感到不舒服。我最不舒服的是你没有提到它可以非常快速地执行(value)*2^n。此外,你提到的例子(虽然正确),但我觉得并没有点睛之笔。 - violet_white
3
@jaked122现在够了吗? :) - Selman Genç

64

那就是按位左移运算符。

每次左移,值就相当于乘以2。例如,写成value << 3将把值乘以8。

它在内部实际上是将值的所有位向左移动一位。所以,如果你有值12(十进制),在二进制中,它是00001100;向左移动一位将其变为00011000,即24。


57

它是按位左移操作,通过将数字的二进制等效形式向右移动给定数量(在右侧)的数字位来实现。

因此:

temp = 14 << 2

14的二进制等价物是00001110,将其向左移动两次意味着从右侧推入零并将每个数字向左移动,这使其等于00111000,即56。

visual

在你的例子中:

i < (1 << list.Count)
  • 0000000001 = 1,如果 list.Count = 0,结果为 0000000001 = 1
  • 0000000001 = 1,如果 list.Count = 1,结果为 0000000010 = 2
  • 0000000001 = 1,如果 list.Count = 2,结果为 0000000100 = 4
  • 0000000001 = 1,如果 list.Count = 3,结果为 0000001000 = 8

依此类推。一般地,结果等于2 ^ list.Count(列表数量的2次方)。


37

这是一个左位移运算符,它将左操作数的位模式向左移动指定在右操作数中的二进制位数。

Get = 1 << 0, // 1
Post = 1 << 1, // 2
Put = 1 << 2,  // 4
Delete = 1 << 3, // 8
Head = 1 << 4  // 16

这在语义上等同于lOperand * Math.Pow(2, rOperand)


在这种情况下,+1实际上展示了左移位的作用。 - auujay
8
更具体地说:00001,00010,00100,01000,10000。 - zmbush
1
“根据右操作数指定的二进制位数” - 实际上,这并不完全正确;例如对于32位,它只考虑前5位,因此<< 33<< 1相同的。同样,在64位数学中,<< 65<< 1相同的。而右移又更加复杂,因为您需要考虑符号以知道要用什么填充。 - Marc Gravell

23

循环的目的很可能是生成或操作列表中所有项目的所有子集。循环体很可能还涉及大量的位运算,即另一个左移和按位与。 (因此,重写为使用Pow将非常愚蠢,我几乎无法相信有这么多人实际建议这样做。)


4
对于这种需要对列表元素的子集进行操作的建议,我给出了+1的评价。这似乎是唯一合理的动机。如果列表长度可能相当长,即超过一个'int'中的位数,那么这将是一种非常糟糕的技术。虽然您可能会猜测将所有位移后的位都执行0次循环,但实际上其行为是未定义的。事实上,我记得正好将位移超过字长通常根本不起作用。 - Marc van Leeuwen

15

这是位移操作。基本上,它只是通过在右侧添加0来将位向左移动。

public enum HttpVerbs {
    Get = 1 << 0,    // 00000001 -> 00000001 = 1
    Post = 1 << 1,   // 00000001 -> 00000010 = 2
    Put = 1 << 2,    // 00000001 -> 00000100 = 4
    Delete = 1 << 3, // 00000001 -> 00001000 = 8
    Head = 1 << 4    // 00000001 -> 00010000 = 16
}

更多信息请访问http://www.blackwasp.co.uk/CSharpShiftOperators.aspx


12

除了Selman22的回答之外,这里还有一些例子:

我将列举一些list.Count的值以及循环会是什么:

list.Count == 0: for (int i = 0; i < 1; i++)
list.Count == 1: for (int i = 0; i < 2; i++)
list.Count == 2: for (int i = 0; i < 4; i++)
list.Count == 3: for (int i = 0; i < 8; i++)

等等,诸如此类。


9
“位左移”。1 << 0的意思是“取整数值1,并将其位向左移动0位。”也就是说,00000001保持不变。1 << 1的意思是“取整数值1,并将其位向左移动一位。”00000001变为00000010

1
对于你的第一个例子,我认为你的意思是“零位除数”,但其他都是正确的。 - Adam Robinson
@Adam 谢谢,你说得完全正确。我已经更新了帖子。 - Dathan

8

它(<<)是一个位左移运算符,它移动了二进制对象的位值。左操作数指定要移位的值,右操作数指定要移位的位数。

在您的情况下,如果list.count的值为4,则循环将运行直到i < (1<< 4),这是16(00010000)。

00000001 << 4 = 00010000(16)


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