Menu

Operatori in C: aritmetici, logici e precedenza

Tutti gli operatori C che vale la pena conoscere, cioè aritmetici, di assegnazione, di confronto, logici e di incremento, più la trappola della divisione intera, come si comporta il modulo con i negativi e una tabella delle precedenze di cui fidarti.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

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:

LivelloOperatoriAssociatività
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
10OR bit a bitda prima a dopo
11&&da prima a dopo
12OR logicoda prima a dopo
13?:da dopo a prima
14= += -= *= /= %= e similida 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.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA