在处理单个短路运算符时如何对子表达式进行分组是否重要?
a && b && c && d
a && (b && (c && d))
(a && b) && (c && d)
((a && b) && c) && d
上面的表达式是等价的吗?
在处理单个短路运算符时如何对子表达式进行分组是否重要?
a && b && c && d
a && (b && (c && d))
(a && b) && (c && d)
((a && b) && c) && d
上面的表达式是等价的吗?
是的,这些表达式都是等价的,包括它们的短路行为。
&&
括号改变了评估单个 s 的顺序。但是,由于&&
始终是左关联的,因此始终以从左到右的顺序评估这些术语。所以一旦发现一个词是假的,剩下的就可以跳过了。
证明三个子表达式的两个简化情况的等价性相对容易:
a && (b && c) --> a && bc // bc is a shorthand for b && c
在这里,a
将首先进行评估。如果它是假的,短路将阻止评估bc
。如果为真,bc
就会被评估,也就是b && c
会被评估。如果b
为假,c
则不会被评估。
(a && b) && c --> ab && c // ab is a shorthand for a && b
在这里,ab
将首先进行评估。(即a && b
首先评估。如果a
为假,短路将阻止对 的评估b
。否则,ab
产生b
。)如果ab
为假,c
则不会被评估。
现在,如果您更喜欢证据而不是证明,可以查看以下 C 代码的汇编输出:
int a(), b(), c(), d();
void e()
{
a() && b() && c() && d();
}
void f()
{
a() && (b() && (c() && d()));
}
void g()
{
(a() && b()) && (c() && d());
}
void h()
{
((a() && b()) && c()) && d();
}
(我使用 C 代码而不是 C++ 代码来防止名称混淆。)
生成的程序集e
:
_e:
// ... enter ...
call _a
testl %eax, %eax
je L1
call _b
testl %eax, %eax
je L1
call _c
testl %eax, %eax
je L1
call _d
testl %eax, %eax
nop
L1:
// ... leave ...
生成的程序集f
:
_f:
// ... enter ...
call _a
testl %eax, %eax
je L4
call _b
testl %eax, %eax
je L4
call _c
testl %eax, %eax
je L4
call _d
testl %eax, %eax
nop
L4:
// ... leave ...
生成的程序集g
:
_g:
// ... enter ...
call _a
testl %eax, %eax
je L7
call _b
testl %eax, %eax
je L7
call _c
testl %eax, %eax
je L7
call _d
testl %eax, %eax
nop
L7:
// ... leave ...
生成的程序集h
:
_h:
// ... enter ...
call _a
testl %eax, %eax
je L10
call _b
testl %eax, %eax
je L10
call _c
testl %eax, %eax
je L10
call _d
testl %eax, %eax
nop
L10:
// ... leave ...
如您所见,除了标签之外,生成的汇编代码完全相同。
在您的示例中,括号无关紧要。但这只是因为&&的性质,所有条款都需要检查(如果为真,或者如果其中任何一个为假,则为假)。
在这个例子中,括号确实有很大的不同:
(a && b) || (c && d) // either a & b are true, or c & d
a && (b || c && d) // a must be true, and either b or c & d
(a && b || c) && d // d must be true, and either c or a & b
当然,由于逻辑不同,短路的工作方式也不同。
在第一行中如果 a 为假,它将继续到第二项 (c && d)。
在第二行中,如果 a 为假,它只会返回假。
此属性称为关联性。来自维基百科文章:
在数学中,结合性是一些二元运算的属性。这意味着,在同一关联运算符的一行中包含两次或多次出现的表达式中,只要操作数的顺序不变,执行操作的顺序无关紧要。也就是说,在这样的表达式中重新排列括号不会改变它的值。
内置operator&&
是完全关联的,因此上述适用。
情况并非总是如此,例如:
operator-
通常是左结合的,即a - b - c == (a - b) - c != a - (b - c)
a ** b ** c == a ** (b ** c) != (a ** b) ** c
(a x b) x c != a x (b x c)
(没有括号,表达式甚至没有意义)请注意,这仅适用于一致使用单个运算符的情况,一旦||
在混合中引入另一个运算符(如 ),那么您必须考虑运算符优先级,这是另一个主题。