web-dev-qa-db-ja.com

「for」ループ内のポストインクリメントとプリインクリメントは同じ出力を生成します

次のforループは、1つがポストインクリメントを使用し、もう1つがプリインクリメントを使用する場合でも、同じ結果を生成します。

コードは次のとおりです。

for(i=0; i<5; i++) {
    printf("%d", i);
}

for(i=0; i<5; ++i) {
    printf("%d", i);
}

両方の「for」ループで同じ出力が得られます。何か不足していますか?

177
theReverseFlick

i++または++iを評価した後、iの新しい値は両方の場合で同じになります。プリインクリメントとポストインクリメントの違いは、式自体を評価した結果にあります。

++iiをインクリメントし、iの新しい値に評価します。

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

Forループでこれが問題にならない理由は、制御の流れがおおよそ次のように機能するためです。

  1. 状態をテストする
  2. falseの場合、終了します
  3. 真の場合、本文を実行します
  4. 増分ステップを実行する

(1)と(4)は分離されているため、プリインクリメントまたはポストインクリメントを使用できます。

306
danben

まあ、これは簡単です。上記のforループは、意味的に次と同等です。

int i = 0;
while(i < 5) {
    printf("%d", i);
    i++;
}

そして

int i = 0;
while(i < 5) {
    printf("%d", i);
    ++i;
}

i++;++i;は、このコードブロックの観点から同じセマンティクスを持っていることに注意してください。これらは両方ともiの値に対して同じ効果を持ち(1ずつインクリメント)、したがってこれらのループの動作に対して同じ効果を持ちます。

ループが次のように書き換えられた場合、違いがあることに注意してください。

int i = 0;
int j = i;
while(j < 5) {
    printf("%d", i);
    j = ++i;
}

int i = 0;
int j = i;
while(j < 5) {
    printf("%d", i);
    j = i++;
}

これは、コードの最初のブロックjがインクリメント後iの値を見る(iが最初にインクリメントされるか、事前にインクリメントされるため、名前)ため、コードの2番目のブロックjがインクリメント前のiの値を見るためです。

112
jason

コードの結果は同じになります。その理由は、2つのインクリメント操作が2つの異なる関数呼び出しとして認識されるためです。両方の関数は変数の増分を引き起こし、戻り値のみが異なります。この場合、戻り値は破棄されるだけです。つまり、出力に顕著な違いはありません。

ただし、フードの下には違いがあります:ポストインクリメントi++は一時変数を作成する必要がありますiの元の値を保存するには、増分を実行して一時変数を返します。事前インクリメント++iは、一時変数を作成しません。もちろん、オブジェクトがintのような単純なものである場合、適切な最適化設定はこれを最適化できるはずですが、++演算子はイテレーターのようなより複雑なクラスでオーバーロードされることに注意してください。オーバーロードされた2つのメソッドには異なる操作がある場合があるため(たとえば、 "Hey、I'm pre-incremented!"をstdoutに出力したい場合があります) (基本的に、このようなコンパイラは解決できない halting problem を解決するため)、myiterator++を記述する場合は、より高価なポストインクリメントバージョンを使用する必要があります。

事前にインクリメントする必要がある3つの理由:

  1. 変数/オブジェクトにオーバーロードされたポストインクリメントメソッドが(たとえばテンプレート関数で)あるかどうかを考え、それを別の方法で処理する(または別の方法で処理するのを忘れる)必要はありません。
  2. 一貫性のあるコードは見栄えが良い.
  3. 誰かが「なぜ事前にインクリメントするのですか?」停止する問題と コンパイラ最適化の理論的限界 について教える機会があります。 :)
88

これは私のお気に入りのインタビューの質問の1つです。最初に答えを説明し、次に質問が好きな理由を説明します。

解決策:

答えは、両方のスニペットが0から4までの数値を出力することです。これは、for()ループが一般にwhile()ループと同等だからです。

for (INITIALIZER; CONDITION; OPERATION) {
    do_stuff();
}

書くことができます:

INITIALIZER;
while(CONDITION) {
    do_stuff();
    OPERATION;
}

ループの一番下でOPERATIONがalwaysされていることがわかります。このフォームでは、i++++iが同じ効果を持つことは明らかです。これらは両方ともiをインクリメントし、結果を無視します。 iの新しい値は、ループの先頭で次の反復が開始されるまでテストされません。


Edit:このfor()からwhile()への同等性は、ループに含まれる制御ステートメント(continueなど)が含まれる場合、notを保持することを指摘してくれたJasonに感謝します。 OPERATIONwhile()ループで実行されるのを防ぎます。 OPERATIONは、for()ループの次の反復の直前に実行されるalwaysです。


なぜ良いインタビューの質問なのか

まず第一に、候補者が正しい答えをすぐに伝えれば1〜2分しかかからないので、次の質問にすぐに進むことができます。

しかし、驚くべきことに(私にとって)、多くの候補者は、ポストインクリメントのループが0から4までの数字を出力し、プリインクリメントのループが0から5、または1から5を出力すると教えてくれます。プリインクリメントとポストインクリメントは正しく行われますが、for()ループのメカニズムを誤解しています。

その場合、while()を使用してループを書き換えるように依頼します。これにより、彼らの思考プロセスの良いアイデアが得られます。それが私がそもそも質問をする理由です。彼らが問題にどのようにアプローチし、私が彼らの世界の仕組みに疑問を投げかけたときにどのように進むのかを知りたいです。

この時点で、ほとんどの候補者はエラーを認識し、正しい答えを見つけます。しかし、私は彼の最初の答えは正しいと主張し、彼がfor()while()に翻訳する方法を変えたと主張した人がいました。魅力的なインタビューになりましたが、申し出はしませんでした!

お役に立てば幸いです!

24
Adam Liss

どちらの場合でも、増分はループの本体の後に行われるため、ループの計算には影響しません。コンパイラが愚かである場合、ポストインクリメントを使用するのは少し効率が悪いかもしれません(通常、後で使用するためにpre値のコピーを保持する必要があるため)この場合、最適化されます。

Forループがどのように実装され、基本的に割り当て、テスト、および分岐命令のセットに変換されるかを考えると便利です。擬似コードでは、プリインクリメントは次のようになります。

      set i = 0
test: if i >= 5 goto done
      call printf,"%d",i
      set i = i + 1
      goto test
done: nop

ポストインクリメントには少なくとももう1つのステップがありますが、最適化して削除するのは簡単です

      set i = 0
test: if i >= 5 goto done
      call printf,"%d",i
      set j = i   // store value of i for later increment
      set i = j + 1  // oops, we're incrementing right-away
      goto test
done: nop
6
tvanfosson

このように書いた場合、それは問題になります:

for(i=0; i<5; i=j++) {
    printf("%d",i);
}

このように書かれている場合よりももう一度繰り返します:

for(i=0; i<5; i=++j) {
    printf("%d",i);
}
4
iss42

Googleの答えはここで読むことができます: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Preincrement_and_Predecrement

だから、要点は、単純なオブジェクトには違いはありませんが、イテレータや他のテンプレートオブジェクトにはプリインクリメントを使用する必要があるということです。

編集済み:

単純型を使用しているため、副作用はなく、ループ本体の後にポストインクリメントまたはプリインクリメントが実行されるため、ループ本体の値に影響がないため、違いはありません。

このようなループで確認できます。

for (int i = 0; i < 5; cout << "we still not incremented here: " << i << endl, i++)
{
    cout << "inside loop body: " << i << endl;
}
2
Yola

For構文の3番目のステートメントは実行されるだけですが、評価された値は破棄され、処理されません。
評価された値が破棄されると、事前と事後の増分は等しくなります。
値が取られる場合にのみ異なります。

1
Petruza

はい、両方でまったく同じ出力が得られます。なぜ彼らはあなたに異なる出力を与えるべきだと思いますか?

次のような状況では、ポストインクリメントまたはプレインクリメントが重要です:

int j = ++i;
int k = i++;
f(i++);
g(++i);

割り当てるか引数を渡すことにより、何らかの値を提供します。 forループでもどちらも行いません。増分のみされます。ポストとプレはそこで意味をなさない!

1
Nawaz

I ++と++ iは、printf( "%d"、i)が毎回実行された後に実行されるため、違いはありません。

1
Hoàng Long