web-dev-qa-db-ja.com

括弧の優先順位が高い場合、インクリメント演算子が最初に解決されるのはなぜですか?

私は単一の行コードを持っています、

_int a = 10;
a = ++a * ( ++a + 5);
_

私の期待される出力は12 * (11 + 5) = 192でしたが、187が得られました。 _()_内のインクリメント演算子が最初に解決されることを私が知っている限り、なぜ外部のものが最初に解決されるのですか?

42
frunkad

式は左から右に評価されます。括弧(および優先順位)はグループ化を表すだけで、評価の順序を表すものではありません。

そう

 11 * (12 + 5)
++a   ++a

等しい

187
99

Eric Lippertのブログからの引用

算術式の評価は、優先ルール、結合ルール、順序ルールの3つのルールセットによって制御されます。

Precedenceルールは、式にさまざまな種類の演算子が混在する場合に、括弧で囲まれた式を括弧で囲む方法を示します。

Associativityルールは、式に同じ種類の演算子の束が含まれている場合に、括弧で囲まれた式を括弧で囲む方法を記述します。

評価の順序ルールは、式の各オペランドが評価される順序を示します。

優先順位が高いと、演算子でオペランドがグループ化され、オペランドの評価を意味しません。式内の部分式の評価の順序を決定するのは、評価の順序です。


更新:

私が見ることができるように、多くのプログラマーはそのステートメントを考えています

a = ++a * ( ++a + 5);  

未定義の動作を呼び出します。はい、演算子のオペランドの評価順序の保証がない場合は、UBを呼び出します。

しかし、これはJavaプログラミング言語のコンテキストでは当てはまりません。これは、Java(およびC#でも)で明確に定義された動作を持っています。)Java言語仕様は次のように述べています:

15.7。評価順序

Javaプログラミング言語は、演算子のオペランドが特定の評価順序、つまり左から右に評価されるように見えることを保証します

例15.7.1-1。左側のオペランドが最初に評価されます

次のプログラムでは、*演算子には、変数への代入を含む左側のオペランドと、同じ変数への参照を含む右側のオペランドがあります。参照によって生成される値は、割り当てが最初に発生したという事実を反映します。

class Test1 {
    public static void main(String[] args) {
        int i = 2;
        int j = (i=3) * i;
        System.out.println(j);
    }
}

このプログラムは出力を生成します:

9

*演算子の評価で9ではなく6を生成することは許可されていません。

しかし、Java仕様には、そのようなコードを記述しないように明記されています。

コードはこの仕様に大きく依存しないことをお勧めします。各式に最も外側の演算として最大で1つの副作用が含まれる場合、および式の左から右への評価の結果として発生する例外にコードが正確に依存しない場合、コードは通常、より明確になります。

30
haccks

そのスニペットから

int a = 10;
a = ++a * ( ++a + 5);

時には、最も簡単な解決策は、javapを使用して評価順序を理解することです。

 0: bipush 10 // Push 10 onto the stack (stack={10})
 2: istore_1  // store 10 into variable 1 (a = 10, stack={})
 3: iinc 1, 1 // increment local variable 1 by 1 (a = 11, stack={})
 6: iload_1   // Push integer in local variable 1 onto the stack (a = 11, stack = {11})
 7: iinc 1, 1 // increment local variable 1 by 1 (a = 12, stack={11})
 10: iload_1  // Push integer in local variable 1 onto the stack (a = 12, stack = {12, 11})
 11: iconst_5 // load the int value 5 onto the stack (a = 12, stack = {5, 12, 11})
 12: iadd     // add 5 and 12 (a = 12, stack = {17, 11})
 13: imul     // multiply 17 and 11 (a = 12, stack = {})
  1. aは1ずつ増加します(3行目)// a = 11
  2. aは1ずつ増加します(7行目)// a = 12
  3. 追加 5 to a(12行目)// a = 17
  4. かける 11から17(13行目)// a = 187

(10 + 1 + 1 + 5)* 11 = 187

括弧の効果は、後続の演算のオペランドとして使用される計算値を制御することです。これらは、オペランドが評価されるまで操作を評価できない範囲でのみ、操作が実行されるシーケンスを制御します。次の式について考えてみます。

(a()+b()) * (c()+d())
a() + (b()*c()) + d()

括弧は、a()、b()、c()、およびJavaの順序に影響を与える必要はありません(およびd()はできません) d()が呼び出される前または後に乗算が実行されるかどうかに影響を与える可能性がありますが、非常にまれな状況(d() Java乗算で使用される数値丸めモードを変更するネイティブインターフェイスJavaは知らない)コードには、乗算は、d()の前または後に実行されました。

それ以外の場合、重要なことは、最初のケースでは、1つの追加演算がa() and b()に作用し、もう1つの演算がc()に作用することです。およびd();乗算はa()+ b()およびc()+ d()に作用します。その他の場合、最初の乗算はb()およびc()、a()および上記の積に対する最初の加算、および最初の合計およびd()に対する2番目の加算。

2
supercat