短路和括号优先是什么意思?

9

在处理单个短路运算符时,我如何分组子表达式是否重要?

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

这些表达式是等价的吗?

在我上一条评论中,我犯了错误,对此我深表歉意。我已将其删除,因为它是不正确的。 - npclaudiu
4个回答

6

是的,这些表达式都是等价的,包括它们的短路行为。

括号改变了单个&&的求值顺序。然而,由于&&总是从左往右结合,因此术语总是按照从左到右的顺序进行评估。因此,一旦发现一个术语为假,就可以跳过其余部分。


5

对于三个子表达式的两种简化情况,证明等价性相对容易:

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`为false,则短路将阻止对`b`的评估。否则,`ab`将产生`b`。)如果`ab`为false,则`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 ...

正如您所看到的,除了标签之外,生成的汇编代码完全一致。


3

在你的例子中,括号并不重要。但这只是由于&&的性质,所有术语都需要被检查(如果为真,或者如果任何一个为假,则为假)。

在这个例子中,括号确实有很大的区别:

(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为false,则会继续执行第二个表达式(c && d)。
在第二行中,如果a为false,则直接返回false。

2
这个属性被称为“结合律”。来自维基百科文章的描述:
在数学中,结合律是一些二元运算的性质。它意味着,在包含两个或更多相同结合运算符的连续出现的表达式中,只要操作数的顺序不改变,执行操作的顺序就无所谓。也就是说,重新排列这种表达式中的括号不会改变其值。
内置的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)(而且没有括号时,表达式甚至没有意义)

请注意,这仅适用于单个运算符被一致使用的情况,一旦引入另一个运算符(如||),则必须考虑运算符优先级,这是另一个主题。


1
这解决了数学定义中“&&”的结合性问题,但它并未解决短路问题(这不是数学定义的一部分)。 - Oliver Charlesworth
@Oli:这句话并未涉及它,但是当我确定operator&&是完全关联时,我就解决了。如果短路行为不能被保持,那么我们就无法将运算符定义为关联的。 - Matthieu M.
根据大多数 C 运算符的描述,&& 是左结合的。公平地说,我并不真正知道这意味着什么,因为没有其他运算符具有相同的优先级。 - Oliver Charlesworth
@Oliпјҡе·Ұе…іиҒ”еҸҜд»Ҙж„Ҹе‘ізқҖa && b && c == (a && b) && c != a && (b && c)пјҢжҲ‘дјҡиҜҙиҝҷжҳҜй”ҷиҜҜзҡ„пјҢжҲ–иҖ…е®ғеҸҜд»Ҙж„Ҹе‘ізқҖa && b && c == (a && b) && cпјҢеҝҳи®°еҸіе…іиҒ”гҖӮеҪ“иҜ„дј°a && (b && c)ж—¶пјҢеҰӮжһңaдёәfalseпјҢеҲҷеҒңжӯўпјҢеҗҰеҲҷиҜ„дј°b && cпјҢеҰӮжһңbдёәfalseеҲҷзҹӯи·ҜпјҢеҗҰеҲҷиҜ„дј°cгҖӮеӣ жӯӨпјҢ&&жҳҜе®Ңе…Ёе…іиҒ”зҡ„пјҢ并且жӮЁеҸҜд»ҘиҜҙе®ғжҳҜе·Ұе…іиҒ”зҡ„пјҲе°Ҫз®ЎдёҚдёҘж јпјүгҖӮ - Matthieu M.
我理解 "left-associative" 的意思是 a && b && c == (a && b) && c。但这并不一定意味着 != a && (b && c) - Oliver Charlesworth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接