Cosa fa l'operatore virgola?


167

Cosa fa l' ,operatore in C?



1
Come noto nella mia risposta, dopo la valutazione dell'operando di sinistra è presente un punto sequenza. Questo è diverso dalla virgola in una chiamata di funzione che è solo grammaticale.
Shafik Yaghmour,

2
@SergeyK. - Dato che questo è stato chiesto e risposto anni prima dell'altro, è più probabile che l'altro sia un duplicato di questa domanda. Tuttavia, l'altro è anche doppiamente codificato con c e c ++ , il che è un fastidio. Questa è una domanda e risposta solo in C, con risposte decenti.
Jonathan Leffler,

Risposte:


129

L'espressione:

(expression1,  expression2)

Viene valutata la prima espressione1, quindi viene valutata expression2 e viene restituito il valore di expression2 per l'intera espressione.


3
quindi se scrivo i = (5,4,3,2,1,0), idealmente dovrebbe restituire 0, giusto? ma mi viene assegnato un valore di 5? Potete per favore aiutarmi a capire dove sbaglio?
Jayesh,

19
@James: il valore di un'operazione virgola sarà sempre il valore dell'ultima espressione. Non avrà imai i valori 5, 4, 3, 2 o 1. È semplicemente 0. È praticamente inutile a meno che le espressioni non abbiano effetti collaterali.
Jeff Mercado,

6
Si noti che esiste un punto di sequenza completo tra la valutazione dell'LHS dell'espressione virgola e la valutazione dell'RHS (vedere la risposta di Shafik Yaghmour per una citazione dallo standard C99). Questa è una proprietà importante dell'operatore virgola.
Jonathan Leffler,

5
i = b, c;è equivalente a (i = b), cperché l'assegnazione =ha una precedenza maggiore rispetto all'operatore virgola ,. L'operatore virgola ha la precedenza più bassa di tutte.
ciclista

1
Temo che le parentesi siano fuorvianti per due motivi: (1) non sono necessarie: l'operatore virgola non deve essere racchiuso tra parentesi; e (2) potrebbero essere confusi con le parentesi attorno all'elenco degli argomenti di una chiamata di funzione - ma la virgola nell'elenco degli argomenti non è l'operatore virgola. Tuttavia, risolverlo non è del tutto banale. Forse: nell'affermazione: expression1, expression2;prima expression1viene valutato, presumibilmente per i suoi effetti collaterali (come chiamare una funzione), quindi c'è un punto sequenza, quindi expression2viene valutato e il valore restituito ...
Jonathan Leffler,

119

Ho visto più usato nei whileloop:

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

Farà l'operazione, quindi eseguirà un test basato su un effetto collaterale. L'altro modo sarebbe farlo in questo modo:

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

21
Ehi, è carino! Ho spesso dovuto fare cose non ortodosse in un ciclo per risolvere quel problema.
staticsan

6
Anche se probabilmente sarebbe meno oscuro e più leggibile se avete fatto qualcosa di simile: while (read_string(s) && s.len() > 5). Ovviamente non funzionerebbe se read_stringnon ha un valore di ritorno (o non ha un valore significativo). (Modifica: scusa, non ho notato quanti anni aveva questo post.)
jamesdlin

11
@staticsan Non abbiate paura di usare while (1)con una break;dichiarazione nel corpo. Cercare di forzare la parte di break-out del codice nel test while o nel test do-while, è spesso uno spreco di energia e rende il codice più difficile da capire.
Potrzebie,

8
@jamesdlin ... e la gente lo legge ancora. Se hai qualcosa di utile da dire, allora dillo. I forum hanno problemi con le discussioni resuscitate perché le discussioni sono solitamente ordinate per data dell'ultimo post. StackOverflow non ha tali problemi.
Dimitar Slavchev,

3
@potrzebie Mi piace l'approccio virgola molto meglio di while(1)e break;
Michael

38

L' operatore virgola valuterà l'operando di sinistra, scarterà il risultato e quindi valuterà l'operando di destra e quello sarà il risultato. L' uso idiomatico come indicato nel collegamento è quando si inizializzano le variabili utilizzate in un forciclo e fornisce l'esempio seguente:

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

Altrimenti non ci sono molti grandi usi dell'operatore virgola , sebbene sia facile abusare di generare codice difficile da leggere e mantenere.

Dalla bozza della norma C99 la grammatica è la seguente:

expression:
  assignment-expression
  expression , assignment-expression

e il paragrafo 2 dice:

L' operando di sinistra di un operatore virgola viene valutato come espressione nulla; c'è un punto sequenza dopo la sua valutazione. Quindi viene valutato l' operando giusto; il risultato ha il suo tipo e valore. 97) Se si tenta di modificare il risultato di un operatore virgola o di accedervi dopo il successivo punto di sequenza, il comportamento non è definito.

La nota 97 dice:

Un operatore virgola non restituisce un valore .

ciò significa che non è possibile assegnare al risultato dell'operatore virgola .

È importante notare che l'operatore virgola ha la precedenza più bassa e quindi ci sono casi in cui l'utilizzo ()può fare una grande differenza, ad esempio:

#include <stdio.h>

int main()
{
    int x, y ;

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

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

avrà il seguente output:

1 4

28

L'operatore virgola combina le due espressioni su entrambi i lati in una, valutandole entrambe nell'ordine da sinistra a destra. Il valore del lato destro viene restituito come valore dell'intera espressione. (expr1, expr2)è come { expr1; expr2; }ma è possibile utilizzare il risultato di expr2una chiamata di funzione o di un compito.

forNei loop si vede spesso inizializzare o mantenere più variabili come questa:

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 */
}

A parte questo, l'ho usato solo "con rabbia" in un altro caso, quando si concludono due operazioni che dovrebbero sempre andare insieme in una macro. Avevamo un codice che copiava vari valori binari in un buffer di byte per l'invio su una rete e un puntatore manteneva il punto in cui eravamo arrivati ​​a:

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

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

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

Dove erano shorti valori intabbiamo fatto questo:

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

In seguito leggiamo che questo non era veramente valido C, perché (short *)ptrnon è più un valore l e non può essere incrementato, anche se al nostro compilatore non importava. Per risolvere questo problema, abbiamo diviso l'espressione in due:

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

Tuttavia, questo approccio si basava su tutti gli sviluppatori che si ricordavano di inserire entrambe le dichiarazioni in ogni momento. Volevamo una funzione in cui si potesse passare il puntatore di output, il valore e il tipo di valore. Essendo C, non C ++ con template, non potevamo avere una funzione che prendesse un tipo arbitrario, quindi abbiamo optato per una macro:

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

Usando l'operatore virgola siamo riusciti a usarlo nelle espressioni o come dichiarazioni come desideravamo:

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

Non sto suggerendo che nessuno di questi esempi sia di buon stile! In effetti, mi sembra di ricordare il Codice Completo di Steve McConnell che mi consigliava di non utilizzare nemmeno gli operatori virgola in un forciclo: per leggibilità e manutenibilità, il ciclo dovrebbe essere controllato da una sola variabile e le espressioni nella forriga stessa dovrebbero contenere solo codice di controllo del ciclo, non altri bit extra di inizializzazione o manutenzione del loop.


Grazie! È stata la mia prima risposta su StackOverflow: da allora forse ho imparato che la concisione deve essere valutata :-).
Paul Stephenson,

A volte apprezzo un po 'di verbosità come nel caso in cui descrivi l'evoluzione di una soluzione (come ci sei arrivato).
diodo verde,

8

Causa la valutazione di più istruzioni, ma utilizza solo l'ultima come valore risultante (valore, credo).

Così...

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

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

dovrebbe comportare che x sia impostato su 8.


3

Come indicato nelle risposte precedenti, valuta tutte le istruzioni ma utilizza l'ultima come valore dell'espressione. Personalmente l'ho trovato utile solo nelle espressioni loop:

for (tmp=0, i = MAX; i > 0; i--)

2

L'unico posto in cui l'ho visto utile è quando scrivi un loop funky in cui vuoi fare più cose in una delle espressioni (probabilmente l'espressione init o l'espressione loop. Qualcosa come:

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

Scusami se ci sono errori di sintassi o se ho mescolato qualcosa che non è rigoroso C. Non sto sostenendo che l'operatore sia in buona forma, ma è per questo che potresti usarlo. Nel caso sopra, probabilmente userei un whileloop, quindi le espressioni multiple su init e loop sarebbero più ovvie. (E inizializzerei i1 e i2 inline invece di dichiarare e quindi inizializzare .... blah blah blah.)


Suppongo che intendi i1 = 0, i2 = taglia -1
Frank

-2

Sto rianimando questo semplicemente per rispondere alle domande di @Rajesh e @JeffMercado che penso siano molto importanti poiché questo è uno dei principali successi dei motori di ricerca.

Prendi ad esempio il seguente frammento di codice

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

Stamperà

1 5

Il icaso viene gestito come spiegato dalla maggior parte delle risposte. Tutte le espressioni vengono valutate nell'ordine da sinistra a destra, ma viene assegnata solo l'ultima i. Il risultato ( dell'espressione ) is1`.

Il jcaso segue regole di precedenza diverse poiché ,ha la precedenza dell'operatore più bassa. A causa di tali norme, il compilatore vede assegnazione-espressione, costante, costante ... . Le espressioni vengono nuovamente valutate nell'ordine da sinistra a destra e i loro effetti collaterali rimangono visibili, pertanto jè 5il risultato di j = 5.

Interessante, int j = 5,4,3,2,1;non è consentito dalle specifiche della lingua. Un inizializzatore si aspetta un'espressione di assegnazione, quindi ,non è consentito un operatore diretto .

Spero che questo ti aiuti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.