Gli operatori sono i verbi del C: prendono dei valori e ne producono di nuovi. Ce ne sono una quarantina, ma una dozzina copre la maggior parte del codice, e due di questi nascondono trappole abbastanza insidiose da meritare una sezione a parte.
Operatori aritmetici
(%% nella stringa di formato stampa un segno di percentuale letterale: un % da solo darebbe inizio a uno specificatore di formato.)
I cinque operatori aritmetici binari sono + - * / %, più il - unario per la negazione e il + unario, che in sostanza non fa nulla.
La trappola della divisione intera
a / b con due interi esegue una divisione intera e butta via il resto. Non c'è arrotondamento: il risultato viene troncato verso lo zero.
Quest'ultimo caso è il bug che sopravvive fino in produzione. Assegnare a un double non aiuta: la divisione total / count è già conclusa, ed è già un int, prima che avvenga l'assegnazione. La soluzione è rendere un operando in virgola mobile dentro l'espressione, di solito con un cast.
Due regole collegate: dividere per zero con gli interi è comportamento indefinito e di solito fa andare in crash il programma, e INT_MIN / -1 va in overflow.
L'operatore modulo
% dà il resto e funziona solo con gli interi:
Controllare x % 2 == 0 per la parità e usare % n per far ripartire un indice dall'inizio di un array sono i due usi quotidiani.
Con i numeri negativi, il % del C segue il segno del dividendo:
Quindi x % 2 == 1 è un test sbagliato per i dispari quando x può essere negativo: -7 % 2 vale -1. Usa invece x % 2 != 0.
Per i resti in virgola mobile, % è un errore di compilazione; usa fmod() di math.h.
Assegnazione e assegnazione composta
= memorizza un valore. Le forme composte combinano un'operazione con la memorizzazione:
x += 5 significa x = x + 5, ma valuta x una sola volta, il che conta quando la destinazione è qualcosa come arr[compute_index()].
L'assegnazione è a sua volta un'espressione che produce il valore assegnato, ed è per questo che a = b = c = 0 funziona (assegna partendo dall'ultimo) e che l'errore = al posto di == compila in silenzio.
Incremento e decremento
++ aggiunge uno, -- sottrae uno. Ognuno ha una forma prefissa e una postfissa:
Come istruzione a sé le due forme sono intercambiabili, e i++ è la scelta convenzionale nell'intestazione di un for per nessun motivo più profondo della tradizione.
Quello che non devi fare è usare una variabile più di una volta in un'espressione in cui viene anche modificata:
int i = 5;
int x = i++ + i++; /* comportamento indefinito */
arr[i] = i++; /* comportamento indefinito */
printf("%d %d", i++, i);/* comportamento indefinito */
Il C non definisce l'ordine in cui vengono valutate quelle sottoespressioni, quindi il risultato non è solo non specificato: il comportamento dell'intero programma è indefinito. Dividi l'istruzione in due e l'ambiguità sparisce.
Operatori di confronto
Sono sei, e producono tutti un int: 1 per vero e 0 per falso.
Il fatto che il risultato sia un semplice int invece di un tipo booleano dedicato è una caratteristica distintiva del C: vedi i booleani in C per le conseguenze, compreso il bug = al posto di ==.
Due errori di confronto sono tipici del C. Il concatenamento non funziona come suggerisce la matematica: if (1 < x < 10) è sempre vero, perché 1 < x vale 0 o 1 e poi quel valore viene confrontato con 10. Scrivi if (x > 1 && x < 10). E confrontare stringhe con == confronta i puntatori, non il testo: usa strcmp di string.h.
Operatori logici
&& è AND, || è OR, ! è NOT. Tutti e tre considerano vero qualsiasi valore diverso da zero.
Sono a corto circuito: && si ferma appena un operando è falso, e || si ferma appena uno è vero. Il resto non viene mai valutato, e non è solo un'ottimizzazione: è una garanzia su cui puoi contare per la sicurezza:
Inverti l'ordine di quei due test e la chiamata con NULL dereferenzia un puntatore nullo e va in crash. Prima la guardia, poi l'uso: l'ordine in una catena di && fa parte della logica.
L'operatore condizionale
L'unico operatore a tre operandi del C sceglie tra due valori:
condition ? value_if_true : value_if_false. È un'espressione, quindi sta dove un'istruzione if non può stare: dentro un argomento di printf, in un inizializzatore. Tienilo breve; i condizionali annidati diventano illeggibili in fretta.
Operatori bit a bit, in breve
Questi lavorano sui singoli bit di un intero:
Si usano per flag, maschere e registri hardware. Due avvertenze: & e | non sono && e || (non vanno a corto circuito e operano bit per bit), e spostare un valore con segno, o spostare di più della larghezza del tipo, è indefinito. Per lavorare sui bit usa tipi senza segno.
Precedenza
Quando un'espressione non ha parentesi, questa tabella decide cosa si lega a cosa. Prima la precedenza più alta:
| Livello | Operatori | Associatività |
|---|---|---|
| 1 | () [] -> . x++ x-- | da prima a dopo |
| 2 | ! ~ ++x --x +x -x *p &x sizeof (type) | da dopo a prima |
| 3 | * / % | da prima a dopo |
| 4 | + - | da prima a dopo |
| 5 | << >> | da prima a dopo |
| 6 | < <= > >= | da prima a dopo |
| 7 | == != | da prima a dopo |
| 8 | & | da prima a dopo |
| 9 | ^ | da prima a dopo |
| 10 | OR bit a bit | da prima a dopo |
| 11 | && | da prima a dopo |
| 12 | OR logico | da prima a dopo |
| 13 | ?: | da dopo a prima |
| 14 | = += -= *= /= %= e simili | da dopo a prima |
| 15 | , | da prima a dopo |
Le conseguenze pratiche:
a + b * c /* significa a + (b * c) */
a < b == c < d /* significa (a < b) == (c < d), raramente quello che volevi */
x & 1 == 0 /* significa x & (1 == 0), cioe' x & 0: un bug classico */
*p++ /* significa *(p++): dereferenzia p, poi lo fa avanzare */
Quella terza riga vale la pena impararla a memoria: == si lega più strettamente di &, quindi i test sulle maschere di bit vogliono le parentesi: (x & 1) == 0.
Nessuno ricorda tutti e quindici i livelli, e a nessuno serve. Impara che * batte +, che i confronti battono && che batte ||, e metti le parentesi a tutto il resto:
if ((flags & MASK) != 0 && (count > 0 || force)) { ... }
Quella riga ha parentesi ridondanti, ed è migliore proprio per questo.
Domande frequenti
Cosa fa % in C?
% è l'operatore modulo: dà il resto della divisione intera. 17 % 5 vale 2. Funziona solo con gli interi: usarlo su un float o un double è un errore di compilazione, e fmod() di math.h è l'equivalente in virgola mobile.
Perché 5 / 2 dà 2 in C?
Perché entrambi gli operandi sono interi, quindi il C esegue una divisione intera e scarta la parte decimale. Rendi uno dei due lati un valore in virgola mobile per ottenere 2.5: 5 / 2.0, oppure fai il cast di un operando con (double)a / b.
Qual è la differenza tra i++ e ++i?
Entrambi aggiungono uno a i. ++i (prefisso) incrementa prima e restituisce il nuovo valore; i++ (postfisso) restituisce il vecchio valore e incrementa dopo. Come istruzione a sé sono identici: la differenza conta solo quando il risultato viene usato, come in int b = a++; rispetto a int b = ++a;.
Cos'è la precedenza degli operatori in C?
L'ordine in cui gli operatori si legano quando un'espressione non ha parentesi. *, / e % si legano più strettamente di + e -, che si legano più strettamente dei confronti, che a loro volta si legano più strettamente di &&, poi di ||, poi dell'assegnazione. Nel dubbio, aggiungi le parentesi: non costano nulla ed eliminano la domanda.