将增量/减量运算符放在三元/条件运算符中是否安全?

时间:2014-09-08 13:54:30

标签: c++ c increment ternary-operator decrement

这是一个例子

#include <iostream>
using namespace std;
int main()
{   
    int x = 0;
    cout << (x == 0 ? x++ : x) << endl; //operator in branch
    cout << "x=" << x << endl;
    cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition
    cout << "x=" << x << endl;
    return 0;
}

输出:

0
x=1
1
x=1

我理解输出,但是否是未定义的行为?两种情况下的评估顺序是否得到保证?

即使有保证,我也非常清楚使用递增/递减会很快成为可读性的问题。我只是问,因为我看到类似的代码,并且立即不确定,因为有很多关于递增/递减运算符的模糊/未定义使用的示例,例如......

  •   

    C ++没有定义评估函数参数的顺序。

    int nValue = Add(x, ++x);
    
  •   

    C ++语言表示你不能在序列点之间多次修改变量。

     x = ++y + y++
    
  •   

    因为递增和递减运算符具有副作用,所以在预处理器宏中使用具有递增或递减运算符的表达式可能会产生不良结果。

     #define max(a,b) ((a)<(b))?(b):(a)
     k = max( ++i, j );
    

5 个答案:

答案 0 :(得分:34)

对于条件运算符(§5.16[expr.cond] / p1):

  

与第一个相关的每个值计算和副作用   在每个值计算和副作用之前对表达式进行排序   与第二或第三个表达相关联。

对于逻辑OR运算符(§5.15[expr.log.or] / p1-2):

  

如果第一个操作数的计算结果为true,则不计算第二个操作数。   [...]如果计算第二个表达式,则每个值计算和   与第一个表达相关的副作用在之前排序   与第二个相关的每个值计算和副作用   表达

您的代码行为定义明确。

答案 1 :(得分:10)

三元运算符和布尔&&||运算中保证执行顺序,因此评估序列点没有冲突。

一次一个

 cout << (x == 0 ? x++ : x) << endl; //operator in branch

将始终输出x但仅在它为0时才会递增。

 cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

这个定义也很明确,如果x为1,它将不会评估RHS,如果不是,它会递减它,但--x永远不会为0,所以iff将是真的x == 1,在这种情况下x现在也将为0。

在后一种情况下,如果x是INT_MIN,那么减去它就没有明确定义的行为(它会执行)。

在第一种情况下,如果INT_MAX x不会为0,则不会发生这种情况。所以你是安全的。

答案 2 :(得分:7)

  

我理解输出,但这是不确定的行为?

代码完美定义。 C11标准说:

6.5.15条件运算符

  

评估第一个操作数; 评估与评估之间存在一个序列点   评估第二或第三操作数(以评估者为准)。第二个操作数   仅当第一个比较不等于0时才进行评估;第三个操作数仅在以下情况下进行评估   第一个比较等于0;结果是第二个或第三个操作数的值   (以评估者为准),转换为下述类型.110)

6.5.14逻辑OR运算符

  

与按位|运算符不同, ||运算符保证从左到右的评估;如果   评估第二个操作数,第一个操作数的评估之间存在一个序列点   和第二个操作数。如果第一个操作数与0不相等,则第二个操作数为   没有评估。

进一步wiki以示例解释:

  
      
  • 评估&&(逻辑AND)的左右操作数,||(逻辑OR)(作为短路评估的一部分)和{{1} }。例如,在表达式comma operators中,在尝试访问*p++ != 0 && *q++ != 0之前,子表达式*p++ != 0的所有副作用都已完成。

  •   
  • 在评估三元“问号”运算符的第一个操作数与第二个或第三个操作数之间。例如,在表达式q中,在第一个a = (*p++) ? (*p++) : 0之后有一个序列点,这意味着它已经在第二个实例执行时增加了。

  •   

*p++||的规则对于C ++(第5.15和5.16节)与C相同。


  

在任何一种情况下,评估顺序是否得到保证?

是。运算符?:||&&,的操作数的评估顺序保证从左到右。

答案 3 :(得分:3)

在C中,对象的存储值只能在两个序列点之间修改一次。

出现一个序列点:

  1. 在完整表达结束时。
  2. &&||?:运营商
  3. 在函数调用中。
  4. 例如,此表达式x = i++ * i++ 未定义,而x = i++ && i++ 完全合法

    您的代码显示已定义的行为

      

    int x = 0;

         

    cout&lt;&lt; (x == 0?x ++:x)&lt;&lt; ENDL;

    在上面的表达式中,x0,因此x++将被执行,此处x ++是后递增的,因此它将输出0

      

    cout&lt;&lt; &#34; X =&#34; &LT;&LT; x&lt;&lt; ENDL;

    在上面的表达式中,x现在具有值1,因此输出将为1

      

    cout&lt;&lt; (x == 1 || - x == 0?1:2)&lt;&lt; ENDL;

    此处x1,因此未评估下一个条件(--x == 0),输出将1

      

    cout&lt;&lt; &#34; X =&#34; &LT;&LT; x&lt;&lt; ENDL;

    由于未评估表达式--x == 0,输出将再次为1

答案 4 :(得分:2)

是的,使用增量/减量运算符是安全的。以下是您的代码中发生的事情:

Snippet#1

cout << (x == 0 ? x++ : x) << endl; //operator in branch

在此代码段中,您正在测试x == 0是否为true。由于它是true,因此您的三元表达式会评估x++。 由于您在此处使用后增量,因此x的原始值会打印到标准输出流,然后 x会增加。

Snippet#2

cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

这个片段有点混乱,但它仍然会产生可预测的结果。此时,来自第一个代码段的x = 1。在三元表达式中,首先评估条件部分;但是,由于Short-Circuiting第二个条件, --x == 0 ,永远不会被评估。

对于C ++,运算符||&&分别是逻辑OR 逻辑AND 的短路布尔运算符。使用这些运算符时,会检查您的条件(从左到右),直到确定最终结果。确定结果后,不再检查条件。

查看代码段#2,您的第一个条件会检查x == 1。由于您的第一个条件评估为true并且您正在使用逻辑OR,因此无需继续评估其他条件。这意味着--x == 0 从未执行


关于短路的快速说明:

短路对提高程序性能非常有用。 假设您有这样的条件,它会调用几个耗时的函数:

if (task1() && task2())
{ 
    //...Do something...
}

在此示例中,除非task2成功完成,否则永远不应调用task1task2取决于task1更改的某些数据)。

因为我们正在使用短路AND运算符,如果task1因返回false而失败,那么if-statement有足够的信息提前退出并停止检查其他条件。这意味着永远不会调用task2