是否强制要求短路逻辑操作员? 以及评估顺序?

ANSI 标准的 授权逻辑运算符是否在 C 或 C + + 中短路?

我很困惑,因为我记得 K & R 的书上说,你的代码不应该依赖于这些操作短路,因为它们可能不会。有没有人能告诉我,在标准中,逻辑操作总是短路?我最感兴趣的是 C + + ,一个同样适用于 C 的答案会很棒。

我还记得读到(不记得在哪里) ,计算顺序没有严格定义,所以你的代码不应该依赖或假定表达式中的函数将按特定的顺序执行: 在语句结束时,所有被引用的函数都将被调用,但编译器可以自由选择最有效的顺序。

标准是否指明了这个表达式的求值顺序?

if( functionA() && functionB() && functionC() ) cout<<"Hello world";
37866 次浏览

如果你信任维基百科:

[ &&||]在语义上不同于位操作符 & 和 | ,因为如果结果可以单独从左边确定,那么它们永远不会计算右边的操作数

C (编程语言)

是的,它规定(评估顺序和短路)。在您的示例中,如果所有函数都返回 true,那么调用的顺序严格从 function A 开始,然后是 function B,最后是 function C。就像这样

if(ptr && ptr->value) {
...
}

逗号运算符也是如此:

// calls a, then b and evaluates to the value returned by b
// which is used to initialize c
int c = (a(), b());

有人说,在 &&||,的左右操作数之间,以及在 ?:(条件运算符)的第一和第二/第三操作数之间,是一个“序列点”。任何副作用在那之前都会被完全评估。所以,这是安全的:

int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1

请注意,逗号运算符不要与用于分隔事物的语法逗号混淆:

// order of calls to a and b is unspecified!
function(a(), b());

5.14/1中的 C + + 标准写道:

& & 运算符从左到右分组。操作数都隐式转换为 bool 类型(子句4)。 如果两个操作数都为 true 和 false,则结果为 true。不像 & ,& & 保证从左到右 计算: 如果第一个操作数为 false,则不计算第二个操作数。

5.15/1:

| | 操作符从左到右分组。操作数都隐式转换为 bool (子句4)。如果它的任何一个操作数为 true,则返回 true,否则返回 false。与 | 不同,| | 保证从左到右计算; 此外,如果第一个操作数的计算结果为 true,则不计算第二个操作数。

上面写着:

结果是一团糟。第一个表达式的所有副作用,除了临时表达式的破坏(12.2)发生在第二个表达式被计算之前。

除此之外,1.9/18表示

在每个表达式的计算中

  • a && b
  • a || b
  • a ? b : C
  • a , b

利用这些表达式(5.14,5.15,5.16,5.18)中运算符的内在意义,在第一个表达式求值之后有一个序列点。

直接来自 K & R 公司:

C 保证从左到右评估 &&||ーー我们很快就会看到这种情况。

短路求值和求值顺序是 C 和 C + + 中强制使用的语义标准。

如果不是这样,这样的代码就不会成为一种常见的习惯用法

   char* pChar = 0;
// some actions which may or may not set pChar to something
if ((pChar != 0) && (*pChar != '\0')) {
// do something useful


}

C99规范 (PDF 连结)6.5.13逻辑与操作符部分说

(4)与按位二进制 & 运算符不同,& & 运算符保证 从左到右的评估; 有一个 评估后的序列点 第一个操作数 操作数比较等于0,则 不计算第二个操作数。

类似地,6.5.14逻辑 OR 运算符节说

(4)与按位 | 运算符不同,| | 操作符保证从左到右 评估; 有一个序列点 在第一次评估之后 如果第一个操作数比较 不等于0时,第二个操作数是 未经评估。

类似的措辞可以在 C + + 标准 核对本草案第5.14节中找到。正如检查器在另一个答案中指出的那样,如果重写 & & 或 | | ,那么两个操作数都必须在它成为常规函数调用时求值。

是的,在 C 和 C + + 标准中,操作员 ||&&都需要短路和评估顺序。

C + + 标准说(在 C 标准中应该有一个等价的子句) :

1.9.18

在下列表达式的计算中

a && b
a || b
a ? b : c
a , b

使用这些表达式中运算符的内置意义,在第一个表达式求值之后有一个序列点(12)。

在 C + + 中有一个额外的陷阱: 短路适用于超载操作符 ||&&的类型。

脚注12: 如第5条所述,本段中指明的操作符是内置的操作符。当这些操作符中的一个在有效上下文中被重载(子句13) ,从而指定一个用户定义的操作符函数时,表达式指定一个函数调用,操作数形成一个参数列表,< strong > ,它们之间没有隐含的序列点。

通常不建议在 C + + 中重载这些运算符,除非您有非常具体的需求。您可以这样做,但是它可能会破坏其他人的代码中的预期行为,特别是如果这些操作符是通过使用类型重载这些操作符的实例化模板间接使用的。

要非常非常小心。

对于基本类型,这些是快捷运算符。

但是,如果为自己的类或枚举类型定义这些运算符,则它们不是快捷方式。由于在这些不同情况下它们的使用存在语义差异,因此建议不要定义这些运算符。

对于基本类型的 operator &&operator ||,求值顺序是从左到右的(否则很难缩短: ——)但是对于你定义的重载操作符,这些基本上是定义方法的语法糖,因此参数的求值顺序是未定义的。

你的问题可以归结为 C + + 运算符优先级和联想性。基本上,在有多个运算符且没有括号的表达式中,编译器通过遵循以下规则来构造表达式树。

对于优先级,当您有类似于 A op1 B op2 C的内容时,您可以将内容分组为 (A op1 B) op2 CA op1 (B op2 C)。如果 op1的优先级高于 op2,就会得到第一个表达式。否则,你会得到第二个。

对于结合性,当您有类似 A op B op C的东西时,您可以再次将瘦分组为 (A op B) op CA op (B op C)。如果 op离开了结合性,我们得到第一个表达式。如果它有正确的联想性,我们得到第二个。这也适用于同一优先级级别的运算符。

在这种特殊情况下,&&的优先级高于 ||,因此表达式将被计算为 (a != "" && it == seqMap.end()) || isEven

顺序本身在表达式树形式上是“从左到右”的。我们先评估 a != "" && it == seqMap.end()。如果整个表达式为真,则转到 isEven。当然,这个过程在 left 子表达式中递归地重复自己。


有趣的花边新闻,但优先级的概念有它的根源在数学符号。同样的情况也发生在 a*b + c中,其中 *的优先级高于 +

更有趣/晦涩的是,对于未括号化的表达式 A1 op1 A2 op2 ... opn-1 An(其中所有操作符具有相同的优先级) ,我们可以形成的二进制表达式树的数目由所谓的 加泰罗尼亚数字给出。对于大的 n,这些生长非常快。 D