繁体   English   中英

关于 '&&' 和 '||' 的令人困惑的例子优先级

[英]Confusing example about '&&' and '||' precedence

我正在测试&&||之间的优先级我有一个令人困惑的例子。 在Java中, &&的运算符优先级高于|| .

因此,如果我们有这 3 个表达式:

//expr1  = true , expr2 = false; expr3 = false;

if(expr1 || expr2 && expr3);

它应该被评估为:

if(expr1 || (expr2 && expr3));

所以expr2 && expr3应该在expr1之前进行评估。 但是,这个例子:

int a1 = 10;
int a2 = 20;
System.out.println(a1 < a2 || ++a1 > a2 && ++a2 < a1);
System.out.println(a1);
System.out.println(a2);

输出:

true
10
20

这证明只有a1 < a2被评估。 你能解释一下为什么会这样吗?

这种表达是短路的 从链接:

当AND函数的第一个参数计算为false时,总值必须为false; 并且当OR函数的第一个参数的计算结果为true时,总值必须为true。

它看到其余条件无关紧要,因为||的操作数之一 已经为真(10 <20)。 如果其中一个操作数为true,则无论其余条件如何,都是如此。

您可以使用按位&| 为了防止这种情况。


但是,(expr2 && expr3)应该在expr1之前评估,不是吗?

否。您必须将优先级评估顺序的概念分开。

  • 优先级 :指定表达式的括号 ,而不是计算表达式的顺序。 例如:

     true || false && false 

    加上括号是因为&&优先级高于||

     true || (false && false) 

    这并不意味着在Java的情况下,首先要评估括号中的内容。 优先级只是阐明了运算符的操作数是什么,在这种情况下为falsefalse ,其中在这种情况下:

     (true || false) && (false || false) 

    &&的操作数是truefalse ,不是falsefalse

  • 评估顺序 :描述评估每个操作数和应用运算符的顺序,有时是特定于语言的。 与优先级不同,这决定了表达式的求值方式。

在这种情况下,您的示例:

true || false && false

如前所述,由于优先级而变为:

true || (false && false)

但是Java与C ++,JavaScript或许多其他语言不同,它具有严格的从左到右的评估。 根据Java语言规范

15.7。 评估单

Java编程语言保证运算符的操作数似乎按照特定的评估顺序(即从左到右)进行评估。

15.7.1。 首先评估左手操作数

在评估右侧操作数的任何部分之前,似乎已对二进制运算符的左侧操作数进行了完全评估。

因此,当您拥有:

true || (false && false)

Java 首先评估左操作数 ,结果为true 然后整个条件短路。 ||的右操作数 括号中的绝对不会被评估。 您的其他示例也是如此:

a1 < a2 || (++a1 > a2 && ++a2 < a1)
           ^^^^^^^^^^^^^^^^^^^^^^^^
           Step 0, precedence and parenthesization

a1 < a2 || (++a1 > a2 && ++a2 < a1)
^^^^^^^
Step 1, left operand evaluated, variables resolved to values 10 and 20, condition is true

true || (++a1 > a2 && ++a2 < a1)
^^^^
Step 2, short circuits, left operand is not evaluated

再举一个更复杂的例子:

false || false || true && (false || true) || false

由于优先级,它变为:

false || false || (true && (false || true)) || false

然后,评估从左至右开始:

false || false || (true && (false || true)) || false
^^^^^^^^^^^^^^
Step 1, false || false, does not short circuit, right operand is evaluated, is false

false || (true && (false || true)) || false
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Step 2, false || (true && (false || true)), does not short circuit, right operand is evaluated
Step 2A, (true && (false || true)), does not short circuit, right operand is evaluated
Step 2B, (false || true), does not short circuit, right operand is evaluated, is true
Step 2C, (true && true), does not short circuit, right operand is evaluated, is true
Step 2D, false || true, does not short circuit, right operand is evaluated, is true

true || false
^^^^
Step 3, true || false short circuits, right operand is not evaluated, is true

因此,整个表达式的计算结果为true 整个表达式在整个过程中从左到右进行评估。 优先级仅通过括号指示运算符的操作数,而不是评估顺序。

丹尼尔·普赖登Daniel Pryden)所述 ,在埃里克·利珀特Eric Lippert)的解释性文章中对优先级,关联性和评估顺序进行了进一步的阅读,它消除了很多困惑。

主要结论是, 优先级并不决定表达式的计算方式。 它仅指示如何对表达式加上括号。 另一方面,求值顺序准确地告诉我们如何对表达式求值,在Java中,求值顺序 始终是从左到右。

由于||短路,第一行显示true 操作员。

a1 < a2是true,因此不需要评估布尔表达式的其余部分,并返回true

expr2应该在expr1之前评估

是错误的,因为运算符优先级会影响表达式的结构,而不是评估顺序(在大多数情况下)。 如果要对表达式重新排序,使其成为(expr2 && expr3) || expr1 (expr2 && expr3) || expr1 ,然后是, expr2将在expr1之前求expr1

优先顺序:

boolean result = a || b && c

为了根据优先级规则获得正确的值,编译器必须在逻辑上将其评估为:

boolean x = b && c
boolean result = a || x

这表明您必须对b && c进行评估,然后才能计算出result 但是,在布尔代数中,可以编写其结果不依赖于所有操作数的表达式。 利用这一事实来实现称为短路的编译器优化。

短路:

对于任何布尔表达式:

a || b

如果a的结果为true则结果不依赖于b atrueb值为true或false都无关紧要。 因此,编译器执行:

a || b && c

好像是这样写的:

boolean result = a;
if (!a) result = b && c;

注意,以这种方式执行时,仍然遵守优先级规则。 该表达式不被评估为(a || b) && c 由于在a为真的情况下表达式的整体结果不依赖于(b && c) ,因此根本就不会评估b && c

这是一件好事。 当一个操作数的正确性取决于另一个操作数的正确性时,短路允许您编写正确的程序。 例如:

if (myString == null || !myString.isEmpty() && myString != "break") return;

如果没有短路评估,则布尔表达式可能会抛出NullPointerException 但是,由于进行了短路评估,因此该表达式如所写,永远不会抛出NullPointerException

短路也可以用作性能优化。 如果评估一个操作数非常昂贵,则先评估另一个可以节省评估值不影响整个表达式最终结果的操作数所需的执行时间。

即使您像这样使用显式括号

if (expr1 || (expr2 && expr3))

首先评估expr1 ,因为运算符的操作数是从左到右评估的。 由于|| 如果是短路运算符,则仅当expr1false才会计算第二个操作数(在您的情况下为(expr2 && expr3) )。

删除括号时,仅当expr1为false时,运算符优先级才起作用。 在这种情况下, &&操作数将在||之前求值。 运算符,其值将是||的第二个操作数 操作员。

这是因为短路。 第一个表达式首先被评估并且如果它能够得出结果为真,那么它会得出结论,并且对于表达式的 rest 甚至不会 go。如果第一个表达式的结果为假,那么将检查其他条件并且结果将被导出。

要了解有关运算符优先级的更多信息。 请参考下文。

https://introcs.cs.princeton.edu/java/11precedence/

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM