Comment fonctionne l'opérateur virgule


175

Comment fonctionne l'opérateur virgule en C ++?

Par exemple, si je fais:

a = b, c;  

Est-ce que a finit par égaler b ou c?

(Oui, je sais que c'est facile à tester - il suffit de documenter ici pour que quelqu'un trouve la réponse rapidement.)

Mise à jour: Cette question a révélé une nuance lors de l'utilisation de l'opérateur virgule. Juste pour documenter ceci:

a = b, c;    // a is set to the value of b!

a = (b, c);  // a is set to the value of c!

Cette question a en fait été inspirée par une faute de frappe dans le code. Ce qui était censé être

a = b;
c = d;

Transformé en

a = b,    //  <-  Note comma typo!
c = d;


1
Double possible de Que fait l'opérateur virgule `,` faire en C? . Il vous a battu d'un jour. Et la réponse de lillq fournit une réponse à la question sur a = (b, c);.
jww

5
Mais dans ce cas, fonctionne-t-il a = b, c = d;réellement comme prévu a = b; c = d;?
Bondolin du

@NargothBond Pas nécessairement. Si bet dsont des évaluations de fonctions qui utilisent (et modifient) un état commun, l'ordre d'exécution n'est défini que C++17.
nyronium le

Réponses:


74

Ce serait égal à b.

L'opérateur virgule a une priorité inférieure à l'affectation.


129

Prenez soin de noter que l'opérateur virgule peut être surchargé en C ++. Le comportement réel peut donc être très différent de celui attendu.

Par exemple, Boost.Spirit utilise assez intelligemment l'opérateur virgule pour implémenter des initialiseurs de liste pour les tables de symboles. Ainsi, cela rend la syntaxe suivante possible et significative:

keywords = "and", "or", "not", "xor";

Notez qu'en raison de la priorité des opérateurs, le code est (intentionnellement!) Identique à

(((keywords = "and"), "or"), "not"), "xor";

Autrement dit, le premier opérateur appelé est keywords.operator =("and")qui renvoie un objet proxy sur lequel les operator,s restants sont invoqués:

keywords.operator =("and").operator ,("or").operator ,("not").operator ,("xor");

Cependant, vous ne pouvez pas changer la priorité, ce qui signifie que vous devriez probablement mettre des parenthèses autour de votre liste.
Jeff Burdges

18
@Jeff Au contraire. Avec une parenthèse autour de la liste, cela ne fonctionnerait pas puisque le compilateur voit juste l'opérateur virgule entre deux char[], qui ne peut pas être surchargé. Le code appelle intentionnellement d' abord le operator=, puis operator,pour chaque élément restant.
Konrad Rudolph

125

L'opérateur virgule a la priorité la plus basse de tous les opérateurs C / C ++. C'est donc toujours le dernier à se lier à une expression, c'est-à-dire ceci:

a = b, c;

est équivalent à:

(a = b), c;

Un autre fait intéressant est que l'opérateur virgule introduit un point de séquence . Cela signifie que l'expression:

a+b, c(), d

est garanti d'avoir ses trois sous-expressions ( a + b , c () et d ) évaluées dans l'ordre. Ceci est important s'ils ont des effets secondaires. Normalement, les compilateurs sont autorisés à évaluer les sous-expressions dans l'ordre de leur choix; par exemple, dans un appel de fonction:

someFunc(arg1, arg2, arg3)

les arguments peuvent être évalués dans un ordre arbitraire. Notez que les virgules dans l'appel de fonction ne sont pas des opérateurs; ce sont des séparateurs.


15
Il vaut la peine de souligner qu'il ,a une priorité si faible, il est même en retard sur lui-même ;) ... C'est-à-dire: l' opérateur virgule-comme- a une priorité inférieure à la virgule-comme- séparateur . Donc, si vous souhaitez utiliser une virgule comme opérateur dans un seul argument de fonction, une affectation de variable ou une autre liste séparée par des virgules, vous devez utiliser des parenthèses, par exemple:int a = 1, b = 2, weirdVariable = (++a, b), d = 4;
underscore_d

68

L'opérateur virgule:

  • a la priorité la plus basse
  • est associatif à gauche

Une version par défaut de l'opérateur virgule est définie pour tous les types (intégrés et personnalisés) et fonctionne comme suit - étant donné exprA , exprB:

  • exprA est évalué
  • le résultat de exprAest ignoré
  • exprB est évalué
  • le résultat de exprBest renvoyé comme le résultat de l'expression entière

Avec la plupart des opérateurs, le compilateur est autorisé à choisir l'ordre d'exécution et il est même nécessaire de sauter l'exécution si cela n'affecte pas le résultat final (par exemple, false && foo()il sautera l'appel à foo). Ce n'est cependant pas le cas pour l'opérateur virgule et les étapes ci-dessus se produiront toujours * .

En pratique, l'opérateur virgule par défaut fonctionne presque de la même manière qu'un point-virgule. La différence est que deux expressions séparées par un point-virgule forment deux instructions distinctes, tandis que la séparation par virgule conserve toutes comme une seule expression. C'est pourquoi l'opérateur virgule est parfois utilisé dans les scénarios suivants:

  • La syntaxe C nécessite une seule expression , pas une instruction. par exemple dansif( HERE )
  • La syntaxe C nécessite une seule instruction, pas plus, par exemple lors de l'initialisation de la forbouclefor ( HERE ; ; )
  • Lorsque vous voulez sauter les accolades et garder une seule déclaration: if (foo) HERE ;(s'il vous plaît ne faites pas ça, c'est vraiment moche!)

Lorsqu'une instruction n'est pas une expression, le point-virgule ne peut pas être remplacé par une virgule. Par exemple, ceux-ci ne sont pas autorisés:

  • (foo, if (foo) bar)( ifn'est pas une expression)
  • int x, int y (la déclaration de variable n'est pas une expression)

Dans votre cas, nous avons:

  • a=b, c;, équivalent à a=b; c;, en supposant qu'il aest d'un type qui ne surcharge pas l'opérateur virgule.
  • a = b, c = d;équivalent à a=b; c=d;, en supposant qu'il aest d'un type qui ne surcharge pas l'opérateur virgule.

Notez que toutes les virgules ne sont pas en fait un opérateur virgule. Quelques virgules qui ont une signification complètement différente:

  • int a, b; --- La liste des déclarations de variables est séparée par des virgules, mais ce ne sont pas des opérateurs par virgule
  • int a=5, b=3; --- c'est aussi une liste de déclarations de variables séparées par des virgules
  • foo(x,y)--- Liste d'arguments séparés par des virgules. En fait, xet ypeut être évalué dans n'importe quel ordre!
  • FOO(x,y) --- Liste d'arguments de macro séparés par des virgules
  • foo<a,b> --- Liste d'arguments de modèle séparés par des virgules
  • int foo(int a, int b) --- Liste de paramètres séparés par des virgules
  • Foo::Foo() : a(5), b(3) {} --- Liste d'initialiseurs séparés par des virgules dans un constructeur de classe

* Ce n'est pas entièrement vrai si vous appliquez des optimisations. Si le compilateur reconnaît que certains morceaux de code n'ont absolument aucun impact sur le reste, il supprimera les instructions inutiles.

Lectures complémentaires: http://en.wikipedia.org/wiki/Comma_operator


Est-il intéressant de noter que si le operator ,est surchargé, vous perdez toutes les garanties d'associativité (tout comme vous perdez les propriétés de court-circuit du operator&&et operator||s'ils sont surchargés)?
YoungJohn

L'opérateur virgule est associatif à gauche, qu'il soit surchargé ou non. Une expression a, b, csignifie toujours (a, b), cet jamais a, (b, c). Cette dernière interprétation pourrait même conduire à une erreur de compilation si les éléments sont de types différents. Qu'est-ce que vous pouvez être après est l'ordre d'évaluation des arguments? Je ne suis pas sûr de cela, mais peut-être avez-vous raison: il peut arriver que ce csoit évalué avant (a, b) même si la virgule est associative à gauche.
CygnusX1

1
Juste un petit commentaire sur la liste d'initialisation séparée par des virgules dans un constructeur de classe, l'ordre n'est pas déterminé par la position dans la liste. L'ordre est déterminé par la position de déclaration de la classe. Par exemple, struct Foo { Foo() : a(5), b(3) {} int b; int a; }évite b(3)avant a(5). Ceci est important si votre liste est comme ceci: Foo() : a(5), b(a) {}. b ne sera pas défini sur 5, mais plutôt sur la valeur non initialisée de a, que votre compilateur peut ou non avertir.
Jonathan Gawrych

Je suis récemment tombé sur un opérateur virgule avec deux flotteurs, quel est l'intérêt d'évaluer et de supprimer un nombre?
Aaron Franke

Je ne pense pas que quiconque puisse répondre à cela. Il faudrait le montrer dans un contexte. Probablement une question distincte?
CygnusX1

38

La valeur de asera b, mais la valeur de l'expression sera c. C'est dedans

d = (a = b, c);

a serait égal à bet dserait égal à c.


19
Presque correct. Les déclarations n'ont pas de valeurs, les expressions en ont. La valeur de cette expression est c.
Leon Timmermans

Pourquoi est-ce utilisé à la place de a = b; d = c;?
Aaron Franke

Cela m'a fait comprendre de quels effets secondaires les gens parlaient.
lacets le

8

La valeur de b sera attribuée à a. Rien n'arrivera à c


2

La valeur de a sera égale à b, car l'opérateur virgule a une priorité inférieure à l'opérateur d'affectation.


2

Oui L'opérateur virgule a une priorité inférieure à l'opérateur d'affectation

#include<stdio.h>
int main()
{
          int i;
          i = (1,2,3);
          printf("i:%d\n",i);
          return 0;
}

Sortie: i = 3
Parce que l'opérateur virgule renvoie toujours la valeur la plus à droite.
En cas d'opérateur virgule avec opérateur d'affectation:

 int main()
{
      int i;
      i = 1,2,3;
      printf("i:%d\n",i);
      return 0;
}

Ouput: i = 1
Comme nous le savons, l'opérateur virgule a une priorité inférieure à l'affectation .....


Alors, en quoi le deuxième exemple est-il différent du simple fait d'avoir i = 1;sur cette ligne?
Aaron Franke

-3

Tout d'abord: la virgule n'est en fait pas un opérateur, pour le compilateur c'est juste un jeton qui prend une signification en contexte avec d'autres jetons.

Qu'est-ce que cela signifie et pourquoi s'embêter?

Exemple 1:

Pour comprendre la différence entre la signification du même jeton dans un contexte différent, nous examinons cet exemple:

class Example {
   Foo<int, char*> ContentA;
}

Habituellement, un débutant en C ++ penserait que cette expression pourrait / voudrait comparer les choses, mais c'est absolument faux, la signification du <, >et, jetons dépendent du contexte d'utilisation.

L'interprétation correcte de l'exemple ci-dessus est bien sûr qu'il s'agit d'une instatiation d'un modèle.

Exemple 2:

Lorsque nous écrivons une boucle typiquement for avec plus d'une variable d'initialisation et / ou plusieurs expressions qui devraient être faites après chaque itération de la boucle, nous utilisons également la virgule:

for(a=5,b=0;a<42;a++,b--)
   ...

La signification de la virgule dépend du contexte d'utilisation, ici c'est le contexte du for construction.

Que signifie réellement une virgule dans le contexte?

Pour le compliquer encore plus (comme toujours en C ++), l'opérateur virgule peut lui-même être surchargé (grâce à Konrad Rudolph pour l'avoir signalé).

Pour revenir à la question, le Code

a = b, c;

signifie pour le compilateur quelque chose comme

(a = b), c;

car la priorité du =jeton / opérateur est supérieure à la priorité du ,jeton.

et ceci est interprété dans un contexte comme

a = b;
c;

(notez que l'interprétation dépend du contexte, ici ce n'est ni un appel de fonction / méthode ni une instatiation de modèle.)


1
Bien sûr, j'ai peut-être utilisé la mauvaise terminologie (pour le lexer, c'est un jeton, bien sûr)
Quonux

2
Comme on travaille avec opérateur, (sic), la virgule est bien un opérateur.
DragonLord

2
Bien que reconnaître si un jeton virgule donné est reconnu comme opérateur virgule (par opposition, par exemple, au séparateur d'arguments) peut être un défi en soi, cette question concerne spécifiquement l' opérateur virgule .
CygnusX1
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.