【问题标题】:Ternary operator assignment with postfix increment带后缀增量的三元运算符赋值
【发布时间】:2015-05-23 22:21:43
【问题描述】:

这可行,k 增量:

k = 0;
k = ( false condition here ) ? 0 : k+=1;

这可行,k 增量:

k = 0;
k = ( false condition here ) ? 0 : ++k;

这不行,k 总是 0:

k = 0;
k = ( false condition here ) ? 0 : k++;

谁能解释一下幕后发生了什么?

编辑: 我不需要其他方法来写这个。 我不在乎这是否可以用更简单的方式编写。

在 for 循环中,i++ 或 ++i 都有效。 为什么这里的行为不同?

【问题讨论】:

  • 让它更容易...int k = 0; k = k++; k 仍然是 0。不需要三元数
  • 你的讲师有没有教你k++等价于k+=1?我不是在开玩笑。我被教过这个。
  • 无论发生什么,都不要将增量与其他任何东西混为一谈。它只会让人们感到困惑,难以阅读而且几乎没有用。 k++++k 一个人就可以了。将它与赋值、算术或上帝禁止的三元或空合并运算符混合在一起,所有的地狱都会崩溃。简短版:这样做会很痛。所以不要这样做。

标签: c# increment ternary-operator


【解决方案1】:

如果您想知道幕后发生的事情,我们可以查看 IL 级别。在此之前,我认为值得看看 xanatos 建议的 ++ 运算符的使用。

无论如何,让我们看看为第二种情况生成的 IL。请看右边的评论:

int k = 0; 
k =  false ? 0 : ++k;

IL_0001:  ldc.i4.0    // Allocate space for int k
IL_0002:  stloc.0     // assign 0 to k
IL_0003:  ldloc.0     // load k on top of the evaluation stack     --> our stack is [k]
IL_0004:  ldc.i4.1    // load value 1 at location 1 for variable k --> [k 1]
IL_0005:  add         // Pops and add the top two values on the evaluation stack, and push the result onto the stack. our stack is --> [1]
IL_0006:  dup         // Copies the current topmost value on the evaluation stack, and then pushes the copy onto the evaluation stack. which in our case is 1 --> [1 1]
IL_0007:  stloc.0     // Pop the top value on the stack at location 0 (e.g. assign it to k) --> [1]
IL_0008:  stloc.0     // same again, the stack is empty now --> []
IL_0009:  ret 

如您所见,最后两个 stloc.0 将堆栈中的两个 1 分配给了 k。事实上,如果你仔细想想,我们有两个任务。一个用于 ++k ,另一个用于分配三元运算的结果。正如你所说,这会产生 1。让我们看看你最后一个产生 0 的案例:

int k = 0; 
k =  false ? 0 : k++;

IL_0001:  ldc.i4.0    // Allocate space for int k
IL_0002:  stloc.0     // assign 0 to k
IL_0003:  ldloc.0     // load k on top of the evaluation stack     --> our stack is [k]
IL_0004:  dup         // Copies the current topmost value on the evaluation stack, and then pushes the copy onto the evaluation stack. which in our case is 1 --> [k k] in this case k is still 0!
IL_0005:  ldc.i4.1    // load value 1 at location 1 for variable k --> [k k 1]
IL_0006:  add         // Pops and add the top two values on the evaluation stack, and push the result onto the stack. our stack is --> [k 1] // because k+1 is equal 1
IL_0007:  stloc.0     // Pop the top value on the stack at location 0 (e.g. assign it to k) --> [1]
IL_0008:  stloc.0     // Pop the top value on the stack at location 0 (e.g. assign it to k) but in this case k is still zero!!!!! --> []

从 IL 中的 cmets 可以看出,两条 stloc.0 指令最终将 k 的原始值(为 0)分配给 k 本身。这就是为什么在这种情况下你得到 0 而不是 1 的原因。

我不是为您的问题提供解决方案,而只是解释如何在 MSIL 的以下级别处理这些“简单”操作。

希望对您有所帮助。

【讨论】:

    【解决方案2】:

    让我们让它变得更简单:

    int x = 1;
    x = x++;
    // x still 1
    

    为什么?

    对于运算符优先级,首先执行++ 运算符,然后执行= 赋值

    让我们看看++。来自https://msdn.microsoft.com/en-us/library/aa691363%28v=vs.71%29.aspx

    If x is classified as a variable:
    
    x is evaluated to produce the variable 
    
    The value of x is saved. 
    **1 is saved**
    
    The selected operator is invoked with the saved value of x as its argument. 
    **2 is produced**
    
    The value returned by the operator is stored in the location given by the evaluation of x. 
    **2 is put into x**
    
    The saved value of x becomes the result of the operation.
    **1 is returned**
    

    现在我们有了任务:x = 1。所以x被分配了twice,一次是2,一次是1

    【讨论】:

    • 一个 += 不使用临时的衬里怎么样?
    • @LewsTherin 如果你想一想,+= 运算符必须先添加,然后再赋值。然后,与所有其他赋值运算符一样,返回分配的值(例如,对于x = y = 5xy 最后都是5)。所以x = (x+=1)相当于写x = (x = (x + 1)),两个赋值为2(如果x == 1开头)
    • 如果您想要一个脑筋急转弯,请考虑 C 代码 int i = 0; printf("%d %d\n", i, i++);,其输出仅由您的编译器和/或编译器设置定义。
    • @plinth 所以在这种情况下,OP 只要求 C# 行为是一件好事,因为 MS 和 Mono 编译器的行为方式完全相同
    • @thorkia :MS 和 Mono 编译器的行为方式相同是件好事,因为 C# 语言规范明确定义了行为。在这些 C# 示例中没有解释的余地​​(与 C 示例相反,这显然是未定义的行为)。
    【解决方案3】:

    i++++i 在 for 循环中的工作方式不同。

    考虑以下 for 循环定义:

    for (int i=0; i<1; ++i)

    可以写成:

    for (int i=0; 1 >= i ; ++i)

    这相当于写作:

    for (int i=0; 1 >= ++i; /*No function here that increments i*/)

    在这种情况下,在比较之前执行增量,并且永远不会执行循环的内容。

    现在考虑这个循环:

    for (int i=0; i<1; i++)

    可以写成:

    for (int i=0; 1 >= i ; i++)

    这相当于写作:

    for (int i=0; 1 >= i++; /*No function here that increments i*/)

    这种情况下,比较后执行递增,循环的内容会执行一次。在循环结束时,i 的值为 1。

    您可以通过以下方式进行测试:

    int i;
    for (i=0; i<1; i++)
    {
      Console.WriteLine("i in the loop has a value of: " + i);
    }
    Console.WriteLine("i after the loop has a value of: " + i);
    

    同样的规则也适用于三元运算符。

    这种情况会在将值赋给 k 之前执行增量,并且 k 将为 26:

    int k=25;
    k= (false) ? 0 : ++k
    

    并且这种情况下,在将值赋给k后会执行增量,k为25:

    int k=25;
    k = (false) ? 0 : k++;
    

    k+=1 与 ++ 运算符不同,k-=1 与 -- 运算符不同。 k+=1其实就是2个操作,写的简洁:

    k = k+1

    它首先取 k 的值,在内存中给它加一,然后将这个值赋回给 k。

    所以在三元例子中:

    int k=25;
    k = (false) ? 0 : k+=1;
    

    k 将是 26。赋值运算符从右到左计算。所以编译器会首先计算 k+=1 - 使 k 为 26。然后执行三元比较,然后将 26 分配给 k。

    总结:

    ++/-- 是特殊的运算符,放置它的位置会影响执行表达式的评估时间。 ++x 或 --x - ++ 或 -- 将在任何比较之前进行评估。 x++ 或 x-- ++ 或 -- 将在任何比较后进行评估

    【讨论】:

    • for (int i=0; 1 &gt;= i ; ++i) 被执行两次。示例here。它等同于for (int i=0; 1 &gt;= ++i;)
    【解决方案4】:

    当您混合发布增量和分配时要小心。当您将它们与分配一起使用时,会出现前后增量之间的差异。

    后自增 (++) 函数实现返回原始未触及的输入值(它保存一个临时副本)并递增输入:

    i = 0
    i = i++
    

    post increment ++ 函数将i=0 作为输入并将其递增。此时i实际上是1,但是后增量函数返回原始值0,所以i将被重新分配,最终值将是0

    在经典的 for 循环 for(int i=0; i &lt; val; i++ ) 中,使用 i++ 或 ++i 不会产生影响,因为在递增时不会发生赋值。这个 for 循环可能会让你头疼for(int i=0; i &lt; val; i=i++ )

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-12
      • 2022-06-28
      • 2013-06-28
      • 2021-04-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多