为什么是“&&”而不是“&”
本文关键字:amp 为什么 | 更新日期: 2023-09-27 18:07:45
为什么&&
比&
更可取,||
比|
更可取?
我问了一个已经编程多年的人,他的解释是:
例如,在if (bool1 && bool2 && bool3) { /*DoSomething*/ }
中,bool1
必须为真才能测试bool2
在继续bool3
之前必须为真,等等。如果我使用单个&
,即使所有测试都必须为真才能进入下一行,测试也没有顺序,那么为什么它很重要呢?
注意:我想指出的是,我相当于一个蹒跚学步的孩子,这不是一个严肃或紧迫的问题。这更多的是理解为什么事情应该以某种方式而不是另一种方式完成。
在大多数情况下,&&
和||
优先于&
和|
,因为前者是短路的,这意味着一旦结果明确,评估就会被取消。
例:
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
。如果改用按位 AND 运算符&
,则在字典中找不到键时会得到NullReferenceException
,因为表达式的第二部分在任何情况下都会执行。
一个类似但更简单的例子是以下代码(如TJHeuvel所述(:
if(op != null && op.CanExecute())
{
// Do Something
}
仅当op
未null
时,才会执行CanExecute
。如果op
null
,表达式的第一部分(op != null
(计算为false
,其余部分(op.CanExecute()
(的计算被跳过。
除此之外,从技术上讲,它们也是不同的:
&&
和||
只能用于bool
而&
和|
可以用于任何整型(bool
、int
、long
、sbyte
、...(,因为它们是按位运算符。 &
是按位 AND 运算符,|
是按位 OR 运算符。
确切地说,在 C# 中,这些运算符(&
、|
[和 ^
](被称为"逻辑运算符"(参见 C# 规范,第 7.11 章(。这些运算符有几种实现:
- 对于整数(
int
,uint
,long
和ulong
,第7.11.1章(:
它们用于计算操作数和运算符的按位结果,即 实现&
以计算按位逻辑AND
等。 - 对于枚举(第 7.11.2 章(:
实现它们是为了执行枚举的基础类型的逻辑操作。 - 对于布尔值和可为空布尔值(第 7.11.3 章和第 7.11.4 章(:
结果不是使用按位计算计算的。结果基本上是根据两个操作数的值查找的,因为可能性的数量太少了。
由于这两个值都用于查找,因此此实现不会短路。
非常清楚地解释这意味着什么(即使其他答案暗示了这一点 - 但可能使用您不理解的术语(。
以下代码:
if (a && b)
{
Foo();
}
真的是编译成这个:
if (a)
{
if (b)
{
Foo();
}
}
其中,以下代码的编译方式与所表示的完全一致:
if (a & b)
{
Foo();
}
这称为短路。一般来说,您应该始终在您的条件下使用&&
和||
。
奖励标记:有一种情况是你不应该这样做的。如果您处于性能至关重要的情况下(这在纳秒至关重要(,请仅在必须时才使用短路(例如 null
检查( - 因为短路是分支/跳跃;这可能会导致您的 CPU 上的分支预测错误;&
比&&
便宜得多.还有一种情况是短路实际上会破坏逻辑 - 看看我的这个答案。
谩骂/独白:关于最幸福地忽略的分支错误预测。引用Andy Firth(他已经从事游戏工作了13年(的话:"这可能是人们认为他们需要去的较低水平......但他们错了。了解您为分支编程的硬件如何在很大程度上影响性能......远远超过大多数程序员所欣赏的RE:千刀万剐。
- 游戏开发人员(以及其他在极端实时条件下工作的人(甚至重组了他们的逻辑以更好地适应预测器。在反编译的mscorlib代码中也有证据。
- 仅仅因为 .NET 可以保护您免受此类事情的影响并不意味着它不重要。分支错误预测在 60 Hz 时非常昂贵;或每秒 10,000 个请求。
- 英特尔没有工具来识别错误预测的位置,Windows也不会为此提供性能计数器,也不会有一个词来描述它,如果这不是一个问题的话。
- 对较低层次和架构的无知不会使意识到它们的人出错。
- 始终尝试了解您正在使用的硬件的限制。
这是非信徒的基准。最好在实时/高中运行该过程以减轻调度程序的影响:https://gist.github.com/1200737
逻辑运算符(||
和&&
(与按位运算符(|
和&
(。
布尔值,而按位运算符采用两个整数并生成一个整数(注意:整数表示任何整数数据类型,而不仅仅是 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,则进行按位运算,然后将非零转换为真,将零转换为 false;碰巧结果会是一样的,如果你只是使用逻辑运算符(检查这个练习(。
另一个重要的区别也是逻辑运算符短路。因此,在某些圈子[1]中,您经常看到人们在做这样的事情:
if (person && person.punch()) {
person.doVictoryDance()
}
翻译过来就是:"如果人存在(即不为空(,试着打他/她,如果出拳成功(即返回真(,那就跳胜利舞"。
如果您改用按位运算符,则如下所示:
if (person & person.punch()) {
person.doVictoryDance()
}
将翻译为:"如果人存在(即不为空(并且出拳成功(即返回 true(,则跳胜利之舞"。
请注意,在短路逻辑运算符中,如果 person
为 null,则可能根本不运行person.punch()
代码。事实上,在这种特殊情况下,如果 person
为 null,第二个代码将产生 null 引用错误,因为无论 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 文件系统权限中。
在以下情况下:
if (obj != null && obj.Property == true) { }
将按预期工作。
但:
if (obj != null & obj.Property == true) { }
可能会引发空引用异常。
简短而简单:
1 && 2
= 真
因为
1 = 在 C
中为真(非零(2 = 在 C 中为真(非零(
true
ANDS 逻辑上与true
给出true
.
但
1 & 2
= 0 = 假
因为
二进制 1 = 0001
2 = 0010 二进制
0001 AND 与 0010 按位排列,得出十进制的 0000 = 0。
同样适用于 || 和 | 运算符...!
&&
是&
的短路版本。
如果我们正在评估false & true
,我们已经从第一个参数中知道结果将是假的。运算符的&&
版本将尽快返回结果,而不是计算整个表达式。|
运算符也有类似的版本,||
.
if (list.Count() > 14 && list[14] == "foo")
是安全的
if (list.Count() > 14 & list[14] == "foo")
如果列表大小不正确,则会崩溃。
C# 运算符应解释原因:
本质上有两个&
或|
意味着它是条件的而不是逻辑的,所以你可以分辨出两者之间的区别。
&Operator 有一个使用一个&
的示例。
好的,在面值上
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
,请不要费心检查密钥,这可能需要更长的时间。
在逻辑表达式(如 if 语句(中使用时&&
可取的,因为一旦遇到第一个错误结果,它就会停止计算表达式。 这是可能的,因为假值将导致整个表达式为假。 类似地(同样在逻辑表达式中(||
更可取,因为它一旦遇到真表达式就会停止计算表达式,因为任何真值都会导致整个表达式为真。
但是,如果 or-ed 或 and-ed 一起的表达式有副作用,并且您希望所有这些副作用都是表达式的结果(无论逻辑表达式的结果如何(,则可以使用 &
和 |
。 相反,&&
和 ||
运算符可以用作防止不需要的副作用(例如导致引发异常的 null 指针(的保护措施。
&
和 |
运算符也可以与整数一起使用,在这种情况下,它们产生一个整数结果,该结果是位级别的两个操作数和 -ed 或 or-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,则不会执行"某事",因为1 & 2
为零。
C# 要求条件语句(如 if
(具有布尔值,并且该语言不允许将整数值转换为布尔值。 因此,上面的代码将生成编译器错误。 更准确地表述如下:
if (foo() != 0 & bar() != 0) {
// do something
}
如果你是一个老式的C程序员,要小心。C#真的让我感到惊讶。
MSDN对|
运营商说:
二进制 | 运算符是为整数类型和布尔值预定义的。对于整型,|计算其操作数的按位 OR。对于布尔操作数, |计算其操作数的逻辑 OR;也就是说,当且仅当其两个操作数都为假时,结果为 false。
(重点是我的。布尔类型是专门处理的,在这种情况下,问题才开始有意义,区别在于,正如其他人在他们的答案中已经阐述的那样:
更可取
&&
和||
短路。&
和|
计算这两种操作数。
的取决于许多因素,例如副作用,性能和代码可读性,但通常短路运算符更可取,因为它们被像我这样具有类似背景的人更好地理解。
原因是:我会这样论证:由于 C 中没有真正的布尔类型,您可以使用按位运算符|
,并在 if 条件下将其结果评估为真或假。但这是 C# 的错误态度,因为布尔类型已经存在特殊情况。
很重要,因为如果 bool2 的评估成本很高(例如(但 bool1 是假的,那么你通过使用 &&over &
因为&&
和||
就像if/else
一样用于流量控制。它并不总是关于条件。 将以下内容写成陈述,而不是if
或while
条件是完全合理的:
a() && b() && c() && d();
甚至
w() || x() || y() || z();
不仅仅是那些比等效的if/else
版本更容易输入;它们也更容易阅读和理解。
&和&意味着两个非常不同的东西,并给你两个不同的答案。
1 && 2
产生 1("真"(
1 & 2
产生 0("假"(
&&
是一个逻辑运算符 -- 它的意思是"如果两个操作数都为真,则为 true">
&
是按位比较。它的意思是"告诉我在两个操作数中设置了哪些位">
不需要知道代码确切操作的人解释这一点的最快(并且有点愚蠢(方法是
&&正在检查这些条件中的每一个,直到它发现一个假并将整个结果返回为假
|| 正在检查这些条件中的每一个,直到它找到一个 true 并将整个结果返回为 true。
并且正在根据所有条件和处理结果进行数学分析。
| 正在做基于两者/所有条件的数学并处理结果。
我从来没有遇到过需要在 if 语句中使用 & or | 的点。我主要使用它来使用按位移位将十六进制值切割成其组件颜色。
例如:
r = fullvalue >> 0xFF & 0xFF;
g = fullvalue >> 0xF & 0xFF;
b = fullvalue & 0xFF;
在此操作中,"&0xFF"强制仅查看二进制值。不过,我个人还没有找到 | 的用途。
简单地说,
if exp1 && exp2
如果 exp1 flase
不要检查 exp2
但
if exp1 & exp2
如果 exp1 false
或true
检查 exp2
很少有人使用&
因为他们很少想检查 exp2 是否false
exp1