循环中前置递增和后置递增的区别是什么?

348

for循环中的++ii++有什么区别吗?这只是一种语法上的差异吗?


3
重复:https://dev59.com/HXRB5IYBdhLWcg3w6bcE请勿重复提问。 - Jon B
27
我对有多少回答完全没有抓住问题的重点感到惊讶。 - Graeme Perrow
5
也许我们应该感到惊讶的是,没有人编辑这个问题以使其更清晰 :) - Jon B
2
这个问题适用于C、Java、C++、PHP、C#、Javascript、JScript、Objective C: http://en.wikipedia.org/wiki/Category:C_programming_language_family - Chris S
1
这里发布了一个好的答案:https://dev59.com/bW445IYBdhLWcg3wws8u#4706225 - Jim Fell
显示剩余4条评论
22个回答

265

a++被称为后缀。

使a加1,返回旧值。

++a被称为前缀。

使a加1,返回新值。

C#:

string[] items = {"a","b","c","d"};
int i = 0;
foreach (string item in items)
{
    Console.WriteLine(++i);
}
Console.WriteLine("");

i = 0;
foreach (string item in items)
{
    Console.WriteLine(i++);
}

输出:

1
2
3
4

0
1
2
3

foreachwhile循环取决于使用的增量类型。像下面这样的for循环没有区别,因为您不使用i的返回值:

for (int i = 0; i < 5; i++) { Console.Write(i);}
Console.WriteLine("");
for (int i = 0; i < 5; ++i) { Console.Write(i); }

0 1 2 3 4
0 1 2 3 4

如果使用所评估的值,则增量类型变得重要:

int n = 0;
for (int i = 0; n < 5; n = i++) { }

14
这甚至不是用户要求的内容。 - Dimitri

238

前置递增 ++i 将i的值加一并返回递增后的新值。

int i = 3;
int preIncrementResult = ++i;
Assert( preIncrementResult == 4 );
Assert( i == 4 );

i++ 是后加加运算符,它会将 i 的值加 1 并返回未被加一的原始值。

int i = 3;
int postIncrementResult = i++;
Assert( postIncrementtResult == 3 );
Assert( i == 4 );

在C++中,通常情况下优先使用前置递增运算符。

这是因为如果你使用后置递增运算符,编译器可能需要生成额外的临时变量来存储被递增变量的前值和新值,因为它们可能在表达式中的其他地方需要使用。

因此,在C++中,根据性能差异来选择使用哪种递增运算符是很重要的。

这个问题主要针对被递增的变量是用户定义类型的情况,该类型重载了++运算符。对于原始类型(int等)没有性能差异。但是,除非确定需要后置递增运算符,否则最好还是遵循使用前置递增运算符的指南。

这里有更多的讨论。

如果在C++中您正在使用STL,则可能会使用迭代器进行循环。这些迭代器主要具有重载的++运算符,因此坚持使用前置递增是一个好习惯。然而,编译器不断变得更聪明,更新的编译器可以执行优化,从而消除性能差异-特别是如果被递增的类型在头文件中定义为内联(因为STL实现通常会这样做),让编译器可以了解该方法的实现方式,然后就可以知道哪些优化是安全的。即使如此,坚持使用前置递增还是值得的,因为循环执行次数很多,这意味着小的性能损失可能很快会被放大。


在其他语言(例如C#)中,由于++运算符无法重载,所以不会有性能差异。在用于推进循环变量的循环中,前置和后置递增运算符是等效的。

更正:在C#中允许重载++。但与C++相比,在C#中不能独立重载前缀和后缀版本。因此,我认为如果在C#中调用++的结果未分配给变量或未作为复杂表达式的一部分使用,则编译器会将++的前缀和后缀版本缩减为执行等效代码。


123
如果C++的名称被命名为++C,表示你可以使用它编写高效的代码,那不是很好吗?请注意,这只是一种翻译,没有加入额外的解释或个人意见。 - Naveen
13
现代编译器不应该能够在结果值显然会被丢弃的情况下进行优化吗? - che
9
@che - 当涉及到简单类型时,它们会这样做,但是重载operator++的类(例如迭代器)则是另一回事。 - Ferruccio
9
这是个好问题。C++编译器没有将"CustomType++;"替换成"++CustomType;"的原因是因为不能保证两个用户自定义函数有完全相同的效果。虽然它们应该是一样的……但不能保证。 - Drew Dormann
2
@michael.bartnett:说得好,C#中似乎确实可以重载++。不过,与C ++相比,在C#中您不能独立地重载前缀和后缀版本。因此,我会假设如果在C#中调用++的结果未分配给变量或用作复杂表达式的一部分,则编译器将把++的前缀和后缀版本简化为执行等效操作的代码。 - Scott Langham
显示剩余5条评论

89

在 C# 中,在 用于 for 循环时 没有区别。

for (int i = 0; i < 10; i++) { Console.WriteLine(i); }

输出的结果与

for (int i = 0; i < 10; ++i) { Console.WriteLine(i); }

正如其他人所指出的,当在一般情况下使用时,i++和++i有一个微妙但重要的区别:

int i = 0;
Console.WriteLine(i++);   // Prints 0
int j = 0;
Console.WriteLine(++j);   // Prints 1

i++ 读取 i 的值,然后将其递增。

++i 把 i 的值递增后再读取它。


3
我认为第一点不相关。在for循环中(无论是C#还是其他语言),递增部分总是在循环体之后执行。一旦执行,该变量就会被修改,无论使用后置递增还是前置递增。 - MatthieuP
10
我理解这个问题是关于在for循环中使用i++或者++i是否有所差别,答案是“没有”。 - Jon B
@Jon B - 只是一点小事。只是制作链接而已。 - xtofl
1
@JonB 答案中的操作顺序并不完全正确。++ii++ 执行相同的操作,顺序也相同:创建 i 的临时副本;将临时值递增以产生新值(而不是覆盖临时值);将新值存储在 i 中;现在如果是 ++i,则返回的结果是新值;如果是 i++,则返回的结果是临时副本。更详细的答案请参见此处:https://dev59.com/tXA75IYBdhLWcg3wVniI#3346729 - PiotrWolkowski
这是唯一应该被接受的答案。问题是“在for循环中,++i和i++有区别吗?”。答案是“没有”。为什么其他每一个答案都要详细解释前缀和后缀递增,而这根本没有被问到? - Vinz
显示剩余3条评论

75

问题是:

在for循环中++i和i++有区别吗?

答案是:没有

为什么其他每一个答案都要详细解释前置和后置递增,而这根本不是问题所问的?

这个for循环:

for (int i = 0; // Initialization
     i < 5;     // Condition
     i++)       // Increment
{
   Output(i);
}

如何不使用循环语句将其翻译成代码:

int i = 0; // Initialization

loopStart:
if (i < 5) // Condition
{
   Output(i);

   i++ or ++i; // Increment

   goto loopStart;
}

现在,你在这里增加的是i++还是++i是否重要?不重要,因为增量操作的返回值无关紧要。 i将在for循环体内的代码执行之后递增。


6
这真是第一个直接切入要点的答案。谢谢。 - Yassir Khaldi
2
这不是最佳答案,因为如果for循环正在递增一个复杂对象(而不是int!),++x的实现可能比x++更快...(请参见https://herbsutter.com/2013/05/13/gotw-2-solution-temporary-objects/) - JCx

31

既然您询问循环中的区别,我猜您是指

for(int i=0; i<10; i++) 
    ...;
在这种情况下,大多数语言中没有区别:无论您写 i++ 还是 ++i,循环的行为都是相同的。在 C++ 中,您可以编写自己的 ++ 运算符版本,并且如果 i 是用户定义类型(例如您自己的类),则可以定义它们的不同含义。
之所以上述情况不重要是因为您不使用 i++ 的值。另一件事是当您执行以下操作时:
for(int i=0, a = 0; i<10; a = i++) 
    ...;

现在,有一个区别,因为正如其他人指出的那样,i++ 表示增加一个值,但计算结果为先前的值,而 ++i 表示增加一个值,并计算结果为 i(因此它将计算为新值)。 在上面的例子中,a 被赋值为 i 的先前值,而 i 被增加了。


3
在C++中,编译器并不总是能够避免创建临时变量,因此建议使用前置自增形式。 - David Thornley
当我写代码时,如果你有一个用户定义类型的 i,它们可能具有不同的语义。但是,如果你使用原始类型的 i,则对于第一个循环来说没有区别。由于这是一个与语言无关的问题,我认为不应该写太多关于 C++ 特定内容的东西。 - Johannes Schaub - litb

15

++i是前增量,i++是后增量。区别在于表达式立即返回的值。

// Psuedocode
int i = 0;
print i++; // Prints 0
print i; // Prints 1
int j = 0;
print ++j; // Prints 1
print j; // Prints 1

编辑:糟糕,完全忽略了循环的一面。当循环的“步骤”部分(for(...; ...; ))时,for循环没有实际区别,但在其他情况下可能会发挥作用。


15

正如这段代码所展示的(请见注释中的反汇编MSIL),C# 3编译器在for循环中不区分i++和++i。如果正在获取i++或++i的值,那么肯定会有区别(这是在Visual Studio 2008 / Release Build下编译的):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PreOrPostIncrement
{
    class Program
    {
        static int SomethingToIncrement;

        static void Main(string[] args)
        {
            PreIncrement(1000);
            PostIncrement(1000);
            Console.WriteLine("SomethingToIncrement={0}", SomethingToIncrement);
        }

        static void PreIncrement(int count)
        {
            /*
            .method private hidebysig static void  PreIncrement(int32 count) cil managed
            {
              // Code size       25 (0x19)
              .maxstack  2
              .locals init ([0] int32 i)
              IL_0000:  ldc.i4.0
              IL_0001:  stloc.0
              IL_0002:  br.s       IL_0014
              IL_0004:  ldsfld     int32 PreOrPostIncrement.Program::SomethingToIncrement
              IL_0009:  ldc.i4.1
              IL_000a:  add
              IL_000b:  stsfld     int32 PreOrPostIncrement.Program::SomethingToIncrement
              IL_0010:  ldloc.0
              IL_0011:  ldc.i4.1
              IL_0012:  add
              IL_0013:  stloc.0
              IL_0014:  ldloc.0
              IL_0015:  ldarg.0
              IL_0016:  blt.s      IL_0004
              IL_0018:  ret
            } // end of method Program::PreIncrement             
             */
            for (int i = 0; i < count; ++i)
            {
                ++SomethingToIncrement;
            }
        }

        static void PostIncrement(int count)
        {
            /*
                .method private hidebysig static void  PostIncrement(int32 count) cil managed
                {
                  // Code size       25 (0x19)
                  .maxstack  2
                  .locals init ([0] int32 i)
                  IL_0000:  ldc.i4.0
                  IL_0001:  stloc.0
                  IL_0002:  br.s       IL_0014
                  IL_0004:  ldsfld     int32 PreOrPostIncrement.Program::SomethingToIncrement
                  IL_0009:  ldc.i4.1
                  IL_000a:  add
                  IL_000b:  stsfld     int32 PreOrPostIncrement.Program::SomethingToIncrement
                  IL_0010:  ldloc.0
                  IL_0011:  ldc.i4.1
                  IL_0012:  add
                  IL_0013:  stloc.0
                  IL_0014:  ldloc.0
                  IL_0015:  ldarg.0
                  IL_0016:  blt.s      IL_0004
                  IL_0018:  ret
                } // end of method Program::PostIncrement
             */
            for (int i = 0; i < count; i++)
            {
                SomethingToIncrement++;
            }
        }
    }
}

8

这里有一个Java示例和字节码,后置和前置自增在字节码中没有区别:

public class PreOrPostIncrement {

    static int somethingToIncrement = 0;

    public static void main(String[] args) {
        final int rounds = 1000;
        postIncrement(rounds);
        preIncrement(rounds);
    }

    private static void postIncrement(final int rounds) {
        for (int i = 0; i < rounds; i++) {
            somethingToIncrement++;
        }
    }

    private static void preIncrement(final int rounds) {
        for (int i = 0; i < rounds; ++i) {
            ++somethingToIncrement;
        }
    }
}

现在让我们来看一下字节码(javap -private -c PreOrPostIncrement):

public class PreOrPostIncrement extends java.lang.Object{
static int somethingToIncrement;

static {};
Code:
0:  iconst_0
1:  putstatic   #10; //Field somethingToIncrement:I
4:  return

public PreOrPostIncrement();
Code:
0:  aload_0
1:  invokespecial   #15; //Method java/lang/Object."<init>":()V
4:  return

public static void main(java.lang.String[]);
Code:
0:  sipush  1000
3:  istore_1
4:  sipush  1000
7:  invokestatic    #21; //Method postIncrement:(I)V
10: sipush  1000
13: invokestatic    #25; //Method preIncrement:(I)V
16: return

private static void postIncrement(int);
Code:
0:  iconst_0
1:  istore_1
2:  goto    16
5:  getstatic   #10; //Field somethingToIncrement:I
8:  iconst_1
9:  iadd
10: putstatic   #10; //Field somethingToIncrement:I
13: iinc    1, 1
16: iload_1
17: iload_0
18: if_icmplt   5
21: return

private static void preIncrement(int);
Code:
0:  iconst_0
1:  istore_1
2:  goto    16
5:  getstatic   #10; //Field somethingToIncrement:I
8:  iconst_1
9:  iadd
10: putstatic   #10; //Field somethingToIncrement:I
13: iinc    1, 1
16: iload_1
17: iload_0
18: if_icmplt   5
21: return

}

7

如果您在循环中不使用增量后的值,则没有任何区别。

for (int i = 0; i < 4; ++i){
cout<<i;       
}
for (int i = 0; i < 4; i++){
cout<<i;       
}

两个循环都会输出0123。

但是当您在循环中使用增量/减量后的值时,就会出现差异,如下所示:

前置增量循环:

for (int i = 0,k=0; i < 4; k=++i){
cout<<i<<" ";       
cout<<k<<" "; 
}

输出结果: 0 0 1 1 2 2 3 3

后置自增循环:

for (int i = 0, k=0; i < 4; k=i++){
cout<<i<<" ";       
cout<<k<<" "; 
}

输出: 0 0 1 0 2 1 3 2

通过比较输出结果,希望能够清楚地看到差异。需要注意的是,增量/减量总是在for循环结束时执行,因此可以解释结果。


5

是的,有区别。区别在于返回值。"++i"的返回值是在增加i后的值。而"i++"的返回值是增加之前的值。这意味着以下代码:

int a = 0;
int b = ++a; // a is incremented and the result after incrementing is saved to b.
int c = a++; // a is incremented again and the result before incremening is saved to c.

因此,a 将为 2,而 b 和 c 将各为 1。
我可以像这样重写代码:
int a = 0; 

// ++a;
a = a + 1; // incrementing first.
b = a; // setting second. 

// a++;
c = a; // setting first. 
a = a + 1; // incrementing second. 

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