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

本文关键字: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时执行,因此valuenull。如果改用按位 AND 运算符&,则在字典中找不到键时会得到NullReferenceException,因为表达式的第二部分在任何情况下都会执行。

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

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

仅当opnull时,才会执行CanExecute。如果op null,表达式的第一部分(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 章(:
    结果不是使用按位计算计算的。结果基本上是根据两个操作数的值查找的,因为可能性的数量太少了。
    由于这两个值都用于查找,因此此实现不会短路。

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

以下代码:

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 &

amp
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 falsetrue 检查 exp2

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