Que fait l'opérateur virgule?

167

Que fait l' ,opérateur en C?

Lillq
la source
1
Comme je le note dans ma réponse, il y a un point de séquence après l'évaluation de l'opérande gauche. Ceci est différent de la virgule dans un appel de fonction qui est juste grammatical.
Shafik Yaghmour
2
@SergeyK. - Étant donné que cela a été posé et répondu des années avant l'autre, il est plus probable que l'autre soit un double de cette question. Cependant, l'autre est également balisé avec c et c ++ , ce qui est une nuisance. Ceci est un Q&A C-only, avec des réponses décentes.
Jonathan Leffler

Réponses:

129

L'expression:

(expression1,  expression2)

La première expression1 est évaluée, puis expression2 est évaluée et la valeur de expression2 est renvoyée pour l'expression entière.

Lillq
la source
3
alors si j'écris i = (5,4,3,2,1,0) alors idéalement il devrait retourner 0, correct? mais je reçois une valeur de 5? Pouvez-vous s'il vous plaît m'aider à comprendre où je vais mal?
Jayesh
19
@James: La valeur d'une opération virgule sera toujours la valeur de la dernière expression. A aucun moment n'aura iles valeurs 5, 4, 3, 2 ou 1. C'est simplement 0. C'est pratiquement inutile à moins que les expressions aient des effets secondaires.
Jeff Mercado
6
Notez qu'il y a un point de séquence complet entre l'évaluation de la LHS de l'expression de virgule et l'évaluation de la RHS (voir la réponse de Shafik Yaghmour pour une citation de la norme C99). C'est une propriété importante de l'opérateur virgule.
Jonathan Leffler
5
i = b, c;équivaut à (i = b), cparce que l'attribution =a une priorité plus élevée que l'opérateur virgule ,. L'opérateur virgule a la priorité la plus basse de tous.
cyclaminist le
1
Je crains que les parenthèses soient trompeuses à deux égards: (1) elles ne sont pas nécessaires - l'opérateur virgule n'a pas à être entouré de parenthèses; et (2) ils pourraient être confondus avec les parenthèses autour de la liste d'arguments d'un appel de fonction - mais la virgule dans la liste d'arguments n'est pas l'opérateur virgule. Cependant, le réparer n'est pas entièrement anodin. Peut-être: Dans la déclaration: d' expression1, expression2;abord expression1est évalué, probablement pour ses effets secondaires (comme l'appel d'une fonction), puis il y a un point de séquence, puis expression2est évalué et la valeur est renvoyée…
Jonathan Leffler
119

J'ai vu le plus utilisé dans les whileboucles:

string s;
while(read_string(s), s.len() > 5)
{
   //do something
}

Il fera l'opération, puis fera un test basé sur un effet secondaire. L'autre façon serait de le faire comme ceci:

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}
crashmstr
la source
21
Hé, c'est chouette! J'ai souvent dû faire des choses peu orthodoxes en boucle pour résoudre ce problème.
staticsan
6
Bien qu'il serait probablement moins obscure et plus lisible si vous avez fait quelque chose comme: while (read_string(s) && s.len() > 5). De toute évidence, cela ne fonctionnerait pas si read_stringn'a pas de valeur de retour (ou n'a pas de valeur significative). (Edit: Désolé,
je
11
@staticsan N'ayez pas peur de l'utiliser while (1)avec une break;déclaration dans le corps. Essayer de forcer la partie break-out du code dans le test while ou dans le test do-while, est souvent un gaspillage d'énergie et rend le code plus difficile à comprendre.
potrzebie
8
@jamesdlin ... et les gens le lisent encore. Si vous avez quelque chose d'utile à dire, dites-le. Les forums ont des problèmes avec les threads ressuscités, car les threads sont généralement triés par date du dernier message. StackOverflow n'a pas de tels problèmes.
Dimitar Slavchev
3
@potrzebie J'aime bien mieux l'approche par virgule que while(1)et break;
Michael
39

L' opérateur virgule évaluera l'opérande gauche, rejettera le résultat, puis évaluera l'opérande droit et ce sera le résultat. L' utilisation idiomatique comme indiqué dans le lien est lors de l'initialisation des variables utilisées dans une forboucle, et il donne l'exemple suivant:

void rev(char *s, size_t len)
{
  char *first;
  for ( first = s, s += len - 1; s >= first; --s)
      /*^^^^^^^^^^^^^^^^^^^^^^^*/ 
      putchar(*s);
}

Sinon, il n'y a pas beaucoup de bonnes utilisations de l' opérateur virgule , bien qu'il soit facile d'abuser de générer du code difficile à lire et à maintenir.

À partir du projet de norme C99, la grammaire est la suivante:

expression:
  assignment-expression
  expression , assignment-expression

et le paragraphe 2 dit:

L' opérande gauche d'un opérateur virgule est évalué comme une expression vide; il y a un point de séquence après son évaluation. Ensuite, l' opérande droit est évalué; le résultat a son type et sa valeur. 97) Si une tentative est faite pour modifier le résultat d'un opérateur virgule ou pour y accéder après le point de séquence suivant, le comportement n'est pas défini.

La note de bas de page 97 dit:

Un opérateur virgule ne produit pas de lvalue .

ce qui signifie que vous ne pouvez pas affecter le résultat de l' opérateur virgule .

Il est important de noter que l'opérateur virgule a la priorité la plus basse et qu'il existe donc des cas où l'utilisation ()peut faire une grande différence, par exemple:

#include <stdio.h>

int main()
{
    int x, y ;

    x = 1, 2 ;
    y = (3,4) ;

    printf( "%d %d\n", x, y ) ;
}

aura la sortie suivante:

1 4
Shafik Yaghmour
la source
28

L'opérateur virgule combine les deux expressions de chaque côté en une seule, en les évaluant toutes les deux dans l'ordre de gauche à droite. La valeur du côté droit est renvoyée comme valeur de l'expression entière. (expr1, expr2)est similaire { expr1; expr2; }mais vous pouvez utiliser le résultat de expr2dans un appel ou une affectation de fonction.

On le voit souvent dans des forboucles pour initialiser ou maintenir plusieurs variables comme ceci:

for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh)
{
    /* do something with low and high and put new values
       in newlow and newhigh */
}

En dehors de cela, je ne l'ai utilisé «en colère» que dans un autre cas, lors de la conclusion de deux opérations qui devraient toujours aller ensemble dans une macro. Nous avions du code qui copiait diverses valeurs binaires dans un tampon d'octets pour l'envoi sur un réseau, et un pointeur maintenu là où nous en étions:

unsigned char outbuff[BUFFSIZE];
unsigned char *ptr = outbuff;

*ptr++ = first_byte_value;
*ptr++ = second_byte_value;

send_buff(outbuff, (int)(ptr - outbuff));

Là où les valeurs étaient shorts ou ints, nous avons fait ceci:

*((short *)ptr)++ = short_value;
*((int *)ptr)++ = int_value;

Plus tard, nous avons lu que ce n'était pas vraiment un C valide, car ce (short *)ptrn'est plus une valeur l et ne peut pas être incrémenté, bien que notre compilateur à l'époque ne s'en soit pas soucié. Pour résoudre ce problème, nous avons divisé l'expression en deux:

*(short *)ptr = short_value;
ptr += sizeof(short);

Cependant, cette approche reposait sur le fait que tous les développeurs se souvenaient de mettre les deux déclarations tout le temps. Nous voulions une fonction où vous pourriez passer le pointeur de sortie, la valeur et et le type de la valeur. Ceci étant C, pas C ++ avec des modèles, nous ne pouvions pas avoir une fonction prendre un type arbitraire, nous nous sommes donc installés sur une macro:

#define ASSIGN_INCR(p, val, type)  ((*((type) *)(p) = (val)), (p) += sizeof(type))

En utilisant l'opérateur virgule, nous avons pu l'utiliser dans des expressions ou comme instructions comme nous le souhaitions:

if (need_to_output_short)
    ASSIGN_INCR(ptr, short_value, short);

latest_pos = ASSIGN_INCR(ptr, int_value, int);

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

Je ne dis pas qu'aucun de ces exemples n'est de bon style! En effet, il me semble me souvenir de Code Complete de Steve McConnell déconseillant même d'utiliser des opérateurs de virgule dans une forboucle: pour la lisibilité et la maintenabilité, la boucle doit être contrôlée par une seule variable, et les expressions dans la forligne elle-même ne doivent contenir que du code de contrôle de boucle, pas d'autres bits supplémentaires d'initialisation ou de maintenance de boucle.

Paul Stephenson
la source
Merci! C'était ma première réponse sur StackOverflow: depuis, j'ai peut-être appris que la concision doit être appréciée :-).
Paul Stephenson
Parfois j'apprécie un peu de verbosité comme c'est le cas ici où vous décrivez l'évolution d'une solution (comment vous y êtes arrivé).
diode verte du
8

Cela provoque l'évaluation de plusieurs déclarations, mais n'utilise que la dernière comme valeur résultante (rvalue, je pense).

Alors...

int f() { return 7; }
int g() { return 8; }

int x = (printf("assigning x"), f(), g() );

devrait aboutir à la mise à 8 de x.

Ben Collins
la source
3

Comme les réponses précédentes l'ont indiqué, il évalue toutes les déclarations mais utilise la dernière comme valeur de l'expression. Personnellement, je ne l'ai trouvé utile que dans les expressions de boucle:

for (tmp=0, i = MAX; i > 0; i--)
DGentry
la source
2

Le seul endroit où je l'ai vu être utile est lorsque vous écrivez une boucle funky où vous voulez faire plusieurs choses dans l'une des expressions (probablement l'expression init ou l'expression de la boucle. Quelque chose comme:

bool arraysAreMirrored(int a1[], int a2[], size_t size)
{
  size_t i1, i2;
  for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--)
  {
    if(a1[i1] != a2[i2])
    {
      return false;
    }
  }

  return true;
}

Pardonnez-moi s'il y a des erreurs de syntaxe ou si j'ai mélangé quelque chose qui n'est pas strict C. Je ne dis pas que l'opérateur, est de bonne forme, mais c'est pour cela que vous pouvez l'utiliser. Dans le cas ci-dessus, j'utiliserais probablement une whileboucle à la place, de sorte que les multiples expressions sur init et loop seraient plus évidentes. (Et j'initialiserais i1 et i2 en ligne au lieu de déclarer puis d'initialiser .... bla bla bla.)

Owen
la source
Je suppose que vous voulez dire i1 = 0, i2 = taille -1
Frankster
-2

Je le ressuscite simplement pour répondre aux questions de @Rajesh et @JeffMercado qui, à mon avis, sont très importantes car c'est l'un des meilleurs résultats des moteurs de recherche.

Prenons par exemple l'extrait de code suivant

int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d\n", i , j);

Il imprimera

1 5

Le icas est traité comme expliqué par la plupart des réponses. Toutes les expressions sont évaluées dans l'ordre de gauche à droite, mais seule la dernière est affectée i. Le résultat de l' ( expression ) is1`.

Le jcas suit des règles de priorité différentes car il ,a la priorité d'opérateur la plus basse. En raison de ces règles, le compilateur voit l' expression d'affectation, constante, constante ... . Les expressions sont évaluées à nouveau dans l' ordre et leurs effets secondaires à droite gauche restent visibles, donc jest 5en raison de j = 5.

Fait intéressant, int j = 5,4,3,2,1;n'est pas autorisé par la spécification de la langue. Un initialiseur attend une expression d'affectation, donc un ,opérateur direct n'est pas autorisé.

J'espère que cela t'aides.

ViNi89
la source