ループ内のプリインクリメントとポストインクリメントの違いは?


303

差がある++ii++forループが?それは単に構文的なものですか?


3
だまされやすい人:stackoverflow.com/questions/467322/...
ジョンB

18
質問のポイントを完全に逃した回答がいくつあるかに驚いています。
Graeme Perrow、

3
おそらく、誰も質問をより明確にするために編集した人がいないことに驚きます:)
Jon B

2
この質問は、C、Java、C ++、PHP、C#、Javascript、JScript、Objective Cに当てはまる可能性があります:en.wikipedia.org/wiki/Category
Chris S

1
ここに投稿された良い答え:stackoverflow.com/a/4706225/214296
Jim Fell

回答:


233

a ++はpostfixとして知られています。

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

foreachそしてwhileループは、使用されインクリメント種類によって異なります。以下のような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++) { }

4
これは、ユーザーが要求したものでもありません。
ディミトリ

223

プリインクリメント++ iは、iの値をインクリメントし、新しいインクリメントされた値に評価します。

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

ポストインクリメントi ++はiの値をインクリメントし、元のインクリメントされていない値に評価します。

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

C ++では、通常、どちらかを使用できる場合、事前増分が推奨されます。

これは、ポストインクリメントを使用する場合、追加の一時変数を作成するコードをコンパイラーが生成する必要があるためです。これは、インクリメントされる変数の以前の値と新しい値の両方が、評価される式の他の場所で必要になる可能性があるため、どこかに保持する必要があるためです。

したがって、少なくともC ++では、どちらを使用するかを選択する際の指針となるパフォーマンスの違いが生じる可能性があります。

これは主に、インクリメントされる変数が、オーバーライドされた++演算子を持つユーザー定義型である場合にのみ問題になります。プリミティブ型(intなど)の場合、パフォーマンスに違いはありません。しかし、ポストインクリメント演算子が明らかに必要なものでない限り、ガイドラインとしてプリインクリメント演算子に固執する価値があります。

ここでいくつかの議論があります:
https //web.archive.org/web/20170405054235/http //en.allexperts.com/q/C-1040/Increment-operators.htm

C ++では、STLを使用している場合は、イテレータを含むforループを使用している可能性があります。これらは主にオーバーライドされた++演算子を持っているので、事前増分に固執することは良い考えです。ただし、コンパイラーは常に賢くなり、新しいコンパイラーは最適化を実行できるため、パフォーマンスの違いはありません。特に、インクリメントされる型が(STL実装がそうであるように)ヘッダーファイルにインラインで定義されている場合は、コンパイラーがどのように見えるかを確認できます。メソッドが実装され、安全に実行できる最適化を知ることができます。それでも、ループは何度も実行されるため、事前増分に固執する価値はあります。これは、わずかなパフォーマンスのペナルティがすぐに増幅される可能性があることを意味します。


++演算子をオーバーロードできないC#などの他の言語では、パフォーマンスに違いはありません。ループ変数を進めるためにループで使用され、事前および事後の増分演算子は同等です。

修正:C#での++のオーバーロードが許可されています。ただし、C ++と比較すると、C#ではプレバージョンとポストバージョンを個別にオーバーロードできないようです。したがって、C#で++を呼び出した結果が変数に割り当てられていないか、複雑な式の一部として使用されていない場合、コンパイラーは++の前バージョンと後バージョンを同等に機能するコードに削減すると考えます。


102
C ++に++ Cという名前が付けられていて、それを使用して適切に最適化されたコードを記述できることを示しているとしたらすばらしいと思いませんか?
Naveen

9
とにかく、結果の値が明らかに破棄される場合、最新のコンパイラーはこれを最適化できませんか?
CHE

6
@che-単純型の場合に実行されますが、operator ++をオーバーロードするクラス(イテレータなど)は別の話です。
フェルッチョ

7
@che:それは良い質問です。C ++コンパイラが「CustomType ++;」を置き換えない理由。「++ CustomType;」これは、両方のユーザー定義関数が同じ効果をもたらす保証がないためです。彼らは...すべきです...しかし、保証はありません。
Drew Dormann、

2
@ michael.bartnett:良い点、C#での++のオーバーロードは利用できるようです。ただし、C ++と比較すると、C#ではプレバージョンとポストバージョンを個別にオーバーロードできないようです。したがって、C#で++を呼び出した結果が変数に割り当てられていないか、複雑な式の一部として使用されていない場合、コンパイラーは++の前バージョンと後バージョンを同等に機能するコードに削減すると考えます。
スコットランガム2012年

83

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の値を増分し、それを読み取ります。


結論:C ++と同じpost / pre incrementセマンティクス。
xtofl 2009年

@xtofl-あなたのポイントが何かわからない?私の例ではたまたまc#を選択しました。
Jon B、

3
最初の点は関係ないと思います。forループ(c#かどうか)では、増分部分は常にループの本体の後に実行されます。実行されると、変数はポストまたはプレインクリメントのどちらが使用されたかに関係なく変更されます。
MatthieuP

9
@MatthieuP-私は「forループでi ++と++ iのどちらを使用するかが重要か」という質問を読みました。答えは「いいえ、それはしません」です。
ジョンB

1
@JonB解答内の操作の順序は正確ではありません。両方++ii++同じ順序で同じ操作を実行する:の一時コピーを作成しますi。temp値をインクリメントして新しい値を生成します(tempを上書きしないため)。新しい値をに保存しiます。これ++iが結果の場合、返される結果は新しい値です。その場合i++、返される結果は一時コピーです。ここでより詳細な回答:stackoverflow.com/a/3346729/3330348
PiotrWolkowski 2015

51

質問は:

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、ここに追加するかは重要ですか?いいえ、インクリメント操作の戻り値は重要ではないため、そうではありませんiforループ本体内にあるコードの実行後に増分されます。


2
これは、文字通り、ポイントに入る最初の答えです。ありがとう。
Yassir

1
forループが複雑なオブジェクト(int以外のもの)をインクリメントしている場合、++ xの実装はx ++よりも高速になる可能性があるため、これは最良の答えではありません...(herbsutter.com/2013/05/13/gotwを参照) -2-solution-temporary-objects
JCx

30

あなたはループの違いについて尋ねるので、私はあなたが意味していると思います

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 2009年

15

このコードが示すように(コメントの解読されたMSILを参照)、C#3コンパイラはforループでi ++と++ iを区別しません。i ++または++ iの値が使用されている場合、間違いなく違いがあります(これはVisutal Studio 2008 /リリースビルドでコンパイルされました)。

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++;
            }
        }
    }
}

14

1つ(++ i)はプリインクリメントで、1つ(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(...; ...;))の場合、実際にはforループに違いはありませんが、それ以外の場合にも機能します。


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ループの最後に実行されるため、結果を説明できるということです。


7

以下は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

}

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. 

4

どちらの場合も実際の違いはありませんi。「」は1ずつ増加します。

ただし、式で使用する場合は次のような違いがあります。

int i = 1;
int a = ++i;
// i is incremented by one and then assigned to a.
// Both i and a are now 2.
int b = i++;
// i is assigned to b and then incremented by one.
// b is now 2, and i is now 3

3

++ iとi ++には、ループとパフォーマンスの違いだけではありません。++ iはl値を返し、i ++はr値を返します。これに基づいて、(++ i)にはできるが(i ++)にはできないことがたくさんあります。

1- It is illegal to take the address of post increment result. Compiler won't even allow you.
2- Only constant references to post increment can exist, i.e., of the form const T&.
3- You cannot apply another post increment or decrement to the result of i++, i.e., there is no such thing as I++++. This would be parsed as ( i ++ ) ++ which is illegal.
4- When overloading pre-/post-increment and decrement operators, programmers are encouraged to define post- increment/decrement operators like:

T& operator ++ ( )
{
   // logical increment
   return *this;
}

const T operator ++ ( int )
{
    T temp( *this );
    ++*this;
    return temp;
}

3

それはなぜ人々がforループでインクリメント式をi ++として書くのか、なぜか私の心を揺さぶります。

forループで、3番目のコンポーネントが次のように単純なインクリメントステートメントである場合

for (i=0; i<x; i++)  

または

for (i=0; i<x; ++i)   

結果の実行に違いはありません。


それは答えですか、それとも質問ですか?
Palec、2016年

2
それが問題ではないので、なぜ誰かがi ++を書いたかどうかあなたの心を揺さぶるのでしょうか?誰かが++ iを書くことを好む理由はありますか?
Dronz 2017年

2

@ジョンBが言う、forループに違いはありません。

しかし、中にwhileまたはdo...whileあなたがと比較して作っている場合はループ、あなたはいくつかの相違点を見つけることができる++iかをi++

while(i++ < 10) { ... } //compare then increment

while(++i < 10) { ... } //increment then compare

2つの反対票?私が書いたものの何が問題になっていますか?そしてそれは質問に関連しています(それは漠然として)。
crashmstr 2009年

2

JavaScriptでは、次の理由により、i ++を使用する方がよい場合があります。

var i=1;
alert(i++); // before, 1. current, 1. after, 2.
alert(i); // before, 2. current, 2. after, 2.
alert(++i); // before, 2. current, 3 after, 3.

配列(私はすべてだと思います)と他のいくつかの関数と呼び出しは開始点として0を使用しますが、++ iを使用するときにループを配列で機能させるには、iを-1に設定する必要があります。

使用するときは、私を++、以下の値が増加した値を使用します。i ++は人間が数える方法だと言えるかもしれません、なぜなら0から始めることができるからです。


2

FORループの機能を理解するには

ここに画像の説明を入力してください

上記の画像は、FORWHILEに変換できることを示しています。これらは最終的に完全に同じアセンブリコード(少なくともgcc)を持つためです。したがって、FORをいくつかの部分に分解して、その機能を理解することができます。

for (i = 0; i < 5; ++i) {
  DoSomethingA();
  DoSomethingB();
}

WHILEバージョンと等しい

i = 0; //first argument (a statement) of for
while (i < 5 /*second argument (a condition) of for*/) {
  DoSomethingA();
  DoSomethingB();
  ++i; //third argument (another statement) of for
}

これは、FORWHILEの単純なバージョンとして使用できることを意味します。

  1. FOR(int i)の最初の引数は、ループの前に、外側で実行されます。

  2. FORの3番目の引数(i ++または++ i)は、ループの最後の行で実行されます。

TL:DR:どんなにかどうi++++iに、スタンドアロンの場合、違いはなく、+ 1します。

学校では通常、i ++の方法を教えていますが、いくつかの理由により、++ iの方法を好む人もたくさんいます。

注:以前は、i ++はそれ自体に1を加えるだけでなく、元の値をレジスターに保持するため、パフォーマンスへの影響はほとんどありませんでした。しかし、今のところ、コンパイラーがプラスワンパーツを同じにするため、違いはありません。


1

ループには違いがある場合があります。これは、ポスト/プリインクリメントの実用的なアプリケーションです。

        int i = 0;
        while(i++ <= 10) {
            Console.Write(i);
        }
        Console.Write(System.Environment.NewLine);

        i = 0;
        while(++i <= 10) {
            Console.Write(i);
        }
        Console.ReadLine();

最初のものは11まで数え、11回ループしますが、2つ目はそうではありません。

ほとんどの場合、これは単純なwhile(x--> 0)で使用されます。--ループして、たとえば配列のすべての要素を反復します(ここではforeach-constructsを除く)。


1

どちらも数を増やします。++iに相当i = i + 1です。

i++そして、++i非常に似ているがまったく同じではありません。どちらも数値を++i増分しますが、現在の式が評価される前にi++数値を増分しますが、式が評価された後に数値を増分します。

int i = 3;
int a = i++; // a = 3, i = 4
int b = ++a; // b = 4, a = 

このリンクを確認してください


0

はい、の間に差がある++ii++forループが異常なユースケースでも、。インクリメント/デクリメント演算子を含むループ変数がforブロックまたはループテスト式内で使用された場合、またはループ変数の1つで使用された場合。いいえ、それは単に構文的なものではありません。

iコードのように、式iを評価することを意味し、演算子は評価を意味するのではなく、操作を意味します。

  • ++iの値をi1増やしi、後で評価することを意味します。
  • i++評価しi、後で値をi1 ずつ増分することを意味します。

したがって、評価されるものはそれぞれ異なるため、2つの式から得られるものは異なります。すべて同じ--ii--

例えば;

let i = 0

i++ // evaluates to value of i, means evaluates to 0, later increments i by 1, i is now 1
0
i
1
++i // increments i by 1, i is now 2, later evaluates to value of i, means evaluates to 2
2
i
2

通常とは異なる使用例では、次の例が役立つかどうかは関係ありませんが、違いを示しています

for(i=0, j=i; i<10; j=++i){
    console.log(j, i)
}

for(i=0, j=i; i<10; j=i++){
    console.log(j, i)
}

これは既存の回答に何を追加しますか?
GManNickG 2017年

それは私が読んだ答えよりも尋ねられたものに直接答えます。
セルチュク2017年

-2

以下のためのiユーザー定義型のの、これらの演算子はできた(ただし、いけません)ループインデックスの文脈における意味の異なるsematicsを有し、これは可能性(しかしならない)説明ループの動作に影響を与えます。

また、c++プリインクリメントフォーム(++i)を使用すると、最適化が容易になるため、一般的に最も安全です。(スコット・ランガムは私をこの一口に殴りました。あなたを呪い、スコット)


postfixのセマンティクスは、prefixよりも大きいと想定されてます。-1
xtofl 2009年

-2

他の言語については知りませんが、Java では++ iプレフィックスの増分です。つまり、iを1 増やしてから、iが存在する式でiの新しい値を使用します。i++は、次を意味する置の増分です。:式で現在のiの値を使用してから、1ずつ増やします。例:

public static void main(String [] args){

    int a = 3;
    int b = 5;
    System.out.println(++a);
    System.out.println(b++);
    System.out.println(b);

出力は次のとおりです。

  • 4
  • 5
  • 6

-3

i ++; ++ i; 式では使用されないため、どちらも類似しています。

class A {

     public static void main (String []args) {

     int j = 0 ;
     int k = 0 ;
     ++j;
     k++;
    System.out.println(k+" "+j);

}}

prints out :  1 1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.