私はいくつかのJavaテキストを読んでいて、次のコードを取得しました。
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
本文では、著者は明確な説明をしていませんでした、そして最後の行の効果は次のとおりです: a[1] = 0;
私は理解しているのかよくわかりません:評価はどのように行われたのですか?
私はいくつかのJavaテキストを読んでいて、次のコードを取得しました。
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
本文では、著者は明確な説明をしていませんでした、そして最後の行の効果は次のとおりです: a[1] = 0;
私は理解しているのかよくわかりません:評価はどのように行われたのですか?
回答:
人々はこれを常に誤解しているので、これを非常に明確に言わせてください。
部分式の評価の順序は、結合性と優先順位の両方に依存しません。結合性と優先順位は、演算子が実行される順序を決定しますが、部分式が評価される順序は決定しません。あなたの質問は、部分式が評価される順序についてです。
考えてみてくださいA() + B() + C() * D()
。乗算は加算よりも優先順位が高く、加算は左結合であるため、これは同等です。(A() + B()) + (C() * D())
ただし、最初の加算は2番目の加算の前に行われ、乗算は2番目の加算の前に行われることだけがわかります。A()、B()、C()、D()がどの順序で呼び出されるかはわかりません。(乗算が最初の加算の前に行われるか後に行われるかもわかりません。)これを次のようにコンパイルすることにより、優先順位と結合性の規則に従うことは完全に可能です。
d = D() // these four computations can happen in any order
b = B()
c = C()
a = A()
sum = a + b // these two computations can happen in any order
product = c * d
result = sum + product // this has to happen last
そこでは、優先順位と結合性のすべての規則に従います。最初の加算は2番目の加算の前に行われ、乗算は2番目の加算の前に行われます。明らかに、A()、B()、C()、およびD()の呼び出しを任意の順序で実行でき、それでも優先順位と結合性の規則に従うことができます。
部分式が評価される順序を説明するには、優先順位と結合性の規則とは関係のない規則が必要です。Java(およびC#)の関連ルールは、「部分式は左から右に評価される」です。A()はC()の左側に表示されるため、C()が乗算に関与し、A()が加算にのみ関与するという事実に関係なく、A()が最初に評価されます。
これで、質問に答えるのに十分な情報が得られました。でa[b] = b = 0
、これがあることを連想性を言うのルールa[b] = (b = 0);
が、それがいることを意味するものではありませんb=0
最初に実行されます!優先順位の規則では、インデックス付けは割り当てよりも優先順位が高いとされていますが、それはインデクサーが右端の割り当ての前に実行されることを意味するものではありません。
(更新:この回答の以前のバージョンでは、次のセクションでいくつかの小さくて実質的に重要でない省略があり、修正しました。また、これらのルールがJavaおよびC#で適切である理由を説明するブログ記事をここに書いています:https:// ericlippert.com/2019/01/18/indexer-error-cases/)
優先順位と結合だけであることを教えゼロの割り当てをするb
起こる必要があります前に割り当てa[b]
ゼロの割り当ては、インデックス操作に割り当てられた値を計算するので、。優先順位と結合規則だけではかどうかについては何も言わないa[b]
で評価される前または後にb=0
。
繰り返しますが、これは次の場合とまったく同じA()[B()] = C()
です。-割り当ての前にインデックスを作成する必要があることだけがわかっています。優先順位と結合性に基づいて、A()、B()、またはC()が最初に実行されるかどうかはわかりません。それを伝えるために別のルールが必要です。
繰り返しになりますが、「最初に何をするかを選択できる場合は、常に左から右に移動する」というルールがあります。ただし、この特定のシナリオには興味深いしわがあります。nullコレクションまたは範囲外のインデックスによって引き起こされたスローされた例外の副作用は、割り当ての左側の計算の一部、または割り当て自体の計算の一部と見なされますか?Javaは後者を選択します。(もちろん、これはコードがすでに間違っている場合にのみ問題となる区別です。正しいコードはnullを逆参照したり、最初から悪いインデックスを渡したりしないためです。)
では、どうなるのでしょうか。
a[b]
左にあるb=0
ように、a[b]
ランが最初に得られますa[1]
。ただし、このインデックス作成操作の有効性の確認は遅れます。b=0
起こります。a
有効a[1]
で範囲内にある検証が行われます。a[1]
最後に発生する値の割り当て。したがって、この特定のケースでは、そもそも正しいコードで発生してはならないまれなエラーケースについて考慮すべき微妙な点がいくつかありますが、一般的には、左側のことが右側の前に発生するという理由が考えられます。それがあなたが探しているルールです。優先順位と結合性の話は、混乱を招き、無関係です。
人々はいつもこのことを間違えています、もっとよく知っているべき人々でさえ。私はルールを間違って述べたプログラミング本をあまりにも多く編集したので、多くの人が優先順位/結合性と評価順序の関係について完全に間違った信念を持っているのは当然です-つまり、実際にはそのような関係はありません; それらは独立しています。
このトピックに興味がある場合は、このテーマに関する私の記事を参照してください。
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
それらはC#に関するものですが、これらのほとんどはJavaにも同様に当てはまります。
それにもかかわらず、エリック・リペットの見事な答えは、別の言語について話しているため、適切に役立ちません。これはJavaであり、Java言語仕様がセマンティクスの決定的な記述です。特に、§15.26.1は、=
演算子の評価順序を説明しているため、関連性があります(これは、右結合性であることは誰もが知っていますよね?)。この質問で気になる部分に少し切り詰めます。
左側のオペランド式が配列アクセス式(§15.13)の場合、多くの手順が必要です。
- 最初に、左側のオペランド配列アクセス式の配列参照部分式が評価されます。この評価が突然完了すると、同じ理由で代入式が突然完了します。(左側のオペランド配列アクセス式の)インデックス部分式と右側のオペランドは評価されず、割り当ては行われません。
- それ以外の場合は、左側のオペランド配列アクセス式のインデックス部分式が評価されます。この評価が突然完了すると、同じ理由で割り当て式が突然完了し、右側のオペランドは評価されず、割り当ては発生しません。
- それ以外の場合は、右側のオペランドが評価されます。この評価が突然完了すると、同じ理由で割り当て式が突然完了し、割り当ては発生しません。
[…次に、割り当て自体の実際の意味について説明します。簡潔にするために、ここでは無視できます…]
要するに、Javaには非常に厳密に定義された評価順序があり、演算子またはメソッド呼び出しの引数内でほぼ正確に左から右になります。配列の割り当ては、より複雑なケースの1つですが、それでもL2Rがあります。(JLSは、このような複雑なセマンティック制約を必要とするコードを記述しないことを推奨しています。そのため、ステートメントごとに1つの割り当てだけで十分な問題が発生する可能性があります!)
CとC ++は、この領域でJavaとは明らかに異なります。それらの言語定義では、より多くの最適化を可能にするために、評価順序が意図的に未定義のままになっています。C#は明らかにJavaに似ていますが、正式な定義を示すことができるほどその文献をよく知りません。(これは実際には言語によって異なりますが、Rubyは厳密にL2Rであり、Tclも同様です。ただし、ここでは関係のない理由で代入演算子自体がありません。PythonはL2Rですが、代入に関してはR2Lです。これは奇妙だと思いますが、そこに行きます。 。)
a[-1]=c
、c
が評価され、前に-1
無効として認識されます。
a[b] = b = 0;
1)配列インデックス演算子は代入演算子よりも優先されます(この回答を参照):
(a[b]) = b = 0;
2)15.26による。JLSの代入演算子
12個の代入演算子があります。すべて構文的に右結合性です(右から左にグループ化されます)。したがって、a = b = cはa =(b = c)を意味し、cの値をbに割り当ててから、bの値をaに割り当てます。
(a[b]) = (b=0);
3)15.7による。JLSの評価順序
Javaプログラミング言語は、演算子のオペランドが特定の評価順序、つまり左から右に評価されているように見えることを保証します。
そして
二項演算子の左側のオペランドは、右側のオペランドのいずれかの部分が評価される前に完全に評価されているように見えます。
そう:
a)(a[b])
最初に評価されたa[1]
b)次に(b=0)
評価された0
c)(a[1] = 0)
最後に評価された
あなたのコードは以下と同等です:
int[] a = {4,4};
int b = 1;
c = b;
b = 0;
a[c] = b;
結果を説明します。
以下の別のより詳細な例を検討してください。
http://introcs.cs.princeton.edu/java/11precedence/のように、これらの質問を解決するときに読むことができる優先順位規則と結合性の表を用意しておくことをお勧めします。
これが良い例です:
System.out.println(3+100/10*2-13);
質問:上記の行の出力は何ですか?
回答:優先順位と結合性のルールを適用する
ステップ1:優先順位の規則に従って:/および*演算子は+-演算子よりも優先されます。したがって、この方程式を実行するための開始点は、次のように絞り込まれます。
100/10*2
ステップ2:ルールと優先順位に従って:/と*の優先順位は同じです。
/演算子と*演算子の優先順位は等しいため、これらの演算子間の結合性を調べる必要があります。
これら2つの特定の演算子の結合法則に従って、左から右に方程式の実行を開始します。つまり、100/10が最初に実行されます。
100/10*2
=100/10
=10*2
=20
ステップ3:方程式は次の実行状態になります。
=3+20-13
ルールと優先順位によると、+と-の優先順位は同じです。
ここで、演算子+演算子と-演算子の間の結合性を調べる必要があります。これら2つの特定の演算子の結合性に従って、左から右に方程式の実行を開始します。つまり、3 +20が最初に実行されます。
=3+20
=23
=23-13
=10
10はコンパイル時の正しい出力です
繰り返しになりますが、これらの質問を解決するときは、優先順位規則と結合性の表を用意しておくことが重要です。例:http://introcs.cs.princeton.edu/java/11precedence/
10 - 4 - 3
。
+
単項演算子(右から左への結合性がある)であるという事実が原因である可能性がありますが、加法+と-は乗法* /%のように残っています右の結合性。