Lezione 1 - Programmazione non strutturata Corso — Programmazione Programmazione e progettazione — Programmazione strutturata Marco Anisetti e-mail: [email protected] web: http://homes.di.unimi.it/anisetti/ Riepilogo Modulo 1 • Pre requisiti matematici, elementi di linguaggi formali, macchine a stati e architettura di riferimento • Valutazioni su algoritmi semplici e loro rappresentazione in un linguaggio adatto ad un elaboratore automatico • Notazione prefissa, infissa e postfissa nelle espressioni matematiche • Linguaggio naturale, Flow chart, pseudocodice, linguaggio di Programmazione • Grammatica di un linguaggio, BNF, espressioni regolari, carte sintattiche • Classificazione dei linguaggi di programmazione in dipendenza dell'esecutore e di chi scrive il programma Problema - algoritmo - programma Algoritmo - Modellazione - Programma • La modellazione del problema attraverso flow chart è utilissima, anche per produrre prove di correttezza del funzionamento, ma deve poi essere tradotto in un linguaggio comprensibile all'elaboratore • Abbiamo visto una anticipazione di un programma scritto per un modello di elaboratore (RAM) con prevalenti finalità di valutazione della complessità • Il linguaggio comprensibile per un elaboratore è quello quanto più possibile vicino alla sua vera logica di ragionamento Il programmatore anche se agisce con strumenti ad alto livello deve aver molto ben presente come funziona la macchina che eseguirà i programmi da esso sviluppati Codice assembler per Macchina (RAM) 1: Mem[0]:=0 2: read(Mem[1]) 3: if Mem[1]≥ 0 then goto 5 4: goto 7 5: Mem[3]:=Mem[0]-Mem[1] 6: if Mem[3]≥ 0 then goto 16 7: write(Mem[1]) 8: read(Mem[2]) 9: Mem[3]:= Mem[2]-Mem[1] 10: if Mem[3]≥ 0 then goto 12 11: goto 14 12: Mem[3]:=Mem[1]-Mem[2] 13: if Mem[3]≥ 0 then goto 8 14: Mem[1]:=Mem[2] +Mem[0] 15: goto 3 16: halt Programmazione non strutturata • Non è semplice capire cosa il codice fa guardandolo • Vediamo un costrutto condizionale if-then e degli assegnameni • Il problema principale è seguire i goto incondizionati • Non sempre posso avere una documentazione addizionale • Il programma deve avere una sua leggibilità indipendente da documentazione aggiuntiva • Al crescere delle dimensioni di un programma, diventa sempre più difficile per il programmatore padroneggiarlo nel suo complesso. • Il problema dei salti incondizionati e della relativa programmazione non strutturata è di cruciale importanza • Un salto condizionato è più facile da comprendere per via della condizione che lo genera Il programma precedente prende in ingresso una sequenza di numeri tipo 112223144 e ritorna la sequenza senza doppioni 12314 Programmazione non strutturata • E' stata per molti anni l'unica possibile • I salti non condizionati sono stati l'unico modo per avere un flusso differente dal flusso sequenziale • Alcuni salti incondizionati mappano strutture iterative come quelle viste nei primi esempi di flow chart • Altri però non hanno nessuna corrispondenza diretta con tali strutture • Questo genere di salti non condizionati (non in relazione con cicli) sono quelli più critici da valutare Flow chart e programmazione non strutturata • I flow chart possono essere usati anche a solo scopo di documentazione di un codice • Come esercizio si provi a scrivere il flow chart del programma RAM visto in precedenza • Evidenziare i goto con delle apposite frecce ed indicare condizioni e operazioni come visto nello scorso modulo • Individuate eventuali strutture iterative Soluzione Lezione 2 - Programmazione strutturata Corso — Programmazione Programmazione e progettazione — Programmazione strutturata Marco Anisetti e-mail: [email protected] web: http://homes.di.unimi.it/anisetti/ Università degli Studi di Milano — Dipartimento di informatica Programmazione imperativa • Programmazione classica in cui si ordina all'esecutore di compiere delle azioni • Fino ad ora abbiamo sempre implicitamente parlato di programmazione imperativa • Gli elementi caratterizzanti sono: Il valore delle variabili cambia durante l'esecuzione (dinamicità) Il programma non è la stessa cosa della computazione (staticità) • Gli elementi più significativi per la programmazione imperativa sono quindi: Elementi strutturati Invarianti Esempio: rimozione dei doppioni • Possiamo scrivere lo stesso programma dell'esempio RAM usando la programmazione imperativa • In questo caso sfrutto ancora i salti incondizionati (Fortran) • E' più leggibile (codificata ad un livello più alto) ma si può fare di meglio usando degli elementi strutturati 1: 2: 3: 4: 5: 6: 7: 8: read(x); if x=0 then goto 8; writeln(x); read(next); if next=x then goto 4; x:=next; goto 2; ; Esempio programmare con goto • Il goto è ancora presente come costrutto di alcuni linguaggi ad alto livello • Proviamo a scrivere il programma che calcola della divisione tra due numeri controllando che il divisore sia diverso da zero 1: 2: 3: 4: 5: 6: 7: leggi il dividendo e il divisore if il divisore è diverso da zero then goto 5 scrivi `errore: divisione per zero' goto 7 calcola dividendo/divisore scrivi il risultato end Programmazione strutturata • E' un paradigma di programmazione emerso fra gli anni sessanta e gli anni settanta nel contesto della programmazione procedurale • Critica della struttura di controllo del salto incondizionato, che rappresentava, negli anni sessanta, lo strumento fondamentale per la definizione di algoritmi complessi • Dijkstra fu tra i primi a evidenziare gli effetti deleteri dei salti incondizionati sulla leggibilità e modificabilità del software (spaghetti code) • In seguito parleremo di programma come di un algoritmo scritto in un linguaggio specifico • I passi dell'algoritmo vengono mappati in una o più istruzioni nel linguaggio • Il flusso dell'algoritmo in una serie di strutture per il controllo di flusso del programma Programmazione strutturata (2) • Obiettivo: ottenere il più possibile un flusso ordinato di istruzioni dall'inizio alla fine (abolizione dei salti incondizionati) • Idealmente ottenibile considerando una sequenza lineare di operazioni, senza alternative (ridotta potenza degli algoritmi) • Nella realtà avviene attraverso regole che portano ad effetti equivalenti all'esecuzione sequenziale di operazioni • Ogni struttura di controllo ha un solo punto di ingresso e un solo punto d'uscita • Il flusso di esecuzione è evidente dalla struttura del codice • Enfasi sulla struttura e sul flusso, è indipendente dalla efficienza Programma proprio (1) • Un programma si definisce proprio se gode delle seguenti proprietà Ha un unico ingresso ed un'unica uscita Ogni elemento appartiene ad un percorso che porta dall'ingresso all'uscita • L'ingresso è l'unica via di accesso al programma e l'uscita è l'unica via di terminazione del programma • Il programma proprio può essere suddiviso in strutture proprie detti blocchi che godono delle stesse due proprietà Programma proprio (2) Programmi equivalenti • Due programmi che realizzano lo stesso algoritmo usando operazioni differenti si dicono equivalenti • In modo formale due programmi propri P e Q sono equivalenti se: P(X ) = Y = Q(X ) • Ovvero se applicati agli stessi argomenti X forniscono lo stesso risultato Y Strutturazione in moduli • La definizione di programma proprio e di blocchi suggerisce una strutturazione modulare dei programmi • I moduli hanno una strutturazione gerarchica ben definita, e definiscono funzioni logiche precise (coesione funzionale) • I moduli devo avere dimensioni accettabili (dimensione) • Le strutture di controllo presenti nei moduli devono essere tenute d'occhio, annidate al max su 3 livelli (profondità) • Un modulo è una fase concettuale dell'algoritmo (fase temporale) • Tutte le attività di un modulo dovrebbero essere eseguite nelle stesse condizioni (stessa base condizionale) • Tutte le attività di uno stesso modulo dovrebbero lavorare sugli stessi dati (condivisione dei dati) Teorema di Böhm Jacopini [Teorema di Böhm Jacopini] Dato un programma proprio P è possibile costruire un programma strutturato S(P) equivalente a P • Il teorema permise di dimostrare che la potenza di calcolo dei programmi strutturati non è inferiore a quella dei programmi che usano il goto (completezza) • Il programma strutturato S(P) è costituito da combinazioni di tre oggetti logici fondamentali: Sequenza, Selezione, Iterazione [Completezza] Tutti i programmi esprimibili tramite istruzioni di salto (goto) o diagrammi di flusso (flow-chart) possono essere riscritti utilizzando esclusivamente le tre strutture di controllo fondamentali (eliminazione dei salti) Strutture di controllo ed invarianti • Come abbiamo visto il programma evolve • Una invariante è una asserzione vera in qualunque momento della esecuzione del programma • Una asserzione è una condizione vera o falsa circa lo stato della computazione (e.g. x > y) • La maggiore difficoltà nello scrivere codice è che la correttezza è una proprietà dinamica della computazione e non statica del codice scritto • Gli invarianti sono associati ad un punto del programma ma ci danno delle indicazioni circa le proprietà della sua computazione • Nella prossima lezione vedremo nel dettaglio le strutture di controllo tipiche della programmazione strutturata Lezione 3 - Costrutti della programmazione strutturata Corso — Programmazione Programmazione e progettazione — Programmazione strutturata Marco Anisetti e-mail: [email protected] web: http://homes.di.unimi.it/anisetti/ Costrutti fondamentali [Sequenza] Le istruzioni vengono eseguite a seconda dell'ordine in cui sono scritte [Selezione] L'esecuzione di un blocco di istruzioni viene scelta tra due possibili in base al valore di una condizione [Iterazione] L'esecuzione di una o più istruzioni viene ripetuta in base al valore di una condizione Sequenza Le istruzioni sono eseguite nello stesso ordine in cui compaiono nel programma, cioè secondo la sequenza in cui sono scritte. [Moltiplicazione tra due numeri] 1: leggi i numeri x e y 2: calcola x ∗ y Sequenza: esempio Calcolo dell'età media dei componenti di un nucleo familiare Selezione (if-then)(1) [Sintassi] IF condizione THEN blocco 1 ENDIF blocco 2 [non strutturata] if condizione then < goto blocco 1> < goto blocco 2> blocco 1 blocco 2 Selezione (if-then)(2) [Esecuzione] Valutazione della condizione: VERA Vengono eseguite le istruzioni del blocco 1 In seguito si esegue blocco 2 essendo la prima istruzione che segue il costrutto FALSA l'esecuzione prosegue direttamente dalla prima istruzione che segue il costrutto ovvero blocco 2 Selezione (if-then-else)(1) [Sintassi] IF condizione THEN blocco 1 ELSE blocco 2 ENDIF blocco 3 [non strutturata] if condizione then < goto blocco 1> blocco 2 < goto blocco 3> blocco 1 blocco 3 Selezione (if-then-else)(2) [Esecuzione] (1) Valutazione della condizione VERA Vengono eseguite le istruzioni del blocco 1 FALSA Vengono eseguite quelle del blocco 2 (1) L'esecuzione procede con le istruzioni in blocco 3 Selezione (if-then-else)(3) L'uso dell'else equivale ad un doppio uso dell'if-then: [Esempio] IF condizione THEN blocco 1 ENDIF IF not condizione THEN blocco 2 ENDIF blocco 3 Esempio(1) Rivediamo l'esercizio del calcolo della divisione tra due numeri controllando che il divisore sia diverso da zero: leggi il dividendo e il divisore IF il divisore è diverso da zero THEN calcola dividendo/divisore scrivi il risultato ELSE scrivi `errore: divisione per zero' ENDIF Esempio: flow chart(1) Calcolo delle soluzioni reali di ax 2 + bx + c = 0 Prima approssimazione della soluzione, approccio modulare Esempio: flow chart(2) Versione completa dettagliano il modulo del calcolo delle radici Esempio: pseudocodifica Versione in pseudocodice sviluppando meglio i moduli di calcolo delle radici leggi i valori dei parametri a, b, c calcola il discriminante IF il discriminante è minore di zero THEN scrivi `nessuna soluzione reale' ELSE IF il discriminante è uguale a zero THEN calcola −b 2a scrivi le due soluzioni coincidenti ELSE √ b2 −4ac calcola −b−√2a b2 −4ac calcola −b+ 2a scrivi le due soluzioni ENDIF ENDIF Iterazione (post-condizionato)(1) Chiamato anche ciclo repeat-until [Sintassi] DO blocco 1 WHILE condizione blocco 2 [Non strutturato] blocco 1 if condizione then goto blocco 1 blocco 2 Iterazione (post-condizionato)(2) [Esecuzione] (1) Viene eseguito blocco 1 (1) Viene valutata condizione: VERA Si ritorna al punto (1) FALSA Si prosegue con la prima istruzione scritta dopo il costrutto iterativo • Il blocco 1 è eseguito almeno una volta • L'iterazione termina quando la condizione diventa falsa Esempio Somma dei primi 1000 P numeri interi senza utilizzare la formula di Gauss ( ni=1 i = n(n+1) ), ma iterativamente 2 inizializza la somma a zero parti dal numero 1 DO aggiungi alla somma il numero passa al numero successivo (incremento) WHILE il numero non supera 1000 scrivi la somma ottenuta Iterazione (pre-condizionato)(1) [Sintassi] WHILE condizione DO blocco 1 END WHILE blocco 2 Iterazione (pre-condizionato)(2) [Esecuzione] (1) Viene valutata la condizione: VERA Viene eseguito blocco 1 quindi si torna al punto (1) FALSA L'esecuzione riprende dalla prima l'istruzione che segue il costrutto iterativo • Il blocco 1 può essere eseguito anche zero volte • L'iterazione termina quando la condizione diventa falsa Esempio iterazione Il comportamento dell'iterazione pre-condizionata può essere simulato combinando un iteratore post-condizionato e una selezione: IF condizione THEN DO blocco 1 WHILE condizione ENDIF Lezione 4 - Variabili ed assegnamenti Corso — Programmazione Programmazione e progettazione — Programmazione strutturata Marco Anisetti e-mail: [email protected] web: http://homes.di.unimi.it/anisetti/ Università degli Studi di Milano — Dipartimento di informatica Variabili • Le abbiamo introdotte nel precedente modulo come paragonabili alle incognite matematiche • Dato che un algoritmo descrive una soluzione per una classe di problemi abbiamo bisogno di variabili (contenitori di valori) per esprimere le varie istanze di problemi • Si definiscono in termini di: • Nome: identificatore (a cui fare riferimento) • Tipo: insieme dei possibili valori che possono essere assunti (char, stringa, intero, reale, booleana) • Valore: valore attualmente assunto dalla variabile Identificatore - valore • Un identificatore è il nome di un oggetto • L'identificatore corrisponde ad una area di memoria che contiene il valore dell'oggetto • Il rapporto tra identificatore e valore è dinamico mentre quello tra identificatore e l'area di memoria è statico • L'identificatore o nome di un variabile deve essere auto-documentante Variabili e assegnamenti(1) • Una variabile è un contenitore preposto a contenere dei valori che possono variare durante il tempo di esecuzione • E' una locazione di memoria che ha una dimensione associata e che è riferibile attraverso il suo nome e nella quale si può scrivere attraverso un assegnamento • Gli assegnamenti li usiamo da molte lezioni e permettono di variare il contenuto di una variabile [Istruzione di assegnamento] variabile ←espressione (oppure variabile := espressione) Variabili e assegnamenti(2) [Semantica assegnamento] (1) Viene calcolato il valore dell'espressione scritta a destra del simbolo ← (1) Il risultato ottenuto è assegnato alla variabile a sinistra del simbolo ← (nel caso sovrascrivendo l'eventuale valore precedentemente contenuto) Molti linguaggi, utilizzano per l'assegnamento il simbolo =, usato comunemente per indicare l'uguaglianza Esempio x←y + z • Valuta l'espressione y + z, recuperando i valori presenti nella celle di memoria relative a y e z • Assegna alla variabile x il risultato di tale somma (sovrascrivendo il valore) Tipo Il tipo di una variabile specifica: • La classe di valori che questa può assumere • L'insieme delle operazioni che possono essere effettuate su di essa. • Quanto spazio occupa in memoria (il numero contiguo di celle da occupare) Ad esempio una variabile x di tipo intero • Può assumere come valori solo numeri interi • Su di essa possono essere effettuate soltanto le operazioni consentite per i numeri interi • La sua occupazione di memoria è solitamente uguale alla dimensione dei registri dell'elaboratore (es. 32, 64 bit) Astrazioni [Variabile] • Il concetto di variabile è un'astrazione del concetto di locazione di memoria • La variabile è il riferimento mnemonico all'indirizzo della locazione di memoria • L'assegnamento di un valore a una variabile è un'astrazione dell'operazione STORE di un ipotetico linguaggio macchina [Tipi] • Sebbene tutte le variabili siano rappresentate nella memoria come sequenze di bit, tali sequenze possono essere interpretate diversamente in base ai tipi • La tipizzazione delle variabili permette di mantenere il controllo sulla correttezza dello sviluppo • La nozione di tipo fornisce un'astrazione rispetto alla rappresentazione effettiva dei dati • Il programmatore può utilizzare variabili di tipi differenti, Dichiarazione delle variabili • Molti linguaggi richiedono di dichiarare le variabili utilizzate nel programma indicandone il tipo • Alcuni linguaggi (ad esempio Pascal) richiedono che le variabili siano dichiarate tutte all'inizio del programma • Alcuni linguaggi richiedono che siano dichiarate prima del loro utilizzo (ad esempio Java) • Vantaggi nella dichiarazione: Accresce la leggibilità dei programmi Diminuisce la possibilità di errori Facilita la realizzazione di compilatori efficienti Esempio: rimozione dei doppioni • Ora possiamo rivedere l'esempio usando la programmazione strutturata read(x); while x 6= 0 do writeln(x); repeat read(next); until x6=next; x:=next; endwhile Condizioni - tipi - assegnamenti • Quello che viene valutato in una condizione è simile al membro destro di un assegnamento • Esiste genericamente un tipo associato ad una espressione il quale dipende dai tipi delle variabili o letterali utilizzati nell'espressione • L'espressione utilizzabile in una condizione può anche essere una uguaglianza una disuguaglianza o una proposizionale logica • Quando l'espressione è usata in una condizione essa viene valutata ed il ritorno viene interpretato dall'istruzione condizionale • Questo passaggio dipende dal linguaggio • Il valore di verità è associato ad un tipo particolare detto booleano • Tale tipo può prendere valore nell'insieme {vero,falso} Lezione 5 - Eliminazione dei salti Corso — Programmazione Programmazione e progettazione — Programmazione strutturata Marco Anisetti e-mail: [email protected] web: http://homes.di.unimi.it/anisetti/ Università degli Studi di Milano — Dipartimento di informatica Eliminazione dei salti • Quando abbiamo parlato di programmazione strutturata e non strutturata abbiamo introdotto il teorema di Böhm Jacopini • In questa lezione vediamo la dimostrazione e come si può utilizzare • Vediamo anche una dimostrazione alternativa al teorema tramite la trasformazione di Ashcroft e Manna Dimostrazione Teorema di Böhm Jacopini • La dimostrazione del teorema porta a del codice strutturato in maniera da avere un unico ciclo (di tipo pre-condizionato) e una serie di variabili booleane di supporto per il mantenimento dell'ordine delle operazioni • Le variabili booleane sono a guardia di ogni operazione e permettono di eseguire una istruzione per ogni ciclo WHILE in maniera simile ad un program counter WHILE .... IF ... THEN istruzione 1 ENDIF . . . IF ... THEN istruzione n ENDIF Teorema di Böhm Jacopini: esempio Teorema di Böhm Jacopini: conclusioni • E' sempre possibile scrivere un qualsiasi programma usando solo i costrutti della programmazione strutturata • Si possono eliminare i salti di un programma non strutturato ottenendo un programma strutturato a patto di inserire un certo numero di selezioni aggiuntive • Il risultato di questa trasformazione è un programma strutturato ma la tecnica presentata nella dimostrazione non porta ad un programma meno intircato del programma non strutturato (programma spaghettiforme) • Esiste una trasformazione che dimostra il teorema e che porta a risultati più rilevanti La trasformazione di Ashcroft e Manna(1) • Tecnica costruttiva per portare alla dimostrazione del teorema di Böhm Jacopini sfruttando i concetti di programma proprio e di scomposizione gerarchica di un programma in sottoprogrammi propri • La dimostrazione sfrutta il diagramma di flusso come rappresentazione non considerando gli stati iniziale e finale e permette di preservare la struttura logica del programma La trasformazione di Ashcroft e Manna(2) La trasformazione di Ashcroft e Manna(2) La trasformazione di Ashcroft e Manna(3) La trasformazione di Ashcroft e Manna(4) Per trasformare questi blocchi ci son due regole: 1. Il blocco contiene un solo test (lo sappiamo già trasformare), oppure non contiene cicli, e anche questo caso è banale 2. Il blocco contiene più di un test e almeno un ciclo. Si identificano i punti dell'insieme di taglio sulle frecce del blocco in modo tale che ciascun ciclo contenga almeno uno di tali punti e che uno di questi punti stia sull'unica freccia uscente. Si traduce il blocco in un pezzo di programma con un WHILE esterno in modo tale che ogni iterazione del ciclo WHILE segua l'esecuzione del blocco da un punto dell'insieme di taglio al successivo. Si introduce un numero i di variabili booleane sufficiente a distinguere tutti i punti dell'insieme di taglio definiti i è il più piccolo intero successivo a dlog2 ne dove n è il numero di punti di taglio. Trasformazione semplice Se contiene solo un test si tratta di una trasformazione semplice sequenza 1 IF condizione THEN salta fuori ENDIF sequenza 2 Salta a sequenza 1 ↓ b ← True WHILE b sequenza 1 b ← ¬ condizione IF b THEN sequenza 2 ENDWHILE La trasformazione di Ashcroft e Manna(5) La trasformazione di Ashcroft e Manna(6) • b1 controlla il WHILE, quindi è logicamente collegata al punto γ • b2 svolge la funzione di selettore tra i due cicli interni controllati dai punti α e β • Dimostrazione è più costruttiva e introduce concetti interessanti di gerarchia Costrutti strutturati minimali • In realtà i 3 costrutti visti possono essere ulteriormente ridotti ai soli sequenza e iterazione per codificare qualsiasi algoritmo • E' possibile rappresentare una selezione come un costrutto iterativo inserendo una variabile booleana in aggiunta. IF condizione THEN blocco 1 ENDIF ↓ b ← condizione WHILE b blocco 1 b ← false ENDWHILE Programmazione strutturata ed efficienza • Il fatto che un programma non strutturato possa essere riscritto in modo strutturato non implica che le due versioni del programma abbiano la stessa dimensione o la stessa efficienza • Esiste infatti un interessante teorema [*] che afferma che per ogni intero n, esiste un programma di n istruzioni che usa i salti, che non puo essere convertito in un √ n programma strutturato di meno di 1. 3 istruzioni, a meno che tale programma non sia più lento di un fattore 12 log2 n. [*] Ricard A. DeMillo, Stanley C. Eisenstat, and Richard J. Lipton. Spacetime trade-offs in structured programming: An improved combinatorial embedding theorem. Journal of the ACM, 1980.
© Copyright 2024 Paperzz