web-dev-qa-db-ja.com

加算割り当て+ =式の動作

最近、この質問に出会いました: 割り当て演算子チェーンの理解

この質問に答えている間、私は加算代入演算子+=または他のoperator=&=*=/=など)の動作についての自分自身の理解を疑い始めました。

私の質問は、評価中に変更された値が式の他の場所に反映されるように、以下の式の変数aがいつ更新されるのか、そしてその背後にあるロジックは何ですか?次の2つの式を見てください。

式1

a = 1
b = (a += (a += a))
//b = 3 is the result, but if a were updated in place then it should've been 4

式2

a = 1
b = (a += a) + (a += a)
//b = 6 is the result, but if a is not updated in place then it should've been 4

最初の式では、最も内側の式(a += a)が評価されると、aの値が更新されないため、結果は3ではなく4として出力されます。

ただし、2番目の式では、aの値が更新されるため、結果は6です。

いつaの値が式の他の場所に反映されると仮定すべきですか?いつすべきではありませんか?

58
11thdimension

a += xは本当にa = a + xを意味することに注意してください。理解すべき重要な点は、加算が左から右に評価されることです-つまり、a + xaxの前に評価されます。

それでは、b = (a += (a += a))の機能を理解しましょう。最初にa += xa = a + xを意味するルールを使用し、次に正しい順序で式の評価を慎重に開始します。

  • b = (a = a + (a = a + a))a += xa = a + xを意味するため
  • b = (a = 1 + (a = a + a))は、aが現在1であるためです。左の用語aを右の用語(a = a + a)の前に評価することを忘れないでください
  • b = (a = 1 + (a = 1 + a))aはまだ1であるため
  • b = (a = 1 + (a = 1 + 1))aはまだ1であるため
  • b = (a = 1 + (a = 2))1 + 12であるため
  • b = (a = 1 + 2)a2になったため
  • b = (a = 3)1 + 23であるため
  • b = 3a3になったため

これにより、上記の理由でa = 3b = 3が残ります。

他の式b = (a += a) + (a += a)でこれを試してみましょう。

  • b = (a = a + a) + (a = a + a)
  • b = (a = 1 + 1) + (a = a + a)、右の用語の前に左の用語を評価することを忘れないでください
  • b = (a = 2) + (a = a + a)
  • b = 2 + (a = a + a)およびaは2です。正しい用語の評価を開始します
  • b = 2 + (a = 2 + 2)
  • b = 2 + (a = 4)
  • b = 2 + 4およびa4になりました
  • b = 6

これにより、a = 4b = 6が残ります。これは、Java/JavaScriptでabの両方を印刷することで確認できます(両方とも同じ動作をします)。


これらの式を解析ツリーと考えることも役立ちます。 a + (b + c)を評価すると、RHS (b + c)の前にLHS aが評価されます。これはツリー構造でエンコードされます:

   +
  / \
 a   +
    / \
   b   c

括弧はもうないことに注意してください-操作の順序はツリー構造にエンコードされます。ツリー内のノードを評価するとき、ノードの子を固定順序で処理します(つまり、+の左から右へ) )。たとえば、ルートノード+を処理するとき、右サブツリーが括弧で囲まれているかどうかに関係なく、右サブツリー(b + c)の前に左サブツリーaを評価します。解析ツリーにも括弧は存在しません)。

このため、Java/JavaScript donotは、最初に「最もネストされた括弧」を最初に評価します。これは、算術について教えられたルールとは対照的です。

Java言語仕様 を参照してください:

15.7。評価順序

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

15.7.1。最初に左オペランドを評価する

二項演算子の左側のオペランドは、右側のオペランドの一部が評価される前に完全に評価されるように見えます。

演算子が複合代入演算子(§15.26.2)の場合、左側のオペランドの評価には、左側のオペランドが示す変数の記憶と、暗黙のバイナリ演算で使用するためのその変数の値のフェッチと保存の両方が含まれます。

あなたの質問に似た他の例は、次のようなJLSのリンク部分にあります。

例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を生成することは許可されていません。

85
pkpnd

以下は、注意が必要なルールです

  • 演算子の優先順位
  • 変数の割り当て
  • 式の評価

    式1

    a = 1
    b = (a += (a += a))
    
    b = (1 += (a += a))  // a = 1
    b = (1 += (1 += a))  // a = 1
    b = (1 += (1 += 1))  // a = 1
    b = (1 += (2))  // a = 2 (here assignment is -> a = 1 + 1)
    b = (3)  // a = 3 (here assignment is -> a = 1 + 2)
    

    式2

    a = 1
    b = (a += a) + (a += a)
    
    b = (1 += a) + (a += a) // a = 1
    b = (1 += 1) + (a += a) // a = 1
    b = (2) + (a += a) // a = 2 (here assignment is -> a = 1 + 1)
    b = (2) + (2 += a) // a = 2 (here here a = 2)
    b = (2) + (2 += 2) // a = 2
    b = (2) + (4) // a = 4 (here assignment is -> a = 2 + 2)
    b = 6 // a = 4
    

    式3

    a = 1
    b = a += a += a += a += a
    
    b = 1 += 1 += 1 += 1 += 1 // a = 1
    b = 1 += 1 += 1 += 2 // a = 2 (here assignment is -> a = 1 + 1)
    b = 1 += 1 += 3 // a = 3 (here assignment is -> a = 1 + 2)
    b = 1 += 4 // a = 4 (here assignment is -> a = 1 + 3)
    b = 5 // a = 5 (here assignment is -> a = 1 + 4)
    
7
Nikhil Aggarwal

Opsの順序のバリエーションを使用するだけです。

Opsの順序のリマインダーが必要な場合:

PEMDAS:

[〜#〜] p [〜#〜] =括弧

[〜#〜] e [〜#〜] =指数

[〜#〜] md [〜#〜] =乗算/除算

[〜#〜] as [〜#〜] =加算/減算

残りは左から右。

このバリエーションは左から右に読み取られますが、括弧が表示されている場合は、その中のすべてを実行し、定数に置き換えてから先に進みます。

最初の例:

var b = (a+=(a+=a))

var b = (1+=(1+=1))

var b = (1+=2)

_var b = 3_

2番目の例:

var b = (a+=a)+(a+=a)

var b = (1+=1)+(a+=a)

var b = 2 + (2+=2)

_var b = 2 + 4_

_var b = 6_

_var a = 1
var b = (a += (a += a))
console.log(b);

a = 1
b = (a += a) + (a += a)
console.log(b);

a = 1
b = a += a += a;
console.log(b);_

最後の_b = a += a += a_は括弧がないため、自動的に_b = 1 += 1 += 1_になります。これは_b = 3_です。

1
Sheshank S.