JavaまたはC#の(post / pre)インクリメント演算子の理由


8

最近、整数に対してポスト/プリインクリメント演算子を頻繁に使用するコードの出力を予測することについて、疑問に遭遇しました。私はCプログラマーの経験があるので、まるで我が家のように感じましたが、JavaがC(またはC ++)から盲目的にコピーしたものであり、これはJavaの役に立たない機能であるとの声がありました。

そうは言っても、Java(またはC#)でその理由(特にpost / preフォームの違いを念頭に置いて)を見つけることはできません。これは、配列を操作してJavaで文字列として使用するようなものではないためです。 。また、私が最後にバイトコードを調べたのはずっと前なので、INC操作があるかどうかはわかりませんが、これがなぜなのかはわかりません

for(int i = 0; i < n; i += 1)

より効果が少ない可能性があります

for(int i = 0; i < n; i++)

これが本当に役立つ言語の特定の部分はありますか、またはこれはCプログラマーを街に連れてくるための単なる機能ですか?


2
「ゴスリングは、C(Javaではない)の経験者が書いたコードを、NeWSでPostScriptを実行するのに慣れた誰かが実行することを望んでいた...」
gnat

7
@TimothyGroote 1)配列は変更可能ですが、文字列は変更できないため、これは公平な評価だとは思いません。2)文字列をchar []に割り当てたり、その逆を行うことはできません。それらは確かに配列に似ており、配列で実装されている可能性が高く、配列との間で変換するのは簡単ですが、まったく同じではありません。
Doval 2014年

4
ここにたくさんの良い答えがあるので、これらの演算子が恐ろしい機能であることを強調することを除いて、他の人がすでに言ったことを再度述べることはしません。彼らは非常に混乱しています。25年以上経った今でも、前後のセマンティクスが混同されています。彼らは、結果の評価と副作用の発生を組み合わせるような悪い習慣を奨励しています。これらの機能がC / C ++ / Java / JavaScript / etcになかったとしたら、C#用に発明されたものではなかったでしょう。
エリックリッペルト2014年

5
人々からの主張に直接対応するには:C#は誰からも盲目的に何もコピーしませんでした。C#のすべての機能は、非常に注意深く設計されています。そして、これらは役に立たない機能ではありません。彼らは用途があります。
Eric Lippert

2
@EricLippert:私はひどいですが、そのコンテキストで...なぜ(希望)私たちのほとんどはそのプリ/ポストインクリメントを同意することができると思いました?彼らはC#で含ま 親しみやすさのために?
Phoshi

回答:


19

これはCプログラマーを街に連れてくるための単なる機能ですか

これは確かに「C(またはC ++)プログラマーを街に連れてくる」機能ですが、ここで「ただ」という言葉を使用すると、そのような類似性の価値が過小評価されます。

  • コード(または少なくともコードスニペット)をC(またはC ++)からJavaまたはC#に簡単に移植できる

  • i++はタイプよりも少なくi += 1x[i++]=0;より慣用的ですx[i]=0;i+=1;

そして、はい、ポスト/プリインクリメント演算子を多用するコードは、読み取りや保守が難しい場合がありますが、これらの演算子が誤用されているコードでは、言語がこれらの演算子を提供しない場合でも、コードの品質が大幅に向上することは期待できません。保守性を気にしない開発者がいると、理解しにくいコードを書く方法が常に見つかるからです。

関連:Java演算子とC ++演算子の間に違いはありますか?この答えから、Cで明確に定義された演算子の動作はJavaでも同様であり、JavaはCの動作が未定義の場合の演算子の優先順位の定義をいくつか追加するだけであると推定できます。これは偶然のようではなく、かなり意図的なもののようです。


10
「保守性を気にしない開発者がいる場合、彼らは常に理解しにくいコードを書く方法を見つけるでしょう」 -非常によく言われました
Lovis

Cプログラマの間で、死ぬ前にCのキーストロークが非常に多いという共通の信念に賛成ですか?その1回のキーストロークを保存することは、私にとってトレードオフのように思えます。
Eric Lippert、2014年

@EricLippert:こんにちはエリック、C#の作成者の1人として、JavaやCと同様にC#のオペレーターを維持する動機について教えてください。
Doc Brown

@DocBrown:あなたの答えはC#にもかなり当てはまります。上のコメントを残しました。
エリックリッペルト2014年

2
@DocBrown:x[i++]=0Cでは慣用的ですが、C#およびJavaでは慣用的であることに同意しません。あなたがエンジニアリング/数学ドメインで働いていない限り、そのようなフレーズに遭遇する頻度はどれくらいですか?これは、私が野生でJava / C#コードの1%以下を構成していると思いますか?
MladenJablanović14年

6

私は時々、returnステートメントでpostfixインクリメントを使用します。例えばこれを考慮してください:

//Java
public class ArrayIterator implements Iterator<T> {
   private final T[] array;
   private int index = 0; 
   public ArrayIterator(T ... array) {
      this.array = array;
   }

   public T next {
      if (! hasNext()) throw new NoSuchElementException();
      return array[index++];   
   }
   ...
}

でこれを行うことはできませんindex += 1。したがって、少なくともpostfixバージョンは、時々、いくつかの行を節約できます。

とはいえ、これらのオペレーターは実際には必要なく、人々を混乱させる可能性があります。Scalaがこれらの演算子に反対して決定したことを知っています(あなたは、あなたが提案したように、としか+= あり-=ません)。Javaが同じ方向に(ゆっくりと)発展することを願っています。

ただし、Javaには、「実際には」あまり目にすることのない他の演算子がたくさんあります(使用したことはあります^=>>>=?)。誰も気にしません。


1
^=かなり一般的なものです。ビットフィールドフラグの反転、一時変数なしの2つの整数の交換、および一部のハッシュ/暗号アルゴリズムで使用できます。>>>=ただし、使用したことはありません。(その後、再び、私は使ったことがない>>>か、<<<いずれかの生産プログラムで...)
ブライアン・S

4

私は何年にもわたって多くの言語を構築してきましたが、そのほとんどは特別な目的のためです。何が起こるかというと、あなたは大勢の聴衆を抱えており、彼らは皆期待を抱いています。各機能を検討する際には、機能の利点と期待に違反する可能性のあるコストとを比較検討する必要があります。

私が個人的にバックペダルを踏む必要があったのは整数除算でした。のよう3 / 2 = 1.5 = 1に?さて、Fortranの初期の頃、誰かが-3 / 2 = -1.5 = -1(-2ではなく)決めました。つまり、被除数が負で除数が正の場合、切り捨てられます。これが意味することに注意してください。その場合、残りは負になります。これは、剰余=係数という通常の仮定に違反しています。整数座標でグラフィックを実行している場合、これはあらゆる種類の問題を引き起こす可能性があります。その言語では、整数の除算を常に下方に切り捨てて、ドキュメントでそう言っています。

そして、何が起こったと思いますか?すべてのh ***が緩んだ。言語は整数除算にバグがありました!

古い方法が壊れていると言っても意味がありませんでした。議論するより変更する方が簡単でした。

したがって、C#やJavaなどの言語での質問に答えるには、理由を説明するよりも、++演算子(私が個人的に気に入っている)を含める方が簡単です。


4
別の例として、Dプログラミング言語(多くの点でC ++からの次の進化的ステップです)にはswitchステートメントbreakがあり、言語がフォールスルーを許可しない場合でも、各句に必要なs を保持します。DはC ++のCとの後方互換性を捨てましたが、彼らのポリシーは、Dが保持したC構文は、Cプログラマーをできるだけ驚かせないようにすることです。
Doval 2014年

@Doval:私はCのようなDと呼ばれる言語を構築しましたが、OOと並列処理機能を備えています。それは(幸いにも)どこにも行かず、あなたが話しているDとは関係ありません。Cで人を狂わせる?? 常に書くことによってbreak; case ...;-)
Mike Dunlavey

1
@MikeDunlavey:私の好みは、と言うことであろう/収率double又はfloat(使用double働くであろういずれかの場合には)、および床のための別個の演算子を有し、切り捨て、および分割、ならびに弾性率、残り、および「割り切れるによって「何を行います」 "[後者はブール値を生成します]。の結果int x=y/z;は意図したものと異なる可能性がかなり高いため、式は結果を生成しないはずです。
スーパーキャット2014年

int-divide-by-intを生成するための個別の演算子を使用してintまたは浮動小数点を生成することは、言語では珍しくありません。また、残りの符号(商に影響します)は大きく異なります。言語のリストについては、en.wikipedia.org / wiki / Modulo_operationを参照してください。FORTRANの決定の一部は、疑いなく、彼らが使用したハードウェア(IBM pre-360?)と、署名された被除数や除数での動作に基づいていました。
Phil Perry

私はPascalとPythonの両方が「yielding int」と「yielding floating-point」演算子を分離していることを知っていますが、丸めモードを分離するものは見たことがありません。たとえば3つの数値の平均を計算する(a+b+c)//3必要があり、Python でほとんどの言語で切り捨てられた分割セマンティクスを処理するために不快な混乱として書かれる必要があるように書かれる可能性があるコードは(特に、多くのプロセッサで) 、floored-divide-by-threeは、切り捨てられたものよりも速く実行できます!)
スーパーキャット、2014年

2

うん、それ自体は重要ではありませんが、その唯一の利点は、入力を容易にする構文上の砂糖です。

そもそもCであるのは、Cの基礎となったPDP-11が昔から1ずつインクリメントする特別な指示を持っていたからです。当時、この指示を使用することが重要でした。システムの速度を上げるため、言語機能。

しかし、私は人々がそのコマンドをとても気に入っていると思います。


正確を期すために、PDP-11が前または後のインクリメントの命令を持っていることはそれほど多くはありませんが、それらのアドレス指定モード(およびデクリメント)を持っています。つまり、レジスタを介してメモリにアクセスするすべての命令も同時に登録する前または後のインクリメントまたはデクリメントのいずれかに設定されます。当時のコンパイラテクノロジは、最適化によって自動的にそれを実行することは実際にはできなかったため、明示的なサポートがそれを機能させる唯一の方法でした。
ジュール

2

これは、多くの状況(for引用したループなど)でのCの慣用的な構文であるため、他の人が言ったように、新しい言語としてJavaに移行するのに役立ち、既存のCスタイルのコードを簡単に移植できます。これらの理由により、Javaの初期の段階で新しい言語の採用を加速することは当然の決定でした。プログラマが厳密に必要ではない構文を学習する必要があるという犠牲を払って、よりコンパクトなコードがもたらされるため、今ではあまり意味がありません。

それが好きな人は、それを便利で自然なものだと感じます。それなしでやるのはぎこちないようです。それを使用しても効率は上がりません。読みやすさの問題です。これらの演算子を使用すると、プログラムが読みやすく、保守しやすいと思われる場合は、それらを使用してください。

Cの本来の意味は、単に「加算1」または「減算1」だけではありませんでした。それは単なる構文上の砂糖や小さなパフォーマンスの向上以上のものです。Cの演算子は、「次の項目への増分(または減分)」を意味します。1バイトより大きいものへのポインターがあり、のように次のアドレスに進む*pointer++場合、実際の増分は4、8、または必要なもののいずれかになります。コンパイラーは、データ項目のサイズを追跡し、正しいバイト数をスキップして、作業を代行します。

int k;
int *kpointer = &k;
*kpointer++ = 7;

私のマシンでは、これはkpointerに4を追加しますが、1は追加しません。これはCでの実際の助けであり、エラーを防ぎ、sizeof()の誤った呼び出しを保存します。Javaには同等のポインター演算がないため、これは考慮事項ではありません。そこでは、何よりも表現力についてです。

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