C语言中逻辑运算符的优先级

13
#include <stdio.h>

int main(void) {
    int a = 0, b = 0, c = 0;
    ++a || ++b && ++c;
    printf("%d %d %d", a, b, c);
    return 0;
}

通过gcc 8.1.0编译,输出结果为100。在此情况下,&&的优先级应该高于||

为什么bc仍然是0


3
因为短路。 - Eugene Sh.
6个回答

14

表达式++a || ++b && ++c分组++a || (++b && ++c)但是,只有在++a0时,||右侧的表达式才会被计算,而这种情况不会发生。


括号中的表达式不应该在“||”之前计算吗? - Joshua Shi
1
@EricShi 绝对不是|| 的左侧先被评估。 - Bathsheba
8
@EricShi,你混淆了优先级和求值顺序的概念。 - Eugene Sh.
@EugeneSh。非常准确和重要。 - SergeyA
@EricShi,在执行||之前应该先对其进行求值,但++a不是||,而是其操作数。 - chris

13

这里有三个问题:

  1. 优先级顺序。
  2. 求值顺序。
  3. 逻辑运算符的短路现象。

优先级顺序意味着 ++a || ++b && ++c 被看作是 ++a || (++b && ++c)

然而,由于逻辑运算符的短路要求,首先会评估 ++a。只有在它的结果为 false 时才会评估 (++b && ++c)。在您的情况下,++a 的结果为 true。因此,(++b && ++c) 永远不会被评估。


2
逻辑 OR 运算符 || (以及逻辑 AND 运算符 &&)是少数执行短路操作的运算符之一。
C 标准的第 6.5.14 节对于逻辑 OR 运算符如下所述:

4 与按位 | 运算符不同,|| 运算符保证从左到右进行求值;如果计算第二个操作数,则在第一个和第二个操作数的计算之间存在一个序列点。 如果第一个操作数与 0 不相等,则不计算第二个操作数。

因为++a的值为1,所以||运算符的结果保证为1,右侧不会被计算。另外,由于&&的优先级高于||,所以||运算符的右侧是++b && ++c,这意味着++b++c都不会被计算。

1
优先级仅控制表达式的解析顺序,而不是它们的求值顺序。算术运算符 * 的优先级高于 +,因此 a * b + c 将被解析为 (a * b) + c。但是,a、b 和 c 中的每个都可以按任意顺序进行求值。必须在知道 a * b 的结果之后才能将其加到 c 的结果中,但这并不意味着必须在求值 c 之前先求值 a * b。
其次,与 C 中的大多数运算符不同,|| 和 && 运算符强制从左到右进行求值。像 a || b && c 这样的表达式将被解析为 a || (b && c),但 a 将始终首先被求值,并且只有在 a 的结果为 0 时才会对 b && c 求值。

0

就优先级而言,x || y && z的作用就像x + y * z一样:第二个运算符比第一个结合得更紧密,这些表达式分别等同于x || (y && z)x + (y * z)

问题中bc没有增加的原因是,除了优先级外,逻辑运算还会短路:一旦你已经足够远地知道结果,剩下的表达式就会被跳过。双管齐下运算符||&&左到右计算它们的参数,所以在a() || b()a() && b()中,调用a()发生在调用b()之前。

在简单情况下,如果 a() 返回 true,那么在表达式 a() || b() 中,调用 b() 将不会被执行,因为它不会影响结果。同样,如果 a() 返回 false,则在表达式 a() && b() 中,调用 b() 将不会被执行。
在示例代码中,对 bc 的递增不会被执行,因为 ++a 产生非零值,所以表达式的结果是 true,无需评估 ++a 后的任何内容。

-1

运算符优先级与求值顺序无关。优先级是为了将具有不同类型的运算符与它们的操作数分组而设置的优先级。

因此,表达式

++a || ++b && ++c;

将被评估为

++a || (++b && ++c);

逻辑与和逻辑或运算符构成序列点,因此保证了它们的操作数按照从左到右的顺序进行评估。

评估顺序

顺序
......

  • 如果在子表达式 E1 和 E2 之间存在序列点,则 E1 的值计算和副作用都先于 E2 的每个值计算和副作用发生。

规则
.....
2) 在以下二元运算符的第一个(左)操作数评估后、第二个(右)操作数评估前,存在序列点:&&(逻辑与)、||(逻辑或)和,(逗号)。

逻辑或运算(expr1 || expr2)采用短路行为。也就是说,如果expr1是逻辑1(true),则不会评估expr2

abc的初始值为0。在表达式中:

++a || ++b && ++c;

++a -> 前缀自增 a
这意味着表达式++a的值是a的递增值,即1。由于||运算符采用短路行为,因此||的右侧表达式不会被评估。因此,您将获得输出-1 0 0


为了更好地理解,只需尝试在表达式中将++a更改为a++
后置递增运算符也会将操作数的值增加1,但表达式的值是递增操作之前操作数的原始值。因此,a++将被评估为0,由于短路行为,||运算符的右侧表达式(++b && ++c)将被评估。

逻辑AND运算符(expr1 && expr2)也采用短路行为。使用逻辑短路,仅当第一个操作数expr1不能完全确定结果时,才会评估第二个操作数expr2。也就是说,只有当expr1是逻辑1 (true)++b将导致1时,才会评估expr2。所以,如果你这样做。

a++ || ++b && ++c;
^^^

输出将是 - 1 1 1

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