Istruzioni Condizionali in C++
Il linguaggio C++ fornisce due istruzioni che permettono l'esecuzione condizionale.
L'istruzione if
determina il flusso di controllo in base a una condizione. L'istruzione switch
valuta un'espressione intero e sceglie uno tra diversi percorsi di esecuzione in base al valore di un'espressione.
- Il linguaggio C++ fornisce due istruzioni che permettono l'esecuzione condizionale:
if
eswitch
. - L'istruzione
if
determina il flusso di controllo in base a una condizione. - L'istruzione
else
fornisce un percorso alternativo quando la condizione dell'if
è falsa. - L'istruzione
switch
valuta un'espressione intero e sceglie uno tra diversi percorsi di esecuzione in base al valore di un'espressione.
Istruzione if
Un'istruzione if
esegue condizionalmente un'altra istruzione in base al fatto che una condizione specificata sia vera. Esistono due forme di if
: una con un ramo else
e una senza. La forma sintattica del semplice if
è
if (condizione) istruzione;
Un'istruzione if else
ha, invece, la forma
if (condizione) istruzione else istruzione2;
In entrambe le versioni, condizione
deve essere racchiusa tra parentesi. condizione
può essere un'espressione o una dichiarazione di variabile inizializzata. L'espressione o la variabile deve avere un tipo convertibile in bool
. Come al solito, sia istruzione
che istruzione2
possono essere un blocco.
Se condizione
è vera, allora istruzione
viene eseguita. Dopo il completamento di istruzione
, l'esecuzione continua con l'istruzione successiva all'if
.
Se condizione
è falsa, istruzione
viene saltata. In un semplice if
, l'esecuzione continua con l'istruzione successiva all'if
. In un if else
, viene eseguita istruzione2
.
Utilizzo di un'Istruzione if
else
Per illustrare un'istruzione if
, calcoleremo un voto in lettere da un voto numerico. Supponiamo che i voti numerici vadano da zero a 100 inclusi. Un voto di 100 ottiene un "A++", i voti sotto 60 ottengono una "F", e gli altri sono raggruppati in fasce di dieci: i voti da 60 a 69 inclusi ottengono una "D", da 70 a 79 una "C", e così via. Useremo un vector<string>
per contenere i possibili voti in lettere:
const vector<string> voti = {"F", "D", "C", "B", "A", "A++"};
Per risolvere questo problema, possiamo usare un'istruzione if else
per eseguire azioni diverse per voti insufficienti e sufficienti:
// se il voto è inferiore a 60 è una F, altrimenti calcola un indice
string voto_lettera;
if (voto < 60)
voto_lettera = voti[0];
else
voto_lettera = voti[(voto - 50)/10];
A seconda del valore di voto
, eseguiamo l'istruzione dopo l'if
o quella dopo l'else
. Nell'else
, calcoliamo un indice dal voto riducendo il voto per tenere conto della gamma più ampia di voti insufficienti. Quindi usiamo la divisione intera, che tronca il resto, per calcolare l'indice appropriato di voti.
Istruzioni if
Annidate
Per rendere il nostro programma più interessante, aggiungeremo un più o un meno ai voti sufficienti. Daremo un più ai voti che terminano con 8 o 9, e un meno a quelli che terminano con 0, 1 o 2:
if (voto % 10 > 7)
voto_lettera += '+'; // i voti che terminano con 8 o 9 ottengono un +
else if (voto % 10 < 3)
voto_lettera += '-'; // quelli che terminano con 0, 1 o 2 ottengono un -
Qui usiamo l'operatore modulo per ottenere il resto e decidere in base al resto se aggiungere più o meno.
Successivamente incorporeremo il codice che aggiunge un più o un meno al codice che recupera il voto in lettere da voti:
// se il voto è insufficiente,
// non c'è bisogno di controllare più o meno
if (voto < 60)
voto_lettera = voti[0];
else {
voto_lettera = voti[(voto - 50)/10]; // recupera il voto in lettere
// aggiungi più o meno solo se non è già un A++
if (voto != 100)
if (voto % 10 > 7)
// i voti che terminano con 8 o 9 ottengono un +
voto_lettera += '+';
else if (voto % 10 < 3)
// quelli che terminano con 0, 1 o 2 ottengono un -
voto_lettera += '-';
}
Nota che usiamo un blocco per racchiudere le due istruzioni che seguono il primo else
. Se il voto è 60 o superiore, abbiamo due azioni da eseguire: recuperare il voto in lettere da voti
e impostare condizionalmente il più o il meno.
Attenzione alle Parentesi Graffe
È un errore comune dimenticare le parentesi graffe quando più istruzioni devono essere eseguite come un blocco. Nell'esempio seguente, contrariamente all'indentazione, il codice per aggiungere un più o un meno viene eseguito incondizionatamente:
if (voto < 60)
voto_lettera = voti[0];
else // ERRORE: manca la parentesi graffa
voto_lettera = voti[(voto - 50)/10];
// nonostante le apparenze, senza la parentesi graffa, questo codice viene sempre eseguito
// i voti insufficienti otterranno erroneamente un - o un +
if (voto != 100)
if (voto % 10 > 7)
// i voti che terminano con 8 o 9 ottengono un +
voto_lettera += '+';
else if (voto % 10 < 3)
// quelli che terminano con 0, 1 o 2 ottengono un -
voto_lettera += '-';
Scovare questo errore può essere molto difficile perché il programma sembra corretto.
Per evitare tali problemi, alcuni stili di codifica raccomandano di usare sempre le parentesi graffe dopo un if
o un else
(e anche intorno ai corpi delle istruzioni while
e for
).
Facendo così si evita qualsiasi possibile confusione. Significa anche che le parentesi graffe sono già presenti se modifiche successive del codice richiedono l'aggiunta di istruzioni.
Molti editor e ambienti di sviluppo hanno strumenti per indentare automaticamente il codice sorgente in modo che corrisponda alla sua struttura. È una buona idea usare tali strumenti se sono disponibili.
else
Pendente
Quando annidiamo un if
dentro un altro if
, è possibile che ci siano più rami if
che rami else
. Infatti, il nostro programma di valutazione ha quattro if
e due else
. Si pone la questione: Come facciamo a sapere a quale if
appartiene un dato else
?
Questo problema, solitamente chiamato else pendente (dangling else in inglese), è comune a molti linguaggi di programmazione che hanno sia istruzioni if
che if else
. Linguaggi diversi risolvono questo problema in modi diversi. In C++ l'ambiguità viene risolta specificando che ogni else
viene abbinato all'if
precedente non abbinato più vicino.
I programmatori a volte si mettono nei guai quando scrivono codice che contiene più if
che rami else
. Per illustrare il problema, riscriveremo l'if else
più interno che aggiunge un più o un meno usando un insieme diverso di condizioni:
// SBAGLIATO: l'esecuzione NON corrisponde all'indentazione;
// l'else va con l'if interno
if (voto % 10 >= 3)
if (voto % 10 > 7)
voto_lettera += '+'; // i voti che terminano con 8 o 9 ottengono un +
else
voto_lettera += '-'; // i voti che terminano con 3, 4, 5, 6 o 7 ottengono un meno!
L'indentazione nel nostro codice indica che intendiamo che l'else
vada con l'if
esterno, ossia intendiamo che il ramo else
venga eseguito quando il voto termina con una cifra inferiore a 3. Tuttavia, nonostante le nostre intenzioni, e contrariamente all'indentazione, il ramo else
fa parte dell'if
interno. Questo codice aggiunge un '-'
ai voti che terminano da 3 a 7 inclusi! Indentato correttamente per corrispondere al percorso di esecuzione effettivo, quello che abbiamo scritto è:
// l'indentazione corrisponde al percorso di esecuzione, non all'intento del programmatore
if (voto % 10 >= 3)
if (voto % 10 > 7)
// i voti che terminano con 8 o 9 ottengono un +
voto_lettera += '+';
else
// i voti che terminano con 3, 4, 5, 6 o 7 ottengono un meno!
voto_lettera += '-';
Controllo del Percorso di Esecuzione con le Parentesi Graffe
Possiamo far sì che l'else
faccia parte dell'if
esterno racchiudendo l'if
interno in un blocco:
// aggiungi un più per i voti che terminano con 8 o 9 e un meno per quelli che terminano con 0, 1 o 2
if (voto % 10 >= 3) {
if (voto % 10 > 7)
// i voti che terminano con 8 o 9 ottengono un +
voto_lettera += '+';
} else // le parentesi graffe forzano l'else ad andare con l'if esterno
// i voti che terminano con 0, 1 o 2 otterranno un meno
voto_lettera += '-';
Le istruzioni non attraversano i confini dei blocchi, quindi l'if
interno termina alla parentesi graffa di chiusura prima dell'else
. L'else
non può far parte dell'if
interno. Ora, l'if
non abbinato più vicino è l'if
esterno, che è ciò che intendevamo fin dall'inizio.
Esercizi
-
Usando un'istruzione
if–else
, scrivi la tua versione del programma per generare il voto in lettere da un voto numerico.Soluzione:
#include <iostream> #include <string> using namespace std; int main() { const string voti[] = {"F", "D", "C", "B", "A", "A++"}; int voto; cout << "Inserisci un voto numerico (0-100): "; cin >> voto; string voto_lettera; if (voto < 60) voto_lettera = voti[0]; else { voto_lettera = voti[(voto - 50) / 10]; if (voto != 100) { if (voto % 10 > 7) voto_lettera += '+'; else if (voto % 10 < 3) voto_lettera += '-'; } } cout << "Il voto in lettere è: " << voto_lettera << endl; return 0; }
-
Riscrivi il tuo programma di valutazione per usare l'operatore condizionale al posto dell'istruzione
if–else
.Soluzione:
#include <iostream> #include <string> using namespace std; int main() { const string voti[] = {"F", "D", "C", "B", "A", "A++"}; int voto; cout << "Inserisci un voto numerico (0-100): "; cin >> voto; string voto_lettera; voto_lettera = (voto < 60) ? voti[0] : voti[(voto - 50) / 10]; if (voto != 100) { voto_lettera += (voto % 10 > 7) ? '+' : '-'; } cout << "Il voto in lettere è: " << voto_lettera << endl; return 0; }
-
Correggi gli errori in ciascuno dei seguenti frammenti di codice:
-
Frammento (a):
if (val_int1 != val_int2) val_int1 = val_int2 else val_int1 = val_int2 = 0;
Soluzione:
Manca un punto e virgola alla fine della prima istruzione. Inoltre, l'uso di
else
senza parentesi graffe può portare a confusione. Ecco la versione corretta:if (val_int1 != val_int2) val_int1 = val_int2; else val_int1 = val_int2 = 0;
-
Frammento (b):
if (val_int < val_minimo) val_minimo = val_int; occorrenze = 1;
Soluzione:
L'istruzione
occorrenze = 1;
viene eseguita sempre, indipendentemente dal risultato dell'if
. Per far sì che venga eseguita solo quando la condizione è vera, racchiudiamo le istruzioni in un blocco:if (val_int < val_minimo) { val_minimo = val_int; occorrenze = 1; }
-
Frammento (c):
if (int val_int = ottieni_valore()) cout << "val_int = " << val_int << endl; if (!val_int) cout << "val_int = 0\n";
Soluzione:
La variabile
val_int
è definita all'interno del primoif
, quindi non è accessibile nel secondoif
. Per risolvere questo problema, dobbiamo definireval_int
al di fuori dell'if
:int val_int = ottieni_valore(); if (val_int) cout << "val_int = " << val_int << endl; if (!val_int) cout << "val_int = 0\n";
-
Frammento (d):
if (val_int = 0) val_int = ottieni_valore();
Soluzione:
L'operatore di assegnazione
=
viene usato invece dell'operatore di confronto==
. La condizione dovrebbe essereval_int == 0
:if (val_int == 0) val_int = ottieni_valore();
-
L'Istruzione switch
Un'istruzione switch
fornisce un modo conveniente per selezionare tra un numero (possibilmente grande) di alternative fisse. Come esempio, supponiamo di voler contare quante volte ciascuna delle cinque vocali appare in un segmento di testo. La logica del nostro programma è la seguente:
- Leggi ogni carattere nell'input.
- Confronta ogni carattere con l'insieme delle vocali.
- Se il carattere corrisponde a una delle vocali, aggiungi 1 al conteggio di quella vocale.
- Visualizza i risultati.
Ad esempio, quando eseguiamo il programma su di un testo di esempio, il possibile output è:
Numero di vocali a: 123
Numero di vocali e: 274
Numero di vocali i: 157
Numero di vocali o: 546
Numero di vocali u: 98
Possiamo risolvere il nostro problema nel modo più diretto usando un'istruzione switch
per confrontare ogni carattere con ciascuna delle cinque vocali:
// inizializza i contatori per ogni vocale
unsigned cont_a = 0, cont_e = 0, cont_i = 0, cont_o = 0, cont_u = 0;
char car;
while (cin >> car) {
// se car è una vocale, incrementa il contatore appropriato
switch (car) {
case 'a':
++cont_a;
break;
case 'e':
++cont_e;
break;
case 'i':
++cont_i;
break;
case 'o':
++cont_o;
break;
case 'u':
++cont_u;
break;
}
}
// stampa i risultati
cout << "Numero di vocali a: \t" << cont_a << '\n'
<< "Numero di vocali e: \t" << cont_e << '\n'
<< "Numero di vocali i: \t" << cont_i << '\n'
<< "Numero di vocali o: \t" << cont_o << '\n'
<< "Numero di vocali u: \t" << cont_u << endl;
Un'istruzione switch
viene eseguita valutando l'espressione tra parentesi che segue la parola chiave switch
. Quell'espressione può essere una dichiarazione di variabile inizializzata. L'espressione viene convertita in tipo intero. Il risultato dell'espressione viene confrontato con il valore associato a ogni case
.
Se l'espressione corrisponde al valore di un'etichetta case
, l'esecuzione inizia con la prima istruzione che segue quell'etichetta. L'esecuzione continua normalmente da quell'istruzione fino alla fine dello switch
o fino a un'istruzione break
.
Poiché l'esecuzione continua fino a un break
o alla fine dello switch
, di solito vogliamo includere un break
dopo l'ultima istruzione di ogni case
. Tuttavia, ci sono situazioni in cui il comportamento predefinito dello switch
è esattamente quello di cui abbiamo bisogno. Ogni etichetta case
può avere un solo valore, ma a volte abbiamo due o più valori che condividono un insieme comune di azioni. In tali casi, omettiamo un'istruzione break
, permettendo al programma di attraversare più etichette case
.
Ad esempio, potremmo voler contare solo il numero totale di vocali:
unsigned cont_vocali = 0;
// ...
switch (car)
{
// qualsiasi occorrenza di a, e, i, o, o u incrementa cont_vocali
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
++cont_vocali;
break;
}
Qui abbiamo impilato diverse etichette case
insieme senza alcun break
intermedio. Lo stesso codice verrà eseguito ogni volta che car
è una vocale.
Poiché i programmi C++ sono a forma libera, le etichette case
non devono necessariamente apparire su una nuova riga. Possiamo enfatizzare che i case
rappresentano un intervallo di valori elencandoli tutti su una singola riga:
switch (car)
{
// sintassi alternativa legale
case 'a': case 'e': case 'i': case 'o': case 'u':
++cont_vocali;
break;
}
Omettere un break
alla fine di un case
accade raramente. Se lo ometti, includi un commento che spieghi la logica.
Dimenticare un break
è una fonte comune di bug
È un malinteso comune pensare che vengano eseguite solo le istruzioni associate all'etichetta case
corrispondente. Ad esempio, ecco un'implementazione errata della nostra istruzione switch
per contare le vocali:
// attenzione: deliberatamente errato!
switch (car) {
case 'a':
++cont_a; // oops: dovrebbe avere un'istruzione break
case 'e':
++cont_e; // oops: dovrebbe avere un'istruzione break
case 'i':
++cont_i; // oops: dovrebbe avere un'istruzione break
case 'o':
++cont_o; // oops: dovrebbe avere un'istruzione break
case 'u':
++cont_u;
}
Per capire cosa succede, supponiamo che il valore di car
sia 'e'
. L'esecuzione salta al codice che segue l'etichetta case 'e'
, che incrementa cont_e
. L'esecuzione continua attraverso le etichette case
, incrementando anche cont_i
, cont_o
e cont_u
.
Sebbene non sia necessario includere un break
dopo l'ultima etichetta di uno switch
, la soluzione più sicura è fornirne uno. In questo modo, se successivamente viene aggiunto un case
addizionale, il break
è già presente.
L'Etichetta default
Le istruzioni che seguono l'etichetta default
vengono eseguite quando nessuna etichetta case
corrisponde al valore dell'espressione dello switch
. Ad esempio, potremmo aggiungere un contatore per tenere traccia di quante non-vocali leggiamo. Incrementeremo questo contatore, che chiameremo cont_altro
, nel case default
:
// se car è una vocale, incrementa il contatore appropriato
switch (car) {
case 'a': case 'e': case 'i': case 'o': case 'u':
++cont_vocali;
break;
default:
++cont_altro;
break;
}
In questa versione, se car
non è una vocale, l'esecuzione inizierà dall'etichetta default
e incrementeremo cont_altro
.
Può essere utile definire un'etichetta default
anche se non c'è lavoro da fare per il case default
. Definire una sezione default
vuota indica ai lettori successivi che il caso è stato considerato.
Un'etichetta non può stare da sola; deve precedere un'istruzione o un'altra etichetta case
. Se uno switch
termina con un case default
che non ha lavoro da fare, allora l'etichetta default
deve essere seguita da un'istruzione nulla o da un blocco vuoto.
Definizioni di Variabili all'Interno del Corpo di uno switch
Come abbiamo visto, l'esecuzione in uno switch
può saltare attraverso le etichette case
. Quando l'esecuzione salta a un particolare case
, qualsiasi codice che si trovava dentro lo switch
prima di quell'etichetta viene ignorato. Il fatto che il codice venga saltato solleva una domanda interessante: Cosa succede se il codice che viene saltato include una definizione di variabile?
La risposta è che è illegale saltare da un punto in cui una variabile con un inizializzatore è fuori scope a un punto in cui quella variabile è in scope:
case true:
// questa istruzione switch è illegale perché
// queste inizializzazioni potrebbero essere saltate
// ERRORE: il controllo salta una variabile inizializzata implicitamente
string nome_file;
// ERRORE: il controllo salta una variabile inizializzata esplicitamente
int val_int = 0;
// OK: perché val_j non è inizializzata
int val_j;
break;
case false:
// OK: val_j è in scope ma non è inizializzata
val_j = prossimo_num(); // OK: assegna un valore a val_j
if (nome_file.empty()) // nome_file è in scope ma non è stata inizializzata
// ...
Se questo codice fosse legale, allora ogni volta che il controllo saltasse al case false
, salterebbe l'inizializzazione di nome_file
e val_int
. Quelle variabili sarebbero in scope. Il codice che segue false
potrebbe usare quelle variabili. Tuttavia, queste variabili non sarebbero state inizializzate. Di conseguenza, il linguaggio non ci permette di saltare oltre un'inizializzazione se la variabile inizializzata è in scope nel punto in cui il controllo viene trasferito.
Se abbiamo bisogno di definire e inizializzare una variabile per un particolare case
, possiamo farlo definendo la variabile dentro un blocco, assicurando così che la variabile sia fuori scope nel punto di qualsiasi etichetta successiva.
case true:
{
// OK: dichiarazione di istruzione all'interno di un blocco di istruzioni
string nome_file = ottieni_nome_file();
// ...
}
break;
case false:
if (nome_file.empty()) // ERRORE: nome_file non è in scope
// ...
Esercizi
-
Scrivi un programma usando una serie di istruzioni
if
per contare il numero di vocali nel testo letto dacin
.Soluzione:
#include <iostream> using namespace std; int main() { unsigned cont_a = 0, cont_e = 0, cont_i = 0, cont_o = 0, cont_u = 0; char car; while (cin >> car) { if (car == 'a') ++cont_a; else if (car == 'e') ++cont_e; else if (car == 'i') ++cont_i; else if (car == 'o') ++cont_o; else if (car == 'u') ++cont_u; } cout << "Numero di vocali a: \t" << cont_a << endl << "Numero di vocali e: \t" << cont_e << endl << "Numero di vocali i: \t" << cont_i << endl << "Numero di vocali o: \t" << cont_o << endl << "Numero di vocali u: \t" << cont_u << endl; return 0; }
-
C'è un problema con il nostro programma di conteggio delle vocali come l'abbiamo implementato: Non conta le lettere maiuscole come vocali. Scrivi un programma che conti sia le lettere minuscole che maiuscole come la vocale appropriata, cioè, il tuo programma dovrebbe contare sia
'a'
che'A'
come parte dicont_a
, e così via.Soluzione:
#include <iostream> #include <cctype> using namespace std; int main() { unsigned cont_a = 0, cont_e = 0, cont_i = 0, cont_o = 0, cont_u = 0; char car; while (cin >> car) { car = tolower(car); // Converti il carattere in minuscolo if (car == 'a') ++cont_a; else if (car == 'e') ++cont_e; else if (car == 'i') ++cont_i; else if (car == 'o') ++cont_o; else if (car == 'u') ++cont_u; } cout << "Numero di vocali a: \t" << cont_a << endl << "Numero di vocali e: \t" << cont_e << endl << "Numero di vocali i: \t" << cont_i << endl << "Numero di vocali o: \t" << cont_o << endl << "Numero di vocali u: \t" << cont_u << endl; return 0; }
In questa versione, usiamo la funzione
tolower
dalla libreria<cctype>
per convertire ogni carattere in minuscolo prima di confrontarlo. -
Modifica il nostro programma di conteggio delle vocali in modo che conti anche il numero di spazi vuoti, tabulazioni e caratteri di nuova riga letti.
Soluzione:
#include <iostream> #include <cctype> using namespace std; int main() { unsigned cont_a = 0, cont_e = 0, cont_i = 0, cont_o = 0, cont_u = 0; unsigned cont_spazi = 0; char car; // Usa cin.get per leggere tutti i caratteri, inclusi spazi e nuove righe while (cin.get(car)) { if (isspace(car)) { ++cont_spazi; // Conta spazi, tabulazioni e nuove righe } else { car = tolower(car); // Converti il carattere in minuscolo if (car == 'a') ++cont_a; else if (car == 'e') ++cont_e; else if (car == 'i') ++cont_i; else if (car == 'o') ++cont_o; else if (car == 'u') ++cont_u; } } cout << "Numero di vocali a: \t" << cont_a << endl << "Numero di vocali e: \t" << cont_e << endl << "Numero di vocali i: \t" << cont_i << endl << "Numero di vocali o: \t" << cont_o << endl << "Numero di vocali u: \t" << cont_u << endl << "Numero di spazi vuoti, tabulazioni e nuove righe: \t" << cont_spazi << endl; return 0; }
In questa versione, usiamo
cin.get(car)
per leggere ogni carattere, inclusi spazi e nuove righe. Usiamo la funzioneisspace
per verificare se un carattere è uno spazio vuoto, una tabulazione o una nuova riga.