【问题标题】:Why '&&' and not '&'?为什么是'&&'而不是'&'?
【发布时间】:2011-11-12 00:00:06
【问题描述】:

为什么&& 优于&|| 优于|

我问了一个编程多年的人,他的解释是:

例如,在if (bool1 && bool2 && bool3) { /*DoSomething*/ } 中,bool1 必须为真才能测试bool2,这必须为真,然后再转到bool3 等。如果我使用单个&相反,即使所有这些都必须是真实的才能进入下一行,测试也没有顺序,所以这有什么关系呢?

注意:我想指出,我是一个蹒跚学步的孩子,这不是一个严肃或紧迫的问题。更多的是理解为什么事情应该以某种方式而不是另一种方式来完成。

【问题讨论】:

  • & 和 && |和 ||是完全不同的运营商
  • 重新标记,因为这不仅适用于 C#
  • 已恢复,因为答案已经是特定于 C# 的,并且内部工作在其他具有大致相同概念的语言中可能会有所不同。
  • @Felice:它们是不同的,但几乎没有完全不同。它们实际上非常相似:x & yx && y 将始终评估相同的结果,如果 x和 y 是布尔类型的表达式。事实上,在这种情况下,唯一的区别似乎是在x & y 中,总是评估 y。
  • @slawekin:我建议通读答案。一些人广泛地写了关于性能差异的文章。不过,答案可能会让您大吃一惊。

标签: c# operators


【解决方案1】:

在大多数情况下,&&|| 优先于 &|,因为前者是短路的,这意味着一旦结果明确就会取消评估。

例子:

if(CanExecute() && CanSave())
{
}

如果CanExecute 返回false,则无论CanSave 的返回值如何,完整的表达式将为false。因此,CanSave 不会被执行。

这在以下情况下非常方便:

string value;
if(dict.TryGetValue(key, out value) && value.Contains("test"))
{
    // Do Something
}

TryGetValue 返回 false 如果在字典中找不到提供的键。由于&& 的短路特性,value.Contains("test") 仅在TryGetValue 返回true 时执行,因此value 不是null。如果您改用按位与 运算符&,如果在字典中找不到键,您将得到NullReferenceException,因为无论如何都会执行表达式的第二部分。

类似但更简单的示例是以下代码(如 TJHeuvel 所述):

if(op != null && op.CanExecute())
{
    // Do Something
}

CanExecute 仅在 op 不是 null 时执行。如果 opnull,则表达式的第一部分 (op != null) 的计算结果为 false,其余部分 (op.CanExecute()) 的计算将被跳过。

除此之外,从技术上讲,它们也是不同的:
&&|| 只能用于 bool,而 &| 可以用于任何整数类型(@ 987654357@, int, long, sbyte, ...),因为它们是按位运算符。 &按位与 运算符,|按位或 运算符。

确切地说,在 C# 中,这些运算符(&| [和 ^])称为“逻辑运算符”(参见 C# spec,第 7.11 章)。这些运算符有多种实现方式:

  1. 对于整数(intuintlongulong,第 7.11.1 章):
    它们用于计算操作数和运算符的按位结果,即& 用于计算按位逻辑AND 等。
  2. 对于枚举(第 7.11.2 章):
    它们被实现来执行枚举的底层类型的逻辑操作。
  3. 对于布尔值和可为空的布尔值(第 7.11.3 和 7.11.4 章):
    结果不是使用按位计算计算的。结果基本上是根据两个操作数的值来查找的,因为可能性的数量实在是太少了。
    因为这两个值都用于查找,所以这个实现不是短路的。

【讨论】:

  • 这对于检查是否为空也很方便。例如:if(op != null && op.CanExecute())。因为当第一个原因不成立时,第二个原因没有被评估,所以这是有效的。
  • @TJHeuvel:这与我在TryGetValue 示例中描述的用法基本相同。但是,是的,这是另一个很好的例子。
  • 好答案。也许您还应该添加一个示例,说明如何将 &| 与非布尔参数(即运算符的操作)一起使用,以造福所有新人。
【解决方案2】:

这很重要,因为如果 bool2 的评估成本(例如)很高但 bool1 为假,那么通过使用 && over & 可以为自己节省相当多的计算量

【讨论】:

    【解决方案3】:

    C# Operators 应该解释原因:

    基本上有两个&'s 或|'s 意味着它是一个条件而不是一个逻辑,所以你可以分辨出两者之间的区别。

    & Operator有一个使用&的例子。

    【讨论】:

    • 两个链接都(有效地)损坏了(重定向到“Visual Studio 2005 Retired documentation”)。
    【解决方案4】:

    &&& 的短路版本。

    如果我们正在评估false & true,我们已经通过查看第一个参数知道结果将是错误的。 && 版本的运算符将尽快返回结果,而不是评估整个表达式。 | 运算符还有一个类似的版本,||

    【讨论】:

      【解决方案5】:

      如果是:

      if (obj != null && obj.Property == true) { }
      

      会按预期工作。

      但是:

      if (obj != null & obj.Property == true) { }
      

      可能会引发空引用异常。

      【讨论】:

        【解决方案6】:
        if (list.Count() > 14 && list[14] == "foo")
        

        很安全

        if (list.Count() > 14 & list[14] == "foo")
        

        如果列表大小不合适会崩溃。

        【讨论】:

        • 我无法想象有人可以写 "if (list.Count() > 14 & list[14] == "foo")" 而不是 "if (list.Count() > 14 && 列表 [14] == "foo")"。在这种情况下,& 只是简单而自然地不能用于 &&,即使 & 肯定是安全的(例如 list[1])。
        【解决方案7】:

        非常清楚地解释这意味着什么(尽管其他答案暗示了这一点 - 但可能使用了您不理解的术语)。

        以下代码:

        if (a && b)
        {
           Foo();
        }
        

        真的是编译成这样的:

        if (a)
        {
            if (b)
            {
                Foo();
            }
        }
        

        以下代码完全按照其表示的方式编译:

        if (a & b)
        {
           Foo();
        }
        

        这称为短路。一般来说,您应该始终在您的条件下使用&&||

        加分:有一种情况你不应该这样做。如果您处于性能至关重要的情况(这是纳秒级关键),请仅在必须时使用短路(例如null检查) - 因为短路是一个分支/跳;这可能会导致您的 CPU 出现分支错误预测; &&& 便宜得多。还有一种情况是短路实际上会破坏逻辑——看看我的this answer

        谩骂/独白:关于最幸福忽略的分支错误预测。引用Andy Firth(从事游戏工作 13 年):“这可能是人们认为他们需要去的较低级别……但他们错了。了解您正在为之编程的硬件如何处理分支可以在很大程度上影响性能......远远超过大多数程序员可能会欣赏的:死亡一千次。”

        • 游戏开发人员(以及在极端实时条件下工作的其他人)尽可能地重构他们的逻辑以更好地适应预测器。在反编译的 mscorlib 代码中也有这方面的证据。
        • 仅仅因为 .NET 保护您免受此类事情的影响并不意味着它不重要。在 60 Hz 时,分支错误预测非常昂贵;或每秒 10,000 个请求。
        • 英特尔不会有工具来识别错误预测的位置,Windows 也不会为此提供性能计数器,也不会有任何词来描述它,如果这不是问题的话。
        • 对较低级别和架构的无知不会使了解它们的人错。
        • 始终尝试了解您正在使用的硬件的限制。

        这是非信徒的基准。最好在 RealTime/High 中运行该进程以减轻调度程序的影响:https://gist.github.com/1200737

        【讨论】:

        • 关于“奖励分数”:我们都知道过早优化带来的好处。 :)
        • @Michael - 这就是为什么“纳秒至关重要”以粗体显示的原因:)。 AAA 游戏开发者通常会担心这样的事情——而且你永远不知道谁会阅读答案;所以最好记录下边界/极端情况。
        • 那个奖励标记对 C# 有效吗?我没想到,因为 MSIL 是被解释的,除非表达式被编译成机器码。
        • @Jeremy MSIL 未被解释。
        • @TheD 重新检查答案 - 我已经添加了一个关于为什么你应该为此担心的独白。而且,仅供参考,(x && y) 转换为 LOAD x; BRANCH_FALSE; LOAD y; BRANCH_FALSE;,其中 (x & y) 转换为 LOAD x; LOAD y; AND; BRANCH_FALSE;。一个分支对两个。
        【解决方案8】:

        好吧,就表面而言

            Boolean a = true;
            Boolean b = false;
        
            Console.WriteLine("a({0}) && b({1}) =  {2}", a, b, a && b);
            Console.WriteLine("a({0}) || b({1}) =  {2}", a, b, a || b);
            Console.WriteLine("a({0}) == b({1}) =  {2}", a, b, a == b);
        
            Console.WriteLine("a({0}) & b({1}) =  {2}", a, b, a & b);
            Console.WriteLine("a({0}) | b({1}) =  {2}", a, b, a | b);
            Console.WriteLine("a({0}) = b({1}) =  {2}", a, b, a = b);
        

        给出相同的答案。但是,正如您所展示的,如果您有更复杂的问题,那么:

        if (a and b and c and d) ..
        

        如果a 不是真的,也许b 是一个必须关闭的功能,连接到某物,得到这个,做那个,做出决定.. 何必呢?浪费时间,知道它已经失败了。为什么要让机器熄火并做额外的无意义的工作?

        我一直使用&&,因为我把最有可能失败的东西放在第一位,因此,在没有意义的情况下继续进行之前的计算更少。如果没有办法预测不太可能的选择,比如你有一个布尔值来限制数据的输出,比如:

        if (limit && !MyDictionary.ContainsKey("name")) 
            continue;
        

        如果不是limit,请不要费心检查密钥,这可能需要更长的时间..

        【讨论】:

          【解决方案9】:

          逻辑运算符(||&&)与位运算符(|&)。

          逻辑运算符和位运算符之间最重要的区别在于逻辑运算符采用两个布尔值并产生一个布尔值,而位运算符采用两个整数并产生一个整数 (注意:整数表示任何整数数据类型,而不仅仅是 int)。

          为了迂腐,按位运算符采用位模式(例如 01101011)并对每个位进行按位与/或。因此,例如,如果您有两个 8 位整数:

          a     = 00110010 (in decimal:    32+16+2   = 50)
          b     = 01010011 (in decimal: 64+   16+2+1 = 83)
          ----------------
          a & b = 00010010 (in decimal:       16+2   = 18)
          a | b = 01110011 (in decimal: 64+32+16+2+1 = 115)
          

          而逻辑运算符仅适用于bool

          a      = true
          b      = false
          --------------
          a && b = false
          a || b = true
          

          其次,通常可以对 bool 使用按位运算符,因为 true 和 false 分别相当于 1 和 0,如果将 true 转换为 1,将 false 转换为 0,则进行按位运算,然后转换非零到真,零到假;如果您刚刚使用逻辑运算符,结果将是相同的(检查此以进行练习)。

          另一个重要的区别是逻辑运算符是短路的。因此,在某些圈子[1] 中,您经常会看到人们这样做:

          if (person && person.punch()) {
              person.doVictoryDance()
          }
          

          翻译为:“如果人存在(即不为空),尝试打他/她,如果打成功(即返回真),则跳胜利舞”

          如果您改用位运算符,则:

          if (person & person.punch()) {
              person.doVictoryDance()
          }
          

          将转换为:“如果 person 存在(即不为 null)并且出拳成功(即返回 true),则进行胜利之舞”

          请注意,在短路逻辑运算符中,如果person 为空,则person.punch() 代码可能根本不会运行。事实上,在这种特殊情况下,如果 person 为空,第二个代码将产生空引用错误,因为无论 person 是否为空,它都会尝试调用 person.punch()。这种不计算正确操作数的行为称为短路

          [1] 一些程序员会拒绝将具有副作用的函数调用放入 if 表达式中,而对其他人来说,这是一个常见且非常有用的习惯用法。

          由于位运算符一次只能处理 32 位(如果您使用的是 32 位机器),如果您需要比较大量条件,它可以生成更优雅、更快速的代码,例如

          int CAN_PUNCH = 1 << 0, CAN_KICK = 1 << 1, CAN_DRINK = 1 << 2, CAN_SIT = 1 << 3,
              CAN_SHOOT_GUNS = 1 << 4, CAN_TALK = 1 << 5, CAN_SHOOT_CANNONS = 1 << 6;
          
          Person person;
          person.abilities = CAN_PUNCH | CAN_KICK | CAN_DRINK | CAN_SIT | CAN_SHOOT_GUNS;
          
          Place bar;
          bar.rules = CAN_DRINK | CAN_SIT | CAN_TALK;
          
          Place military;
          military.rules = CAN_SHOOT_CANNONS | CAN_PUNCH | CAN_KICK | CAN_SHOOT_GUNS | CAN_SIT;
          
          CurrentLocation cloc1, cloc2;
          cloc1.usable_abilities = person_abilities & bar_rules;
          cloc2.usable_abilities = person_abilities & military_rules;
          
          // cloc1.usable_abilities will contain the bit pattern that matches `CAN_DRINK | CAN_SIT`
          // while cloc2.usable_abilities will contain the bit pattern that matches `CAN_PUNCH | CAN_KICK | CAN_SHOOT_GUNS | CAN_SIT`
          

          对逻辑运算符做同样的事情需要大量的比较:

          Person person;
          person.can_punch = person.can_kick = person.can_drink = person.can_sit = person.can_shoot_guns = true;
          person.can_shoot_cannons = false;
          
          Place bar;
          bar.rules.can_drink = bar.rules.can_sit = bar.rules.can_talk = true;
          bar.rules.can_punch = bar.rules.can_kick = bar.rules.can_shoot_guns = bar.rules.can_shoot_cannons = false;
          
          Place military;
          military.rules.can_punch = military.rules.can_kick = military.rules.can_shoot_guns = military.rules.can_shoot_cannons = military.rules.can_sit = true;
          military.rules.can_drink = military.rules.can_talk = false;
          
          CurrentLocation cloc1;
          bool cloc1.usable_abilities.can_punch         = bar.rules.can_punch         && person.can_punch,
               cloc1.usable_abilities.can_kick          = bar.rules.can_kick          && person.can_kick,
               cloc1.usable_abilities.can_drink         = bar.rules.can_drink         && person.can_drink,
               cloc1.usable_abilities.can_sit           = bar.rules.can_sit           && person.can_sit,
               cloc1.usable_abilities.can_shoot_guns    = bar.rules.can_shoot_guns    && person.can_shoot_guns,
               cloc1.usable_abilities.can_shoot_cannons = bar.rules.can_shoot_cannons && person.can_shoot_cannons
               cloc1.usable_abilities.can_talk          = bar.rules.can_talk          && person.can_talk;
          
          bool cloc2.usable_abilities.can_punch         = military.rules.can_punch         && person.can_punch,
               cloc2.usable_abilities.can_kick          = military.rules.can_kick          && person.can_kick,
               cloc2.usable_abilities.can_drink         = military.rules.can_drink         && person.can_drink,
               cloc2.usable_abilities.can_sit           = military.rules.can_sit           && person.can_sit,
               cloc2.usable_abilities.can_shoot_guns    = military.rules.can_shoot_guns    && person.can_shoot_guns,
               cloc2.usable_abilities.can_talk          = military.rules.can_talk          && person.can_talk,
               cloc2.usable_abilities.can_shoot_cannons = military.rules.can_shoot_cannons && person.can_shoot_cannons;
          

          在 Unix/Linux 文件系统权限中使用位模式和按位运算符的经典示例。

          【讨论】:

          • 该示例似乎有点暴力,但似乎其他答案过于关注短路,而不足以关注整数和布尔运算之间的区别。
          • 在实现细节(短路/副作用)发挥作用之前,必须了解功能。很高兴您澄清了布尔逻辑与整数逻辑的主要区别,而不是短路。
          【解决方案10】:

          简短:

          1 &amp;&amp; 2 = 真
          因为
          1 = C 中的真(非零)
          2 = C 中的真(非零)

          truetrue 进行逻辑与运算得到true

          但是

          1 &amp; 2 = 0 = 假
          因为
          1 = 0001 二进制
          2 = 0010 二进制

          0001 与 0010 按位与,得到十进制的 0000 = 0。

          同样适用于 ||和 |运营商也...!

          【讨论】:

          • -1:我们在这里谈论 C#...1 &amp;&amp; 2 在 C# 中是非法的
          • 但这是一个非常重要的例子,它解释了为什么你不能简单地交换 & 和 &&(很多人似乎都这么认为)。跨度>
          【解决方案11】:

          因为&amp;&amp;|| 用于流控制,就像if/else 一样。它并不总是与条件有关。写成语句而不是ifwhile条件是完全合理的,如下:

           a() && b() && c() && d();
          

          甚至

           w() || x() || y() || z();
          

          这不仅仅是因为它们比同等的if/else 版本更容易输入;它们也更容易阅读和理解。

          【讨论】:

            【解决方案12】:

            && 和 & 表示两个非常不同的东西,给你两个不同的答案。

            1 &amp;&amp; 2 产生 1(“真”)
            1 &amp; 2 产生 0(“假”)

            &amp;&amp; 是一个逻辑运算符——这意味着“如果两个操作数都为真,则为真”
            &amp; 是按位比较。它的意思是“告诉我在两个操作数中都设置了哪些位”

            【讨论】:

            • 问题是关于 C# 的。在 C# 中,没有办法将数字转换为布尔值,因此 0 不是“假”,非零不是“真”;根本没有等价物。
            • 要将数字转换为布尔值,以 1 表示真,0 表示假的方式,说“n!=0”(我假设......我不太熟悉 C#)。实际上我想撤回这条评论,因为它没有经过充分研究,而且我认为它没有帮助或与之前的评论真正相关,因为我想了更多,但我不小心按了 Enter,现在我认为我不能取消它,所以你去吧,不管它值多少钱:-)
            • 1 &amp;&amp; 2 给出编译器错误:“错误 4 运算符 '&&' 不能应用于 'int' 和 'int' 类型的操作数”
            【解决方案13】:

            在逻辑表达式中使用时,例如 if 语句 &amp;&amp; 更可取,因为它会在遇到第一个错误结果时立即停止计算表达式。这是可能的,因为假值将导致整个表达式为假。类似地(同样在逻辑表达式中)|| 更可取,因为它会在遇到真表达式时立即停止计算表达式,因为任何真值都会导致整个表达式为真。

            但是,如果被 or-ed 或 and-ed 一起使用的表达式有副作用,并且您希望所有这些都作为您的表达式的结果发生(无论逻辑表达式的结果如何),那么 &amp;| 可以使用。相反,&amp;&amp;|| 运算符可用于防止不必要的副作用(例如导致引发异常的空指针)。

            &amp;| 运算符也可以与整数一起使用,在这种情况下,它们会产生一个整数结果,即两个操作数在位级别上加或加。当整数值的二进制位用作真值和假值的数组时,这可能很有用。为了测试某个位是打开还是关闭,位掩码与该值按位与-ed。要打开位,可以将相同的掩码与该值进行按位或运算。最后要关闭一点,掩码的按位补码(使用~)与值按位与-ed。

            int a = 0; // 0 means all bits off
            a = a | 4; // set a to binary 100
            if ((a & 4) != 0) {
                // will do something
            }
            a = a & (~4) // turn bit off again, a is now 000
            

            在 C# 以外的语言中,必须注意 & 和 | 的逻辑与位模式。在上面的代码中,if 语句的条件表达式(a &amp; 4) != 0 是表达此条件的一种安全方式,但在许多类似 C 的语言中,条件语句可以简单地将零整数值视为假,将非零整数值视为真。 (原因与可用的条件分支处理器指令有关,以及它们与在每个整数运算后更新的零标志的关系。)因此可以删除 ìf 语句的零测试,并且可以将条件缩短为 @ 987654334@.

            当表达式使用按位和运算符返回值组合时,这可能会导致混淆,甚至可能导致问题,而这些返回值没有对齐的位。考虑以下示例,其中需要两个函数的副作用,然后检查它们是否都成功(由它们返回非零值定义):

            if (foo() & bar()) {
                // do something
            }
            

            在 C 中,如果 foo() 返回 1 并且 bar() 返回 2,则“某事”将不会完成,因为 1 &amp; 2 为零。

            C# 要求像if 这样的条件语句具有布尔操作数,并且该语言不允许将整数值强制转换为布尔值。所以上面的代码会产生编译器错误。更正确的表述如下:

            if (foo() != 0 & bar() != 0) {
                // do something
            }
            

            【讨论】:

              【解决方案14】:

              向不需要知道代码的确切操作的人解释这一点的最快(并且稍微笨拙)的方式是

              && 正在检查每个条件直到它发现一个错误并将整个结果返回为错误

              || 正在检查每个条件直到它找到一个真并将整个结果返回为真。

              & 正在根据 BOTH/ALL 条件进行数学运算并处理结果。

              | 正在根据 BOTH/ALL 条件进行数学运算并处理结果。

              我从来没有遇到过需要在 if 语句中使用 &| 的情况。我主要使用它来使用按位移位将十六进制值切割成其组件颜色。

              EG:

              r = fullvalue >> 0xFF & 0xFF;
              g = fullvalue >> 0xF & 0xFF;
              b = fullvalue & 0xFF;
              

              在此操作中,“& 0xFF”强制只查看二进制值。 不过,我个人还没有找到 | 的用途。

              【讨论】:

                【解决方案15】:

                简单地说,

                if exp1 &amp;&amp; exp2

                如果 exp1 是 flase 不要检查 exp2

                但是

                if exp1 &amp; exp2

                如果 exp1 是 falsetrue 检查 exp2

                很少有人使用&amp;,因为如果exp1 是false,他们很少想检查exp2

                【讨论】:

                  【解决方案16】:

                  如果您是老 C 程序员,请小心。 C# 真的让我很惊讶。

                  MSDN 表示 | 运算符:

                  二进制 |运算符是为整数类型 和 bool 预定义的。对于整数类型,|计算其操作数的按位或。对于布尔操作数,|计算其操作数的逻辑或;也就是说,当且仅当它的两个操作数都为假时,结果才为假。

                  (重点是我的。)布尔类型是经过特殊处理的,在这种情况下,问题才开始有意义,不同之处在于,正如其他人已经在他们的答案中解释的那样:

                  &amp;&amp;|| 短路。 &amp;| 评估 两个 操作数。

                  什么更可取取决于副作用、性能和代码可读性等许多因素,但通常短路运算符更可取,因为像我这样具有相似背景的人更容易理解它们。

                  原因是:我会这样论证:由于 C 中没有真正的布尔类型,您可以使用按位运算符 | 并在 if 条件下将其结果评估为真或假。但这对 C# 来说是错误的态度,因为布尔类型已经存在特殊情况。

                  【讨论】:

                    猜你喜欢
                    • 2010-12-12
                    • 2010-10-11
                    • 1970-01-01
                    • 2023-02-16
                    • 2018-12-10
                    • 2018-07-05
                    • 2014-12-10
                    • 2014-05-07
                    • 2013-03-15
                    相关资源
                    最近更新 更多