L’operatore ternario (condizionale) in C

Qual è la necessità dell’operatore condizionale? Funzionalmente è ridondante, poiché implementa un costrutto if-else. Se l’operatore condizionale è più efficiente dell’assegnazione if-else equivalente, perché non può essere altrimenti interpretato in modo più efficiente dal compilatore?

L’operatore ternario è un vantaggio sintattico e di leggibilità, non una scorciatoia prestazionale. Le persone sono divise nel merito per condizionali di varia complessità, ma per condizioni brevi, può essere utile avere un’espressione a una riga.

Inoltre, poiché è un’espressione, come scrisse Charlie Martin , ciò significa che può apparire sul lato destro di un’affermazione in C. Questo è utile per essere concisi.

In C, la vera utilità è che è un’espressione invece di un’affermazione; cioè, puoi averlo sul lato destro (RHS) di una dichiarazione. Quindi puoi scrivere certe cose in modo più conciso.

Alcune delle altre risposte fornite sono grandiose. Ma sono sorpreso che nessuno abbia menzionato che può essere usato per aiutare a rafforzare la correttezza const in modo compatto.

Qualcosa come questo:

 const int n = (x != 0) ? 10 : 20; 

quindi fondamentalmente n è una const cui valore iniziale dipende da un’istruzione di condizione. L’alternativa più semplice è di rendere n non un const , ciò consentirebbe un ordinario if inizializzarlo. Ma se vuoi che sia const , non può essere fatto con un ordinario if . Il miglior sostituto che potresti fare sarebbe usare una funzione di aiuto come questa:

 int f(int x) { if(x != 0) { return 10; } else { return 20; } } const int n = f(x); 

ma la versione ternaria se è molto più compatta e probabilmente più leggibile.

È fondamentale per l’offuscamento del codice, come questo:

 Look-> See?! No :( Oh, well ); 

Compattezza e capacità di allineare un costrutto if-then-else in un’espressione.

Ci sono molte cose in C che non sono tecnicamente necessarie perché possono essere implementate più o meno facilmente in termini di altre cose. Ecco una lista incompleta:

  1. mentre
  2. per
  3. funzioni
  4. struct

Immagina come sarebbe il tuo codice senza questi e potresti trovare la tua risposta. L’operatore ternario è una forma di “zucchero sintattico” che, se usato con cura e abilità, facilita la scrittura e la comprensione del codice.

A volte l’operatore ternario è il modo migliore per portare a termine il lavoro. In particolare quando vuoi che il risultato del ternario sia un valore di l.

Questo non è un buon esempio, ma sto disegnando un vuoto su qualcosa di meglio. Una cosa è certiana, non è spesso quando hai davvero bisogno di usare il ternario, anche se lo uso ancora un po ‘.

 const char* appTitle = amDebugging ? "DEBUG App 1.0" : "App v 1.0"; 

Una cosa che vorrei mettere in guardia contro è stringere insieme i ternari. Diventano reali
problema al tempo di maintennance:

 int myVal = aIsTrue ? aVal : bIsTrue ? bVal : cIsTrue ? cVal : dVal; 

EDIT : Ecco un esempio potenzialmente migliore. Puoi usare l’operatore ternario per assegnare riferimenti e valori const dove altrimenti avresti bisogno di scrivere una funzione per gestirli:

 int getMyValue() { if( myCondition ) return 42; else return 314; } const int myValue = getMyValue(); 

…potrebbe diventare:

 const int myValue = myCondition ? 42 : 314; 

La cosa migliore è una domanda discutibile che scelgo di non discutere.

Dal momento che nessuno lo ha ancora menzionato, l’unico modo per ottenere istruzioni smart printf consiste nell’utilizzare l’operatore ternario:

 printf("%d item%s", count, count > 1 ? "s\n" : "\n"); 

Avvertenza: ci sono alcune differenze nella precedenza degli operatori quando ci si sposta da C a C ++ e si può essere sorpresi dai sottili bug che ne derivano.

Il fatto che l’operatore ternario sia un’espressione, non un’istruzione, consente di utilizzarlo nelle macroespansioni per macro di tipo funzione che vengono utilizzate come parte di un’espressione. Const potrebbe non aver fatto parte dell’originale C, ma il pre-processore macro risale al passato.

Un luogo in cui l’ho visto è in un pacchetto di array che utilizzava macro per gli accessi agli array con controllo. La syntax per un riferimento verificato era qualcosa come aref(arrayname, type, index) , dove arrayname era in realtà un puntatore a una struttura che includeva i limiti dell’array e un array di caratteri non firmato per i dati, tipo era il tipo effettivo dei dati, e l’indice era l’indice. L’espansione di questo è stata piuttosto pelosa (e non ho intenzione di farlo dalla memoria), ma ha usato alcuni operatori ternari per eseguire il controllo vincolato.

Non è ansible farlo come una chiamata di funzione in C a causa della necessità di polimorfismo dell’object restituito. Quindi era necessaria una macro per eseguire il casting del tipo nell’espressione. In C ++ puoi farlo come una chiamata di funzione sovraccaricata basata su modelli (probabilmente per operator []), ma C non ha tali caratteristiche.

Modifica: Ecco l’esempio di cui stavo parlando, dal pacchetto dell’array CAD Berkeley (versione 1.4 glu). La documentazione dell’uso di array_fetch è:

 type array_fetch(type, array, position) typeof type; array_t *array; int position; 

Recupera un elemento da una matrice. Si verifica un errore di runtime in un tentativo di fare riferimento all’esterno dei limiti dell’array. Non esiste un controllo di tipo che il valore nella posizione data sia effettivamente del tipo utilizzato durante la dereferenziazione dell’array.

ed ecco la definizione della macro di array_fetch (si noti l’uso dell’operatore ternario e dell’operatore di sequenziamento della virgola per eseguire tutte le sottoespressioni con i giusti valori nell’ordine corretto come parte di una singola espressione):

 #define array_fetch(type, a, i) \ (array_global_index = (i), \ (array_global_index >= (a)->num) ? array_abort((a),1) : 0,\ *((type *) ((a)->space + array_global_index * (a)->obj_size))) 

L’espansione per array_insert (che aumenta la matrice se necessario, come un vettore C ++) è ancora più felpa, coinvolgendo più operatori ternari nidificati.

È zucchero sintetico e una comoda scorciatoia per brevi blocchi if / else che contengono solo un’istruzione. Funzionalmente, entrambi i costrutti dovrebbero funzionare in modo identico.

L’operatore ternario può avere più prestazioni di una normale clausola if else, questo può essere fondamentale nelle applicazioni embedded, ma anche l’ottimizzazione del compilatore potrebbe far crollare questa differenza.

Come ha detto Dwn, Performance è stato uno dei suoi benefici durante l’ascesa di processori complessi, blog MSDN Processore non classico: come fare qualcosa può essere più veloce di non farlo dà un esempio che dice chiaramente la differenza tra operatore (condizionale) ternario e se / else dichiarazione.

dare il seguente codice:

 #include  #include  #include  #include  int array[10000]; int countthem(int boundary) { int count = 0; for (int i = 0; i < 10000; i++) { if (array[i] < boundary) count++; } return count; } int __cdecl wmain(int, wchar_t **) { for (int i = 0; i < 10000; i++) array[i] = rand() % 10; for (int boundary = 0; boundary <= 10; boundary++) { LARGE_INTEGER liStart, liEnd; QueryPerformanceCounter(&liStart); int count = 0; for (int iterations = 0; iterations < 100; iterations++) { count += countthem(boundary); } QueryPerformanceCounter(&liEnd); printf("count=%7d, time = %I64d\n", count, liEnd.QuadPart - liStart.QuadPart); } return 0; } 

il costo per i diversi confini è molto diverso e strano (vedi il materiale originale). mentre se cambia:

  if (array[i] < boundary) count++; 

a

  count += (array[i] < boundary) ? 1 : 0; 

Il tempo di esecuzione è ora indipendente dal valore limite, poiché:

l'ottimizzatore è stato in grado di rimuovere il ramo dall'espressione ternaria.

ma sul mio desktop intel i5 cpu / windows 10 / vs2015, il mio risultato del test è abbastanza diverso con il blog msdn.

quando si usa la modalità di debug , se / else costa:

 count= 0, time = 6434 count= 100000, time = 7652 count= 200800, time = 10124 count= 300200, time = 12820 count= 403100, time = 15566 count= 497400, time = 16911 count= 602900, time = 15999 count= 700700, time = 12997 count= 797500, time = 11465 count= 902500, time = 7619 count=1000000, time = 6429 

e costo dell'operatore ternario:

 count= 0, time = 7045 count= 100000, time = 10194 count= 200800, time = 12080 count= 300200, time = 15007 count= 403100, time = 18519 count= 497400, time = 20957 count= 602900, time = 17851 count= 700700, time = 14593 count= 797500, time = 12390 count= 902500, time = 9283 count=1000000, time = 7020 

quando si usa la modalità di rilascio , se / altro costa:

 count= 0, time = 7 count= 100000, time = 9 count= 200800, time = 9 count= 300200, time = 9 count= 403100, time = 9 count= 497400, time = 8 count= 602900, time = 7 count= 700700, time = 7 count= 797500, time = 10 count= 902500, time = 7 count=1000000, time = 7 

e costo dell'operatore ternario:

 count= 0, time = 16 count= 100000, time = 17 count= 200800, time = 18 count= 300200, time = 16 count= 403100, time = 22 count= 497400, time = 16 count= 602900, time = 16 count= 700700, time = 15 count= 797500, time = 15 count= 902500, time = 16 count=1000000, time = 16 

l'operatore ternario è più lento di if / else sulla mia macchina!

quindi secondo diverse tecniche di ottimizzazione del compilatore, operatore ternal e se / else può comportarsi in modo molto diverso.

ternario = forma semplice di if-else. È disponibile principalmente per la leggibilità.

  • Alcuni degli operatori più oscuri in C esistono solo perché consentono l’implementazione di varie macro di tipo funzione come una singola espressione che restituisce un risultato. Direi che questo è lo scopo principale per cui ?: E , operatori possono esistere, anche se la loro funzionalità è altrimenti ridondante.

    Diciamo che desideriamo implementare una macro simile a una funzione che restituisca il più grande dei due parametri. Sarebbe quindi chiamato come ad esempio:

     int x = LARGEST(1,2); 

    L’unico modo per implementare questo come una macro simile a una funzione sarebbe

     #define LARGEST(x,y) ((x) > (y) ? (x) : (y)) 

    Non sarebbe ansible con un’istruzione if ... else , poiché non restituisce un valore di risultato. Nota)

  • L’altro scopo di ?: È che in alcuni casi effettivamente aumenta la leggibilità. Molto spesso if...else è più leggibile, ma non sempre. Prendiamo ad esempio dichiarazioni switch lunghe e ripetitive:

     switch(something) { case A: if(x == A) { array[i] = x; } else { array[i] = y; } break; case B: if(x == B) { array[i] = x; } else { array[i] = y; } break; ... } 

    Questo può essere sostituito con il molto più leggibile

     switch(something) { case A: array[i] = (x == A) ? x : y; break; case B: array[i] = (x == B) ? x : y; break; ... } 
  • Si noti che ?: Non risulta mai in un codice più veloce di if-else . Questo è un mito strano creato da principianti confusi. In caso di codice ottimizzato, ?: Offre prestazioni identiche come if-else nella maggior parte dei casi.

    Se qualcosa, ?: Può essere più lento di if-else , perché include promozioni obbligatorie di tipo implicito, anche dell’operando che non verrà utilizzato. Ma ?: Non può mai essere più veloce di if-else .


Nota) Ora, naturalmente, qualcuno sosterrà e si chiederà perché non usare una funzione. Infatti, se puoi usare una funzione, è sempre preferibile su una macro simile a una funzione. Ma a volte non è ansible utilizzare le funzioni. Supponiamo ad esempio che x nell’esempio sopra sia dichiarato nell’ambito del file. L’inizializzatore deve quindi essere un’espressione costante, quindi non può contenere una chiamata di funzione. Altri esempi pratici di dove si devono usare macro simili a funzioni implicano la programmazione di tipo sicuro con _Generic o “X macros”.

Lo stesso di

 if(0) do(); if(0) { do(); }