6

前置および後置インクリメント演算子の必要性は何ですか? 1つで十分ではありませんか?

要するに、同様の while/do-while 必然性問題のようなものがありますが、両方を持っていても (理解と使用において) それほど混乱することはありません。ただし、接頭辞と接尾辞の両方があります(これらの演算子の優先度、関連付け、使用法、機能など)。そして、「やあ、私は後置インクリメントを使うつもりだ。ここで役に立つ」と言った状況を経験した人はいますか?

4

8 に答える 8

3

[OPの最初の部分に答えるために編集]

明らかi++に、++i両方とも同じ影響を与えますiが、異なる値を返します。 操作が異なります。したがって、多くのコードがこれらの違いを利用しています。

両方の演算子を持つ最も明白な必要性は、C の 40 年のコード ベースです。言語の機能が広く使用されると、削除するのが非常に困難になります。

確かに、新しい言語は、1 つだけで定義することも、まったく定義しないこともできます。しかし、それはピオリアで再生されますか? -を使用するだけで、演算子も削除できますがa + -b、それを売り込むのは難しいと思います。

両方必要ですか?

前置演算子は、代替コードで簡単に模倣できます。 for++iは とほとんど同じi += 1です。括弧が解決する演算子の優先順位以外に、違いはありません。

後置演算子は、この失敗した試みif(i++)if(i += 1).

将来の C がこれらのいずれかを減価償却するように動いた場合、前に説明したように、その機能のためにプレフィックス演算子を減価償却することになると思います。

前向きな考え: >> および << 演算子は、整数ビット シフトとはまったく異なることを行うために C++ で使用されました。おそらく、++pre と post++ は別の言語で拡張された意味を生成します。

【原文が続きます】

末尾の OP の質問への回答「あなたが言った状況を経験した人はいますか?」ここで役に立ちますか?」

char[] などのさまざまな配列処理が役立ちます。0 から始まる配列のインデックス付けは、後置インクリメントに適しています。配列要素をフェッチ/設定した後、次の配列アクセスの前にインデックスを処理する唯一のことは、インデックスをインクリメントすることです。すぐにそうするのもよいでしょう。

プレフィックスのインクリメントでは、0 番目の要素に対して 1 つのタイプのフェッチが必要になる場合があり、残りの要素に対して別のタイプのフェッチが必要になる場合があります。

size_t j = 0;

for (size_t i = 0, (ch = inbuffer[i]) != '\0'; i++) {
  if (condition(ch)) {
    outbuffer[j++] = ch;  // prefer this over below
  }
}
outbuffer[j] = '\0';

対。

for (size_t i = 0, (ch = inbuffer[i]) != '\0'; ++i) {
  if (condition(ch)) {
    outbuffer[j] = ch;
    ++j;
  }
}
outbuffer[j] = '\0';
于 2013-07-02T17:46:06.737 に答える
1

C言語のその他のあいまいなメカニズムについては、さまざまな歴史的理由があります。恐竜が地球を歩いていた太古の時代、コンパイラi++i+=1. 場合によっては、後でインクリメントするために値を保存する必要があるため、コンパイラは fori++よりも効率の悪いコードを生成します。恐竜コンパイラーを持っていない限り、これは効率の点で少しも問題になりません。++ii++

C言語のその他のあいまいなメカニズムについては、存在する場合、人々はそれを使い始めるでしょう。*p++例として一般的な式を使用します(つまりp、 はポインターであり、 の内容を取得し、pそれを式の結果として使用し、ポインターをインクリメントします)。後置を使用する必要があり、接頭辞を使用しないでください。そうしないと、まったく別のことを意味します。

ある恐竜はかつて and のような不必要に複雑な式を書き始めた*p++ので、それが一般的になり、今日ではそのようなコードは些​​細なものと見なされています。それがそうであるからでなく、私たちがそれを読むことに慣れているからです.

しかし、現代のプログラミングでは、 を書く理由はまったくありません*p++。たとえば、memcpyこれらの前提条件を持つ関数の実装を見ると、次のようになります。

void* memcpy (void* restrict s1, const void* restrict s2, size_t n)
{
  uint8_t* p1 = (uint8_t*)s1;
  const uint8_t* p2 = (const uint8_t*)s2;

次に、実際のコピーを実装する一般的な方法の 1 つを次に示します。

while(n--)
{
  *p1++ = *p2++;
}

ごくわずかなコードしか使用していないので、一部の人々は喜んでくれるでしょう。しかし、数行のコードが必ずしも優れたコードの尺度であるとは限りません。多くの場合、これは逆です。これを 1 行に置き換えることを検討すると、while(n--)*p1++=*p2++;これが正しい理由がわかります。

どちらのケースもあまり読みやすいとは思いません。5 分間頭を悩ませずに理解するには、ある程度経験のある C プログラマーである必要があります。そして、次のように同じコードを書くことができます:

while(n != 0)
{
  *p1 = *p2;

   p1++;
   p2++;  
   n--;
}

はるかに明確で、最も重要なことは、最初の例とまったく同じマシン コードを生成することです。

そして、何が起こったのか見てみましょう: 1 つの式に多くのオペランドを持つあいまいなコードを記述しないことにしたので、 and を使用++p1した方がよいでしょう++p2。同じマシンコードが得られます。接頭辞または接尾辞は関係ありません。しかし、あいまいなコードを使用した最初の例では*++p1 = *++p2、意味が完全に変わってしまいます。

要約すると:

  • 歴史的な理由から、前置および後置インクリメント演算子が存在します。
  • 現代のプログラミングでは、同じ式で複数の演算子を使用してあいまいなコードを記述しない限り、このような 2 つの異なる演算子を使用することはまったく不要です。
  • あいまいなコードを書くと、接頭辞と接尾辞の両方の使用を動機付ける方法が見つかります。ただし、そのようなコードはすべていつでも書き換えることができます。

これをコードの品質尺度として使用できます。接頭辞と接尾辞のどちらを使用しているかが重要なコードを書いていることに気付いた場合は、悪いコードを書いています。やめて、コードを書き直してください。

于 2013-07-03T07:34:09.803 に答える
1

それらはすでに多くのコードで使用されているため必要です。したがって、それらを削除すると、多くのコードがコンパイルに失敗します。

そもそもなぜそれらが存在したのかというと、古いコンパイラは++iandi++よりも効率的なコードを生成できましi+=1(i+=1)-1。新しいコンパイラでは、これは通常問題になりません。

C の他のどこにも、そのオペランドを変更するが、そのオペランドの前の値に評価される演算子がないため、後置バージョンは異常です。

前置または後置インクリメント演算子のいずれかを使用するだけで、確かに取得できます。私の見解では、それらの差は接頭辞と接尾辞の増分の差よりも大きいため、whileorのいずれか一方のみを使用して取得するのは少し難しいでしょう。do while

もちろん、接頭辞または接尾辞のインクリメント、または を使用しなくてもうまくいく可能性がありwhileますdo while。しかし、不要なものと有用な抽象化の境界線はどこにあるのでしょうか?

于 2013-07-02T18:16:33.340 に答える
0

プレフィックス演算子は、最初に値をインクリメントしてから、式で使用します。後置演算子、最初に式の値を使用し、値をインクリメントします

前置/後置演算子の基本的な使用法は、アセンブラーが単一のインクリメント/デクリメント命令に置き換えることです。インクリメントまたはデクリメント演算子の代わりに算術演算子を使用すると、アセンブラーはそれを 2 つまたは 3 つの命令に置き換えます。そのため、インクリメント/デクリメント演算子を使用します。

于 2013-07-02T16:53:48.973 に答える
0

両方は必要ありません。

スタックを実装するのに便利なので、一部の機械語に存在します。そこから間接的に C に継承され (この冗長性は依然としてある程度有用であり、一部の C プログラマーは 2 つの無関係な操作を 1 つの式に結合するという考えを好むようです)、C から他の C に似た言語に継承されています。

于 2013-07-02T20:52:23.443 に答える