Tutorial
5
Scritto da Roberto Navigli (roberto.navigli@iol.it)
Game Programming Italia: www.gpi.eden.it
Istruzioni
di controllo - prima parte
La
maggior parte dei linguaggi di programmazione permette
al programmatore di attuare delle scelte sulla base
del verificarsi o meno di determinate condizioni.
Questo tipo di istruzioni fa parte delle cosiddette
"istruzioni di controllo", quelle istruzioni,
cioè, che permettono di controllare il flusso
del programma.
L'istruzione
if
La
regina di queste istruzioni è l'if (in italiano:
se). La forma più generale di questa istruzione
è la seguente (la parte tra parentesi quadre
è opzionale):
if
(espressione) blocco1
[else blocco2]
dove
blocco1 e blocco2 possono essere una sequenza di istruzioni
racchiusa da parentesi graffe oppure una singola istruzione.
Il significato è il seguente: valuta l'espressione
e, se è vera, esegui le istruzioni del blocco1
altrimenti esegui le istruzioni del blocco2 (se l'else
è specificato). Vediamo alcuni esempi:
if
(a > 10) cout << "a è maggiore
di 10";
valuta
l'espressione a > 10 e, se è vera, stampa
la stringa:
a
è maggiore di 10
Un
altro esempio:
if
(a > 10) a = 11;
else a = 12;
In
questo caso se l'espressione a > 10 è vera,
cioè se a è maggiore di 10, allora viene
assegnato il valore 11 ad a, altrimenti ad a viene
assegnato il valore 12.
Vediamo un esempio completo:
#include
<iostream>
using
namespace std;
void
main()
{
// dichiarazione della variabile intera x e inizializzazione
int x = 10;
if (x > 10)
{
cout << "x > 10" << endl;
if (x > 20) cout << "x > 20"
<< endl;
}
else
{
cout << "x <= 10" << endl;
if (x > 0) cout << "x > 0" <<
endl;
}
}
L'esempio
mostra i diversi modi di costruire condizioni, utilizzando
blocchi di istruzioni (racchiusi da parentesi graffe)
o singole istruzioni. L'esempio è comunque
poco significativo perché la condizione x >
10 è banalmente sempre falsa. Una cosa da notare
è l'inizializzazione della variabile al momento
stesso della dichiarazione:
int
x = 10;
Esaminiamo
ora un programma che richiede un numero in input all'utente:
#include
<iostream>
using
namespace std;
void
main()
{
int numero;
cout << "inserisci un numero intero: ";
cin >> numero;
if (numero > 10) cout << "numero >
10";
else if (numero > 5) cout << "5 <
numero <= 10";
else cout << "numero <= 5";
}
Si
noti la "cascata" di if: se il numero è
maggiore di 10 allora fai questo, altrimenti se non
è maggiore di 10 e se è maggiore di
5 fai quest'altro, altrimenti fai quest'altro ancora.
L'unica linea interessante nell'esempio è:
cin
>> numero;
cin
è un altro identificatore predefinito del C++
e rappresenta l'input da console. >> è
l'operatore di input e legge dal dispositivo di input
standard un valore assegnandolo alla variabile specificata
alla sua destra.
L'operatore di input >> può essere utilizzato
con la maggior parte dei tipi di dato. Ad esempio
il seguente frammento richiede l'inserimento di un
numero in virgola mobile:
float f;
cin >> f;
cout << "hai inserito il numero: "
<< f;
Operatori
logici e relazionali
L'espressione
che viene valutata per permettere la "scelta"
di quale ramo dell'if eseguire, come abbiamo visto,
può contenere diversi operatori e, vedremo,
che può essere composta da più sotto
espressioni.
Gli operatori che introduciamo in questa sezione sono
utilizzati allo scopo di mettere in relazione e connettere
diverse espressioni tra di loro. Ad esempio, quando
scriviamo:
10
> 20
vogliamo
sapere se questa espressione è vera oppure
falsa, mettendo 10 in relazione con 20. In questo
caso essa è banalmente vera, ma non è
sempre possibile dedurre la veridicità di un'espressione
senza conoscere il contesto in cui essa viene valutata.
Nel paragrafo precedente abbiamo utilizzato alcuni
operatori relazionali, operatori cioè che permettono
di mettere in relazione valori diversi. Vediamoli:
Operatore
relazionale Significato
== Uguale
!= Diverso
> Maggiore
< Minore
>= Maggiore uguale
<= Minore uguale
Spesso
può tornare utile mettere insieme diverse espressioni
e connetterle in modo logico. A questo scopo, il C
fornisce i seguenti operatori logici:
Operatore
logico Significato
&& And
|| Or
! Not
L'operatore
&&, per rendere vera l'intera espressione,
richiede che entrambe le espressioni-operando siano
vere. Ad esempio:
(a
> 10) && (b < 20)
è
vera se le espressioni a > 10 e b < 20 sono
entrambe vere. Ancora:
(a
== 10) || (b != 5)
è
vera se la prima o la seconda espressione è
vera (o entrambe lo sono).
L'operatore ! è, al contrario degli altri due,
un operatore unario, cioè con un solo operando.
Se il suo operando è un'espressione vera, allora
esso la falsifica e viceversa. Ad esempio:
!(a
>= 6)
è
vera se è falsa l'espressione a >= 6 e viceversa.
Vediamo le tavole di verità degli operatori
logici:
a
b a && b a || b !a
0 0 0 0 1
1 0 0 1 0
0 1 0 1
1 1 1 1
dove
1 rappresenta vero e 0 falso. A questo proposito,
ricordiamo che tra i tipi predefiniti del C++ c'è
il tipo booleano. Una variabile di tipo booleano può
assumere solo due valori: vero (true) o falso (false).
Quando si è certi che il risultato di un'espressione
è booleano, è buona pratica utilizzare
i tipi booleani. Ad esempio, nel seguente frammento:
int
a = 10, b = 20;
bool test = a > b;
l'espressione
a > b restituisce valore true oppure false, quindi
essa può essere assegnata a una variabile di
tipo booleano. In questo frammento si può notare
che la dichiarazione (e l'inizializzazione) di più
di una variabile dello stesso tipo può essere
fatta semplicemente inserendo delle virgole tra l'una
e l'altra e senza ripetere il tipo. Ad esempio:
//
dichiara e inizializza f, dichiara g,
// entrambi floating point (virgola mobile)
float f = 5, g;
//
dichiara il carattere a e il carattere b senza inizializzarli
char a, b;
Per
confrontare una variabile con il valore 0 esiste un
modo "abbreviato" rispetto a quello tradizionale.
Il metodo più spontaneo è certamente:
if
(a == 0) cout << "a è uguale a 0!";
ma
il metodo più utilizzato dai programmatori
C è il seguente:
if
(!a) cout << "a è uguale a 0!";
il
che equivale a dire: se a non è vero, cioè
se a è falso, allora stampa la stringa. Forse
a prima vista questo può confondere un po'
le idee, ma cerchiamo di capire meglio: l'istruzione
if esegue l'istruzione che segue la condizione nel
solo caso in cui quest'ultima sia vera. Ma !a è
vera se a è falsa, cioè se è
uguale a 0.
Analogamente, invece di scrivere:
if
(a != 0) cout << "a è diverso da
0!";
si
può scrivere in forma abbreviata:
if
(a) cout << "a è diverso da 0!";
Infatti
a è vera se è diversa da 0 e falsa se
a è uguale a 0. Infatti le espressioni intere
vengono valutate vere se sono diverse da 0 e false
altrimenti.
Cortocircuitazione
Consideriamo
la seguente linea di codice:
if
((a > 10)&&(b < 20)&&(d)) d
= 0;
E'
bene tenere a mente che il C valuta le espressioni
utilizzando un meccanismo di cortocircuitazione, ovvero
valuta il meno possibile. In particolare, nell'esempio,
se la prima sottoespressione a > 10 non è
vera, non ha senso continuare a valutare il resto,
perché l'intera espressione risulta già
falsa (l'and richiede che i suoi operandi siano tutti
veri). Ad esempio:
if
((a == 1)||(b == 2)||(d > 5)) d = 0;
non
valuta le altre due sottoespressioni se a == 1 o non
valuta l'ultima espressione se a != 1 ma b == 2, perché
l'espressione risulta già vera.
L'istruzione switch
A
volte si viene a creare la situazione in cui è
necessario confrontare la stessa variabile con molti
valori. In questo caso viene in aiuto un'altra istruzione
del C: switch. La forma generale di switch è
la seguente:
switch(variabile)
{
case costante1:
istruzioni
break;
case costante2:
istruzioni
break;
// ...
case costanten:
istruzioni
break;
default:
istruzioni
}
il
significato dell'istruzione è il seguente:
se la variabile è uguale alla costante1, allora
esegui le istruzioni che si trovano subito sotto l'etichetta
costante1, se la variabile è uguale alla costante2,
allora esegui le istruzioni che si trovano subito
sotto l'etichetta costante2 e così via. Nel
caso in cui nessuno degli n casi si verifichino, vengono
eseguite le istruzioni che si trovano subito sotto
l'etichetta default. Non è obbligatorio specificare
il caso di default. Se non viene specificato e se
nessuno dei casi si verifica non viene eseguita nessuna
istruzione. Ad esempio:
#include
<iostream>
using
namespace std;
void
main()
{
char c;
cout << "Inserisci un carattere e premi
invio: ";
cin >> c;
switch(c)
{
case 'a':
cout << "Hai inserito la lettera a"
<< endl;
break;
case 'b':
cout << "Hai inserito la lettera b"
<< endl;
break;
case 'c':
cout << "Hai inserito la lettera c"
<< endl;
break;
default:
cout << "Carattere diverso da a, b e c"
<< endl;
}
}
L'utilità
dell'istruzione switch salta all'occhio se ci sono
molti "casi" da prevedere. Ovviamente non
possono essere inserite due etichette uguali, perché
il compilatore non saprebbe quali istruzioni associare
a quel caso.
L'istruzione break è il "delimitatore"
di un caso. Esso cioè comunica che il blocco
di istruzioni da eseguire è terminato e che
è possibile saltare alla fine dell'intero blocco
switch. È anche possibile omettere il break:
in questo caso l'esecuzione continua con le istruzioni
del caso immediatamente successivo o del caso di default.
Questa situazione è molto pericolosa, soprattutto
per un principiante, perché spesso si dimentica
distrattamente il break. Vediamo un esempio:
#include
<iostream>
using
namespace std;
void
main()
{
int a;
cout << "Inserisci un numero tra 0 e 3
e premi invio: ";
cin >> a;
switch(a)
{
case 0:
cout << "zero, ";
case 1:
cout << "uno, ";
case 2:
cout << "due, ";
case 3:
cout << "tre!" << endl;
break;
default:
cout << "Numero non compreso tra 0 e 3!"
<< endl;
}
}
Dopo
aver compilato ed eseguito il programma, se si preme
0 si ottiene:
zero,
uno, due, tre!
Se
si preme due si ottiene:
due,
tre!
Se
si preme tre si ottiene solo:
tre!
Se
si inserisce un numero non compreso tra 0 e 3 si ottiene
il messaggio:
Numero
non compreso tra 0 e 3!
|