if/while ed espressioni logiche
Espressioni logiche
Nel flusso di esecuzione visto finora il processore del computer esegue le istruzioni del linguaggio in sequenza ed al termine conclude l’esecuzione del programma. Questo può andare bene per programmi di calcolo dove si richiede uno o piú input, e si generano uno o piú output, ma dove l’algoritmo non prevede istruzioni condizionali e/o cicli.
Nel momento in cui si sviluppa un programma che prevede invece sequenze di istruzioni condizionali, ovvero che si svolgono solo se viene verificata una condizione, occorre introdurre il modo in cui esprimere il concetto di condizione stessa.
Per questo usiamo le espressioni logiche. Questo tipo di costrutti ha la caratteristica specifica di poter essere vero oppure falso. Queste espressioni sono utilizzate per confrontare due o più valori (ad esempio numerici) e prevedono degli operatori di confronto specifici che identificano il tipo di confronto. Quelli usati dal C sono i seguenti:
Operatore | Significato |
== | Uguaglianza |
!= | Diversità |
> | Maggiore |
< | Minore |
>= | Maggiore uguale |
<= | Minore uguale |
Esempi:
a == b (vera se a è uguale a b)
a < 3 (vera se a minore di 3)
k == 4 (vera se k è uguale a 4)
c <= 6 (vera se c è minore o uguale a 6).
L’espressione logica é fondamentale per permettere una esecuzione non sequenziale del codice, per gestire quindi due possibili scenari:
- condizione: in base al valore dell’espressione, si decide di eseguire o meno un blocco di istruzioni.
- ciclo: in base al valore dell’espressiojne, si decide se ripetere o meno un blocco di istruzioni.
Vediamoli dettagliatamente.
Costrutto if
Le espressioni logiche servono per eseguire istruzioni in modo condizionale: se una espressione logica è vera, allora viene eseguita una certa sequenza di operazioni. Se non è vera quelle operazioni non vengono eseguite.
La sintassi è questa:
if (espressione logica) {
sequenza istruzioni A...
}
sequenza istruzioni B...
In pratica se l’espressione logica è vera, vengono eseguite le istruzioni dentro il blocco (sequenza istruzioni A).
Invece le istruzioni dopo il blocco (sequenza istruzioni B), che vengono eseguite in qualsiasi caso.
E’ possibile anche eseguire sequenze in modo alternativo:
if (espressione logica) {
sequenza A...
} else {
sequenza B...
}
sequenza C...
In pratica con if viene valutata l’espressione logica tra parentesi e se questa è vera, allora vengono eseguite le istruzioni della sequenza A, altrimenti vengono eseguite le istruzioni della sequenza B. Infine si eseguono, in entrambi i casi le istruzioni della sequenza c.
L’else è facoltativo, possiamo decidere anche di non metterlo se non ci serve.
Vediamo un paio di esempi (si riporta solo una porzione del programma).
int x;
cin >> x;
if (x == 3) {
cout << "x è uguale a 3" << endl;
}
In questo secondo esempio usiamo else.
int y;
cin >> y;
if (y > 5) {
cout << "y è maggiore di 5" << endl;
} else {
cout << "y è minore o uguale a 5" << endl;
};
Costrutto while
In molti problemi di programmazione è necessario ripetere più volte la stessa sequenza di istruzioni. Pensiamo ad applicazione di ricerca dati dove la ricerca viene ripetuta su file differenti, oppure una pagina di login che chiede più volte all’utente di inserire una password perché sbagliata. Il concetto lo abbiamo già visto nella lezione impariamo a programmare: l’obiettivo è scrivere una volta sola le istruzioni che devono essere ripetute e aggiungere una istruzione di condizione che verifica se ripeterle o meno.
Possiamo quindi definire il ciclo nel seguente modo: è una sequenza di istruzioni che viene eseguita più volte finché resta valida una condizione che richiede di ripetere il ciclo.
La condizione è necessaria perché senza di essa avremmo un ciclo infinito e il programma non terminerebbe mai. E’ inoltre necessario che l’espressione logica valutata cambi ad ogni ciclo. se venisse valutata ogni volta la stessa espressione logica ancora una volta avremmo un ciclo infinito. Per ottenere questo occorre quindi che ci sia almeno una variabile che cambi nel ciclo, e che questa variabile sia valutata per verificare se ripetere il ciclo. Ad esempio se vogliamo stampare i valori da 1 a 100, sarà necessario usare una variabile che tenga traccia del numero corrente e che dopo ogni stampa permetta di verificare se siamo arrivati a 100.
I cicli possono essere di due tipi:
- a iterazione definita:
In fase di esecuzione è conosciuto il numero di cicli che occorre eseguire. Un esempio è proprio la stampa di valori da 1 a N, dove N può essere un numero conosciuto in anticipo dal programmatore, o inserito dall’utente, in ogni caso è un valore noto al computer prima dell’inizio del ciclo.
In questo tipo di ciclo è possibile usare una variabile contatore che appunto ha il ruolo di contare il numero di volte che si esegue il ciclo. Per realizzare il ciclo è quindi necessario avere tre istruzioni:
- Una istruzione di inizializzazione del contatore (prima del ciclo);
- Una condizione che verifica se il valore del contatore raggiunge il valore finale del ciclo (ad inizio o fine ciclo)
- Una istruzione che modifica il contatore (dentro il ciclo).
- a iterazione non definita:
In questo caso il numero di cicli non è noto, perché dipende da fattori non dipendenti dal programma (ad es. input dell’utente). In questo caso è comunque necessaria una variabile, detta sentinella e sono previste almeno due istruzioni:
- Una istruzione di inizializzazione della sentinella (in generale ma non sempre prima del ciclo)
- Una condizione che verifica la sentinella (detta condizione sentinella)
In linguaggio C/C++ usiamo il costrutto while per rappresentare entrambe le tipologie di iterazione (vedremo successivamente altri costrutti, utili in situazioni specifiche). La sua sintassi è la seguente:
while(espressione logica) {
sequenza A...
}
sequenza B...
While valuta una espressione e se questa è vera, esegue il blocco di istruzioni. Al termine del blocco, viene verificata di nuovo la condizione del while, e se vera, viene ripetuto il ciclo. Se falsa si passa alla sequenza B e il programma prosegue.
Proviamo ad applicarlo ad una iterazione definita.
int n;
cout << "Inserisci un valore: ";
cin >> n;
int i = 0; // inizializzazione
while (i < n) { // condizione (n è noto in fase di esecuzione)
cout << i << endl;
i = i + 1; // modifica della variabile
}
La condizione viene ripetuta fino a quando la condizione diventa falsa. E’ compito del programmatore fare in modo che il ciclo non diventi infinito, facendo in modo che la condizione cambi come nell’esempio riportato.
Vediamo ora una iterazione non definita. Poniamo di volere scrivere un programma che richiede all’utente un valore che deve essere sempre > 0:
int n = -1; // inizializzazione sentinella
while (n <= 0) { // condizione sentinella
cout << "Inserisci un valore positivo: ";
cin >> n; // modifica del valore da inserimento utente
}
cout << n << endl;
Come si può vedere in questo caso inizializziamo n ad un valore non valido (-1) in modo tale che la condizione sentinella consenta di entrare una prima volta nel ciclo while. A questo punto l’utente inserisce il valore, e questo viene valutato di nuovo dalla condizione sentinella. Se valido, si esce dal ciclo, altrimenti il valore viene richiesto di nuovo. Come si può vedere è una iterazione non definita, perché non possiamo sapere nemmeno in esecuzione quante volte l’utente sbaglierà.
Do-While
Quando vogliamo eseguire il ciclo almeno una volta, è prevista una variante del while, il costrutto do-while, che ha questa sintassi:
do {
sequenza A...
} while(espressione logica)
sequenza B...
Il principio di funzionamento è identico al while ma in questo caso il ciclo viene eseguito almeno una volta, e la condizione viene verificata solo al termine. Questo tipo di costrutto ha particolarmente senso proprio nella situazione descritta sopra, in cui almeno una volta dobbiamo chiedere un valore all’utente. Proviamo quindi a riscrivere l’esempio:
int n; // notare che non serve inizializzare
do { // condizione sentinella
cout << "Inserisci un valore positivo: ";
cin >> n; // modifica del valore da inserimento utente
} while (n <= 0); // condizione sentinella
cout << n << endl;
Operatori booleani
Gli operatori booleani sono usati per confrontare tra loro due espressioni logiche come quelle del paragrafo precedente. Hanno come risultato un valore booleano, quindi VERO o FALSO. Essi sono:
Operatore | Significato |
! | Not logico |
&& | And logico |
|| | Or logico |
Vediamoli in dettaglio.
AND
💡
in C si usa il simbolo && (doppio ampersand)
Detto operatore AND, confronta due condizioni, e ci dice che è vera SOLO se entrambe le condizioni sono vere.
Ad esempio:
a == b && c == b : vera se a è uguale a b E INOLTRE che c è uguale a b
z < 4 && z > 1 : vera se z è compreso tra 1 e 4 esclusi
Tabella di verità:
condizione 1 vera | condizione 1 falsa | |
condizione 2 vera | VERO | FALSO |
condizione 2 falsa | FALSO | FALSO |
OR
💡
in C si usa il simbolo || (doppio pipe)
Detto operatore OR, confronta due condizioni, e ci dice che è vera se anche solo una delle condizioni è vera.
Ad esempio:
a == b || b == C : vera se a è uguale a b OPPURE se b è uguale a C
z == 0 || z != 3 : vera se z è uguale a 0 OPPURE diverso da 3
condizione 1 vera | condizione 1falsa | |
condizione 2 vera | VERO | VERO |
condizione 2 falsa | VERO | FALSO |
NOT
💡
in C si usa il simbolo ! (not)
Detto operatore NOT, si usa per negare la condizione successiva (se falsa diventa vera, se vera diventa falsa).
Ad esempio:
!(x > 3): vera solo se x minore o uguale a 3;
!(x != 3): vera solo se è uguale a 3
è quindi possibile creare if contenenti espressioni ed operatori:
if (a > b && a > c) { cout << "a è il più grande";}