在值不在语句中其他地方使用的情况下,为什么要使用++i而不是i++?

27

我非常清楚,在C++中

int someValue = i++;
array[i++] = otherValue;

相较于其他,有不同的影响

int someValue = ++i;
array[++i] = otherValue;

但是偶尔我会看到在for循环中或者单独使用的前缀递增语句:

for( int i = 0; i < count; ++i ) {
     //do stuff
}
或者
for( int i = 0; i < count; ) {
    //do some stuff;
    if( condition ) {
        ++i;
    } else {
        i += 4;
    }
}
在后两种情况下,++i 看起来像是想要制造出聪明的代码。我有没有忽略什么?在后两种情况下使用 ++i 而不是 i++ 有什么理由吗?

2
在语句中值没有被其他地方使用的情况下,为什么要使用 i++ 而不是 ++i - dave4420
“尝试编写看起来很聪明的代码” - 这个问题有点反向势利眼吗?;-) - Steve Jessop
@onebyone:是的,我认为代码越简单越好,这意味着“不要让我思考”。 - sharptooth
3
我不认为 i++ 比 ++i 更 "简单",它怎么可能是呢?尽管使用 ++i 可能会让你思考,甚至可能让大多数人思考,但我相信这更多地与你(和别人)对使用它的抵制有关,而不是运算符本身。我把 "++i" 理解为 "增加 i",我真的不理解它似乎会给那些非常愉快地使用三目运算符 ?: 的人带来什么问题 ;-p - Steve Jessop
6
换句话说——你养成了一个习惯,在循环中使用i++,但你其实也可以使用++i。因此,你会觉得换用"++i"这个习惯很不自然。但是反对使用"++i"的整个论点仅仅是"我们习惯于使用i++"。而反对使用i++的论据则是"有时候它效率比较低,除了熟悉以外没有其他好处"。所以问题在于是否要用另一个习惯替代一个习惯。我认为,仅仅因为熟悉而坚持一个"不好的"习惯是站不住脚的理由。 C++已经存在非平凡的迭代器功能10年了,足够时间来重新学习一种新的习惯。 - Steve Jessop
1
三元运算符,甚至可以说是愚蠢的英语语言。 - Steve Jessop
7个回答

50

查看自己代码中两个运算符的可能实现:

// Pre-increment
T*& operator ++() {
    // Perform increment operation.
    return *this;
}

// Post-increment
T operator ++(int) {
    T copy = *this;
    ++*this;
    return copy;
}

后缀运算符调用前缀运算符执行自己的操作:按设计和原则,前缀版本始终比后缀版本更快,尽管编译器在许多情况下(特别是对于内置类型)可以优化此过程。因此,人们更倾向于使用前缀运算符;另一种情况需要解释:为什么有这么多人对在不重要的情况下使用前缀运算符感到好奇,但没有人会对使用后缀运算符感到惊讶。

17
因为 C++ 是标准,但是有谁听说过 ++C? - MaxVT
12
如果你想要疯狂幻想 C++ 语言的名称实际上与代码如何编写有任何关系,那么这个名称应该是 C+1,因为提到它事实上并没有修改 C 语言... - Steve Jessop
然而,每当有人遇到我的带有前缀递增的for循环时,我总是会得到奇怪的反应。 - Jules
@Jules:是谁对此有奇怪的反应?也许是Java/C#/C的人,但绝对不会是经验丰富的C++程序员。 - Konrad Rudolph
主要是Java、C#和PHP的开发人员。我从未在专业水平上使用过C++,因此与C++专家的接触很少。我开始使用它是在大学学习运算符重载时,并且自那以后一直在任何类型的C语法语言中使用它。尽管由于编译器优化没有产生任何好处。就这么多吧。为了支持你的观点:前缀和后缀表示法在我看来传达了某种意义,所以我完全同意你——使用后缀风格才是真正的“变态”。 - Jules

40

忽略惯性因素,'++i' 从概念上来说是一个更简单的操作:它只是将 i 的值加一,然后使用它。

另一方面,'i++' 则是“取 i 的原始值,将其存储为临时值,将 i 加一,然后返回该临时值”。这要求我们即使在 i 已经更新之后仍需保留旧值。

正如 Konrad Rudolph 所示,使用 'i++' 操作可能会带来与用户定义类型有关的性能损失。

那么问题在于,为什么不总是默认使用 '++i' 呢?

如果没有使用 'i++' 的理由,为什么还要这么做呢?为什么要默认选择更复杂、执行速度可能更慢的操作呢?


1
另一个方面的描述很好,但是推理起来比较复杂。我认为这是一个重要的方面。 - Timothy Pratley

7

正如您所指出的 - 这对结果没有影响。

对于非原始类型,存在性能考虑。

从语义上讲,当返回值被使用时,通常使用前缀递增更清晰地显示测试的意图,因此习惯性地使用它比后缀递增更好,以避免意外测试旧值。


不是所有情况都是这样的。例如,x = (++x % 10) 中,x 从0到9循环,如果使用后置递增,则无法正常工作。有时候相反的情况也是成立的。了解区别并“说出你的意思”比希望优化器为您正确猜测更为重要。 - Chris Huang-Leaver
嗨Chris - 你的评论似乎不相关。问题是关于在使用++x或x++时产生完全相同结果的情况。显然,区别就在于是否使用了结果,如果使用了结果,就需要选择正确的方式。也许你不同意我认为后缀使用不够清晰,我猜这是主观的...但是我发现一眼看上去很容易混淆a[x++] = 1的意思,它可以替代地写成a[x] = 1; ++x;而b[++y] = 2则很少被误解。 - Timothy Pratley

4

有一个原因与运算符重载有关。在重载后置递增函数中,函数必须记住对象的先前值,将其递增,然后返回先前的值。而在前置递增函数中,函数可以简单地递增对象,然后返回对自身(新值)的引用。

对于整数,上述情况可能不适用,因为编译器知道递增是在哪个上下文中进行的,并将在任一情况下生成适当的递增代码。


这是否意味着对于给定的示例,其中i是一个int,它是虚假的,因为它不会带来任何额外的加速? - Toad
我想补充一点,这只适用于对象。我不认为你可以重载 int 的 ++,对吗?这可能会让傻瓜编译器运行得更快,但任何像样的编译器都能将 i++ 和 ++i 优化为相同的序列(至少对于整数而言)。 - paxdiablo
好的,我刚刚修改了我的答案,包括了那个。 - Greg Hewgill
@Pax:在C++中,当你写int i;时,i也是一个“对象”。(C++不是Java。) - sbi
@reinier - 那里通常使用的论点是,在整数和指针类型中使用后增量是愚蠢的“因为没有加速”,但是对于非平凡迭代器,则切换到前增量。说在没有性能差异的情况下使用++i是“虚假的”预设i ++出于其他原因更可取,这仅仅是一个断言,即您不相信同事们足够了解C语言,以轻松阅读任何一种方式。如果人们在其余时间也如此谨慎地考虑可读性,那么我在生活中处理的垃圾代码就会少得多... - Steve Jessop

2

是的,出于性能原因。在后增量的情况下,需要在进行增量操作之前制作变量 i 的副本,以便可以返回旧值(即使您不使用返回值)。 在前增量的情况下,不需要这个副本。


1

当你谈论像整数这样的自然类型时,几乎没有理由偏爱前增量而不是后增量。编译器通常能够在两种情况下生成相同的代码,假设你不使用返回值。但对于更复杂的类型,如迭代器,则不是这样。


1
True,但在这种情况下训练自己始终使用前缀递增形式仍然更好,而不是考虑变量的类型。代码更一致,并且如果类型发生变化,它也更健壮。 - tragomaskhalos

0

C++ FAQ Lite中有一个关于i++++i的讨论,链接如下:

[13.15] 哪个更高效:i++还是++i?

特别是在for循环中使用哪种形式时,FAQ更倾向于使用++i。以下是文本内容:

所以,如果你写的是语句而不是作为更大表达式的一部分,为什么不直接写++i呢?你不会失去任何东西,有时候还能获得一些好处。老一代C程序员习惯于写i++而不是++i。例如,他们会说:for (i = 0; i <10; i++)...由于这个使用了i++作为语句而不是作为更大表达式的一部分,因此你可能想使用++i。出于对称性的考虑,即使在不提高速度的情况下,我个人也主张采用这种风格,例如对于内置类型和返回void的后缀运算符的类类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接