Operatori Logici e Relazionali in C++
- Gli operatori logici e relazionali in C++ includono AND, OR, NOT e vari operatori di confronto.
- La precedenza degli operatori determina l'ordine in cui le operazioni vengono eseguite in un'espressione.
- Gli operatori logici includono
&&
(AND),||
(OR) e!
(NOT). - Gli operatori relazionali includono
<
,<=
,>
,>=
,==
e!=
. - Gli operatori logici e relazionali restituiscono valori di tipo
bool
. - La valutazione di cortocircuito si applica agli operatori logici
&&
e||
.
Operatori Logici e Relazionali
Gli operatori relazionali prendono operandi di tipo aritmetico o puntatore; gli operatori logici prendono operandi di qualsiasi tipo che possa essere convertito in bool
. Questi operatori restituiscono tutti valori di tipo bool
. Gli operandi aritmetici e puntatore con un valore di zero sono false
; tutti gli altri valori sono true
. Gli operandi di questi operatori sono r-value e il risultato è un r-value.
Associatività | Operatore | Funzione | Uso | Precedenza |
---|---|---|---|---|
Destra | ! |
NOT logico | !expr |
5 |
Sinistra | < |
Minore di | expr < expr |
4 |
Sinistra | <= |
Minore o uguale di | expr <= expr |
4 |
Sinistra | > |
Maggiore di | expr > expr |
4 |
Sinistra | >= |
Maggiore o uguale di | expr >= expr |
4 |
Sinistra | == |
Uguale a | expr == expr |
3 |
Sinistra | != |
Non uguale a | expr != expr |
3 |
Sinistra | && |
AND logico | expr && expr |
2 |
Sinistra | || |
OR logico | expr || expr |
1 |
Operatori AND e OR Logici
Il risultato complessivo dell'operatore AND logico è true
se e solo se entrambi i suoi operandi valutano true
. L'operatore OR logico (||
) valuta true
se uno qualsiasi dei suoi operandi valuta true
.
Gli operatori AND e OR logici valutano sempre il loro operando sinistro prima del destro. Inoltre, l'operando destro viene valutato se e solo se l'operando sinistro non determina il risultato. Questa strategia è nota come valutazione di cortocircuito:
- Il lato destro di un
&&
viene valutato se e solo se il lato sinistro ètrue
. - Il lato destro di un
||
viene valutato se e solo se il lato sinistro èfalse
.
Diversi programmi che abbiamo scritto in precedenza hanno usato l'operatore AND logico. Quei programmi hanno usato l'operando sinistro per verificare se fosse sicuro valutare l'operando destro. Ad esempio, supponiamo di voler verificare se la divisione tra due numeri interi i
e j
sia maggiore di 10. Per evitare di dividere per zero, potremmo scrivere:
if (j != 0 && i / j > 10)
cout << "i/j è maggiore di 10\n";
Il codice controlla prima che j
non sia zero. Così abbiamo la garanzia che l'operando destro, ossia la divisione, non verrà valutato a meno che j
non sia diverso da zero.
Come esempio che usa l'OR logico, immaginiamo di avere del testo in un vector
di stringhe. Vogliamo stampare le stringhe, aggiungendo una nuova riga dopo ogni stringa vuota o dopo una stringa che termina con un punto. Useremo un ciclo for
basato su range per elaborare ogni elemento:
// nota s come riferimento a const;
// gli elementi non vengono copiati e non possono essere modificati
for (const auto &s : text) { // per ogni elemento in text
cout << s; // stampa l'elemento corrente
// le righe vuote e quelle che terminano
// con un punto ottengono una nuova riga
if (s.empty() || s[s.size() - 1] == '.')
cout << endl;
else
cout << " "; // altrimenti separa solo con uno spazio
}
Dopo aver stampato l'elemento corrente, controlliamo se dobbiamo stampare una nuova riga. La condizione nell'if
controlla prima se s
è una stringa vuota. Se sì, dobbiamo stampare una nuova riga indipendentemente dal valore dell'operando destro. Solo se la stringa non è vuota valutiamo la seconda espressione, che controlla se la stringa termina con un punto. In questa espressione, ci affidiamo alla valutazione di cortocircuito di ||
per assicurarci di indicizzare s
solo se s
non è vuota.
Vale la pena notare che abbiamo dichiarato s
come riferimento a const
. Gli elementi in text
sono stringhe e potrebbero essere grandi. Facendo di s
un riferimento, evitiamo di copiare gli elementi. Poiché non abbiamo bisogno di scrivere negli elementi, abbiamo fatto di s
un riferimento a const
.
Operatore NOT Logico
L'operatore NOT logico (!
) restituisce l'inverso del valore di verità del suo operando. Come esempio, assumendo che vec
sia un vector
di int
, potremmo usare l'operatore NOT logico per vedere se vec
ha elementi negando il valore restituito da empty
:
// stampa il primo elemento in vec se ce n'è uno
if (!vec.empty())
cout << vec[0];
La sottoespressione
!vec.empty()
valuta a true
se la chiamata a empty
restituisce false
.
Gli Operatori Relazionali
Gli operatori relazionali (<
, <=
, >
, >=
) hanno i loro significati ordinari e restituiscono valori bool
. Questi operatori sono associativi a sinistra.
Poiché gli operatori relazionali restituiscono bool
, il risultato del concatenamento di questi operatori è probabilmente sorprendente:
// ERRORE!
// questa condizione confronta k con il risultato bool di (i<j)
if (i < j < k) // true se k è maggiore di 1!
Questa condizione raggruppa i
e j
al primo operatore <
. Il risultato bool di quell'espressione è l'operando sinistro del secondo operatore minore di. Cioè, k
viene confrontato con il risultato true
/false
del primo confronto! Per ottenere il test che intendevamo, possiamo riscrivere l'espressione come segue:
// OK: la condizione è true se i è minore di j e j è minore di k
if (i < j && j < k) { /* ... */ }
Test di Uguaglianza e i Letterali bool
Se vogliamo testare il valore di verità di un oggetto aritmetico o puntatore, il modo più diretto è usare il valore come condizione:
// true se val è qualsiasi valore non zero
if (val) { /* ... */ }
// true se val è zero
if (!val) { /* ... */ }
In entrambe le condizioni, il compilatore converte val
in bool
. La prima condizione ha successo finché val
è diverso da zero; la seconda ha successo se val
è zero.
Potremmo pensare di poter riscrivere un test di questo tipo come
// true solo se val è uguale a 1!
if (val == true) { /* ... */ }
Ci sono due problemi con questo approccio. Primo, è più lungo e meno diretto del codice precedente (anche se ammettendo che quando si impara C++ per la prima volta questo tipo di abbreviazione può essere sconcertante). Molto più importante, quando val
non è un bool
, questo confronto non funziona come previsto.
Se val
non è un bool
, allora true
viene convertito nel tipo di val
prima che l'operatore ==
venga applicato. Cioè, quando val
non è un bool
, è come se avessimo scritto
if (val == 1) { /* ... */ }
Come abbiamo visto, quando un bool
viene convertito in un altro tipo aritmetico, false
si converte in 0 e true
si converte in 1. Se ci importasse davvero se val
fosse lo specifico valore 1, dovremmo scrivere la condizione per testare quel caso direttamente.
Di solito è una cattiva idea usare i letterali booleani true
e false
come operandi in un confronto. Questi letterali dovrebbero essere usati solo per confrontare con un oggetto di tipo bool
.
Esercizi
-
Spiega il comportamento della condizione nel seguente
if
:const char *cp = "Hello World"; if (cp && *cp)
Soluzione: La condizione verifica che
cp
non sia un puntatore nullo e che il primo carattere a cuicp
punta non sia il carattere nullo ('\0'
). In altre parole, controlla checp
punti a una stringa valida e non vuota. -
Scrivi la condizione per un ciclo
while
che leggerebbeint
dallo standard input e si fermerebbe quando il valore letto è uguale a 42.Soluzione:
int val; while (cin >> val && val != 42) { // ... }
-
Scrivi un'espressione che testa quattro valori,
a
,b
,c
ed
, e assicura chea
sia maggiore dib
, che è maggiore dic
, che è maggiore did
.Soluzione:
if (a > b && b > c && c > d) { // ... }
-
Assumendo che
i
,j
ek
siano tuttiint
, spiega cosa significai != j < k
.Soluzione: L'espressione
i != j < k
viene valutata comei != (j < k)
. Prima viene valutatoj < k
, che restituisce un valore booleano (true
ofalse
). Poi, questo valore booleano viene confrontato coni
usando l'operatore!=
. Quindi, l'espressione verifica sei
è diverso dal risultato del confrontoj < k
.