i ++と++ iの違いは何ですか?


204

私は両方のC#コードの数々の作品で使用されているそれらを見てきた、と私は使用したときに知りたいのですi++++iiのような数の変数であることintfloatdouble、など)。これを知っている人はいますか?


13
それがまったく違いがない場合を除いて、どちらか一方を使用してはなりません。なぜなら、あなたが今求めているのと同じ質問を人々に尋ねさせるだけだからです。「考えさせない」は、コードだけでなくデザインにも当てはまります。
インスタンスハンター


2
@Dlaor:私のコメントにあるリンクまで読んだ?1つ目はC#に関するもので、2つ目は言語に依存せず、C#に焦点を当てた回答が受け入れられています。
gnovice

7
@ gnovice、1つ目は実際のコードごとの違いについて尋ねたときにパフォーマンスの違いについて尋ね、2つ目は一般的な違いについて尋ねたときのループの違いについて、3つ目はC ++についてです。
Dlaor

1
これはループ内のi ++と++ i違いの重複ではないと思いますか?-Dloarがコメントで述べているように、他の質問はループ内での使用について具体的に尋ねています。
キューx 2014

回答:


201

奇妙なことに、他の2つの回答では詳しく説明されていないようです。


i++「の値を教えてiから、増分する」を意味します

++i「インクリメントしi、値を教えて」を意味します


これらは、プリインクリメント、ポストインクリメントの演算子です。 どちらの場合も変数はインクリメントされますが、両方の式の値をまったく同じ場合に取ると、結果は異なります。



65
どちらの文も正しくありません。最初のステートメントを検討してください。i ++は、実際には「値を保存し、それをインクリメントし、iに保存して、元の保存値を教えて」という意味です。つまり、テリングは、あなたがそれを述べたではなく、インクリメントのに起こります。2番目のステートメントを検討してください。i ++は、実際には「値を保存し、インクリメントして、iに格納し、インクリメントした値を教えて」という意味です。あなたが言った方法では、値がiの値なのか、iに割り当てられた値なのかが不明確になります。それらは異なる場合があります
Eric Lippert、2007

8
@エリック、あなたの答えでさえ私には思えるかもしれません、2番目のステートメントは断固として正しいです。彼はいくつかのステップを除外しますが。
エヴァンキャロル

20
確かに、ほとんどの場合、運用のセマンティクスを説明するずさんで不正確な方法は、正確で正しい説明と同じ結果になります。第一に、私は誤った推論を通じて正しい答えを得ることに説得力のある価値を見いだしていません。実際のプログラマーから、インクリメントやデクリメントや配列の逆参照がぎっしり詰まった特定の式が、想定したように機能しない理由について、おそらく1年に6問程度の質問を受けます。
Eric Lippert、2007

12
@supercat:ディスカッションはCではなくC#に関するものです
Thanatos

428

この質問に対する典型的な回答は、残念ながらすでにここに投稿されていますが、一方は残りの操作の「前」の増分を行い、もう一方は残りの操作の「後」の増分を行います。これは直感的に理解を深めますが、その発言は完全に間違っています。時間のイベントのシーケンスは、 C#で、非常に明確に定義されており、それが強調されていない ++の接頭辞(++ VAR)と後置(VAR ++)のバージョンが他の操作に対して異なる順序で物事を行うことをケース。

この質問に対して多くの間違った回答が表示されるのは当然のことです。非常に多くの「C#を自分で教える」本も間違っています。また、C#が行う方法は、Cが行う方法とは異なります。多くの人々は、C#とCが同じ言語であるかのように考えています。ではない。私の意見では、C#のインクリメント演算子とデクリメント演算子の設計は、Cでのこれらの演算子の設計上の欠陥を回避します。

C#での接頭辞++と接尾辞++の操作を正確に判断するには、2つの質問に答える必要があります。最初の質問は結果何ですか?2番目の質問は、増分の副作用いつ発生するかです。

どちらの質問に対する答えが何であるかは明らかではありませんが、実際に見ると、それは実際には非常に簡単です。変数xに対してx ++と++ xが何をするかを正確に説明します。

プレフィックス形式(++ x)の場合:

  1. xは変数を生成するために評価されます
  2. 変数の値が一時的な場所にコピーされます
  3. 一時的な値はインクリメントされて新しい値を生成します(一時的な値を上書きしません!)
  4. 新しい値は変数に格納されます
  5. 操作の結果は、新しい値(つまり、一時的な値の増分)です。

Postfixフォーム(x ++)の場合:

  1. xは変数を生成するために評価されます
  2. 変数の値が一時的な場所にコピーされます
  3. 一時的な値はインクリメントされて新しい値を生成します(一時的な値を上書きしません!)
  4. 新しい値は変数に格納されます
  5. 操作の結果は一時的な値です

注意すべき点:

まず、時間内のイベントの順序はどちらの場合もまったく同じです。ここでも、時間内のイベント順序が前置と後置の間で変化することは絶対にありません。評価が他の評価の前または後に行われると言うのは完全に誤りです。手順1から4が同一であることからわかるように、評価は両方のケースでまったく同じ順序で行われます。唯一の違いは、最後のステップ -結果は、一時的、または新しい、インクリメント値の値であるかどうか。

簡単なC#コンソールアプリでこれを簡単に示すことができます。

public class Application
{
    public static int currentValue = 0;

    public static void Main()
    {
        Console.WriteLine("Test 1: ++x");
        (++currentValue).TestMethod();

        Console.WriteLine("\nTest 2: x++");
        (currentValue++).TestMethod();

        Console.WriteLine("\nTest 3: ++x");
        (++currentValue).TestMethod();

        Console.ReadKey();
    }
}

public static class ExtensionMethods 
{
    public static void TestMethod(this int passedInValue) 
    {
        Console.WriteLine("Current:{0} Passed-in:{1}",
            Application.currentValue,
            passedInValue);
    }
}

これが結果です...

Test 1: ++x
Current:1 Passed-in:1

Test 2: x++
Current:2 Passed-in:1

Test 3: ++x
Current:3 Passed-in:3

最初のテストでは、期待どおり、両方currentValueTestMethod()拡張に渡されたものが同じ値を示していることがわかります。

ただし、2番目のケースでは、の呼び出し後にインクリメントがcurrentValue発生することを伝えようとしますが、結果からわかるよう、「Current:2」の結果で示されるよう、呼び出しの前にインクリメントが発生します。TestMethod()

この場合、最初にの値がcurrentValue一時的に格納されます。次に、その値のインクリメントされたバージョンがcurrentValue、元の値をまだ格納している一時に触れずに、再び格納されます。最後に、その一時変数がに渡されTestMethod()ます。呼び出し後にインクリメントが発生し場合、インクリメントTestMethod()されていない同じ値が2回書き込まれますが、そうではありません。

これは、値は両方から返されることに注意することが重要だcurrentValue++++currentValue操作が時間のいずれかの操作を終了時に変数に格納されている実際の値が一時的ではないに基づいています。

上記の操作の順序で思い出してください。最初の2つのステップでは、変数の現在の値を一時変数にコピーします。これが戻り値の計算に使用されます。接頭辞バージョンの場合は、その一時的な値が増分されますが、接尾辞バージョンの場合は、その値は直接/増分されません。変数自体は、一時変数に最初に格納された後は再度読み込まれません。

簡単に言うと、postfixバージョンは変数から読み取られた値(つまり一時変数の値)を返し、prefixバージョンは変数に書き戻された値(つまり一時変数の増分された値)を返します。どちらも変数の値を返しません。

変数自体が揮発性であり、別のスレッドで変更されている可能性があるため、これらの操作の戻り値が変数に格納されている現在の値と異なる可能性があるため、これは理解することが重要です。

驚くべきことに、優先順位、結合性、副作用の実行順序について人々が非常に混乱することはよくありますが、Cでの混乱が主な原因であると考えられます。C#は、これらすべての点で混乱が少ないように注意深く設計されています。これらの問題のいくつかの追加の分析については、前置および後置操作が「時間内にものを移動する」という考えの偽りをさらに示す私を含めて、以下を参照してください。

https://ericlippert.com/2009/08/10/precedence-vs-order-redux/

このSOの質問につながった:

int [] arr = {0}; int値= arr [arr [0] ++]; 値= 1?

この件に関する私の以前の記事にも興味があるかもしれません。

https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/

そして

https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/

そして、Cが正しさについて推論するのを難しくする興味深いケース:

https://docs.microsoft.com/archive/blogs/ericlippert/bad-recursion-revisited

また、チェーンされた単純な割り当てなど、副作用のある他の操作を検討すると、同様の微妙な問題が発生します。

https://docs.microsoft.com/archive/blogs/ericlippert/chaining-simple-assignments-is-not-so-simple

そして、ここで、インクリメント演算子が変数ではなくC#のになる理由についての興味深い投稿があります

Cライクな言語で++ i ++を実行できないのはなぜですか?


4
+1:本当に好奇心旺盛な方のために、「Cでのこれらの演算の設計上の欠陥」が何を意味するのかについて言及してもらえますか?
Justin Ardini、2010

21
@ジャスティン:リンクをいくつか追加しました。しかし基本的には、Cでは、物事が時間内にどのような順序で発生するかについては、何の保証もありません。適合コンパイラは、同じシーケンスポイントに2つのミューテーションがある場合に、なんでも好きなことを行うことができ、実装定義の動作であると言っている必要はありません。これにより、一部のコンパイラでは機能し、他のコンパイラではまったく異なる処理を行う、危険な移植性のないコードを書くようになります。
Eric Lippert、2007

14
本当に好奇心が強い人にとっては、これは良い知識ですが、平均的なC#アプリケーションの場合、他の回答の表現と実際に行われていることの違いは、言語の抽象化レベルをはるかに下回っているため、変わりはない。C#はアセンブラーではなく、99.9%の時間のうち、i++または++iコードで使用されている場合、バックグラウンドで行われていることはそれだけです。バックグラウンドで。私はC#を記述して、このレベルで何が起こっているかよりも抽象化レベルに上るようにしています。そのため、これが本当にC#コードにとって重要な場合は、すでに間違った言語になっている可能性があります。
Tomas Aschan 2010

24
@Tomas:まず、平均的なアプリケーションの質量だけでなく、すべての C#アプリケーションについて心配しています。非平均的なアプリケーションの数は多いです。次に、違いがある場合を除いて、違いはありません。実際のコードの実際のバグに基づいて、この問題についておそらく1年に6回程度質問されます。第三に、私はあなたのより大きな点に同意します。++の全体的なアイデアは、最新のコードでは非常に趣のある非常に低レベルのアイデアです。Cライクな言語がこのような演算子を持つことは慣用的であるため、実際に存在するのはそれだけです。
Eric Lippert、2007

11
+1しかし、私は本当のことを言わなければなりません...私が行う後置演算子の唯一の使用法は、行の中で単独ではない(つまり、そうではないi++;)だけであるfor (int i = 0; i < x; i++)...そして、私は非常に嬉しい!(そして、私はこれまでに前置演算子を使用していません)。上級のプログラマーが解読するのに2分かかるようなものを書く必要がある場合...うーん...もう1行のコードを書くか、一時的な変数を導入する方がいいです:-)私はあなたの「記事」(私が勝った)だと思います「答え」と呼ばないでください)私の選択を証明します:-)
xanatos

23

あなたが持っている場合:

int i = 10;
int x = ++i;

その後にxなります11

しかし、あなたが持っている場合:

int i = 10;
int x = i++;

その後にxなります10

エリックが指摘しているように、増分は両方の場合に同時に発生しますが、結果として異なる値が与えられます(エリックに感謝します!)。

一般的には、使用++iする正当な理由がない限り、使用するのが好きです。たとえば、ループを作成するときは、次のように使用します。

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

または、変数をインクリメントするだけの場合は、次のように使用します。

++x;

通常、どちらか一方の方法ではあまり意味がなく、コーディングスタイルになりますが、(元の例のように)他の割り当て内で演算子を使用している場合は、潜在的な副作用に注意することが重要です。


2
コード行を順番に並べることで、例を明確にすることができます。つまり、「var x = 10; var y = ++ x;」と言います。および「var x = 10; var y = x ++;」代わりに。
Tomas Aschan

21
この答えは危険なほど間違っています。増分が発生しても、残りの操作に関しては変化しないため、一方の場合は残りの操作の前に行われ、もう一方の場合は残りの操作ので行われるため誤解を招く恐れがあります。どちらの場合も、増分は同時に行われます。異なるのは、インクリメントが行われたときではなく、結果として与えられる値です
Eric Lippert、2007

3
これを編集して、C#キーワードではiなく変数名に使用しましたvar
ジェフイェーツ2010

2
したがって、変数を割り当てずに、「i ++」とだけ記述します。または「++ i;」まったく同じ結果が得られますか、それとも間違っていますか?
Dlaor

1
@エリック:元の文言が「危険」である理由を明確にしていただけませんか?詳細が間違っていても、正しい使い方につながります。
Justin Ardini、2010

7
int i = 0;
Console.WriteLine(i++); // Prints 0. Then value of "i" becomes 1.
Console.WriteLine(--i); // Value of "i" becomes 0. Then prints 0.

これはあなたの質問に答えますか?


4
接尾辞インクリメントと接頭辞デクリメントが変数に影響を与えないと想像することができます:P
Andreas

5

演算子が機能する方法は、それが同時にインクリメントされることですが、それが変数の前にある場合、式はインクリメント/デクリメントされた変数で評価されます。

int x = 0;   //x is 0
int y = ++x; //x is 1 and y is 1

変数の後にある場合、現在のステートメントは、まだインクリメント/デクリメントされていないかのように、元の変数で実行されます。

int x = 0;   //x is 0
int y = x++; //'y = x' is evaluated with x=0, but x is still incremented. So, x is 1, but y is 0

必要な場合を除いて、プリインクリメント/デクリメント(++ x)を使用する場合、dcpに同意します。実際、私がポストインクリメント/デクリメントを使用するのは、whileループまたはそのようなループのみです。これらのループは同じです:

while (x < 5)  //evaluates conditional statement
{
    //some code
    ++x;       //increments x
}

または

while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
    //some code
}

配列などのインデックスを作成するときにもこれを行うことができます。

int i = 0;
int[] MyArray = new int[2];
MyArray[i++] = 1234; //sets array at index 0 to '1234' and i is incremented
MyArray[i] = 5678;   //sets array at index 1 to '5678'
int temp = MyArray[--i]; //temp is 1234 (becasue of pre-decrement);

などなど...


4

参考までに、C ++では、いずれかを使用できる場合(つまり)操作の順序を気にしない場合(インクリメントまたはデクリメントして後で使用したいだけ)、前置演算子は効率的ではないため、より効率的です。オブジェクトの一時的なコピーを作成する必要があります。残念ながら、ほとんどの人は接頭辞(++ var)ではなくposfix(var ++)を使用しています。(私はインタビューでこれについて尋ねられました)。これがC#で当てはまるかどうかはわかりませんが、当てはまると思います。


2
これが、c#から++ iを単独で使用する場合(つまり、より大きな式ではない場合)に使用する理由です。i ++でac#ループを見るときはいつでも少し泣きますが、c#でこれが実際に私がより良く感じているコードとほとんど同じであることを知ったので、個人的には、習慣が根絶するため、++ iを引き続き使用します。
Mikle
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.