关于c#:为什么’&&’

Why '&&' and not '&'?

为什么&&&|||更可取?

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

例如,在if (bool1 && bool2 && bool3) { /*DoSomething*/ }中,bool1必须是真的,以便在继续进行bool3等之前测试bool2必须是真的。如果我使用的是单&而不是单&的话,即使所有的测试都必须是真的才能进入下一行,也没有进行测试的顺序,那么为什么这无论如何都很重要?

注意:我想指出的是,我的编程相当于一个刚学走路的孩子,这不是一个严重或紧急的问题。更重要的是理解为什么事情应该以某种方式而不是另一种方式进行。


在大多数情况下,&&||&|更受欢迎,因为前者是短路的,这意味着一旦结果明确,评估就被取消。

例子:

1
2
3
if(CanExecute() && CanSave())
{
}

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

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

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

如果在字典中找不到提供的密钥,TryGetValue返回false。由于&&的短路性质,value.Contains("test")只在TryGetValue返回true时执行,因此value不是null时执行。如果使用按位与运算符&,那么如果在字典中找不到键,则会得到NullReferenceException,因为表达式的第二部分在任何情况下都是执行的。

类似但更简单的例子是以下代码(如Tjeuvel所提到的):

1
2
3
4
if(op != null && op.CanExecute())
{
    // Do Something
}

只有当op不是null时,才执行CanExecute。如果opnull,则表达式的第一部分(op != null计算为false,其余部分(op.CanExecute()计算为跳过。

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

更确切地说,在c中,这些运算符(&|^)称为"逻辑运算符"(见c规范第7.11章)。这些运算符有几种实现:

  • 对于整数(intuintlongulong,第7.11.1章):它们被实现来计算操作数的位结果,而运算符,即&被实现来计算逻辑位AND等。
  • 对于枚举(第7.11.2章):它们被实现来执行枚举的基础类型的逻辑操作。
  • 对于喷杆和可空喷杆(第7.11.3和7.11.4章):结果不是通过按位计算得到的。结果基本上是根据两个操作数的值来查找的,因为可能性的数目非常小。因为这两个值都用于查找,所以此实现不会短路。

  • 非常清楚地解释这意味着什么(即使其他答案也暗示了这一点——但可能使用了你不理解的术语)。

    以下代码:

    1
    2
    3
    4
    if (a && b)
    {
       Foo();
    }

    是这样的:

    1
    2
    3
    4
    5
    6
    7
    if (a)
    {
        if (b)
        {
            Foo();
        }
    }

    其中,以下代码的编译与所表示的完全相同:

    1
    2
    3
    4
    if (a & b)
    {
       Foo();
    }

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

    加分:有一种情况是你不应该这样做的。如果你处在一个性能至关重要(这是纳秒的关键)的情况下,只在你必须(例如null检查)的时候使用短路,因为短路是一个分支/跳变;这可能导致你的CPU上的分支预测失误;&&&。还有一种情况是,短路实际上会破坏逻辑——看看我的答案。

    批判/独白:关于分支错误预测,最幸福地忽略。引用安迪·费斯(他在游戏方面工作了13年)的话:"这可能是人们认为他们需要去的较低水平……但他们错了。了解正在为treats分支编程的硬件如何在很大程度上影响性能…比大多数程序员可能更欣赏的是Re:Deather by a Celly Cuts。"

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

    这里是非信徒的基准。最好实时/高速运行该进程,以减轻调度程序的影响:https://gist.github.com/1200737


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

    逻辑运算符和位运算符之间最关键的区别是,逻辑运算符接受两个布尔值并生成一个布尔值,而位运算符接受两个整数并生成一个整数(注意:整数表示任何整数数据类型,而不仅仅是int)。

    为了学究,位运算符采用位模式(例如01110111),并对每个位执行位和/或。例如,如果有两个8位整数:

    1
    2
    3
    4
    5
    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中工作:

    1
    2
    3
    4
    5
    a      = true
    b      = false
    --------------
    a && b = false
    a || b = true

    其次,通常可以对bool使用位运算符,因为true和false分别等于1和0,如果将true转换为1,将false转换为0,则执行位运算,然后将非零转换为true,将零转换为false;如果仅使用逻辑运算符,则结果将相同(选中t他的运动)。

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

    1
    2
    3
    if (person && person.punch()) {
        person.doVictoryDance()
    }

    也就是说:"如果人存在(即不为空),试着打他/她,如果打成功(即返回真),那么就跳一支胜利之舞。"

    如果使用位运算符,则:

    1
    2
    3
    if (person & person.punch()) {
        person.doVictoryDance()
    }

    将翻译为:"如果人存在(即不为空),并且击打成功(即返回真值),那么就跳一支胜利之舞。"

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

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

    由于位运算符一次只能处理32位(如果您在32位计算机上),因此如果需要比较大量的条件(例如,如果您需要比较多个条件),它可能会导致代码更优雅、更快。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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`

    对逻辑运算符执行相同操作需要进行大量的比较:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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文件系统权限中使用位模式和位运算符的经典示例。


    在以下情况下:

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

    会按预期工作。

    但是:

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

    可能引发空引用异常。


    简明扼要:

    1 && 2为真因为1=C中的真(非零)2=真(非零),单位:C

    truetrue逻辑上给予true

    但是

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

    0.001和0.01的位,以给出0.000=0的十进制数。

    同样,对于和操作员也一样…!


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

    但是,如果表达式being或ed或ed加在一起有副作用,并且您希望所有这些都是表达式的结果(不管逻辑表达式的结果如何),那么可以使用&|。相反,&&||运算符可用于防止不必要的副作用(例如导致抛出异常的空指针)。

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

    1
    2
    3
    4
    5
    6
    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 & 4) != 0是表达这种条件的一种安全方法,但是在许多类似C语言中,条件语句可以简单地将零整数值视为假整数值,而非零整数值则视为真整数值。(原因与可用的条件分支处理器指令以及它们与每个整数操作后更新的零标志的关系有关)因此,可以删除ìf语句的零测试,并将条件缩短为(a & 4)

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

    1
    2
    3
    if (foo() & bar()) {
        // do something
    }

    在C语言中,如果foo()返回1,bar()返回2,则"something"不会执行,因为1 & 2为零。

    c要求像if这样的条件语句具有布尔oeprand,并且该语言不允许将整数值强制转换为布尔值。所以上面的代码会产生编译器错误。更准确地说是:

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


    如果你是一个老的定时器C程序员,小心点。C真的让我吃惊。

    对于|运营商,msdn说:

    Binary | operators are predefined for the integral types and bool. For integral types, | computes the bitwise OR of its operands. For bool operands, | computes the logical OR of its operands; that is, the result is false if and only if both its operands are false.

    (重点是我的。)布尔类型是专门处理的,在这种情况下,问题才开始有意义,不同之处在于,其他类型的问题已经在其答案中进行了解释:

    && and || are short-circuiting. & and | evaluate both operands.

    最好的选择取决于很多方面,比如副作用、性能和代码可读性,但一般来说,短路运算符更可取,因为它们更容易被我这样背景相似的人理解。

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


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

    是安全的

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

    如果列表大小不正确,将崩溃。


    &&&的短路版本。

    如果我们正在评估false & true,我们从第一个论点就已经知道结果是错误的。运算符的&&版本将尽快返回结果,而不是计算整个表达式。还有一个与|运算符||类似的验证。


    C操作员应解释原因:

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

    &;operator有一个使用一个&的示例。


    好的,按面值计算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        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);

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

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

    如果a不是真的,可能b是一个函数,它必须离开,连接到某个东西,得到这个,做那个,做一个决定。何苦?浪费时间,你知道已经失败了。为什么要让机器熄火,做额外的无意义的工作?

    我一直使用&&,因为我把最有可能失败的放在第一位,因此,在没有任何意义的情况下继续之前,计算要少一些。如果无法预测不太可能的选择,例如您有一个布尔值来限制数据输出,例如:

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

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


    &&;和&;意味着两件截然不同的事情,并给出两个不同的答案。

    1 && 2生成1("真")。1 & 2产生0("假")。

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


    因为&&||用于流量控制,就像if/else一样。它并不总是关于条件的。将以下内容作为陈述而不是作为ifwhile条件来写是完全合理的:

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

    甚至

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

    它不仅比同等的if/else版本更容易输入,而且更容易阅读和理解。


    向不需要知道代码的精确操作的人解释这一点的最快(并且稍微变小)的方法是

    &正在对这些条件中的每个条件进行检查,直到发现错误并将整个结果返回为错误为止。

    ||正在对这些条件中的每一个进行检查,直到找到一个true并将整个结果返回为true。

    &正在进行基于数学的apon,包括/所有条件和处理结果。

    |正在做基于数学的apon,包括所有条件和处理结果。

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

    如:

    1
    2
    3
    r = fullvalue >> 0xFF & 0xFF;
    g = fullvalue >> 0xF & 0xFF;
    b = fullvalue & 0xFF;

    在此操作中,"0xff"将强制只查看二进制值。不过,我个人还没有找到一种用途。


    简单地说,

    if exp1 && exp2

    如果exp1是flase。不检查exp2

    但是

    if exp1 & exp2

    如果exp1是falsetrue。检查Exp2

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


    这很重要,因为如果bool2(例如)的评估成本很高,但bool1是错误的,那么通过使用&;&;over&;可以为自己节省一点计算成本。