【问题标题】:When does ++ not produce the same results as +1?++ 何时不会产生与 +1 相同的结果?
【发布时间】:2010-09-11 23:32:25
【问题描述】:

以下两个 C# 代码 sn-ps 产生不同的结果(假设变量 level 在递归调用之前和之后都使用)。为什么?

public DoStuff(int level)
{
  // ...
  DoStuff(level++);
  // ...
}

,

public DoStuff(int level)
{
  // ...
  DoStuff(level+1);
  // ...
}

在阅读了下面的一些回复后,我认为发布 level++、++level 和 level+1 的堆栈跟踪是值得的,以突出这个问题的欺骗性。

我已经为这篇文章简化了它们。递归调用序列从 DoStuff(1) 开始。

//级别++

DoStuff(int level = 1)
DoStuff(int level = 2)
DoStuff(int level = 2)
DoStuff(int level = 2)

//++级

DoStuff(int level = 4)
DoStuff(int level = 4)
DoStuff(int level = 3)
DoStuff(int level = 2)

// 等级+1

DoStuff(int level = 4)
DoStuff(int level = 3)
DoStuff(int level = 2)
DoStuff(int level = 1)

【问题讨论】:

  • 好问题和好答案!多年来我一直在使用 C++,最近使用 C#,但我不知道!
  • 您的堆栈跟踪错误。 level++ 应该是 1, 1, 1, 1; ++级应该是1、2、3、4;并且 level + 1 应该是 1, 2, 3, 4
  • Orion - 堆栈跟踪直接取自 VS2008。我将函数调用跟踪到四个递归级别并进行了剪切和粘贴。
  • 啊..我正在编写堆栈跟踪,最后一次调用位于底部,而不是顶部。我看的不是级别的值,而是传递给每个函数的值。要正确评估堆栈,您应该在函数的第一行放置一个断点。

标签: c# stack increment


【解决方案1】:

澄清所有其他回复:

++++++++++++++++++++++++

DoStuff(a++);

相当于:

DoStuff(a);
a = a + 1;

++++++++++++++++++++++++

DoStuff(++a);

相当于:

a = a + 1;
DoStuff(a);

++++++++++++++++++++++++

DoStuff(a + 1);

相当于:

b = a + 1;
DoStuff(b);

++++++++++++++++++++++++

【讨论】:

  • 除了DoStuff(a++) 的示例是错误的。它应该是: int temp = a;一 = 一 + 1; DoStuff(temp);
  • 参数在它们所属的函数调用之后永远不会被评估。编译器优化可能会改变调用的顺序,但这超出了这个简单的例子。可以重新组织任意数量的事物。
  • a++ 在调用之前使用旧值创建一个临时变量并立即递增,而不是之后递增。在某些情况下,差异非常明显。
  • 第一个例子真的对吗?在 C++ 中,至少你的例子是错误的。参数的评估在调用之前完成(在调用之前有一个序列点)。如果 C# 也是如此,那么您的示例(第一个示例)是错误的。
  • 确保f(x++)不等价于f(x)的要点; x = x + 1;是当你得到像 f(x++, x++) 这样的东西时,你不会认为你最终会得到 f(x, x); x = x + 1; x = x + 1;相反,你会得到 temp = x; x = x + 1;温度2 = x; x = x + 1; f(温度,温度2);。这就是为什么以下是一个不好的假设。这也导致了“神奇”的想法,因为可以在调用返回后修改参数。
【解决方案2】:

因为第一个例子真的等价于:

public DoStuff(int level)
{  
  // ...
  int temp = level;
  level = level + 1;
  DoStuff(temp);
  // ...
}

注意你也可以写++level;这相当于:

public DoStuff(int level)
{  
  // ...
  level = level + 1;
  DoStuff(level);
  // ...
}

我认为最好不要过度使用 ++ 和 -- 运算符;它很快就会让人感到困惑和/或不确定到底发生了什么,而且现代 C++ 编译器无论如何都不会使用这些运算符生成更高效的代码。

【讨论】:

  • 同意不要过度使用它们。同样“非常有趣”的是用一个类重载 post 和 pre ++,因为所有的赌注都没有了。
  • 我不同意; ++-- 非常直观和简单。唯一出现问题的时候是人们要么试图变得可爱,要么甚至懒得去查看他们正在使用的操作员的行为。
  • 那么这有什么直观和简单的呢? :-) DoMoreStuff(++level, ++level);
  • 我认为这被归类为“试图变得可爱”:)
  • 另外,他的例子是错误的。我并不惊讶人们经常不太了解后增量(变量++),但我几乎每次都看错了。调用后不会评估后增量。它在调用之前进行评估,特别是: int temp = a;一 = 一 + 1; DoStuff(temp);
【解决方案3】:

level++ 会将 level 传递给 DoStuff,然后递增 level 以用于函数的其余部分。这可能是一个相当讨厌的错误,因为递归永远不会结束(从所显示的 DoStuff 总是被传递相同的值)。也许是 ++level 的意思,因为这与 level++ 相反(增加 level 并将增加的值传递给 DoStuff)? p>

level+1 会将 level+1 传递给 DoStuff 并在其余部分保持 level 不变函数。

【讨论】:

  • ++level 也会产生不同的行为。查看对我的问题的修改。
【解决方案4】:

level++ 的返回值为levelthereforelevel 传递给DoStuff。这可能是一个相当讨厌的错误,因为递归永远不会结束(从所显示的DoStuff 总是以相同的值传递)。也许是++levellevel + 1 的意思?

level + 1 将把level + 1 传递给DoStuff 并在函数的其余部分保持level 不变。


后自增运算符(变量++)与函数精确等价

int post_increment(ref int value)
{
    int temp = value;
    value = value + 1
    return temp;
}

而前置自增运算符(++variable)正好等价于函数

int pre_increment(ref int value)
{
    value = value + 1;
    return value;
}

因此,如果将操作符内联展开到代码中,则操作符等价于:

DoStuff(a + 1)

int temp = a + 1;
DoStuff(temp);

DoStuff(++a)

a = a + 1;
DoStuff(a);

DoStuff(a++);

int temp = a;
a = a + 1;
DoStuff(temp);

请务必注意,后增量等同于:

DoStuff(a);
a = a + 1;

此外,作为一种风格,不应增加值,除非打算使用增加的值(规则的特定版本,“除非您打算使用那个值”)。如果值 i + 1 不再使用,则首选用法应该是 DoStuff(i + 1) 而不是 DoStuff(++i)

【讨论】:

  • 你说的是 100% 真实的。但值得一提的是,对于后自增版本,对于简单的情况(例如使用基本类型),允许编译器省略临时并重新定位 inc 直到使用后。
  • Evan 这是编译器可能进行的一种优化,但它也可能导致非常微妙的问题。
  • 这也不是您可以依赖的优化。这是编译器的一个实现细节,因此除非你也愿意说它发生在这些编译器的这些版本中,否则你不应该说它一定会发生。
  • 您的第一个代码示例中有一个错误。 temp 已声明但从未使用过。
  • Orion - 您的 level++ 堆栈跟踪不正确。第一次调用 DoStuff 将值 1 放入堆栈。然后在第二次调用发生之前将值修改为 2,但在将值放入堆栈以进行下一次调用之后。您的堆栈最终成为一系列 2。
【解决方案5】:

第一个是使用 level 中的值,然后递增它。

后者使用level+1作为传递变量。

【讨论】:

    【解决方案6】:

    level++ 返回level 的当前值,然后递增levellevel+1 根本不会更改 level,但会使用 (level + 1) 的值调用 DoStuff

    【讨论】:

      【解决方案7】:

      在 level++ 中,您使用的是后缀运算符。此运算符在使用变量后起作用。也就是说,在它被放入被调用函数的堆栈之后,它就会递增。另一方面 level + 1 是简单的数学表达式,它被评估并将结果传递给被调用的函数。 如果要先递增变量,然后将其传递给被调用函数,可以使用前缀运算符:++level

      【讨论】:

        【解决方案8】:
        public DoStuff(int level)
        {
        
          // DoStuff(level);
          DoStuff(level++);
          // level = level + 1;
          // here, level's value is 1 greater than when it came in
        }
        

        它实际上增加了level的值。

        public DoStuff(int level)
        {
          // int iTmp = level + 1;
          // DoStuff(iTmp);
          DoStuff(level+1);
          // here, level's value hasn't changed
        }
        

        实际上并不增加 level 的值。

        函数调用前问题不大,但函数调用后值会不一样。

        【讨论】:

        • 你把第一个弄错了:它会先调用 DoStuff(level) 然后增加 level。
        • 糟糕。哈哈,我的回答很仓促:-p
        【解决方案9】:

        第一个代码 sn-p 使用后操作自增运算符,因此调用为 DoStuff(level);。如果你想在这里使用增量运算符,请使用 DoStuff(++level);。

        【讨论】:

          【解决方案10】:

          level+1 将任何 level+1 发送给函数。 level++ 将 level 发送到函数,然后递增它。

          你可以做 ++level,这可能会给你想要的结果。

          【讨论】:

          • ++level 会产生不同的结果。请参阅我原来的问题中的堆栈跟踪。
          【解决方案11】:

          第一个示例使用 'index' 的值,递增该值并更新 'index'。

          第二个例子使用'index'的值加1,但不改变'index'的内容。

          所以,根据您想在这里做什么,可能会有一些惊喜!

          【讨论】:

          • 示例使用“级别”而不是索引。建议您编辑此回复以关注?
          【解决方案12】:

          虽然重写为:

          DoStuff(++level);
          

          我个人认为这比在方法调用之前增加变量的可读性差。正如上面的几个答案所指出的,以下内容会更清楚:

          level++;
          DoStuff(level);
          

          【讨论】:

          • 前增量和后增量运算符旨在为代码添加一定程度的简洁性,而不一定是可读性。如果您追求可读性,请不要使用这种级别的运算符。使用级别 = 级别 + 1;
          • 我并没有说它更简洁,只是有助于提高可读性。我不同意使用 level = level + 1,因为它涉及更多的打字:) - 我认为大多数人都知道 ++ 的作用,但是(根据原始问题)有时会对排序感到困惑。
          【解决方案13】:

          当您使用允许运算符重载的语言时,并且“+ ”已被定义为执行后置和前缀“++”以外的其他操作。

          再说一次,我只在学校项目中看到过这种可憎的事情*,如果你在野外遇到这种情况,你可能有一个非常好的、有据可查的理由。

          [* 一堆整数,如果我没记错的话。 '++' 和 '--' 推送和弹出,而 '+' 和 '-' 执行正常算术]

          【讨论】:

            【解决方案14】:

            用最简单的方式说,++var 是一个前缀运算符,它将在计算表达式的其余部分之前 递增变量。 var++ 是一个后缀运算符,表达式的其余部分被计算之后递增一个变量。当然,正如其他人所提到的,var+1 仅创建一个临时变量(在内存中独立),它以 var 启动并以常量 1 递增。

            【讨论】:

              【解决方案15】:

              就我的经验而言,首先对参数表达式求值,然后得到一个值 level。 变量本身在调用函数之前递增,因为编译器不关心您是否使用表达式作为参数...它所知道的是它应该递增值并获取旧值作为表达。

              但是在我看来,这样的代码真的很草率,因为试图变得聪明,它会让你不得不三思而后行。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2020-02-09
                • 2017-06-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多