C ++での増分-x ++または++ xを使用する場合


91

私は現在C ++を学んでおり、インクリメントについて少し前に学びました。「++ x」を使用して前にインクリメントを行い、「x ++」を使用して後にインクリメントできることを知っています。

それでも、どちらを使用するかは本当にわかりません...「++ x」を実際に使用したことがなく、これまでのところ常に問題なく機能しているので、いつ使用するべきですか?

例:forループで、「++ x」を使用するのが望ましいのはいつですか?

また、誰かがさまざまな増分(または減分)がどのように機能するかを正確に説明できますか?とても感謝しております。

回答:


114

それは好みの問題ではなく、論理の問題です。

x++現在のステートメントを処理した、変数xの値をインクリメントします。

++x現在のステートメントを処理する前に、変数xの値をインクリメントします。

だからあなたが書くロジックを決めるだけです。

x += ++iiをインクリメントし、i + 1をxに追加します。 x += i++xにiを追加し、iを増分します。


27
また、forループのプリミティブでは、まったく違いはありません。多くのコーディングスタイルでは、誤解される可能性がある場合はインクリメント演算子を使用しないことをお勧めします。つまり、x ++または++ xは、それ自体の行にのみ存在し、y = x ++として存在してはなりません。個人的に、私はこれが好きではありませんが、それは珍しいことです
Mikeage 2009年

2
また、独自の行で使用すると、生成されたコードはほぼ同じになります。
Nosredna 2009年

14
これは一歩踏み込んだように見えるかもしれませんが(主に:)なので)、C ++では、増分前x++の値を持つ右辺値であり、増分後の値を持つ左辺値です。どちらの式も、実際にインクリメントされた値がxに格納されるときは保証しません。それは、次のシーケンスポイントの前に発生することが保証されるだけです。「現在のステートメントを処理した後」は、一部の式にシーケンスポイントがあり、一部のステートメントが複合ステートメントであるため、厳密には正確ではありません。xx++x
CBベイリー

10
実際、答えは誤解を招くものです。変数xが変更される時点は、実際にはおそらく変わらないでしょう。違いは、++++は以前のxの右辺値を返すように定義されているのに対し、++ xはまだ変数xを参照していることです。
sellibitze

5
@BeowulfOF:答えは、存在しない注文を意味します。インクリメントが行われるときを言うために標準に何もありません。コンパイラーは、「x + = i ++」を次のように実装することができます。int j = i; i = i + 1; x + = j; "(つまり、「現在のステートメントの処理」の前に「i」がインクリメントされます。)これが「i = i ++」の動作が未定義であり、答えが「微調整」を必要とする理由です。「x + = ++ i "は正しい順序ではありません。「iをインクリメントし、i + 1をxに追加します」
Richard Corden

53

Scott Meyersは、ロジックがpostfixが適切であると指示する場合を除いて、prefixを優先するように指示しています。

「より効果的なC ++」項目#6-それは私にとって十分な権限です。

本を所有していない人のために、ここに適切な引用があります。32ページから:

Cプログラマとしての日々から、インクリメント演算子のプレフィックス形式は「インクリメントアンドフェッチ」と呼ばれることがあり、ポストフィックス形式は「フェッチアンドインクリメント」と呼ばれることが多いことを思い出してください。2つのフレーズは覚えておくことが重要です。これらはすべて正式な仕様として機能するためです...

そして34ページで:

効率が気になる人なら、postfixのインクリメント関数を最初に見たとき、おそらく汗を流したことでしょう。その関数は、戻り値の一時オブジェクトを作成する必要があり、上記の実装は、構築および破棄する必要がある明示的な一時オブジェクトも作成します。プレフィックスのインクリメント関数にはそのような一時的なものはありません...


4
コンパイラーは、増分前の値が不必要であることを認識しない場合、いくつかの命令で後置増分を実装する可能性があります。古い値をコピーしてから増分します。プレフィックスの増分は、常に1つの命令でなければなりません。
gnud 2009年

8
値を実行した後に捨てられたループのために:私はgccで、この昨日テストするために起こったi++++i、生成されたコードは同じです。
Giorgio

forループの外で試してください。割り当ての動作は異なる必要があります。
duffymo 2015年

私はスコットマイヤーズの2番目の点で明確に同意しません。「x ++」または「++ x」の90%以上のケースは通常、割り当てから分離されており、オプティマイザは一時変数が不要であることを認識するのに十分賢いので、通常は無関係です。そのような場合に作成されます。その場合、2つの形式は完全に交換可能です。これの意味するところは、 "x ++"で処理された古いコードベースはそのままにしておく必要があることです。パフォーマンスをどこよりも向上させるよりも、 "++ x"に変更するときに微妙なエラーが発生する可能性が高くなります。おそらく「x ++」を使用して人々に考えさせる方が良いでしょう。
omatai 2016年

2
Scott Meyersは好きなだけ信頼できますが、コードがパフォーマンスに依存しているために++xx++実際のパフォーマンスとの違いが問題になる場合は、どちらのバージョンでも完全かつ適切に最適化できるコンパイラを実際に使用することが非常に重要です。環境。「私はこのくだらない古いハンマーを使用しているので、43.7度の角度でしか釘を打ち込むことができません」は、43.7度だけで釘を打ち込んで家を建てるのには不十分な議論です。より良いツールを使用してください。
Andrew Henle

28

イテレータをインクリメントするときのcppreferenceから:

古い値を使用しない場合は、前置インクリメント演算子(++ iter)より後置インクリメント演算子(iter ++)を優先する必要があります。ポストインクリメントは通常、次のように実装されます。

   Iter operator++(int)   {
     Iter tmp(*this); // store the old value in a temporary object
     ++*this;         // call pre-increment
     return tmp;      // return the old value   }

明らかに、それは事前インクリメントよりも効率的ではありません。

事前増分では一時オブジェクトは生成されません。オブジェクトの作成にコストがかかる場合、これは大きな違いを生む可能性があります。


8

(pre / postの)セマンティックが重要ではない場所でpre / postのインクリメントを使用する場合、生成されたコードが同じであることに気づきたいだけです。

例:

pre.cpp:

#include <iostream>

int main()
{
  int i = 13;
  i++;
  for (; i < 42; i++)
    {
      std::cout << i << std::endl;
    }
}

post.cpp:

#include <iostream>

int main()
{

  int i = 13;
  ++i;
  for (; i < 42; ++i)
    {
      std::cout << i << std::endl;
    }
}

_

$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s   
1c1
<   .file   "pre.cpp"
---
>   .file   "post.cpp"

5
整数のようなプリミティブ型の場合は、はい。のようなものの違いがどうなるかを確認しましたstd::map::iteratorか?もちろん、2つの演算子は異なりますが、結果が使用されない場合にコンパイラーが接尾辞を接頭辞に最適化するかどうかについて私は興味があります。私はそれが許されているとは思いません-postfixバージョンが副作用を含む可能性があることを考えると。
2009年

また、「コンパイラはおそらく副作用が不要で最適化されることを認識します」は、おそらく非常に多くのことを除いて、理由もなく、より複雑な後置演算子を使用するずさんなコードを書く言い訳にはなりません。想定される教材は明らかな理由なくPostfixを使用し、卸売りでコピーされます。
underscore_d

6

覚えておくべき最も重要なこと、imoは、インクリメントが実際に行われる前にx ++が値を返す必要があるということです-したがって、オブジェクトの一時的なコピー(プリインクリメント)を作成する必要があります。これは、インプレースでインクリメントされて返される++ xよりも効率的ではありません。

ただし、言及する価値のあるもう1つのことは、ほとんどのコンパイラーが、可能な場合にそのような不要なものを最適化できることです。たとえば、両方のオプションで同じコードがここに表示されます。

for (int i(0);i<10;++i)
for (int i(0);i<10;i++)

5

@BeowulfOFに同意しますが、明確にするために、ステートメントを分割して、ロジックが完全に明確になるようにすることを常に推奨します。つまり、

i++;
x += i;

または

x += i;
i++;

したがって、私の答えは、明確なコードを記述した場合、これはほとんど問題になりません(そして、問題がある場合、コードはおそらく十分に明確ではありません)。


2

++ xはx ++ より高速であることが期待されることを強調し直したかったので(特にxが任意のタイプのオブジェクトである場合)、論理的な理由で必要でない限り、++ xを使用する必要があります。


2
この強調は誤解を招く可能性が高いことを強調しておきます。孤立した "x ++"で終わるループを見ていて、 "Aha!-これが非常に低速で実行されている理由です!"と考えている場合 そして、それを "++ x"に変更すると、違いはまったく期待できません。オプティマイザは、だれも結果を使用しないときに一時変数を作成する必要がないことを認識できるほど賢いです。つまり、 "x ++"でなぞられた古いコードベースはそのままにしておく必要があります。パフォーマンスをどこかで改善するよりも、エラーを変更してエラーが発生する可能性が高くなります。
omatai 2016年

1

あなたは違いを正しく説明しました。ループを実行するたびに、またはその後にxを増分するかどうかに依存します。何が適切かは、プログラムロジックによって異なります。

STLイテレーター(これらの演算子も実装する)を扱う際の重要な違いは、it ++がイテレーターが指すオブジェクトのコピーを作成し、増分して、そのコピーを返すことです。一方、++ itは最初にインクリメントを行ってから、イテレータが現在ポイントしているオブジェクトへの参照を返します。これはほとんどすべてのパフォーマンスが重要である場合、または独自のSTLイテレータを実装する場合にのみ関連します。

編集:接頭表記と接尾表記の混同を修正


ループの反復の「前」または「後」の話は、前/後のインク/減少が条件で発生する場合にのみ意味があります。多くの場合、クラス型がpostfixを使用するのは遅くなる可能性があり、人々は理由なくそれを使用するべきではありませんが、それはロジックを変更できない継続句にあります。
underscore_d

1

++、-演算子のPostfix形式は、use-then-changeルールに従います。

プレフィックス形式(++ x、-x)は、change-then-useの規則に従います

例1:

coutを使用して複数の値が<<でカ​​スケードされると、計算(ある場合)は右から左に行われますが、印刷は左から右に行われます(例:valの場合、最初は10)

 cout<< ++val<<" "<< val++<<" "<< val;

結果になります

12    10    10 

例2:

ターボC ++では、式で++または(任意の形式の)の複数の出現が見つかった場合、最初にすべての接頭辞形式が計算され、次に式が評価され、最後に後置形式が計算されます。

int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;

Turbo C ++での出力は

48 13

それが現代のコンパイラで出力されるのに対し(それらは厳密にルールに従っているため)

45 13
  • 注:1つの式の同じ変数でインクリメント/デクリメント演算子を複数使用することはお勧めしません。このような
    式の処理/結果は、コンパイラによって異なります。

複数のinc / decrement演算を含む式が「コンパイラごとに異なる」というわけではなく、むしろ悪いことに、シーケンスポイント間のこのような複数の変更は、未定義の動作をしてプログラムを汚染します。
underscore_d

0

コードの明確さを検討するときは、言語構文を理解することが重要です。たとえばポストインクリメントを使用して、文字列をコピーすることを検討してください:

char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
  b[i] = a[i];
} while (a[i++]);

文字列の最後にあるゼロ文字(falseをテスト)に遭遇してループを実行する必要があります。そのためには、値を事前にインクリメントし、インデックスをインクリメントする必要があります。ただし、必ずしもこの順序である必要はありません。これをプリインクリメントでコーディングする方法は次のとおりです。

int i = -1;
do {
  ++i;
  b[i] = a[i];
} while (a[i]);

これはより明確な好みの問題であり、a [i]が高価な関数や副作用がある関数であっても、マシンにレジスタがたくさんある場合、両方の実行時間は同じでなければなりません。大きな違いは、インデックスの終了値です。

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