Diagrammi di flusso e strutture di controllo Peppe Poma
Il diagramma di flusso descrive graficamente un algoritmo: utilizza un insieme di simboli grafici (rettangoli, rombi, ovali, nodi,...) contenenti la descrizione delle istruzioni da svolgere e collegati tra loro da frecce orientate. I terminali di I/O dei blocchi devono essere sempre collegati. Dal diagramma di flusso è possibile ricavare agevolmente le istruzioni in tutti i linguaggi di programmazione. I diagrammi di flusso sono facilmente leggibili se si usano poche strutture di controllo.
Le strutture di controllo sono insiemi di istruzioni e servono per specificare l'ordine di esecuzione delle istruzioni stesse (controllo di esecuzione del flusso delle istruzioni).
In base al teorema di Bohm-Jacopini un programma, per quanto complesso, si può formulare utilizzando solo tre strutture logiche fondamentali:
sequenza
selezione
iterazione
Ogni struttura ha un solo ingresso e una sola uscita ed ognuna può essere concatenata con la successiva, oppure contenuta in tutte le altre quante volte si vuole (nidificazione); ma non è possibile l'intreccio o l'accavallamento; in quest'ultimo caso si ottiene il cosiddetto codice spaghetti.
L'uso delle strutture porta alla programmazione strutturata che vincola all'utilizzo di poche strutture logiche ma rende il test, la correzione e la manutenzione del SW più semplice, proprio perchè è più semplice individuare i blocchi base.
SIMBOLO GRAFICO
START - END
LINGUAGGIO C++
SIGNIFICATO
int main()
{
}
Inizio delle istruzioni eseguibili.
Per la parentesi graffa aperta: digitare ALT 123.
Termine delle istruzioni.
Per la parentesi graffa chiusa: digitare ALT 125.
Start e End sono gli unici simboli grafici che hanno un solo terminale.
Istruzioni eseguite sequenzialmente
SEQUENZA
istruzione1;
istruzione2;
Un insieme comunque complesso di istruzioni, eventualmente comprese tra parentesi graffe, si comporta come una singola struttura sequenza.
Un'unica istruzione è una singola struttura sequenza.
OUTPUT
cout<<”Hello world”;
cout<<var;
Stampa a video la stringa “Hello World”.
Stampa il contenuto della variabile “var”.
Per poter utilizzare una variabile bisogna prima dichiararla. (Vedi manuale)
Il cursore ritorna a capo.
Il cursore ritorna a capo.
L'esecuzione si arresta e il programma attende una immissione da tastiera conclusa da invio. Il valore immesso è memorizzato nella variabile var.
cin e cout sono le uniche funzioni C++ che useremo e dipendono dalle istruzioni:
#include <iostream>
using namespace std;
Il loro utilizzo prevede diverse opzioni e si rimanda al manuale.
Si valuta l’espressione booleana tra le parentesi tonde che seguono la parola chiave if :
cout<< endl;
cout<<”\n”;
INPUT
cin>>var;
SELEZIONE SEMPLICE
if ( espressione bool)
{
blocco istruzioni;
}
• se è vera si esegue il blocco di istruzioni comprese tra le prossime parentesi graffe aperte e chiuse;
• se è falsa il blocco delle istruzioni non è eseguito e si continua oltre le due parentesi graffe.
Quando “ if ( espressione booleana )” non è seguito da una parentesi graffa aperta, il blocco istruzioni è rappresentato dall'istruzione successiva
L'espressione viene valutata dal punto di vista booleano e contiene, in genere, gli operatori maggiore, minore, uguale, diverso, and, or, not.
Nel caso di espressione generica (ad es. aritmetica) se il risultato è zero si intende che l'espressione è falsa, se è diversa da zero l'espressione è vera.
if ( espressione booleana )
NON deve essere terminato da ';'
SELEZIONE DOPPIA
if ( espressione bool )
{
istruzioni1;
}
else
{
istruzioni2;
Se l'espessione booleana è vera si esegue soltanto il blocco istruzioni1 che segue la parola chiave if;
se è falsa si esegue soltanto il blocco istruzioni2 che segue la parola chiave else.
ATTENZIONE ALLE PARENTESI GRAFFE.
Le scritture seguenti generano errori di compilazione (else non preceduto da if):
if ( espressione )
if ( espressione )
}
istruzione1;
istruzione2;
else
{
istruzione3;
{
istruzione3;
istruzione1;
}
istruzione2;
else
{
istruzione3;
}
}
SELEZIONE MULTIPLA
if ( condizione1)
{
istruzione1;
}
else if ( condizione2)
{
istruzione2;
}
else
{
istruzione3;
}
Si valuta condizione1, come espressione booleana:
• se è vera si esegue soltanto il blocco contenente l’istruzione1, si esce dalla struttura;
• altrimenti si valuta la condizione2: se è vera si esegue soltanto il blocco contenente l’ istruzione2, si esce dalla struttura;
• Se entrambe le condizioni risultano false si esegue l’istruzione3.
Il numero di casi può essere esteso a piacere: basta aggiungere “else if”.
L'alternativa finale può non essere presente, basta non inserire else.
CICLO DO - WHILE
Il ciclo do-while si utilizza per rieseguire un blocco istruzioni finché una condizione risulta vera.
Inizialmente si esegue il blocco delimitato dalle parentesi graffe che seguono do, successivamente si valuta se la condizione booleana è vera o è falsa.
do
{
Istruzione 1;
Istruzione 2;
}while(condizione bool);
Se questa risulta vera si ripete il blocco istruzioni e si ricontrolla la condizione ad ogni ciclo. Finché la condizione è vera il blocco istruzioni sarà rieseguito. Quando essa diventa falsa si esce dal ciclo e si procede con la prossima struttura.
Il blocco istruzioni è eseguito almeno una volta.
Nel linguaggio C:
• il blocco istruzioni da iterare è racchiuso tra le parentesi graffe che seguono la parola chiave do.
• la condizione booleana da valutare è racchiusa tra le parentesi tonde che seguono la parola chiave while.
CICLO WHILE
while (condizione) è terminato dal carattere “;”
In effetti il compilatore non deve aspettarsi altre informazioni per eseguire la struttura, diversamente dal ciclo while.
Il ciclo while si utilizza per rieseguire un blocco istruzioni finchè una condizione risulta vera.
while (condizione bool)
{
istruzione 1;
istruzione 2;
A differenza del ciclo do-while la condizione in questo caso viene valutata prima di eseguire il blocco istruzioni.
Viene valutata la condizione booleana:
se è falsa il blocco istruzioni non verrà eseguito, si esce dal ciclo e si esegue la successiva struttura.
se è vera si esegue il blocco istruzioni e si ritorna a valutare la condizione.
Nel ciclo do-while il blocco istruzioni è eseguito almeno una volta, nel ciclo while potrebbe non essere mai eseguito.
L’istruzione while NON deve essere terminata dal ';' per comunicare al compilatore che deve associare un blocco istruzioni. Tuttavia l'errore non è segnalato (dato che il do- while richiede il ';' !).
Se si inserisce il ';' il while semplicemente non esegue istruzioni pur valutando continuamente la condizione: si può entrare in un loop infinito! errore tipico! Il programma sembra bloccato e non produce risultati.
CICLO FOR
}
Il ciclo for è un ciclo while associato ad una variabile contatore e conviene utilizzarlo quando è noto il numero delle iterazioni ( o cicli ) che si devono effettuare.
Per definire un for sono necessarie tre espressioni racchiuse da parentesi tonde e separate dal carattere ';':
for ( inizializzazione; condizione; step)
{
Istruzione1;
Istruzione2;
}
l’inizializzazione di una variabile contatore (spesso si usano le lettere i,j,k) viene eseguita una sola volta, prima del ciclo e stabilisce il primo estremo del contatore;
la condizione booleana, che viene valutata prima di ogni iterazione, e definisce il secondo estremo del contatore;
lo step che stabilisce la variazione del contatore (incremento o decremento) e viene eseguito al termine del blocco istruzioni
SWITCH CASE
switch (espressione)
{
case esp1: ist1; break;
case esp2: ist2; break;
case esp3: ist3; break;
case espn: istn; break;
default: istr default;
Se la condizione è vera viene eseguito il blocco istruzioni successivo; quindi si esegue l'espressione di step e si ritorna a valutare la condizione.
E’ possibile utilizzare il ciclo for per ripetere un blocco istruzioni all’infinito:
for (;;)
{
blocco istruzioni;
}
La struttura switch-case è un modo più elegante per gestire un numero piuttosto alto di costrutti if-else; presenta una elevata leggibilità.
Lo switch valuta l'espressione con risultato intero tra parentesi tonde e confronta il suo risultato con tutti i casi in successione (con l'espressione che segue la parola chiave case): il primo caso uguale all'espressione viene eseguito.
Il caso chiamato default viene eseguito solo se nessun case precedente è stato soddisfatto e si può omettere.
Ogni caso deve essere associato ad un valore intero costante: numero, carattere o espressione costante. Ad es. NON è possibile scrivere:
case (a<0) : istr1; break;
case (a!=6): istr2; break;
il compilatore risponde con:
error: 'a' cannot appear in a constant-expression|
mentre è possibile scrivere:
# define PI 3
..........
int a =2;
switch(a)
{
case 1: cout << "1" << break;
case 2: cout << "2" << break;
case PI: cout << "3.14" << break;
default: cout << "altro";
}
Invece se
# define PI 3.14
il compilatore risponde con:
error: case label does not reduce to an integer constant|
Per una migliore leggibilità è opportuno inserire l'espressione del case entro parentesi tonde.
IMPORTANTE:
Ogni case(n) va terminato con l'istruzione break che interrompe lo switch-case e ripassa il controllo al programma.
Se l'istruzione break manca, il diagramma di flusso in figura non è rispettato, e il compilatore esegue le istruzioni di tutti i casi successivi.
}
2. Istruzione break Peppe Poma
Nel punto precedente, esaminando l’istruzione switch, si è potuto comprendere l'utilità dell'istruzione break. L’istruzione break può essere utilizzata per uscire da un ciclo prima che la condizione diventi falsa.
Quando si esce da un ciclo con un’istruzione break, l’esecuzione del programma continua con l’istruzione che segue il ciclo stesso.
Vediamo un semplice esempio:
// Esempio di utilizzo dell’istruzione break
#include <iostream>
using namespace std;
int main()
{
int i;
for (i=1; i<10; i++)
{
if(i>5)
break;
cout << "Riga N: " << i << endl;
}
}
Cosa accade?
All'interno del ciclo for è inserita un'istruzione che stampa il numero della riga (in base al valore del contatore) e una selezione semplice. Avviando il programma si otterrà questa stampa:
Riga N: 1
Riga N:2
Riga N:3
Riga N:4
Riga N:5
Infatti l'istruzione che stampa il numero della riga viene eseguita mentra la variabile contatore (i) risulta minore di 5. Alla sesta volta la variabile contatore assume valore 6, pertanto la condizione della selezione risulta vera e viene eseguita l'istruzione break. Questa provoca l'immediata fine del ciclo, anche se la condizione del ciclo (i<10) risulta ancora vera.
2. Istruzione continue Peppe Poma
Come si è visto sopra il break provoca l’uscita immediata dal ciclo mente l’istruzione continue invece, fa in modo che le istruzioni che la seguono vengano ignorate ma non impedisce il controllo della condizione del ciclo. In sostanza permette di saltare il blocco di istruzioni che va dall’istruzione immediatamente successiva alla fine del ciclo considerato. Normalmente viene condizionata, poiché in caso contrario quel blocco di istruzioni non verrebbe mai eseguito, in nessun caso
Un esempio potrà chiarire ogni dubbio:
// Esempio di utilizzo dell’istruzione continue
// Stampa a video dei primi dieci multipli del 2
#include <iostream>
using namespace std;
int main()
{
int i;
for (i=1; i<=21; i++)
{
if (i%2==1)
continue;
cout << i << " ";
}
}
I numeri analizzati vanno dal 1 al 21, pertanto la stampa a video sarà la seguente:
2 4 6 8 10 12 14 16 18 20
Questo semplice programma valuta il contatore del ciclo che assume i valori (in questo caso) da 1 a 21 inclusi:
se il resto della divisione tra il valore del contatore (i) e 2 è uguale a 1 (caso numero dispari) l'istruzione stampa NON viene eseguita a causa dell'azione dell'istruzione continue;
se il resto resto della divisione tra il valore del contatore (i) e 2 è diversa da 1 (caso numero pari) l'istruzione continue NON viene eseguita e si stampa il valore.