为什么'&&'而不是'&'?

时间:2011-09-07 09:38:09

标签: c# operators

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

我问过多年来一直在编程的人,他的解释是:

例如,在if (bool1 && bool2 && bool3) { /*DoSomething*/ }中,bool1必须为真,以便在转到bool2等之前测试bool3必须为真。如果我使用单&而不是测试没有顺序,即使它们都必须是真的进展到下一行,所以为什么它仍然重要?

注意:我想指出我是幼儿的编程,这不是一个严肃或紧迫的问题。更重要的是理解为什么事情应该以某种方式而不是另一种方式完成。

16 个答案:

答案 0 :(得分:183)

在大多数情况下,&&||优先于&|,因为前者是短路的,这意味着评估会在结果很清楚。

示例:

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

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

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

string value;
if(dict.TryGetValue(key, out value) && value.Contains("test"))
{
    // Do Something
}
如果在字典中找不到提供的密钥,

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

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

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

CanExecute仅在op不是null时执行。如果opnull,则表达式的第一部分(op != null)会计算为false,并且会跳过对其余部分(op.CanExecute())的评估。< / p>

除此之外,从技术上讲,它们也是不同的:
&&||只能在bool上使用,而&|可用于任何整数类型(bool,{{1 }},intlong,...),因为它们是按位运算符。 sbyte按位AND 运算符,&按位OR 运算符。

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

  1. 对于整数(^intuintlong,第7.11.1章):
    实现它们是为了计算操作数和运算符的按位结果,即ulong用于计算按位逻辑&等。
  2. 列举(第7.11.2章):
    它们被实现为执行枚举的基础类型的逻辑操作。
  3. 对于bool和nullable bools(第7.11.3和7.11.4章):
    结果不是使用按位计算计算的。基本上根据两个操作数的值查找结果,因为可能性的数量很小 由于这两个值都用于查找,因此该实现不会短路。

答案 1 :(得分:82)

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

以下代码:

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

真的编译成这个:

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

以下代码的编译与表示完全相同:

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

这称为短路。一般情况下,您应始终在条件中使用&&||

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

Diatribe / Monologue :关于最幸福忽略的分支错误预测。引用Andy Firth(已经从事游戏工作13年):“这可能是人们认为他们需要去的较低级别......但他们错了。了解你为之编程的硬件如何对待分支可能会影响到巨大程度的表现......远远超过大多数程序员可能会欣赏的重新:一千次削减死亡。“

  • 游戏开发者(以及其他在极端实时条件下工作的人)甚至可以重组他们的逻辑以更好地适应预测器。在反编译的mscorlib代码中也有这方面的证据。
  • 仅仅因为.NET屏蔽了你这种类型的东西并不意味着它并不重要。分支误预测在60 Hz时非常昂贵;或者每秒10,000次请求。
  • 英特尔不会有工具来识别错误预测的位置,Windows也没有这方面的性能计数器,也不会有一个词来描述它,如果不是问题。
  • 对较低级别和架构的无知并不会让那些意识到错误的人感到错误。
  • 始终尝试了解您正在使用的硬件的限制。

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

答案 2 :(得分:68)

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

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

为了迂腐,按位运算符采用位模式(例如01101011)并对每个位执行逐位AND / OR。因此,例如,如果您有两个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()
}

转换为:“如果人存在(即不为空),尝试打他/她,如果打孔成功(即返回true),则进行胜利舞蹈”

如果您使用了按位运算符,那么:

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

将转换为:“如果人存在(即不为空)并且打孔成功(即返回true),则执行胜利舞蹈

请注意,在短路逻辑运算符中,如果person.punch()为空,则可能根本不运行person代码。实际上,在这种特殊情况下,如果person为空,则第二个代码会产生空引用错误,因为无论person是否为null,它都会尝试调用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文件系统权限。

答案 3 :(得分:8)

以下情况:

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

会按预期工作。

可是:

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

可能会抛出空引用异常。

答案 4 :(得分:2)

简短而简单:

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

中的真(非零)

true ANDS与true合作,提供true

但是

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

0001 ANDs按位与0010以十进制表示0000 = 0。

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

答案 5 :(得分:1)

C# Operators 应解释原因:

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

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

答案 6 :(得分:1)

&&&的短路版本。

如果我们正在评估false & true,我们已经从查看结果为假的第一个参数中得知。 &&版本的运算符将尽快返回结果,而不是评估整个表达式。 |运算符||也有类似的版本。

答案 7 :(得分:1)

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

是安全的

if (list.Count() > 14 & list[14] == "foo")
如果列表的大小不正确,

会崩溃。

答案 8 :(得分:1)

好的,面值

    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 :(得分:1)

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

但是,如果表达式是or-ed或ed-ed有副作用,并且你希望所有这些都是由于你的表达式而发生的(无论逻辑表达式的结果如何),那么{{1} }和&可以使用。相反,|&&运算符可用作防止不必要的副作用(例如引发异常的空指针)。

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

~

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

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

(a & 4)

在C中,如果if (foo() & bar()) { // do something } 返回1且foo()返回2,则“某事”将无法完成,因为bar()为零。

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

if

答案 10 :(得分:1)

如果您是老式C程序员,小心。 C#真让我感到惊讶。

MSDN代表|运营商:

  

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

(强调是我的。)布尔类型是专门处理的,在这种情况下,问题才开始有意义,差别在于,正如其他人已经在答案中所阐述的那样:

  

&&||正在短路。 &|评估两个操作数。

什么是更好的取决于许多因素,如副作用,性能和代码可读性,但通常短路运算符也是首选,因为与我一样具有类似背景的人更好地理解它们。

原因是:我会这样说:由于C中没有真正的布尔类型,你可以使用按位运算符|并在if条件下将其结果评估为truthy或falsy。但这对C#来说是错误的态度,因为布尔类型已有一个特例。

答案 11 :(得分:0)

这很重要,因为如果bool2(例如)的评估成本很高但是bool1是假的,那么你通过使用&amp;&amp;和over&amp;

答案 12 :(得分:0)

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

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

甚至

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

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

答案 13 :(得分:0)

&安培;&安培;和&amp;意思是两个非常不同的东西,给你两个不同的答案。

1 && 2收益率1(“真实”)
1 & 2得出0(“假”)

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

答案 14 :(得分:0)

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

&amp;&amp; 正在检查每个条件直到它发现为false并将整个结果返回为false

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

&amp; 正在做基于MATHS的所有条件并处理结果。

| 正在做基于MATHS的所有条件并处理结果。

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

EG:

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

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

答案 15 :(得分:0)

简单地说,

if exp1 && exp2

如果exp1是flase 不要检查exp2

但是

if exp1 & exp2

如果exp1为falsetrue 检查exp2

很少有人使用&,因为如果exp1为false

,他们很少想检查exp2