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

Dan*_*ani 136 c# operators

为什么更&&喜欢&||更喜欢|

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

例如,在中if (bool1 && bool2 && bool3) { /*DoSomething*/ },bool1必须是真实的,以便bool2在继续之前测试哪个必须为真bool3,等等.如果我使用单个&而不是测试的顺序,即使所有这些都必须是真的进展到下一行,为什么它至关重要?

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

Dan*_*rth 183

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

例:

if(CanExecute() && CanSave())
{
}
Run Code Online (Sandbox Code Playgroud)

如果CanExecute返回false,则完整表达式将是false,无论返回值如何CanSave.因此,CanSave不执行.

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

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

TryGetValuefalse如果在字典中找不到提供的密钥,则返回.由于短路性质&&,value.Contains("test")仅在执行时TryGetValue返回true,因此value不执行null.如果你要使用按位AND运算符&,你会得到一个NullReferenceException如果在字典中找不到键,因为表达式的第二部分在任何情况下都会被执行.

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

if(op != null && op.CanExecute())
{
    // Do Something
}
Run Code Online (Sandbox Code Playgroud)

CanExecute只有在op没有的情况下才会执行null.如果opnull,则表达式(op != null)的第一部分求值为,falseop.CanExecute()跳过对rest()的求值.

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

准确地说,在C#中,那些运算符(&,|[和^])被称为"逻辑运算符"(参见C#规范,第7.11章).这些运算符有几种实现方式:

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

  • 这对于检查某些内容是否为空也很方便.例如:`if(op!= null && op.CanExecute())`.因为当第一个原因不正确时,第二个原因没有被评估,这是有效的. (31认同)
  • 好答案.也许你还应该添加一个例子,说明`&`或`|`如何与非bool参数一起使用(即运算符的作用),以造福所有新人. (4认同)
  • @TJHeuvel:这与我在`TryGetValue`示例中描述的用法基本相同.但是,是的,这是另一个很好的例子. (2认同)

Jon*_*son 82

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

以下代码:

if (a && b)
{
   Foo();
}
Run Code Online (Sandbox Code Playgroud)

真的编译成这个:

if (a)
{
    if (b)
    {
        Foo();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

if (a & b)
{
   Foo();
}
Run Code Online (Sandbox Code Playgroud)

这称为短路.一般来说,你应该始终使用&&||在你的条件.

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

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

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

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

  • 关于"奖励标记":我们都知道过早优化带来的好处.:) (7认同)
  • @Jeremy MSIL没有解释. (7认同)
  • @Michael - 这就是为什么'纳秒关键'是粗体:).AAA游戏开发者通常会担心这样的事情 - 你永远不知道谁会阅读答案; 所以最好甚至记录边缘/极端情况. (6认同)
  • @TheD重新检查答案 - 我添加了一个独白,说明为什么你应该为此而烦恼.而且,FYI,`(x && y)`转换为`LOAD x; BRANCH_FALSE; 加载y; BRANCH_FALSE;```(x&y)`转换为`LOAD x; 加载y; 和; BRANCH_FALSE;`.一个分支与两个分支. (2认同)

Lie*_*yan 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)
Run Code Online (Sandbox Code Playgroud)

而逻辑运算符仅适用于bool:

a      = true
b      = false
--------------
a && b = false
a || b = true
Run Code Online (Sandbox Code Playgroud)

其次,通常可以在bool上使用按位运算符,因为true和false分别等于1和0,并且如果将true转换为1并将false转换为0,则执行按位运算,然后转换为非零为真,零到假; 如果你刚刚使用了逻辑运算符,那么结果将是相同的(检查这个运动).

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

if (person && person.punch()) {
    person.doVictoryDance()
}
Run Code Online (Sandbox Code Playgroud)

转换为:"如果人存在(即不是空),试着打他/她,如果拳打成功(即返回真),那就做一个胜利舞蹈".

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

if (person & person.punch()) {
    person.doVictoryDance()
}
Run Code Online (Sandbox Code Playgroud)

将翻译为:"如果人存在(即不为空)并且拳击成功(即返回true),则进行胜利舞蹈".

请注意,在短路逻辑运算符中,person.punch()如果person为null ,则可能根本不运行代码.实际上,在这种特殊情况下,如果person为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`
Run Code Online (Sandbox Code Playgroud)

对逻辑运算符执行相同操作需要进行大量的比较:

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;
Run Code Online (Sandbox Code Playgroud)

使用位模式和按位运算符的经典示例是Unix/Linux文件系统权限.

  • 边的+1会影响问题.很惊讶它没有提到过 (3认同)
  • 这个例子看起来有点暴力,但似乎其他答案过于集中于短路,而对整数操作和布尔值之间的区别不够. (3认同)

fat*_*tty 8

如果是:

if (obj != null && obj.Property == true) { }
Run Code Online (Sandbox Code Playgroud)

会按预期工作.

但:

if (obj != null & obj.Property == true) { }
Run Code Online (Sandbox Code Playgroud)

可能会抛出空引用异常.