在C#中,两个左尖括号"<<"是什么意思?

lan*_*der 43 .net c# operators bit-shift bitwise-operators

基本上是标题中的问题.我正在看MVC 2源代码:

[Flags]
public enum HttpVerbs {
    Get = 1 << 0,
    Post = 1 << 1,
    Put = 1 << 2,
    Delete = 1 << 3,
    Head = 1 << 4
}
Run Code Online (Sandbox Code Playgroud)

而我只是好奇双左眼角饼干的<<作用.

pid*_*pid 131

当你写作

1 << n
Run Code Online (Sandbox Code Playgroud)

你却将位组合000000001n离开,因此把时间n成2的指数:

2^n
Run Code Online (Sandbox Code Playgroud)

所以

1 << 10
Run Code Online (Sandbox Code Playgroud)

真的是

1024
Run Code Online (Sandbox Code Playgroud)

对于5个项目的列表,您for将循环32次.

  • @IlyaIvanov哈哈,是的.而且你的雇主再也不会让你走了:你的代码很快,其他开发人员也无法理解. (29认同)
  • 我不确定为什么每个人都认为这是*优化*.对我来说,这是表达二次幂的自然习惯,我绝不会考虑用另一种方式写作.使用库函数执行二次幂计算会使读取更加困难(可怕的前缀表示法,而不是"更值可读的值运算符值").说真的,你真的*认为`Convert.ToInt32(Math.Pow(2,value))`比`1 << value`更具可读性,现在你已经解释了运算符的含义了吗? (26认同)
  • @Robert Fricke:是的.比特移位仅限于2(不利),但比Math.Pow()更快(优势),它更灵活,甚至可以有浮点基数和指数.它成为单个机器代码指令. (8认同)
  • @Ingo那么OP和31位赞助人应该翻转汉堡吗? (8认同)
  • 我发现很难相信性能如此高,以至于"Math.Pow"_once_找到循环计数会更有意义.然后你不必担心会因为胡言乱语而惹恼开发人员. (7认同)
  • @Plutor不知道它没问题(这就是语言规范的用途).发现它"难以理解","难以阅读","其他开发者无法理解"等等,是不愿意学习+, - ,*,/之外的任何东西的标志.无论是那个还是完全不熟悉整数是什么以及如何表示它们.解决方法`Math.pow(n,2)`简直怪诞.例如,在Java中,它将需要与double进行转换 (6认同)
  • @RobertFricke你看,突然怎么突然`1 << list.Count`只是闪耀,比较`Convert.ToInt32(Math.Pow(2,list.Count)))`) (5认同)
  • @Ingo:这里不是每个人都有和你一样的经历.我保证每天都有一次你不知道<<操作员.对那些仍在学习的人有一些基本的尊重(没有他们,就没有SO). (5认同)
  • 你第一次遇到左/右班时似乎令人困惑和外国.不久之后,你会认识到它的光彩,并开始在各处使用它们.这就是为什么如果你正在学习任何C衍生物,那么熟悉按位运算符总是一个好主意.它会派上用场. (5认同)
  • 左移和其他按位运算符通常用于嵌入式开发,即使是几个周期对性能也很重要.位运算符直接由处理器支持,并在其他编程语言中使用.您不必添加函数使它们在低内存情况下节省空间.是的,如果你在现代计算机上使用基于.NET的应用程序,你可以只包括数学库而不用担心bitmath,但不要期望在像AVR-GCC工具链这样的东西上做同样的事情而不会影响它性能. (4认同)
  • 他们使用list.Count这一事实让我觉得他们过度优化了错误的代码部分 (4认同)
  • 假设在循环内没有更改列表,在循环之前仅计算一次迭代次数会更有效. (3认同)
  • 正如@Andris所说,无论如何,这段代码很可能效率低下.另外,为什么左移比Math.Pow(2,n)更快?在这种情况下,这里有没有人希望Math.Pow执行左移? (3认同)
  • @Shautieh,即使`Math.Pow`在2的基数的情况下使用左移,你得到_minimum_:(1)方法调用,(2)条件检查基数是否等于2,(3)条件确保可以将功率转换为整数而不损失精度,以及(4)左移.与单个操作的显式左移相比,"Pow"会更慢. (3认同)
  • 如果**list.Count**在循环期间不会改变,那么_might_会更有效地将操作移出循环作为变量赋值(取决于优化器的工作情况).在任何情况下,评论温和地提醒读者这种不寻常的操作(很少使用的习语)("上限是2 ^ list.Count")是适当的,即使对于有经验的程序员也是如此. (3认同)
  • @Gusdor:对于企业应用程序,我不鼓励难以阅读的代码.为可读性而言,为性能付出要好得多.所以我同意你的意见. (2认同)
  • @BrianS如果优化是在编译时完成的,那就没有了.除非性能是严格必要的,并且您知道编译器不会执行优化,否则这可能是过早优化的情况. (2认同)
  • 难道你不认为你有责任理解你所有语言的内置操作的含义吗? (2认同)
  • @BrianS我的观点是`````pow`的理论效率是无关紧要的,除非你知道你的编译器不会优化`pow`.如果你使用`<<`来表示可重现的真实世界性能场景,那么你应该记录它,以便将来认为`pow`更清晰的维护者不会随心所欲地替换它. (2认同)
  • 我认为值得一提的是左/右移位运算符不适用于浮点精度变量,如float或double,因此对于那些变量必须将其转换为int/long,执行移位,然后返回.如果处理不当,数据很可能在此过程中丢失. (2认同)
  • 我想知道为什么这个问题吸引了如此多的关注.到目前为止,这真的是我回答的最微不足道的事情...... (2认同)

Sel*_*enç 82

它被称为left-shift运营商.看一下文档

左移运算符使第一个操作数中的位模式向左移动第二个操作数指定的位数.通过换档操作腾出的位是零填充的.这是逻辑移位而不是移位和旋转操作.

演示left-shift运算符的简单示例:

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
Run Code Online (Sandbox Code Playgroud)

向左移动一位是两个等于多个.事实上,移动位比标准乘法更快.让我们看一个证明这个事实的例子:

假设我们有两种方法:

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);
            }
      }
}
Run Code Online (Sandbox Code Playgroud)

我们想要像这样测试它们:

ShiftBits(1, 10000000);
ShiftBits(1, 100000000);
ShiftBits(1, 1000000000);
...
MultipleAndDivide(1, 10000000);
MultipleAndDivide(1, 100000000);
MultipleAndDivide(1, 1000000000);
...
Run Code Online (Sandbox Code Playgroud)

结果如下:

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
Run Code Online (Sandbox Code Playgroud)

  • @ jaked122现在够了吗?:) (3认同)

Aar*_*ght 61

这将是按位左移运算符.

对于每个左移,该值实际上乘以2.因此,例如,写入value << 3将乘以该值8.

内部真正的作用是将值的所有实际位移到一处.所以如果你有12(十进制)的值,那就是二进制00001100; 将它移到一个地方将把它变成00011000,或24.


Zah*_*med 57

它是按位左移它通过给定(右手侧)数字移动数字的二进制等价数字来工作.

所以:

temp = 14 << 2
Run Code Online (Sandbox Code Playgroud)

二进制当量为14 00001110将它移动2次意味着从右手侧推零并将每个数字移到左侧使其00111000等于56.

视觉

在你的例子中:

i < (1 << list.Count)
Run Code Online (Sandbox Code Playgroud)
  • 0000000001 = 1 如果list.Count = 0结果是 0000000001 = 1
  • 0000000001 = 1 如果list.Count = 1,则结果为 0000000010 = 2
  • 如果list.Count = 2,0000000001 = 1,结果为 0000000100 = 4
  • 如果list.Count = 3,0000000001 = 1,结果为 0000001000 = 8

等等.一般来说它是相等的2 ^ list.Count(2提升到list.Count的力量)


Ada*_*son 36

那是左移位操作符.它将左操作数的位模式向左移动右操作数中指定的二进制数字的数量.

Get = 1 << 0, // 1
Post = 1 << 1, // 2
Put = 1 << 2,  // 4
Delete = 1 << 3, // 8
Head = 1 << 4  // 16
Run Code Online (Sandbox Code Playgroud)

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

  • 或更具体地:00001,00010,00100,01000,10000 (8认同)

use*_*227 23

循环的目的很可能是生成或操作列表中项集的所有子集.并且循环体很可能也具有按位运算的良好位(har har),即另一个左移位和按位运算.(所以重写它以使用Pow将是非常愚蠢的,我几乎不相信有那么多人实际建议.)

  • +1表示这涉及列表元素的子集,这似乎是做这种事情的唯一合理动机.有人可能想补充说,如果列表总是很长的话,这是一个非常糟糕的技术,即长度比`int`中的位数长(可能会猜到所有位都移开了循环将被执行0次,但实际上我认为行为是未定义的;实际上我记得在完全单词长度上移位比特通常什么都不做. (4认同)

Fab*_*ian 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
}
Run Code Online (Sandbox Code Playgroud)

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


Tho*_*mar 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++)
Run Code Online (Sandbox Code Playgroud)

等等.


Dat*_*han 9

"比特左移." 1 << 0表示"取整数值1并将其位向左移位".即,00000001保持不变. 1 << 1表示"取整数值1并将其位移到一处." 00000001成为00000010.


小智 8

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

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

00000001 << 4 = 00010000(16)


Pet*_*art 7

该表达式在c#中(1 << N)使用了位移.

在这种情况下,它用于执行2 ^ N的快速整数计算,其中n为0到30.

一个很好的工具 年轻的whippersnappers不理解位移如何工作的开发人员是程序员模式下的Windows Calc,它可视化移位对各种大小的有符号数的影响.的LshRsh功能等同于<<>>分别.

在循环条件内使用Math.Pow进行评估(在我的系统上)比N = 10的问题代码慢约7倍,这是否重要取决于上下文.

在单独的变量中缓存"循环计数"会稍微加快它,因为涉及列表长度的表达式不需要在每次迭代时重新计算.


GaT*_*mas 7

它隐含在许多答案中,但从未直接说明......

对于您移动二进制数的每个位置,您将该数字的原始值加倍.

例如,

左移1的十进制5二进制是十进制10,或十进制5加倍.

向左移动3的十进制5二进制是十进制40,或十进制5加倍3倍.


Jul*_*les 6

以前的答案已经解释它的作用,但似乎没有人猜测为什么.我觉得这个代码的原因很可能就是循环遍历列表成员的每个可能组合 - 这是我可以看到为什么你想要迭代到2 ^ {list的唯一原因.计数}.i因此,变量将被严格命名:而不是索引(这是我通常将'i'解释为含义),其位代表列表中项目的组合,因此(例如)如果位可以选择第一项零i是set((i & (1 << 0)) != 0),第二项是第一项是否设置((i & (1 << 1)) != 0)等等.1 << list.Count因此,第一个整数与列表中的项的有效组合不对应,因为它表示选择不存在的项list[list.Count].

  • 我认为这是一个答案.因为它给它带来了不同的亮点:它不仅仅是*2 ^ list.Count:为了一个特别方便的方法来枚举列表中的选择,它正在计算(我怀疑)第一个不对应的数字有效选择.这恰好是2 ^ list.Count,但意图是(我有理由相当)枚举所有这些组合,所以这是可能组合的数量这一事实附带于循环退出的真正含义条件,"当我们用完列表项的组合时停止计数". (3认同)
  • 但这不是问题,所以这不是一个真正的答案. (2认同)

Mar*_*ark 5

我知道这个答案已经解决了,但我认为可视化可能对某人有所帮助.

[Fact] public void Bit_shift_left()
{
    Assert.Equal(Convert.ToInt32("0001", 2), 1 << 0); // 1
    Assert.Equal(Convert.ToInt32("0010", 2), 1 << 1); // 2
    Assert.Equal(Convert.ToInt32("0100", 2), 1 << 2); // 4
    Assert.Equal(Convert.ToInt32("1000", 2), 1 << 3); // 8
}
Run Code Online (Sandbox Code Playgroud)