Scaricare la presentazione
PubblicatoBiagino Rossa Modificato 9 anni fa
1
Informatica A.A. 2009/2010 Parte 4 Dai diagrammi di flusso alla programmazione strutturata: le istruzioni if, for, while, … Corso A: Prof. Stefano Berardi Corso B: Prof. Ugo de’ Liguoro
2
“Corrado Bohm” Milano, 1923. Professore emerito di
Teoria e applicazione delle macchine calcolatrici presso l’Università di Roma La Sapienza
3
Indice Parte 4: Diagrammi
Il flusso dell’esecuzione e i diagrammi di flusso. Il salto condizionale: l’istruzione IF. Iterazione: le istruzioni WHILE e FOR. La programmazione strutturata e il teorema di Bohm-Jacopini. I diagrammi di flusso venivano usati per descrivere i programmi nell’epoca in cui si programmava in Assembly. Oggi sono ancora adatti per spiegare programmi molto brevi.
4
1. Il flusso dell’esecuzione
L’esecuzione dei programmi C++, assembly, e in generale dei programmi detti “imperativi”, consiste in una successione di trasformazioni dello stato (delle memorie della RAM e del program counter, l’indirizzo della prossima istruzione da eseguire) Ogni trasformazione di stato è l’effetto dell’esecuzione di un’istruzione. Per es., una assegnazione x=3; oppure un’istruzione JB L1 che fa “saltare” l’esecuzione a un’istruzione lontana di indirizzo L1 se una certa condizione è vera. Il flusso è l’ordine in cui le istruzioni di un programma vengono eseguite. 4-Diagrammi
5
Diagrammi di flusso I diagrammi di flusso sono una rappresentazione in forma di grafo del flusso dell’esecuzione di un programma. Erano un progresso rispetto al linguaggio assembly, perché spostavano l’attenzione dalle istruzioni del programma alla loro relazione reciproca. inizio/fine input/output direzione del flusso decisione assegnazione 4-Diagrammi
6
Esempio di diagramma di flusso: area di un rettangolo
Ogni freccia rappresenta la scelta della prossima istruzione da eseguire. inizio leggi base area = base*altezza leggi altezza stampa area fine
7
Il programma C++ corrispondente
int main() {double base, altezza, area; cout << "base = "; cin >> base; cout << "altezza = "; cin >> altezza; area = base*altezza; cout << "area = " << area << endl; system(“pause”); } 4-Diagrammi
8
Sequenza di diagrammi e Blocchi di istruzioni in C++
Ck … {C1; C2; … Ck;} Sequenza di diagrammi Blocco di istruzioni Più in generale, rappresentiamo una sequenza di diagrammi in C++ scrivendo le istruzioni che li rappresentano di seguito, quindi racchiudendole tra graffe, per formare una singola istruzione detta istruzione composta o blocco. 4-Diagrammi
9
Il flusso dell’esecuzione in C++
Per gestire le variabili il C++ segue da vicino la nozione di indirizzo di macchina di von Neumann. Invece per la gestione dei “salti” da una istruzione all’altra il C++ segue un’idea completamente diversa, quella della programmazione strutturata. Noi spiegheremo la programmazione strutturata attraverso i diagrammi di flusso. I diagrammi di flusso spiegano come un programma funziona, ma hanno l’incoveniente di essere leggibili solo per programmi brevissimi. 4-Diagrammi
10
Programmazione strutturata
La programmazione strutturata individua i diagrammi di flusso piu’ utili tra quelli con una sola entrata ed una sola uscita. Per ciascuno di essi viene introdotto una istruzione: blocchi, IF, WHILE, FOR, … del C/C++. I programmi in C++ sono costruiti con le istruzioni if, while, for cosi’ introdotte. Oggi usiamo i diagrammi di flusso per introdurre le istruzioni IF, WHILE, FOR, … e in pochi altri casi. Vediamo ora i diagrammi di flusso più importanti insieme alla loro traduzione in C++. 4-Diagrammi
11
2. Il diagramma di selezione (IF)
L’istruzione IF rappresenta in C++ e in quasi tutti i linguaggi il seguente “diagramma di selezione a due vie” L’IF fa “saltare” l’esecuzione all’indirizzo di C se l’espressione B vale vero, e all’indirizzo dell’istruzione D se B vale falso. B C D true false if (B) C; else D;
12
L’istruzione IF in C++ L’IF viene tradotto nei compilatori nel linguaggio Assembly con un salto condizionato, che fa eseguire come prossima istruzione C oppure D a seconda se B è vero oppure falso. Noi utilizzeremo l’IF solo come istruzione primitiva, con la seguente sintassi: Al posto di istruzione1 e istruzione2 possiamo scrivere una qualunque istruzione o blocco di istruzioni del C++, compresi altri IF: in questo caso si parla di IF annidati. Il C++ consente un secondo tipo di IF, l’IF tronco. if (<espr.booleana>) <istruzione1>; else <istruzione2>;
13
IF “tronco” (senza ELSE)
B C true false if (B) C; if (<espr. booleana>) <istruzione>; L’IF tronco rappresenta la selezione a due vie con una sola azione C. Se l’espressione booleana B è falsa saltiamo all’istruzione successiva senza far nulla Vediamo ora alcuni esempi di uso di IF, IF tronco e IF annidato
14
Primo esempio di IF: equazione di 1° grado ax=b
Discussione: se a = 0 eq. indeterminata o impossibile, altrimenti x = b/a. inizio leggi a, b a 0 true false stampa b/a stampa “indet. o imposs.” fine 4-Diagrammi
15
Il programma per l’equazione di 1° grado
int main() { double a, b; cout << "a = "; cin >> a; cout << "b = "; cin >> b; if (a != 0) cout << "x = " << b/a << endl; else cout << “Indet. o impossibile" << endl; system(“pause”);} 4-Diagrammi
16
Secondo esempio di IF: equazione di 2° grado, IF “annidati”
Sia: > 0: due soluzioni reali = 0: una soluzione reale < 0: non ci sono soluzioni reali. Per scrivere questo programma dovremo utilizzare IF scritti dentro altri IF (IF“annidati”), per distinguere il secondo caso dal terzo.
17
Esempio di IF: diagramma equazione di 2° grado
leggi a, b, c inizio Delta=b*b–4*a*c Questa viene detta una decisione annidata true Delta>0 false Delta=0 rad=Delta false true Stampa: (– b rad)/(2*a) Stampa:–b/(2*a) Stampa: “no sol. reali” fine 4-Diagrammi
18
Soluzione eq. di 2° grado in C++
main(){ double a, b, c; cin >> a; cin >> b; cin >> c; double Delta = b*b - 4*a*c; if (Delta > 0){double rad= sqrt(Delta); cout << "prima sol. reale = " << (-b + rad)/2*a << endl; cout << "seconda sol. reale = "<< (-b - rad)/2*a<<endl;} else if (Delta == 0) cout << "Unica sol. reale = " << -b/(2*a) << endl; else cout << "Nessuna soluzione reale" << endl; system("pause");} sqrt = radice quadrata (funzione della libreria cmath) Questo è un if “annidato” Questo else si riferisce al secondo if Ma come facciamo a stabilirlo?
19
Quando ci sono IF “annidati” come accoppiamo un if con un else?
Per definizione, la clausola else si riferisce all’ultimo if non accoppiato con un else precedente: if (B) if (B’) C; else C’; else D; Il secondo “else” viene accoppiato con il primo IF. In questo caso, l’istruzione D verrà eseguita se B è falso; l’istruzione C’ verrà eseguita se B è vero, ma B’ è falso. 4-Diagrammi
20
If “annidati”: un altro esempio ambiguita’
Dato che la clausola else si riferisce all’ultimo if non accoppiato con un else precedente, se manca un solo else (se un IF è tronco) tutto cambia: if (B) if (B’) C; else D; Questa volta “else” viene accoppiato con il secondo IF. Dunque l’istruzione D verrà eseguita se B è vero e B’ è falso, non se B è falso. 4-Diagrammi
21
If annidati: come eliminare le ambiguita’
Avvolgendo ogni sottoistruzione in parentesi graffa ogni ambiguitá scompare: Queste parentesi sono necessarie se vogliamo accoppiare il primo IF con l’else if (B) { if (B’) C; } else D; L’istruzione D verrà eseguita se B è falso. Le parentesi non sono sempre necessarie come in questo caso, peró sono sempre utili per aggiungere chiarezza. Esempio: es Hubbard: ``indovina il numero’’
22
Come rappresentare una scelta tra n+1 blocchi
È sufficiente annidare n comandi IF, ciascuno dopo l’else dell’IF precedente. Il risultato è un’istruzione composta che esegue il primo blocco se la prima espressione booleana è vera, il secondo blocco se la prima è falsa e la seconda è vera, eccetera, l’ultimo blocco se tutte le espressioni booleane sono false. if (B1) <blocco_1>; //se B1 vera else if (B2) <blocco_2>; //se B1 falsa e B2 vera else if (B3) <blocco_3>; //se B1,B2 false e B3 vera … … … … else <blocco_(n+1)>;//se B1,…,Bn false
23
Un IF con più di due scelte: l’istruzione SWITCH(sconsigliata)
Un tempo per rappresentare la scelta tra più di due alternative veniva usata una istruzione chiamata SWITCH. Per completezza nel prossimo lucido spieghiamo il funzionamento dello SWITCH. Tuttavia ne sconsigliamo l’uso, sia per ragioni semplicità di scrittura, sia perché è facile sbagliarsi a usarlo. Al posto dello SWITCH, usate un IF annidato come spiegato nel lucido precedente.
24
Esecuzione dello SWITCH
switch (<espressione>) { case <costante_1>: <istruzioni_1>; break; ... case <costante_k>: <istruzioni_k>; break; default: <istruzioni>; } Esecuzione dello SWITCH. Confrontiamo il valore dell’espressione con le costanti. Trovata la prima che coincide col valore eseguiamo le istruzioni relative. Arrivati al break usciamo dal blocco (le parentesi {…}). Quando non troviamo una costante uguale al valore, eseguiamo le istruzioni che seguono il “default”.
25
3. L’iterazione: il ciclo while
L’iterazione è l’esecuzione ripetuta di una o più istruzioni (dette “corpo” dell’iterazione). Un diagramma iterativo viene detto un ciclo. Un ciclo è controllata dal valore di verità di un certo test. Viene tradotto dal compilatore in linguaggio Assembly mediante un “salto” condizionato JB (ovvero mediante una assegnazione del program counter) che va “all’indietro”, verso una istruzione già eseguita in precedenza. Come primo e fondamentale esempio di ciclo vediamo il ciclo while.
26
Il diagramma di flusso del ciclo while
Un ciclo while rappresenta il seguente diagramma di flusso: B C true false while (B) C; Il corpo puo’ essere un qualunque blocco di istruzioni C++. Finché B è vero, il while esegue il corpo C e poi di nuovo B. La prima volta che B è falso il while termina: dunque se B è sempre vero il while non termina mai. La sintassi del while è while (<espr. booleana>) <corpo>;
27
Un programma che richiede il while: la somma dei reciproci
Problema: dato un intero n calcolare e stampare: Soluzione: utilizziamo il seguente algoritmo. Partiamo con s=0, i=1. Finché i<=n, eseguiamo s=s+1.0/i, i=i+1. In questo modo, s prende i valori 1, 1+1/2, 1+1/2+1/3, …, mentre i prende i valori 1, 2, 3, … . Quando i>n, terminiamo e stampiamo s, che a questo punto vale (1+1/2+1/3 + … 1/n). Vediamo ora come tradurre tutto con un while. 4-Diagrammi
28
Somma dei reciproci calcolata con un while
int main() { int n; cout << "n = "; cin >> n; double s = 0.0; int i = 1; while (i <= n) {s = s + 1.0/i; i++;} cout << "La somma dei primi " << n << " reciproci = " << s << endl; system("pause");} Scrivendo invece: s = s + 1/i; la divisione 1/i è intera, dunque viente arrotondata: per es. 1/2 diventa 0 ++i è detto incremento. Cosa succede se lo eliminiamo ???
29
Il ruolo di ++i nella “somma dei reciproci”
L’incremento ++i modifica il valore di verità del test,l’azione svolta dal corpo, ed è cruciale per il buon funzionamento del ciclo. Se lo eliminiamo e lasciamo: while (i <= n) {s = s + 1.0/i;} allora, mentre ripetiamo il corpo {s = s + 1.0/i;} del ciclo while, il valore di i non cambia. Resta il valore iniziale i=1. Dunque continuiamo a eseguire s=s+1.0/i con i=1: quindi continuiamo a eseguire s = s + 1; ne segue che s assume i valori 0,1,2,3,4, …, anziche’ 1,1+1/2, … come dovrebbe. Inoltre il test (i <= n) resta uguale a (1 <= n) e quindi vale sempre true, il ciclo continua per sempre.
30
Un altro esempio di iterazione: il ciclo for
for ([<inizializzazione>]; [<test>]; [<step>]) <corpo> Le inizializzazioni sono definizioni di variabili o assegnazioni di valori a variabili precedentemente dichiarate; se più d’una, sono separate da virgole (e non da punti e virgole come al solito) Lo step o incremento di solito è l’incremento di una o più variabili; se più d’una, sono separate da virgole. L’incremento serve a modificare il valore di verità del test e, a volte, l’azione svolta dal corpo. Nota. A differenza di quanto avviene per il for nel Pascal, il test è un’espressione booleana arbitraria. 4-Diagrammi
31
Il diagramma di flusso per il ciclo for
L’inizializza-zione I viene eseguita una sola volta prima di entrare nel ciclo for (I; B; S) C; I false B true Al termine di S avviene sempre un salto all’indietro, verso l’istruzione che prima calcola B, poi a seconda del valore di B sceglie se ripetere il corpo del for o saltare alla prima istruzione dopo il for C Lo step S viene eseguito al termine di ogni esecuzione del corpo C S
32
La relazione tra while e for
A differenza di quanto succede in altri linguaggi, in C++ il ciclo while ed il ciclo for sono interdefinibili, usando le seguenti equazioni: for(I;B;S) C; = {I; while(B){C;S;}} while(B) C; = for(;B;) C; Ne segue che la differenza tra i due cicli è solo di stile: di solito il for è più chiaro, perché mette in evidenza la parte di inizializzazione e incremento. Come capita per il while, il for non termina mai se il test B resta sempre vero. Vediamo ora come usare il for per ripetere n volte la stessa azione. 4-Diagrammi
33
Il for consente di ripetere n volte una certa azione
Inizialmente i vale 0 for(i=0,n=100;i<n;++i) cout << “CIAO”; i = 0 n = 100 true false Al termine di ogni iterazione i viene incrementato di 1 i<n Scrivi “CIAO” Quando i vale 100 il ciclo termina ++i Il ciclo viene eseguito 100 volte, mentre i cresce da 0 a 99. Ogni volta stampiamo un “CIAO”.
34
Un diagramma per il calcolo della media aritmetica m = (a0 + … an-1)/ n
inizio i < n false leggi n true stampa m = s/n a = i-esimo addendo ai i = 0 leggi a s = 0 fine s = s + a A partire da questo diagramma di flusso si puó scrivere un programma in C++ usando un ciclo FOR i = i + 1
35
Il diagramma di flusso per la media (definito da un for)
Eseguito una sola volta prima di entrare nel ciclo for (I; B; S) C; i = 0 s = 0 true false i<n Leggi a s = s + a Eseguito al termine di ogni iterazione ++i 4-Diagrammi
36
Un programma C++ per la media aritmetica
++i è detto incremento. Se lo eliminiamo cosa succede ??? int main(){int n; int i, a, s; cout << "Media di n valori. n="; cin >> n; for (i = 0, s = 0; i < n; i++) { cout << "inserisci un valore="; cin >> a; s = s + a;} cout << "Media = " << (double) s / (double) n << endl; system("pause");} “Casting” necessario per avere la divisione senza troncamento 4-Diagrammi
37
Il ruolo di ++i nella “media aritmetica”
L’incremento ++i serve a modificare il valore di verità del test, ed è cruciale per il buon funzionamento del ciclo. Se lo eliminiamo e lasciamo: for (i = 0, s = 0; i < n; ) {cout << "inserisci un valore="; cin >> a; s = s + a;} allora mentre il corpo del ciclo for viene ripetuto il valore di i non cambia, resta il valore iniziale i=0. Dunque il test (i < n) resta uguale (0 < n) e quindi vale true. Il ciclo for continua per sempre. In generale, se dimentico di inserire gli incrementi il ciclo (for o while) non termina. 4-Diagrammi
38
Un ciclo meno usato: l’istruzione do…while
La differenza con il while è che il corpo del do while viene eseguito almeno una volta do <comando>; while (<espr. booleana>); B C true false do C; while (B); Vi chiediamo di saper riconoscere questa istruzione, ma vi consigliamo di usare solo while e for. Il Pascal ha una istruzione simile: do … until, che esce però quando il test è vero. 4-Diagrammi
39
Esempio: stampa dei fattoriali inferiori a un certo limite
int main(){ long limite; cout << "Inserire una limitazione superiore: "; cin >> limite; cout << "Elenco fattoriali minori di " << limite << endl; long f = 1, i = 0; /* f=1=0!=i! Faremo sì che la condizione f=i! resti vera durante tutta l’esecuzione del ciclo (continua nel prossimo lucido) */ 4-Diagrammi
40
Esempio: stampa dei fattoriali inferiori a un certo limite
/* (continua dal lucido precedente) */ do {cout << i <<"! = " << f << endl; //stampo i! = f f = f * (i+1); //ora f vale: f*(i+1)=i!*(i+1)=(i+1)! i++; /*aggiorno i ad (i+1), di modo che per la nuova i (uguale alla vecchia i+1) valga la condizione f=i!.*/ } while (f < limite); system("pause");} ++i è detto incremento. Provate a eliminarlo e vedrete che, anche in questo caso, il ciclo continua per sempre 4-Diagrammi
41
4. Programmazione strutturata
Un’istruzione di “jump” (salto) è ad esempio: JB <etichetta> dove l’etichetta indica il punto del programma a cui saltare. JB è un’istruzione tipica del linguaggio Assembly, come abbiamo visto parlando della macchina di Von Neumann. In C++ l’istruzione JB esiste e si scrive goto. La programmazione strutturata, tuttavia, proibisce l’uso delle istruzioni di salto anche se disponibili, e si basa soltanto su sequenza (blocchi), selezione (IF) ed iterazione (WHILE, FOR, ecc.) per controllare il flusso del programma.
42
Programmazione strutturata
L’esperienza mostra che si parte da un diagramma non strutturato (cioè non combinazione di diagrammi per if e while) è difficile trasformarlo in un programma strutturato equivalente. Ma almeno in linea di principio, è sempre possibile trasformare un diagramma qualunque in uno equivalente, che pero’ corrisponda a un programma strutturato, cioè scritto con solo if e while? La risposta è si’ per il Teorema di Böhm-Jacopini del 1966 In altre parole: if e while sono in linea di principio sufficienti a scrivere qualunque programma. 4-Diagrammi
43
Teorema di Böhm-Jacopini
Ogni funzione Turing calcolabile è definibile in un linguaggio di programmazione con i comandi di: Blocco di istruzioni, IF e WHILE Ma cosa sono le funzioni Turing calcolabili? Per mostrare l’idea della prova senza definire le funzioni Turing-calcolabili, assumiamo che tutti gli algoritmi si possano descrivere con diagrammi di flusso, e mostriamo come un diagramma qualsiasi si possa tradurre in un programma strutturato con solo blocchi, if, while.
44
Cenno di prova: partiamo da un diagramma qualunque …
1 3 2 4 5 6 7 8 9 10 … e ad ogni nodo del “grafo” associamo un numero + - B1 C1 C2 + - + - B2 B3 C3 C4 C5 4-Diagrammi
45
Dopo aver numerato i nodi …
… usiamo una variabile contatore cp per sapere in quale nodo ci troviamo e per decidere verso quale nodo proseguire. A questo punto … 1 3 2 4 5 6 7 8 9 10 + - B1 C1 C2 + - + - B2 B3 C3 C4 C5 4-Diagrammi
46
… traduciamo il nostro diagramma con un ciclo while e tanti if:
main(){int cp = 1;//cp=nodo del diagramma in cui ci troviamo while (cp < 10) { if (cp == 1) cp = 2; else if (cp == 2) {if (B1) cp = 3; else cp = 4;} else if (cp == 3) {C1; cp = 5;} else if (cp == 4) {C2; cp = 6;} else if (cp == 5) {if (B2) cp = 8; else cp = 4;} else if (cp == 6) {if (B3) cp = 9; else cp = 7;} else if (cp == 7) {C3; cp = 2;} else if (cp == 8) {C4; cp = 3;} else if (cp == 9) {C5; cp = 10}}} 1 3 2 4 5 6 7 8 9 10 + - B1 C1 C2 + - + - B2 B3 C3 C4 C5 4-Diagrammi
47
Conclusioni In base al Teorema di Böhm-Jacopini, per il controllo del flusso sono sufficienti i costrutti programmativi della programmazione strutturata (blocchi, if, while, senza l’istruzione goto). Attenzione però: la dimostrazione del teorema di Böhm-Jacopini non ci dà alcuna informazione su come trasformare un diagramma caotico in un programma strutturato leggibile. Per questo motivo non useremo i diagrammi di flusso, ma scriveremo direttamente il codice in forma strutturata, con if, while, for, …. 4-Diagrammi
48
Istruzioni di cui sconsigliamo l’uso
Il C/C++ comprende, oltre al goto, altre istruzioni di salto che erano tipiche dei più antichi linguaggi Assembly. Noi però non ne faremo uso, perche’ i “salti” producono facilmente errori e programmi poco leggibili. Solo per completezza, elenchiamo qui le istruzioni di salto del C++: goto break continue exit. Un’altra istruzione che sconsigliamo è lo SWITCH. Vedi esempi 4.16 e 4.19 dell’Hubbard
Presentazioni simili
© 2024 SlidePlayer.it Inc.
All rights reserved.