162

ANSI 标准是否要求在 C 或 C++ 中将逻辑运算符短路?

我很困惑,因为我记得 K&R 书中说你的代码不应该依赖于这些被短路的操作,因为它们可能不会。有人可以指出标准中说逻辑操作总是短路的地方吗?我对 C++ 最感兴趣,C 的答案也很好。

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

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

if( functionA() && functionB() && functionC() ) cout<<"Hello world";
4

7 回答 7

173

||是的,运算符以及&&C 和 C++ 标准都需要短路和评估顺序。

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

1.9.18

在以下表达式的评估中

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

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

在 C++ 中有一个额外的陷阱:短路不适用于重载运算符||&&.

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

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

于 2009-03-10T00:37:18.570 回答
77

短路评估和评估顺序是 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 规范的第6.5.13 节逻辑与运算符(PDF 链接)

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

同样,第6.5.14 节逻辑或运算符

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

在 C++ 标准中可以找到类似的措辞,请查看此草稿副本中的第 5.14 节。正如检查员在另一个答案中指出的那样,如果您覆盖 && 或 ||,则必须评估两个操作数,因为它成为常规函数调用。

于 2009-03-10T00:28:49.073 回答
19

是的,它要求(评估顺序和短路)。在您的示例中,如果所有函数都返回 true,则调用的顺序严格从 functionA 然后 functionB 然后 functionC。用于这个喜欢

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());

C++ 标准中说5.14/1

&& 运算符从左到右分组。操作数都被隐式转换为 bool 类型(第 4 条)。如果两个操作数都为真,则结果为真,否则为假。与 & 不同,&& 保证从左到右的评估:如果第一个操作数为假,则不评估第二个操作数。

并在5.15/1

|| 运算符组从左到右。操作数都被隐式转换为 bool(第 4 条)。如果其中一个操作数为真,则返回真,否则返回假。与 |、|| 不同 保证从左到右的评估;此外,如果第一个操作数的计算结果为真,则不计算第二个操作数。

它对旁边的两个都说:

结果是一个布尔值。第一个表达式的所有副作用,除了临时变量(12.2)的破坏外,都发生在第二个表达式被计算之前。

除此之外,1.9/18

在每个表达式的评估中

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

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

于 2009-03-10T00:27:53.397 回答
9

直接来自优秀的 K&R:

C 保证了这一点,&&并且||从左到右进行评估——我们很快就会看到这很重要的案例。

于 2009-03-10T00:28:49.027 回答
4

非常非常小心。

对于基本类型,这些是快捷操作符。

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

对于operator &&operator ||对于基本类型,评估顺序是从左到右(否则快捷方式将很难:-) 但是对于您定义的重载运算符,这些基本上是定义方法的语法糖,因此参数的评估顺序是不明确的。

于 2009-03-10T02:05:38.840 回答
0

如果您信任维基百科:

[&&||] 在语义上不同于按位运算符 & 和 | 因为如果可以仅从左侧确定结果,它们将永远不会评估右侧操作数

C(编程语言)

于 2009-03-10T00:27:38.733 回答
0

您的问题归结为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。当然,该过程在左子表达式内递归地重复。


有趣的花絮,但优先级的概念源于数学符号。同样的事情发生在a*b + c, where*的优先级高于+

更有趣/更晦涩的是,对于A1 op1 A2 op2 ... opn-1 An所有运算符具有相同优先级的无括号表达式,我们可以形成的二叉表达式树的数量由所谓的加泰罗尼亚数字给出。对于大n,这些增长非常快。d

于 2016-11-16T09:38:42.687 回答