为什么是“ &&”而不是“&”?


135

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

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

例如,在中if (bool1 && bool2 && bool3) { /*DoSomething*/ }bool1必须先对in 进行测试bool2,然后再继续对进行测试bool3,等等。如果我只使用一个&,则即使他们都必须对前进到下一行,那为什么仍然重要呢?

注意:我想指出的是,我在编程上等同于蹒跚学步,这不是一个严重或紧急的问题。更多的是理解为什么应该以某种方式而不是另一种方式来完成事情。


55
&和&& | 和|| 完全不同的运营商
Felice Pollano 2011年

3
重新标记,因为这不仅适用于C#
Jonathan Dickinson

12
已恢复,因为答案已经特定于C#,并且内部工作方式在其他具有相同概念的语言中可能有所不同。
Daniel Hilgarth 2011年

8
@Felice:它们是不同的,但几乎完全不同。它们实际上非常相似:x & y并且x && y如果x和y是布尔类型的表达式,则将始终得出相同的结果。实际上,在那种情况下唯一的区别似乎是,x & y总是对y求值。
Joren

1
@slawekin:我建议通读答案。一些人广泛地介绍了性能差异。答案可能会让您感到惊讶。
亚伯

Answers:


183

在大多数情况下,&&||优于&以及|因为前者短路,这意味着评估只要结果是明确取消。

例:

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

如果CanExecute返回false,则完整表达式将是false,而不考虑的返回值CanSave。因此,CanSave不执行。

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

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

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

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

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

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

除此之外,从技术上讲,它们是不同的,太:
&&||只能在使用bool,而&|可以在任何整型(可使用boolintlongsbyte,...),因为它们是位运算符。&按位AND运算符,|按位OR 运算符。

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

  1. 对于整数(intuintlongulong,章7.11.1):
    它们用于计算操作数和运算符的按位结果,即&是实现计算按位逻辑AND等。
  2. 对于枚举(第7.11.2章):
    它们被实现为执行枚举的基础类型的逻辑运算。
  3. 对于布尔值和可为空的布尔值(第7.11.3和7.11.4章):
    不使用按位计算来计算结果。基本上根据两个操作数的值查找结果,因为可能性很小。
    由于两个值都用于查找,因此该实现方式不会造成短路。

31
这对于检查某些内容是否为null也很方便。例如:if(op != null && op.CanExecute())。因为当第一个不为true时第二个原因未评估,所以这是有效的。
TJHeuvel 2011年

2
@TJHeuvel:这基本上与我在TryGetValue示例中描述的用法相同。但是,是的,这是另一个很好的例子。
Daniel Hilgarth 2011年

4
好答案。也许您还应该添加一个示例,说明如何&|将其与非布尔参数(即运算符的作用)一起使用,以使所有人都受益。
2011年

81

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

如下代码:

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

确实是这样编译的:

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

下列代码完全按照其表示进行编译:

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

这称为短路。一般来说,你应该总是使用&&||您的条件。

奖励标记:在一种情况下,您不应该这样做。如果您在性能至关重要的情况下(这是至关重要的纳秒级),则仅在必要时才使用短路(例如null检查)-因为短路是分支/跳跃;这可能导致您的CPU出现分支错误预测;一个&便宜得多&&。在某些情况下,短路实际上会破坏逻辑-请看一下我的回答

Diatribe / Monologue:关于最幸福地忽略的分支错误预测。引用安迪·弗斯Andy Firth)(从事游戏已有13年的历史):“这可能是人们认为他们需要去的较低水平……但他们错了。了解您为编程编写的硬件如何对待分支机构可以在很大程度上影响性能……远远超出了大多数程序员所能理解的:一千次的死亡。”

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

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


7
关于“奖励标记”:我们都知道过早优化带来的好处。:)
CVn

6
@Michael-这就是'nano-secondscritical'用粗体显示的原因:)。AAA游戏开发人员通常会担心此类问题-而且您永远都不知道谁会阅读答案。因此,即使是临界/极端情况,也总是最好记录下来。
乔纳森·迪金森

1
该奖金标记对C#有效吗?除非将表达式编译为机器代码,否则我不会考虑,因为已解释了MSIL。
杰里米·麦基

7
@Jeremy MSIL未解释。
乔纳森·迪金森

2
@TheD重新检查答案-我添加了一个独白,说明为什么您应该为此感到担忧。而且,仅供参考,(x && y)转化为LOAD x; BRANCH_FALSE; LOAD y; BRANCH_FALSE;在那里(x & y)转化为LOAD x; LOAD y; AND; BRANCH_FALSE;。一个分支对两个分支。
乔纳森·迪金森

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

其次,由于布尔值true和false分别等于1和0,因此通常可以在bool上使用按位运算符,并且碰巧如果将true转换为1并将false转换为0,则进行按位运算,然后转换为非零为真,零为假;碰巧,如果您只是使用逻辑运算符,结果将是相同的(请检查此内容以进行练习)。

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

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

意思是:“如果某人存在(即不为null),则尝试对他/她进行打孔,如果该打孔成功(即,返回true),则进行一次胜利舞蹈”

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

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

会翻译为:“如果人存在(即不为null),并且拳打成功(即返回true),则进行胜利舞蹈”

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

[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
侧面的+1影响问题。惊讶的是之前没有提到过
康拉德·弗里克斯

3
该示例似乎有点暴力,但似乎其他答案过于侧重于短路,而对整数和布尔运算之间的差额却不够。
R0MANARMY 2011年

在实施细节(短路/副作用)发挥作用之前,必须先了解功能。很高兴您清除了布尔逻辑与整数逻辑之间的主要区别,而不是短路。
亚伯

@ROMANARMY-暴力,我喜欢您的僧侣的讽刺意味。尼斯的工作
brumScouse

8

如果是:

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

会按预期工作。

但:

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

可能会引发null引用异常。


2

简短:

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

trueANDS在逻辑上true给予true

1 & 2= 0 =否,
因为
1 =二进制二进制
2 = 0010二进制二进制

0001与0010按位进行AND运算,以十进制表示0000 = 0。

对于||同样 和 运营商也...!


2
-1:我们在这里谈论C#... 1 && 2在C#中是非法的
Daniel Hilgarth 2011年

但这是一个非常重要的示例,它解释了为什么您不能简单地交换&和&&(许多人似乎认为)。
bobobobo 2012年

1

&&是的短路版本&

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


1
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 && list [14] ==” foo“)”。在这种情况下,即使&肯定是安全的,&也不能简单地自然地将它用于&&(例如,list [1])。
Tien Do


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,请不要费心检查密钥,这可能需要更长的时间。


1

在逻辑表达式(如if语句)中使用时&&首选,因为一旦遇到第一个错误结果,它将停止对表达式的求值。这是可能的,因为false值将导致整个表达式为false。同样(在逻辑表达式中同样)||是可取的,因为一旦遇到真表达式,它将停止对表达式的求值,因为任何真值都将导致整个表达式为真。

然而,如果表达式正在或-ED或和ED一起有副作用,并且希望所有这些发生作为你表达的结果(无论是逻辑表达式的结果),那么&|可以使用。相反,&&||运算符可用作防止有害副作用(例如,空指针导致引发异常)的保护措施。

&|运营商也可以与使用的整数,在这种情况下,它们产生一个整数由此是两个操作数和-ED或或-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 & 4) != 0是表达此条件的安全方法,但是在许多类似C的语言中,条件语句可以简单地将零整数值视为false,将非零整数值视为true。(其原因与可用的条件分支处理器指令有关,以及它们与在每次整数运算后更新的零标志的关系。)因此, ìf可以删除语句对零的检验,并将条件缩短为(a & 4)

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

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

在C语言中,如果foo()返回1并bar()返回2,则由于“ 0”将不会执行“操作” 1 & 2

C#要求条件语句像if布尔布尔值,并且该语言不允许将整数值强制转换为布尔值。因此,上面的代码将生成编译器错误。它将更正确地表示为:

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

1

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

MSDN|运营商说:

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

(强调是我的。)布尔类型是专门处理的,在这种情况下,这个问题才变得有意义,区别在于,正如其他答案中已经解释的那样:

&&并且||短路。&|评估两个操作数。

首选取决于副作用,性能和代码可读性等许多方面,但是总的来说,短路运算符也是可取的,因为类似背景的人会更好地理解它们。

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


0

这很重要,因为如果bool2的评估成本很高(例如),而bool1为假,那么使用&& over&可以节省大量计算时间


0

由于&&||用于流量控制就像if/else是。它并不总是关于条件的。这是完全合理的,写成一份声明,不作为ifwhile条件,下面:

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

甚至

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

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


0

&&和&表示两个截然不同的事物,并给您两个不同的答案。

1 && 2产生1(“ true”)
1 & 2产生0(“ false”)

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


2
问题是关于C#的。在C#中,无法将数字强制转换为布尔值,因此0不是'false',非零不是'true';根本没有对等。
Nate CK

要将数字转换为布尔值,以1表示真,0表示假的方式,说“ n!= 0”(我想...我不太熟悉C#)。实际上,我想撤消此评论,因为它没有经过充分研究,并且我认为它与以前的评论无关,因此我认为它与以前的评论无关或没有任何意义,但是我不小心按了Enter键,现在我不认为我可以取消它,所以就在这里,不管它是什么值:-)
唐·哈奇

1 && 2给出编译器错误:“错误4运算符'&&'不能应用于类型为'int'和'int'的操作数”
Peter Mortensen

0

向那些不需要在执行此操作时不需要知道代码的确切操作的人进行解释的最快方法(略微简化)是

&&会对每个条件进行检查,直到找到错误并返回整个结果为false

|| 正在检查所有这些条件,直到找到一个真并返回整个结果为真。

正在根据所有条件同时处理MATHS,并处理结果。

| 正在根据所有条件同时进行MATHS并处理结果。

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

例如:

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

在此操作中,“&0xFF”被迫仅查看二进制值。我个人还没有发现|的用途。但是。


0

只是,

if exp1 && exp2

如果exp1是flase 不检查exp2

if exp1 & exp2

如果exp1是falsetrue 检查exp2

很少有人使用,&因为他们很少想检查exp2是否为exp1false

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.