【问题标题】:++i or i++ in for loops ?? [duplicate]++i 或 i++ 在 for 循环中?? [复制]
【发布时间】:2011-05-14 18:51:59
【问题描述】:

可能重复:
Is there a performance difference between i++ and ++i in C++?

是否有一些程序员在正常的 for 循环中编写 ++i 而不是编写 i++ 的原因?

【问题讨论】:

  • @djacobson:嗯,你可以在 YouTube 上搜索它,并且可能会发现有多少天使可以同时在针头上跳舞。但我的意思是,如果“非凡的主张需要非凡的证据”,那么选择预增量几乎不需要任何理由。它完全不寻常,尤其是与替代品相比......
  • 这个问题肯定被问过一百遍了..
  • @Martin York:奇怪的是,人们还懒得回答。

标签: c++ for-loop post-increment pre-increment


【解决方案1】:

++i 由于其语义,效率略高:

++i;  // Fetch i, increment it, and return it
i++;  // Fetch i, copy it, increment i, return copy

对于类似 int 的索引,效率增益是最小的(如果有的话)。对于迭代器和其他较重的对象,避免该副本可能是一个真正的胜利(特别是如果循环体不包含太多工作)。

例如,考虑以下循环,它使用理论上的 BigInteger 类提供任意精度整数(以及某种类似向量的内部结构):

std::vector<BigInteger> vec;
for (BigInteger i = 0; i < 99999999L; i++) {
  vec.push_back(i);
}

该 i++ 操作包括复制构造(即操作符 new,逐位复制)和破坏(操作符删除),用于循环,除了本质上再制作索引对象的一个​​副本之外,不会做任何事情。从本质上讲,您只需使用前缀就足够的后缀增量,就可以将要完成的工作加倍(并且很可能会增加内存碎片)。

【讨论】:

  • 伟大而简洁的例子
  • 我想为你的答案投票,但它不会再被评为 42
  • @KiJéy 你现在可以回来做。 (但它不会再被评为 64...)
  • @wizzwizz4 让我们把它变成 128
【解决方案2】:

正如其他人已经指出的那样,对于用户定义的类型,前置增量通常比后置增量更快。要了解为什么会这样,请查看实现这两个运算符的典型代码模式:

Foo& operator++()
{
    some_member.increase();
    return *this;
}

Foo operator++(int dummy_parameter_indicating_postfix)
{
    Foo copy(*this);
    ++(*this);
    return copy;
}

如您所见,前缀版本只是简单地修改对象并通过引用返回它。

另一方面,后缀版本必须在执行实际增量之前进行复制,然后将该副本按值复制回调用者。从源代码中可以明显看出后缀版本必须做更多的工作,因为它包含了对前缀版本的调用:++(*this);

对于内置类型,只要你丢弃该值就没有任何区别,即只要你不将++ii++嵌入到更大的表达式中,例如a = ++i或@987654326 @。

【讨论】:

  • 我认为“必须复制”有点夸大其词。如果结果未被使用,并且操作符和复制构造函数都可以完全内联,就像迭代器的常见情况一样,那么优化器应该完全删除copy - 没有边的未使用值 -效果。 ++i 确实让您不知道优化是否成功,更重要的是它显然更有意义 ;-)
【解决方案3】:

没有任何编译器值得在盐中发挥作用

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

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

++i 和 i++ 成本相同。唯一不同的是++i的返回值是i+1,而i++的返回值是i。

所以对于那些更喜欢 ++i 的人来说,可能没有正当理由,只是个人喜好。

编辑:这对班级来说是错误的,正如在其他所有帖子中所说的那样。如果 i 是一个类,i++ 将生成一个副本。

【讨论】:

  • 这对于类似 int 的索引可能是正确的,但对于迭代器和其他可能充当索引的对象通常不是正确的。
  • i++ 和 ++i 不是调用同一个运算符吗?
  • 不,++i 调用 i.operator++()i++ 调用 i.operator(int)。实现细节见my answer
  • 100% 同意@FredOverflow 所说的。这样想,没有理由写i++,但可能有理由写++i。
【解决方案4】:

对于整数,这是首选。

如果循环变量是一个类/对象,它可以产生影响(只有分析可以告诉你它是否是一个显着的差异),因为后增量版本要求你创建一个被丢弃的对象的副本。

如果创建该副本是一项昂贵的操作,那么您每次通过循环都要支付一次费用,完全没有理由。

如果您养成了在 for 循环中始终使用 ++i 的习惯,则无需停下来思考在这种特殊情况下所做的事情是否有意义。你一直都是。

【讨论】:

    【解决方案5】:

    这是有原因的:性能。 i++ 生成一个副本,如果您立即丢弃它,那是一种浪费。当然,如果i 是一个原语,编译器可以优化掉这个副本,但如果不是,它就不能。请参阅this 问题。

    【讨论】:

    • 这是不诚实的。几乎在每一种情况下,++ii++ 的性能都完全相同。
    • @John:一点也不虚伪。对于迭代器对象,编译器甚至没有理由期望前缀和后缀增量之间存在某种等价(类设计器可以让它们执行任意不同的操作)——它必须调用后缀形式(带有结果副本)。对于重量级迭代器和其他轻量级循环,性能损失可能很大。例如,如果 i 不是 int 或 std::vector::iterator,而是 BigInteger 或等价物怎么办。副本是不必要的,应该避免,句号。
    • @Drew:在这种情况下,程序员会知道(或者应该,无论如何),我的免责声明“如果你必须问,没关系”将不适用。问题是关于“正常的for循环”。性能与它无关。都是关于个人风格的。
    • John Dibling:“正常的 for 循环”通常涉及迭代器。
    • @John:也许吧。我想我只是认为这是一个好习惯——总是写 ++i 没有缺点,但是(有时)写 i++ 有缺点。
    【解决方案6】:

    对于整数,前后增量没有区别。

    如果i 是一个非平凡类的对象,那么++i 通常是首选,因为该对象被修改然后评估,而i++ 在评估之后修改,因此需要制作副本。

    【讨论】:

    • 虽然同样的代码是用整数生成的,但是因为第二段,所有类型都用++i可能是个好主意,这样你就养成了对所有类型都使用它的习惯类型。
    • @Token:我原则上同意。但是,i++ 在 C 代码中有些惯用语。如果您经常使用这两种语言,可能会让人感到困惑......
    • 请原谅我的无知,但这听起来确实很愚蠢。想象i 是某个类的实例,它重载了自增运算符。你是说如果我只是后增量(不是在循环控制语句中,而是只是增加它)(即i++;),编译器将无法看到我不是 doing 对返回值做任何事情,并自动将其重写为++i?
    • @Jim:编译器必须调用相关的运算符重载(operator++()operator++(int) 分别用于前增量和后增量)。如果它们实际上是等价的,并且如果调用是内联的,并且如果涉及的副本都被优化器删除,那么发出的代码最终可能是相同的 - 试试看。但是编译器当然不能仅仅因为结果未使用而将其中一个替换为另一个。我并不特别在意,因为为了清楚起见,我写了++i(据我所知)。 “增量 i” -> “++i”。
    • @Jim Mischel:编译器可以看到返回值没有被使用,但它没有理由期望 ++i 和 i++ 具有相同的语义模副本。所以它必须调用程序员要求的版本。
    【解决方案7】:

    ++i 是一个前置增量; i++ 是后增量。
    后增量的缺点是它会产生额外的价值。它在修改i 时返回旧值的副本。因此,您应该尽可能避免使用它。

    【讨论】:

      【解决方案8】:

      当您使用后缀时,它会在内存中的更多对象上实例化。有人说for循环最好用后缀操作符

      【讨论】:

        【解决方案9】:

        个人喜好。

        通常。有时这很重要,但不要在这里看起来像个混蛋,但如果你不得不问,它可能不会。

        【讨论】:

          猜你喜欢
          • 2013-04-26
          • 1970-01-01
          • 1970-01-01
          • 2017-05-17
          • 2011-01-19
          • 1970-01-01
          • 2015-07-05
          相关资源
          最近更新 更多