Domanda

    

Questa domanda ha già una risposta qui:

    
            
  •              Che cosa fa l'operatore virgola?                                      9 risposte                          
  •     
    

Lo vedi usato per le istruzioni loop, ma è sintassi legale ovunque. Che usi hai trovato altrove, se ce ne sono?

È stato utile?

Soluzione

Il linguaggio C (così come C ++) è storicamente un mix di due stili di programmazione completamente diversi, a cui si può fare riferimento come "programmazione di istruzioni" e "programmazione di espressioni". Come sapete, ogni linguaggio di programmazione procedurale normalmente supporta costrutti fondamentali come sequenziamento e ramificazione (vedere Programmazione strutturata ). Questi costrutti fondamentali sono presenti nei linguaggi C / C ++ in due forme: uno per la programmazione di istruzioni, un altro per la programmazione di espressioni.

Ad esempio, quando scrivi il tuo programma in termini di istruzioni, potresti usare una sequenza di istruzioni separate da ; . Quando vuoi fare delle ramificazioni, usi le istruzioni if . Puoi anche utilizzare cicli e altri tipi di istruzioni per il trasferimento dei controlli.

Nella programmazione delle espressioni sono disponibili anche gli stessi costrutti. Qui è in realtà l'operatore , che entra in gioco. L'operatore , in nient'altro che un separatore di espressioni sequenziali in C, ovvero l'operatore , nella programmazione delle espressioni ha lo stesso ruolo di ; nell'istruzione programmazione. La ramificazione nella programmazione delle espressioni viene effettuata tramite l'operatore ?: e, in alternativa, tramite le proprietà di valutazione del corto circuito degli operatori & amp; & amp; e || . (La programmazione delle espressioni non ha cicli però. E per sostituirli con la ricorsione dovresti applicare la programmazione delle istruzioni.)

Ad esempio, il seguente codice

a = rand();
++a;
b = rand();
c = a + b / 2;
if (a < c - 5)
  d = a;
else
  d = b;

che è un esempio di programmazione di istruzioni tradizionali, può essere riscritto in termini di programmazione di espressioni come

a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? d = a : d = b;

o come

a = rand(), ++a, b = rand(), c = a + b / 2, d = a < c - 5 ? a : b;

o

d = (a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? a : b);

o

a = rand(), ++a, b = rand(), c = a + b / 2, (a < c - 5 && (d = a, 1)) || (d = b);

Inutile dire che, in pratica, la programmazione delle istruzioni di solito produce un codice C / C ++ molto più leggibile, quindi normalmente usiamo la programmazione delle espressioni in quantità ben misurate e limitate. Ma in molti casi è utile. E il confine tra ciò che è accettabile e ciò che non lo è è in larga misura una questione di preferenza personale e la capacità di riconoscere e leggere modi di dire consolidati.

Come nota aggiuntiva: il design stesso del linguaggio è ovviamente adattato alle dichiarazioni. Le dichiarazioni possono invocare liberamente espressioni, ma le espressioni non possono invocare dichiarazioni (oltre a chiamare funzioni predefinite). Questa situazione è cambiata in un modo piuttosto interessante nel compilatore GCC, che supporta il cosiddetto " espressioni espressioni " come estensione (simmetrica a " espressioni di espressione " nella norma C). " espressioni di dichiarazione " consentire all'utente di inserire direttamente il codice basato sulle istruzioni nelle espressioni, proprio come possono inserire il codice basato sulle espressioni nelle istruzioni nella norma C.

Come un'altra nota aggiuntiva: nel linguaggio C ++ la programmazione basata su funzioni di gioco svolge un ruolo importante, che può essere visto come un'altra forma di "programmazione di espressioni". Secondo le attuali tendenze nella progettazione di C ++, potrebbe essere considerato preferibile rispetto alla programmazione di istruzioni tradizionali in molte situazioni.

Altri suggerimenti

Penso che in genere la virgola di C non sia un buon stile da usare semplicemente perché è molto molto facile perdere - o da qualcun altro che cerca di leggere / comprendere / correggere il tuo codice, o tu stesso un mese lungo la linea. Al di fuori delle dichiarazioni variabili e dei loop, ovviamente, dove è idiomatico.

Puoi usarlo, ad esempio, per impacchettare più istruzioni in un operatore ternario (? :), ala:

int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117;

ma i miei dei, perché?!? (L'ho visto usato in questo modo in codice reale, ma purtroppo non ho accesso ad esso per mostrarlo)

L'ho visto usato nelle macro in cui la macro finge di essere una funzione e vuole restituire un valore ma deve prima fare qualche altro lavoro. È sempre brutto e spesso sembra un trucco pericoloso.

Esempio semplificato:

#define SomeMacro(A) ( DoWork(A), Permute(A) )

Qui B = SomeMacro (A) " restituisce " il risultato di Permute (A) e lo assegna a " B " ;.

Due funzioni dell'operatore virgola killer in C ++:

a) Leggi dallo stream fino a quando non viene rilevata una stringa specifica (aiuta a mantenere il codice DRY):

 while (cin >> str, str != "STOP") {
   //process str
 }

b) Scrivi codice complesso negli inizializzatori del costruttore:

class X : public A {
  X() : A( (global_function(), global_result) ) {};
};

Ho dovuto usare una virgola per eseguire il debug dei blocchi mutex per mettere un messaggio prima che il blocco inizia ad attendere.

Non potevo fare a meno del messaggio di registro nel corpo del costruttore di blocchi derivato, quindi ho dovuto inserirlo negli argomenti del costruttore della classe di base usando: baseclass ((log (" message "), actual_arg)) in l'elenco di inizializzazione. Nota la parentesi extra.

Ecco un estratto delle classi:

class NamedMutex : public boost::timed_mutex
{
public:
    ...

private:
    std::string name_ ;
};

void log( NamedMutex & ref__ , std::string const& name__ )
{
    LOG( name__ << " waits for " << ref__.name_ );
}

class NamedUniqueLock : public boost::unique_lock< NamedMutex >
{
public:

    NamedUniqueLock::NamedUniqueLock(
        NamedMutex & ref__ ,
        std::string const& name__ ,
        size_t const& nbmilliseconds )
    :
        boost::unique_lock< NamedMutex >( ( log( ref__ , name__ ) , ref__ ) ,
            boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ),
            ref_( ref__ ),
            name_( name__ )
    {
    }

  ....

};

La libreria Boost Assignment è un buon esempio di sovraccaricare l'operatore virgola in modo utile e leggibile. Ad esempio:

using namespace boost::assign;

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

Dallo standard C:

  

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. (Un operatore virgola non restituisce un valore.) Se si tenta di modificare il risultato di un operatore virgola o di accedervi dopo il punto di sequenza successivo, il comportamento non è definito.

In breve, ti consente di specificare più di un'espressione in cui C ne prevede solo una. Ma in pratica è usato principalmente per i loop.

Nota che:

int a, b, c;

NON è l'operatore virgola, è un elenco di dichiaratori.

A volte viene utilizzato nelle macro, come le macro di debug come questa:

#define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size)))

(Ma guarda questo orribile fallimento , davvero tuo, per quello che può succedere quando esageri.)

Ma a meno che tu non ne abbia davvero bisogno, o non sei sicuro che renda il codice più leggibile e gestibile, consiglierei di non utilizzare l'operatore virgola.

Puoi sovraccaricarlo (purché questa domanda abbia un tag " C ++ "). Ho visto del codice, in cui la virgola sovraccarica è stata utilizzata per generare matrici. O vettori, non ricordo esattamente. Non è carino (anche se un po 'confuso):

MyVector foo = 2, 3, 4, 5, 6;

Al di fuori di un ciclo for, e anche se ce n'è uno può avere un aroma di odore di codice, l'unico posto che ho visto come un buon uso per l'operatore virgola è come parte di una cancellazione:

 delete p, p = 0;

L'unico valore rispetto all'alternativa è che puoi copiare / incollare accidentalmente solo metà di questa operazione se è su due righe.

Mi piace anche perché se lo fai per abitudine, non dimenticherai mai l'assegnazione zero. (Naturalmente, perché p non si trova in qualche modo di auto_ptr, smart_ptr, shared_ptr, etc wrapper è una domanda diversa.)

Data la citazione di @Nicolas Goy dallo standard, allora sembra che potresti scrivere una riga per loop come:

int a, b, c;
for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--);
printf("%d", c);

Ma buon Dio, amico, vuoi davvero rendere il tuo codice C più oscuro in questo modo?

In generale, evito di usare l'operatore virgola perché rende il codice meno leggibile. In quasi tutti i casi, sarebbe più semplice e chiaro fare solo due affermazioni. Come:

foo=bar*2, plugh=hoo+7;

non offre alcun chiaro vantaggio rispetto a:

foo=bar*2;
plugh=hoo+7;

L'unico posto oltre ai loop in cui l'ho usato in costrutti if / else, come:

if (a==1)
... do something ...
else if (function_with_side_effects_including_setting_b(), b==2)
... do something that relies on the side effects ...

Potresti mettere la funzione prima di IF, ma se la funzione impiega molto tempo per essere eseguita, potresti voler evitare di farlo se non è necessario, e se la funzione non dovrebbe essere eseguita a meno che a! = 1, allora questa non è un'opzione. L'alternativa è nidificare gli IF un livello aggiuntivo. In realtà è quello che faccio di solito perché il codice sopra è un po 'criptico. Ma l'ho fatto ogni tanto con la virgola perché anche l'annidamento è criptico.

È molto utile aggiungere alcuni commenti nelle macro ASSERT :

ASSERT(("This value must be true.", x));

Poiché la maggior parte delle macro di stili di asserzione genererà l'intero testo del loro argomento, questo aggiunge un po 'di informazioni utili nell'asserzione.

Lo uso spesso per eseguire una funzione di inizializzazione statica in alcuni file cpp, per evitare problemi di inizializzazione pigra con i singoli classici:

void* s_static_pointer = 0;

void init() {
    configureLib(); 
    s_static_pointer = calculateFancyStuff(x,y,z);
    regptr(s_static_pointer);
}

bool s_init = init(), true; // just run init() before anything else

Foo::Foo() {
  s_static_pointer->doStuff(); // works properly
}

Per me l'unico caso davvero utile con virgole in C è usarli per eseguire qualcosa in modo condizionale.

  if (something) dothis(), dothat(), x++;

questo equivale a

  if (something) { dothis(); dothat(); x++; }

Non si tratta di "scrivere meno", a volte sembra molto chiaro.

Anche i loop sono proprio così:

while(true) x++, y += 5;

Naturalmente entrambi possono essere utili solo quando la parte condizionale o la parte eseguibile del ciclo è piuttosto piccola, due tre operazioni.

L'unica volta in cui ho mai visto l'operatore , usato al di fuori di un ciclo per è stato eseguire un'asserzione in una dichiarazione ternaria. È stato tanto tempo fa quindi non posso ricordare l'esatta affermazione ma era qualcosa del tipo:

int ans = isRunning() ? total += 10, newAnswer(total) : 0;

Ovviamente nessuna persona sana di mente scriverebbe un codice del genere, ma l'autore era un genio malvagio che costruiva dichiarazioni c basate sul codice assembler che generavano, non sulla leggibilità. Ad esempio, a volte usava i loop anziché le istruzioni if ??perché preferiva l'assemblatore che generava.

Il suo codice era molto veloce ma non mantenibile, sono contento di non dover più lavorare con esso.

L'ho usato per una macro per assegnare un valore di qualsiasi tipo a un buffer di output puntato da un carattere *, quindi incrementare il puntatore del numero richiesto di byte " ;, in questo modo:

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

L'uso dell'operatore virgola significa che la macro può essere utilizzata nelle espressioni o come istruzioni come desiderato:

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

Ha ridotto la digitazione ripetitiva ma bisogna fare attenzione che non diventi troppo illeggibile.

Vedi la mia versione troppo lunga di questa risposta qui .

Può essere utile per " code golf " ;:

Golf del codice: giocare a cubi

Il , in se (i > 0) t = i, i = 0; salva due caratteri.

qemu ha del codice che utilizza l'operatore virgola all'interno della parte condizionale di un ciclo for (vedere QTAILQ_FOREACH_SAFE in qemu-queue.h). Quello che hanno fatto si riduce a quanto segue:

#include <stdio.h>

int main( int argc, char* argv[] ){
  int x = 0, y = 0;

  for( x = 0; x < 3 && (y = x+1,1); x = y ){
    printf( "%d, %d\n", x, y );
  }

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

  for( x = 0, y = x+1; x < 3; x = y, y = x+1 ){
    printf( "%d, %d\n", x, y );
  }

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

... con il seguente output:

0, 1
1, 2
2, 3

3, 3

0, 1
1, 2
2, 3

3, 4

La prima versione di questo ciclo ha i seguenti effetti:

  • Evita di fare due assegnazioni, quindi le possibilità che il codice si alzi dalla sincronizzazione si riducono
  • Poiché utilizza & amp; & amp; , l'assegnazione non viene valutata dopo l'ultima iterazione
  • Dato che l'assegnazione non viene valutata, non proverà a de-referenziare l'elemento successivo nella coda quando è alla fine (nel codice di qemu, non nel codice sopra).
  • All'interno del loop, hai accesso all'elemento corrente e successivo

Trovato nell'inizializzazione dell'array:

In C cosa succede esattamente se uso () per inizializzare un array a doppia dimensione anziché {}?

Quando inizializzo un array a [] [] :

int a[2][5]={(8,9,7,67,11),(7,8,9,199,89)};

e quindi visualizzare gli elementi dell'array.

Ottengo:

11 89 0 0 0 
0 0 0 0 0
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top