是否要求短路逻辑运营商?和评估顺序?

时间:2009-03-10 00:23:39

标签: c++ c logical-operators short-circuiting operator-precedence

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

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

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

标准是否表明此表达式的评估顺序?

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

7 个答案:

答案 0 :(得分:141)

是的,C和C ++标准中的运营商||&&都需要短路和评估顺序。

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

  

<强> 1.9.18

     

评估以下表达式

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

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

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

  

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

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

答案 1 :(得分:70)

短路评估和评估顺序是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 link) 6.5.13逻辑运算符部分

  

(4)。与按位二进制和&amp;操作员,&amp;&amp;运营商保证   从左到右的评价;有一个   评估后的序列点   第一个操作数。如果是第一个   操作数比较等于0,即   第二个操作数未被评估。

同样, 6.5.14逻辑OR运算符部分表示

  

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

类似的措辞可以在C ++标准check section 5.14 in this draft copy中找到。正如跳棋在另一个答案中指出的那样,如果你覆盖&amp;&amp;或者||,然后必须评估两个操作数,因为它变为常规函数调用。

答案 2 :(得分:17)

是的,它要求(评估顺序和短路)。在您的示例中,如果所有函数都返回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中说:

  

&amp;&amp;操作员组从左到右。操作数都隐式转换为bool类型(第4节)。   如果两个操作数都为真,则结果为true,否则为false。不像&amp;,&amp;&amp;保证从左到右   评估:如果第一个操作数为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)中运算符的内置含义,在评估第一个表达式后有一个序列点。

答案 3 :(得分:11)

直接来自古老的K&amp; R:

  

C保证&&||从左到右进行评估 - 我们很快就会看到重要的案例。

答案 4 :(得分:4)

非常小心。

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

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

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

答案 5 :(得分:0)

如果您信任维基百科:

  

[&&||]在语义上与逐位运算符&amp;和|因为如果可以从单独的左侧确定结果,他们永远不会评估正确的操作数

http://en.wikipedia.org/wiki/C_(programming_language)#Characteristics

答案 6 :(得分:0)

您的问题归结为C++ operator precedence和相关性。基本上,在具有多个运算符且没有括号的表达式中,编译器通过遵循这些规则来构造表达式树。

对于优先级,如果您有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

订单本身是&#34;从左到右&#34;在表达式树形式上。因此,我们首先评估a != "" && it == seqMap.end()。如果它是真的,那么整个表达式都是正确的,否则我们转到isEven。当然,该过程在左子表达式内递归重复。

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

对于非语言强调表达式A1 op1 A2 op2 ... opn-1 An更加有趣/模糊,其中所有运算符具有相同的优先级,我们可以形成的二进制表达式树的数量由所谓的Catalan numbers给出。对于较大的n,这些增长速度非常快。 d