当运算符优先级说不应该时,为啥短路评估会起作用?

Posted

技术标签:

【中文标题】当运算符优先级说不应该时,为啥短路评估会起作用?【英文标题】:Why does short-circuit evaluation work when operator precedence says it shouldn't?当运算符优先级说不应该时,为什么短路评估会起作用? 【发布时间】:2018-03-12 09:05:08 【问题描述】:

javascript 和Java 中,等于运算符(=====)的优先级高于或运算符(||)。然而两种语言(JS、Java)都支持在if 语句中进行短路:

当我们有if(true || anything()) 时,anything() 不会被评估。

您还可以使用以下表达式:true || foo == getValue()) - 例如在 console.log(...); 等输出语句或赋值中。

现在,根据运算符优先级,不应该发生短路,因为在优先级方面=== = == > ||。 (换句话说,应该首先进行比较,应该调用getValue(),因为相等性检查的优先级高于OR比较。)但确实如此。 getValue() 未被调用(可以通过将输出语句放入其主体中轻松检查)。

为什么(当运算符优先级表明不应该短路时,短路会起作用)? 还是我把事情搞糊涂了?

【问题讨论】:

相关:What are the rules for evaluation order in Java? 你的问题有矛盾。你说“......应该首先进行比较...... getValue() 应该被调用......但它确实如此。”所以你的问题的一部分暗示 getValue() 没有被调用,但是这个语句说是 is 被调用。是哪个? @Chloe:他的 “但确实如此” 指的是 “不应该发生短路”,这与他对 "getValue() 应该被称为..." 并且他观察到 "getValue() 不被称为" 【参考方案1】:

还是我把事情搞糊涂了?

你是。我认为将优先级视为分组比排序要简单得多。它会影响评估的顺序,但只是因为它会改变分组。

我不确定 Javascript,但在 Java 中,操作数总是按从左到右的顺序计算。 == 的优先级高于 || 的事实只是意味着

true || foo == getValue()

被评估为

true || (foo == getValue())

而不是

(true || foo) == getValue()

如果您只是以这种方式考虑优先级,然后考虑评估始终是从左到右的(例如,|| 的左操作数总是在右操作数之前评估)那么一切都很简单 - 并且getValue() 由于短路而从未被评估。

要从等式中消除短路,请考虑以下示例:

A + B * C

... 其中ABC 可以只是变量,也可以是其他表达式,例如方法调用。在 Java 中,这保证被评估为:

评估A(并记住它以备后用) 评估B 评估CBC 的评估结果相乘 将计算A 的结果与乘法的结果相加

请注意,即使* 的优先级高于+A 仍会在BC 之前进行评估。如果您想要考虑排序方面的优先级,请注意乘法仍然在加法之前发生 - 但它仍然满足从左到右的评估顺序。

【讨论】:

虽然我完全短路了,但我仍然不明白为什么它符合规则。本质上,我们有operand1 operatorA operand2 operatorB operand3operatorA 的优先级低于operatorB 的优先级,因此运算符优先级应该导致o2 oB o3 首先被评估。相反(由于短路)解释器看到operatorA 是一个或,因此只评估operand1。计算结果为true,因此没有其他内容得到评估。尽管运算符优先级说明它应该是相反的; o2 oB o3 应该首先运行。 @Christian:“所以运算符优先级应该导致首先评估 o2 oB o3” - 不,它确实没有。运算符优先级意味着它被分组,使得 operatorA 的操作数是“operand1”和“计算 operatorB 的结果”。但是operand1 仍然首先被评估。这就是我所说的更多关于分组的意思。 事实上,从左到右的规则只适用于相同顺序的操作数:1 + 2 + 3 => 3 + 3 => 61 + 2 * 3 => 1 + 6 => 7,不是3 * 3 => 9,对吧?如果是这样,我们又回到了第一格,因为 || 的顺序比 == 低,但首先计算(即我们得到 9,而不是 6)... @Christian:不,那是你想的关联性。请参阅docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.7:“在评估右侧操作数的任何部分之前,二元运算符的左侧操作数似乎已被完全评估。”因此,|| 的左侧操作数在评估右侧操作数之前进行评估。 @Christian:您可能会发现我不久前写的关于该主题的这篇博文很有用:codeblog.jonskeet.uk/2015/04/21/precedence-ordering-or-grouping【参考方案2】:

根据语言规范,https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.24

在运行时,首先计算左侧操作数表达式;如果结果为布尔类型,则进行拆箱转换(第 5.1.8 节)。

如果结果值为真,则条件或表达式的值为真,并且不计算右侧操作数表达式。

所以如果你有a || b==c,它不会被解释为(a || b) == c,因为|| 的优先级较低,正如你在教程中发现的那样。相反,它被解释为a || (b==c)。现在因为a|| 的左侧,所以首先计算它。

【讨论】:

我发现a || (b==c) 并没有那么令人困惑,因为括号在两种语言中的优先级更高,对于 Java,请参阅 docs.oracle.com/javaee/6/tutorial/doc/bnaik.html【参考方案3】:

在这种情况下没有运算符优先级。您所质疑的就像在f(callback) 语句中的callback 函数甚至在f 之前被评估。这不可能发生。

另一方面,在 JS 中,|| 是少数几个可以在展示中看到懒惰的地方之一。 == 操作数(就像在全功能语言中一样认为它是一个中缀函数)有两个参数,左边的一个首先被评估。如果它解析为true,则第二个参数甚至不会被评估。

【讨论】:

以上是关于当运算符优先级说不应该时,为啥短路评估会起作用?的主要内容,如果未能解决你的问题,请参考以下文章

c#:为啥当我尝试在模拟中使用合并运算符时它不起作用

布尔运算符的Python优先规则[重复]

当 vuex 文档说它不应该时,为啥 splice 对对象数组类型属性起作用?

Python `or`、`and` 运算符优先级示例

() 优先级最高,为啥会短路?

一些赋值运算符在 AngularJS 表达式中不起作用.. 为啥不呢?