私はOCPJP試験のために勉強しているので、Javaの少し奇妙な詳細をすべて理解する必要があります。これには、インクリメント前およびインクリメント後の演算子が変数に適用される順序が含まれます。次のコードは私に奇妙な結果を与えています:
int a = 3;
a = (a++) * (a++);
System.out.println(a); // 12
答えは11であるべきではありませんか?または多分13?しかし、12ではありません!
フォローアップ:
次のコードの結果は何ですか?
int a = 3;
a += (a++) * (a++);
System.out.println(a);
後最初のa++
a
は4になります。つまり、3 * 4 = 12
になります。
(a
は2番目のa++
の後に5になりますが、割り当てa =
がオーバーライドするため、破棄されます)
あなたの声明:
a += (a++) * (a++);
それらのいずれかと同等です:
a = a*a + 2*a
a = a*(a+2)
a += a*(a+1)
代わりにそれらのいずれかを使用してください。
a++
は 'aの値を意味し、aは1ずつ増加します'。だからあなたが走るとき
(a++) * (a++)
最初 a++
が最初に評価され、値3が生成されます。次にa
が1ずつインクリメントされます。2番目のa++
が評価されます。 a
は4の値を生成し、その後再びインクリメントされます(ただし、これは今は問題ではありません)
だからこれは
a = 3 * 4
これは12に相当します。
int a = 3;
a += (a++) * (a++);
最初に構文ツリーを構築します。
+=
a
*
a++
a++
それを評価するには、最も外側の要素から始めて、再帰的に降下します。各要素について、次のことを行います。
+=
演算子は特別です。left = left + right
のようなものに展開されますが、式left
を1回だけ評価します。それでも、右側が値に評価される前に、左側が(変数だけでなく)値に評価されます。
これはにつながります:
+=
の評価を開始しますa
への代入の左側を評価します。a
を追加で使用される値3
に評価します。*
の評価を開始しますa++
を評価します。これにより、3
の現在の値が返され、a
が4
に設定されます。a++
を評価します。これにより、4
の現在の値が返され、a
が5
に設定されます。+=
を実行します。左側は3番目のステップで3
と評価され、右側は12
です。したがって、3 + 12 = 15をa
に割り当てます。a
の最終値は15です。ここで注意すべきことの1つは、演算子の優先順位は評価の順序に直接影響しないということです。それは木の形にのみ影響し、したがって間接的に順序に影響します。ただし、ツリー内の兄弟の間では、演算子の優先順位に関係なく、評価は常に左から右になります。
(a++)
はポストインクリメントであるため、式の値は3です。
(a++)
はポストインクリメントであるため、式の値は4になります。
式の評価は左から右に行われています。
3 * 4 = 12
A ++を使用するたびに、aをポストインクリメントします。つまり、最初のa ++は3と評価され、2番目は4と評価されます。3* 4 = 12。
オペレーターがどのように機能するかについての一般的な理解の欠如があります。正直なところ、すべての演算子は糖衣構文です。
あなたがしなければならないのは、すべてのオペレーターの背後で実際に何が起こっているのかを理解することです。次のように想定します。
a = b -> Operators.set(a, b) //don't forget this returns b
a + b -> Operators.add(a, b)
a - b -> Operators.subtract(a, b)
a * b -> Operators.multiply(a, b)
a / b -> Operators.divide(a, b)
次に、これらの一般化を使用して複合演算子を書き直すことができます(簡単にするために戻り値の型は無視してください)。
Operators.addTo(a, b) { //a += b
return Operators.set(a, Operators.add(a, b));
}
Operators.preIncrement(a) { //++a
return Operators.addTo(a, 1);
}
Operators.postIncrement(a) { //a++
Operators.set(b, a);
Operators.addTo(a, 1);
return b;
}
あなたはあなたの例を書き直すことができます:
int a = 3;
a = (a++) * (a++);
なので
Operators.set(a, 3)
Operators.set(a, Operators.multiply(Operators.postIncrement(a), Operators.postIncrement(a)));
これは、複数の変数を使用して分割できます。
Operators.set(a, 3)
Operators.set(b, Operators.postIncrement(a))
Operators.set(c, Operators.postIncrement(a))
Operators.set(a, Operators.multiply(b, c))
その方が確かに冗長ですが、1行で3つ以上の操作を実行したくないことがすぐに明らかになります。
の場合には :
int a = 3;
a = (a++) * (a++);
a = 3 * a++; now a is 4 because of post increment
a = 3 * 4; now a is 5 because of second post increment
a = 12; value of 5 is overwritten with 3*4 i.e. 12
の場合には :
a += (a++) * (a++);
a = a + (a++) * (a++);
a = 3 + (a++) * (a++); // a is 3
a = 3 + 3 * (a++); //a is 4
a = 3 + 3 * 4; //a is 5
a = 15
ここで注意すべき重要な点は、この場合、コンパイラは左から右に解決し、ポストインクリメントの場合、インクリメント前の値が計算に使用され、左から右に移動するとインクリメントされた値が使用されることです。
(a++)
は、a
を返し、インクリメントすることを意味します。したがって、(a++) * (a++)
は3 * 4を意味します
これがJavaコード:
int a = 3;
a = (a++)*(a++);
バイトコードは次のとおりです。
0 iconst_3
1 istore_1 [a]
2 iload_1 [a]
3 iinc 1 1 [a]
6 iload_1 [a]
7 iinc 1 1 [a]
10 imul
11 istore_1 [a]
これが何が起こるかです:
3をスタックにプッシュし、スタックから3をポップして、に格納します。これでa = 3になり、スタックは空になります。
0 iconst_3
1 istore_1 a
ここで、値を「a」(3)からスタックにプッシュしてから、a(3-> 4)をインクリメントします。
2 iload_1 [a]
3 iinc 1 1 [a]
したがって、「a」は「4」に等しく、スタックは{3}に等しくなります。
次に、「a」を再度ロードし(4)、スタックにプッシュして「a」をインクリメントします。
6 iload_1 [a]
7 iinc 1 1 [a]
これで、「a」は5に等しく、スタックは{4,3}に等しくなります。
したがって、最終的にスタックから最初の2つの値(4と3)をポップし、乗算してスタックに戻します(12)。
10 imul
ここで、「a」は5に等しく、スタックは12に等しくなります。
最後に、スタックからポップ12があり、に格納されます。
11 istore_1 [a]
多田!
12です。式は左から評価を開始します。だからそれはします:
a = (3++) * (4++);
最初の部分(3 ++)が評価されると、aは4になるため、次の部分ではa = 3 * 4 = 12になります。最後のポストインクリメント(4 ++)は実行されますが、効果はありません。この後、aに値12が割り当てられるためです。
例1
int a = 3;
a = (++a) * (a++);
System.out.println(a); // 16
例2
int a = 3;
a = (++a) * (++a);
System.out.println(a); // 20
場所に基づいて値を変更する++
式をどこに配置するかを確認するだけです。
誰もが最初の式と、aの値が12である理由を明確に説明しています。
次の質問の場合、答えはカジュアルな観察者には完全に明白です。
17
次回使用するときにa ++を使用すると、1ずつ増加します。だからあなたのやって
a = 3 * (3 + 1) = 3 * 4 = 12
プレフィックスの前後の増分は、乗算演算子よりも優先されます。したがって、式は3 * 4として評価されます。