59 votes

Comportement de l'affectation de l'addition += dans l'expression

Récemment, je suis tombé sur cette question : Compréhension de la chaîne des opérateurs d'affectation .

En répondant à cette question, j'ai commencé à douter de ma propre compréhension du comportement de l'opérateur d'affectation d'addition. += ou tout autre operator= ( &= , *= , /= etc.).

Ma question est la suivante : quand la variable a dans les expressions ci-dessous mis à jour en place, de sorte que sa valeur modifiée est reflétée dans d'autres endroits dans l'expression pendant l'évaluation, et quelle est la logique derrière cela ? Veuillez examiner les deux expressions suivantes :

Expression 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

Expression 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

Dans la première expression, lorsque l'expression la plus interne (a += a) est évaluée, il semble qu'elle ne mette pas à jour la valeur de l'élément a Le résultat est donc le suivant 3 au lieu de 4 .

Cependant, dans la deuxième expression, la valeur de a est mis à jour et le résultat est donc 6.

Quand devons-nous supposer que a La valeur de l'entreprise sera reflétée à d'autres endroits de l'expression et quand ne le ferions-nous pas ?

19 votes

Maintenant que la poussière est retombée, ne faites jamais cela en production !

13 votes

Est-ce que c'est Java ou JavaScript ?

0 votes

@muru Soit, le comportement est le même.

87voto

pkpnd Points 5044

Rappelez-vous que a += x signifie vraiment a = a + x . Le point clé à comprendre est que l'addition est évaluée de gauche à droite -- c'est-à-dire le a sur a + x est évalué avant x .

Alors trouvons ce que b = (a += (a += a)) fait. Nous utilisons d'abord la règle a += x signifie a = a + x puis nous commençons à évaluer l'expression avec soin dans l'ordre correct :

  • b = (a = a + (a = a + a)) parce que a += x signifie a = a + x
  • b = (a = 1 + (a = a + a)) parce que a est actuellement 1 . Rappelez-vous que nous évaluons le terme de gauche a avant le bon terme (a = a + a)
  • b = (a = 1 + (a = 1 + a)) parce que a est toujours 1
  • b = (a = 1 + (a = 1 + 1)) parce que a est toujours 1
  • b = (a = 1 + (a = 2)) parce que 1 + 1 es 2
  • b = (a = 1 + 2) parce que a est maintenant 2
  • b = (a = 3) parce que 1 + 2 es 3
  • b = 3 parce que a est maintenant 3

Cela nous laisse avec a = 3 y b = 3 comme indiqué ci-dessus.

Essayons avec l'autre expression, b = (a += a) + (a += a) :

  • b = (a = a + a) + (a = a + a)
  • b = (a = 1 + 1) + (a = a + a) rappelons que nous évaluons le terme de gauche avant celui de droite.
  • b = (a = 2) + (a = a + a)
  • b = 2 + (a = a + a) y a est maintenant 2. Commencez à évaluer le terme de droite
  • b = 2 + (a = 2 + 2)
  • b = 2 + (a = 4)
  • b = 2 + 4 y a est maintenant 4
  • b = 6

Cela nous laisse avec a = 4 y b = 6 . Cela peut être vérifié en imprimant les deux a y b en Java/JavaScript (les deux ont le même comportement ici).


Il peut également être utile de considérer ces expressions comme des arbres d'analyse. Lorsque nous évaluons a + (b + c) le LHS a est évalué avant le RHS (b + c) . Ceci est codé dans l'arborescence :

   +
  / \
 a   +
    / \
   b   c

Notez que nous n'avons plus de parenthèses -- l'ordre des opérations est encodé dans l'arborescence. Lorsque nous évaluons les nœuds de l'arbre, nous traitons les enfants du nœud dans une séquence ordre fixe (c'est-à-dire, de gauche à droite pour les + ). Par exemple, lorsque nous traitons le nœud racine + nous évaluons le sous-arbre de gauche a avant le sous-arbre de droite (b + c) indépendamment du fait que le sous-arbre de droite soit entre parenthèses ou non (puisque les parenthèses ne sont même pas présentes dans l'arbre d'analyse).

Pour cette raison, Java/JavaScript ne pas évaluent toujours les "parenthèses les plus imbriquées" en premier, contrairement aux règles que l'on vous a peut-être enseignées en arithmétique.

Voir le Spécification du langage Java :

15.7. Ordre d'évaluation

Le langage de programmation Java garantit que les opérandes des opérateurs sont évalués dans un format spécifique. commande d'évaluation à savoir, de gauche à droite.
...

15.7.1. Évaluer l'opérateur de gauche en premier

L'opérande de gauche d'un opérateur binaire semble être entièrement évalué avant que toute partie de l'opérande de droite ne le soit.

Si l'opérateur est un opérateur d'affectation composé (§15.26.2), l'évaluation de l'opérande de gauche comprend à la fois la mémorisation de la variable que l'opérande de gauche désigne et l'extraction et la sauvegarde de la valeur de cette variable pour l'utiliser dans l'opération binaire implicite.

D'autres exemples similaires à votre question peuvent être trouvés dans la partie liée du JLS, comme par exemple :

Exemple 15.7.1-1. L'opérateur de gauche est évalué en premier

Dans le programme suivant, l'opérateur * a un opérande de gauche qui qui contient une affectation à une variable et un opérande de droite qui contient une référence à la même variable. La valeur produite par l'opérateur reflétera le fait que l'affectation a eu lieu en premier.

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

Ce programme produit la sortie :

9

Il n'est pas permis que l'évaluation de l'opérateur * produise 6 au lieu de 9.

1 votes

Je ne comprends pas le raisonnement derrière l'évaluation de gauche à droite en présence d'un opérateur de plus haute priorité, à savoir () De plus, la première expression peut être écrite simplement comme suit b = a += a += a qui doit être évalué de droite à gauche, en raison de l'associativité de droite de l'opérateur d'affectation.

2 votes

@11thdimension C'est comme ça que la langue fonctionne. Supposons que nous voulions évaluer a + (b + c) . Le LHS a est évalué en premier, que le RHS soit entre parenthèses ou non. Ensuite, le RHS (b + c) est évaluée. Cela peut aller à l'encontre des "règles" de l'arithmétique selon lesquelles nous évaluons d'abord les parenthèses les plus "intérieures". Au lieu de cela, il est utile de considérer ces calculs comme un arbre d'analyse, ce qui élimine toutes les parenthèses et intègre l'ordre des opérations dans la structure de l'arbre.

0 votes

Je ne pense pas que dans a + (b + c) l'évaluation commencerait de gauche à droite, comme la notation postfixe serait abc++ ce qui signifie b+c serait évalué en premier.

7voto

Nikhil Aggarwal Points 21966

Les règles à respecter sont les suivantes

  • Préséance des opérateurs
  • Affectation des variables
  • évaluation de l'expression

    Expression 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)

    Expression 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

    Expression 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)

2 votes

Ça n'a vraiment aucun sens d'écrire 1 += 2 ou quelque chose de similaire, puisque 1 n'est pas une variable qui peut être assignée.

0 votes

@pkpnd - Vrai. Toutefois, ce qui précède est destiné à faciliter la compréhension.

6 votes

Pour moi, cela rend votre réponse moins compréhensible parce que ce que vous voulez dire n'est pas évident. 1 += 2 (puisque ce n'est pas un code valide).

1voto

Sheshank S. Points 1939

Il utilise juste une variation de l'ordre des opérations.

Si vous avez besoin d'un rappel de l'ordre des opérations :

PEMDAS :

P \= Parenthèse

E \= Exponents

MD \= Multiplication/Division

AS \= Addition/Soustraction

Les autres, de gauche à droite.

Cette variation se lit de gauche à droite, mais si vous voyez une parenthèse, faites tout ce qu'elle contient, remplacez-la par une constante et continuez.

Premier ex :

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

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

var b = (1+=2)

var b = 3

Deuxième ex :

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);

Le dernier b = a += a += a puisqu'il n'y a pas de parenthèses, il devient automatiquement b = 1 += 1 += 1 qui est b = 3

0 votes

Dans la première expression, vous avez remplacé la valeur de a par 1 dans toutes les occurrences en même temps, mais dans la deuxième expression, c'est fait un par un et chaque fois la dernière valeur calculée est utilisée, c'est la question : quelle est la règle pour faire cela ?

0 votes

@11thdimension Dans la première expression, j'ai remplacé toutes les valeurs de a par 1 dans la première série de parenthèses.

1 votes

@11ththdimension Fondamentalement, chaque fois que le code va à un point avec "a", il le remplace par la valeur actuelle de a. Avant qu'il n'atteigne l'endroit suivant dans les cas avec parenthèses, a change donc quand le code atteint a est différent. Est-ce que cela a un sens ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X