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

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

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

例如,在 if (bool1 && bool2 && bool3) { /*DoSomething*/ }中,为了测试 bool2bool1必须为真,而在转移到 bool3之前,bool2必须为真,等等。如果我使用一个单独的 &代替,那么即使所有的 &都必须为真才能进行到下一行,测试也没有顺序,那么这又有什么关系呢?

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

20314 次浏览

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

例如:

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。如果使用 false0操作符 &,那么如果在字典中没有找到键,就会得到一个 NullReferenceException,因为表达式的第二部分在任何情况下都会执行。

一个类似但更简单的例子是下面的代码(正如 TJHeuvel 提到的) :

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

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

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

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

  1. 对于整数(intuintlongulong,第7.11.1章) :
    它们的实现是为了计算操作数和运算符的按位结果,即 &的实现是为了计算按位逻辑 AND等。
  2. 列举(第7.11.2章) :
    它们的实现是为了执行枚举的基础类型的逻辑操作。
  3. 适用于黑名单及可为空白的黑名单(第7.11.3及7.11.4章) :
    结果不使用按位计算进行计算。结果基本上是根据两个操作数的值来查找的,因为可能性的数目非常小。
    因为这两个值都用于查找,所以这个实现不会短路。

这一点很重要,因为如果评估 bool 2(例如)的成本很高,但是 bool 1是假的,那么通过使用 & & over & 可以为自己节省相当多的计算量

C # 操作符 应该解释为什么:

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

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

&&&的短路版本。

如果我们正在计算 false & true,我们已经通过查看第一个参数知道结果将为 false。操作符的 &&版本将尽快返回结果,而不是计算整个表达式。还有一个类似版本的 |操作符 ||

在下列情况下:

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

会像预期的那样奏效。

但是:

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

可能会抛出空引用异常。

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

是安全的

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

如果列表没有合适的大小就会崩溃。

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

以下代码:

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

实际上是这样编辑的:

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

下列代码完全按照所示编译的:

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

这就是所谓的短路。一般来说,你应该始终使用 &&||在您的条件。

奖励标记: 有一种情况下你不应该这样做。如果你正处于性能至关重要的情况下(这是 纳秒至关重要) ,只有在必要的时候才使用短路(例如 null检查)——因为短路是一个分支/跳转; 这可能导致 CPU 上的分支预测错误; &&&便宜得多。还有一种情况,短路实际上可以打破逻辑-看看我的 这个答案

谩骂/独白 : 对于最幸福忽视的分支预测错误。引用 Andy Firth的话(他从事游戏开发已经13年了) : “这可能是人们认为他们需要去的较低水平... ... 但是他们错了。理解你为治疗分支编程的硬件是如何影响性能到一个巨大的程度... 远远超过大多数程序员可能会理解的重复: 千刀万剐

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

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

好吧,从表面上看

    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,就不用检查钥匙了,可能会花更长的时间。

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

逻辑运算符和位运算符之间最重要的区别是,逻辑运算符接受 两个布尔值并产生一个布尔值,而位运算符接受 两个整数并产生一个整数(注意: 整数意味着任何整数数据类型,而不仅仅是 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转换为 true,把0转换为 false,如果你刚刚使用了逻辑运算符,结果也会是一样的。

另一个重要的区别是逻辑运算符是 短路了。因此,在某些圈子里,你经常看到人们做这样的事情:

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

翻译过来就是: 如果一个人存在(即不是无效的) ,尝试打他/她,如果打他/她成功(即返回真值) ,然后跳一支胜利之舞

如果使用位运算符,则如下:

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

翻译成: 如果某人存在(即不为零) ,而冲击成功(即为真) ,则跳胜利之舞

注意,在短路逻辑运算符中,如果 person为空,则 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 文件系统权限。

简而言之:

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

逻辑上与 true一起给出 true

但是

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

用0010按位设置0001 AND,以十进制表示0000 = 0。

对于 | | 和 | 操作员也是如此... !

因为 &&||用于 流量控制,就像 if/else一样。并不总是条件句。以 一份声明,而不是以 ifwhile条件式写作是完全合理的,如下:

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

甚至

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

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

意思完全不同,给你两个不同的答案。

1 && 2产生1(“ true”)
1 & 2的收益率为0(“ false”)

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

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

然而,如果表达式或-ed 或-ed 一起有副作用,并且您希望所有这些作为表达式的结果发生(不管逻辑表达式的结果如何) ,那么可以使用 &|。相反,&&||操作符可以用来防止不必要的副作用(例如导致抛出异常的空指针)。

&|运算符也可以与整数一起使用,在这种情况下,它们会产生一个整数结果,即两个操作数在位级上一起使用-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,则不会执行“ something”,因为 1 & 2为零。

C # 要求像 if这样的条件语句有一个布尔 oeprand,并且该语言不允许将整数值转换为布尔值。因此,上面的代码将生成编译器错误。更正确的表述如下:

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

这是向那些不需要了解代码的确切操作的人解释这一点的最快方法(并且稍微简化了一些)

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

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

就是基于所有的条件和处理结果来做数学运算。

| 正在基于所有条件和处理结果进行数学运算。

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

例如:

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

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

如果你是一个老的 C 语言程序员,小心点.C # 确实让我感到惊讶。

MSDN |运营商说:

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

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

&&||短路。 &|计算 都有操作数。

什么更好取决于许多事情,如副作用,性能和代码可读性,但一般来说,短路操作符更可取,也因为他们更好地理解像我这样的相似背景的人。

原因是: 我的参数是这样的: 因为在 C 中没有实际的布尔类型,所以可以使用位运算符 |,并在 if 条件下将其结果计算为 true 或 false。但是这种态度对于 C # 来说是错误的,因为对于布尔类型已经有了一个特例。