La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Introduzione alla programmazione Dott. Ing. Leonardo Rigutini Dipartimento Ingegneria dellInformazione Università di Siena Via Roma 56 – 53100 – SIENA.

Presentazioni simili


Presentazione sul tema: "Introduzione alla programmazione Dott. Ing. Leonardo Rigutini Dipartimento Ingegneria dellInformazione Università di Siena Via Roma 56 – 53100 – SIENA."— Transcript della presentazione:

1 Introduzione alla programmazione Dott. Ing. Leonardo Rigutini Dipartimento Ingegneria dellInformazione Università di Siena Via Roma 56 – – SIENA Uff

2 Algoritmo

3 Che cosè linformatica Linformatica è lo studio sistematico degli algoritmi che descrivono e trasformano linformazione: la loro teoria, analisi, progetto, efficienza, realizzazione (ACM Association for Computing Machinery) Linformatica è lo studio sistematico degli algoritmi che descrivono e trasformano linformazione: la loro teoria, analisi, progetto, efficienza, realizzazione (ACM Association for Computing Machinery) Nota Nota: È possibile svolgere unattività concettualmente di tipo informatico senza lausilio del calcolatore, per esempio nel progettare ed applicare regole precise per svolgere operazioni aritmetiche con carta e penna; lelaboratore, tuttavia, è uno strumento di calcolo potente, che permette la gestione di quantità di informazioni altrimenti intrattabili

4 Algoritmo Algoritmo Algoritmo: sequenza di istruzioni attraverso le quali un operatore umano è capace di risolvere ogni problema di una data classe; non è direttamente eseguibile dallelaboratore Programma Programma: sequenza di operazioni atte a predisporre lelaboratore alla soluzione di una determinata classe di problemi algoritmo Il programma è la descrizione di un algoritmo in una forma comprensibile allelaboratore macchina universale Lelaboratore è una macchina universale: cambiando il programma residente in memoria, è in grado di risolvere problemi di natura diversa (una classe di problemi per ogni programma)

5 Algoritmi nella realtà una sequenza di passi, definiti con precisione, che portano alla realizzazione di un compito Come abbiamo già detto un algoritmo può essere descritto in maniera informale come una sequenza di passi, definiti con precisione, che portano alla realizzazione di un compito Es: Le istruzioni di montaggio di un elettrodomestico Il calcolo del MCD (massimo comune divisore) Il prelievo di denaro dal bancomat: Inserimento tessera Inserimento codice segreto.ecc… Molte attività umane sono algoritmi

6 Algoritmo Un algoritmo deve essere: comprensibile Altrimenti lesecutore non può capirlo non-ambiguo: Altrimenti vi potrebbero essere diverse valide interpretazioni dello stesso algoritmo corretto Ovviamente dovrebbe effettivamente fare quello per cui è stato pensato efficiente (utile ma non vincolante) Dovrebbe eseguire il suo compito nel modo più veloce possibile

7 Codifica di un algoritmo Quindi per risolvere un problema è necessario esprimerlo sotto forma di algoritmo: Una volta descritto in termini di passi più o meno elementari è possibile codificare questi passi utilizzando il linguaggio di programmazione prescelto Ricordiamo anche che per un dato problema possono esistere molteplici algoritmi che lo risolvono e che possono distinguersi per la migliore o peggiore complessità Luso di un linguaggio di alto livello permette di utilizzare nomi per le variabili e per le funzioni molto vicini alla loro funzione: tassoInteresse per una variabile che indica il tasso dinteresse, ecc…

8 Schema dellalgoritmo Solitamente la codifica di un problema in algoritmo avviene utilizzando i diagrammi di flusso, ovvero diagrammi grafici che permettono di visualizzare il flusso delle operazioni da compiere per arrivare alla soluzione Un diagramma di flusso schematizza lordine delle operazioni più o meno semplici richieste per risolvere un problema Ogni operazione di assegnamento è racchiusa in un rettangolo mentre le operazioni di verifica sono visualizzate in rombi

9 Diagrammi di flusso Esempio di codifica di un banale algoritmo con un diagramma di flusso: Rappresentare un grosso programma tramite un unico diagramma di flusso diventa comunque impossibile: Solitamente un algoritmo viene scomposto in unità funzionali distinte (funzioni, procedure o moduli) Ogni unità dovrebbe essere così semplice da poter essere facilmente schematizzabile tramite un diagramma di flusso leggi x e memorizzalo in a a > 100 Fai qualcosaStampa errore false true

10 Linguaggi Il linguaggio utilizzato per descrivere lalgoritmo deve essere interpretabile dallesecutore dellalgoritmo: Se abbiamo le istruzioni di montaggio di un elettrodomestico in cinese, non siamo in grado di seguire i passi necessari a terminare il lavoro (a meno che non si conosca il cinese!!) In informatica: I calcolatori sono esecutori di algoritmi Gli algoritmi sono descritti tramite programmi, cioè sequenze di istruzioni scritte in un opportuno linguaggio, comprensibile al calcolatore Compito dellesperto informatico: Produrre algoritmi per un dato problema Codificarli in programmi (renderli comprensibili al calcolatore)

11 Precisione Il calcolatore esegue un programma passo-passo, in modo preciso, veloce e potente, senza deviare dal flusso delle istruzioni anche se esso è palesemente sbagliato: Questo perché il calcolatore è privo di intelligenza, non può decidere se una istruzione è sbagliata, se cè, la esegue. Questo richiede quindi che le istruzioni siano puntuali e che lalgoritmo sia chiaro e puntuale: per fare la torta di mele occorrono 3 Kg di mele, 3 uova e 0,5 Kg di farina è sufficientemente precisa legate larrosto e salatelo non è sufficientemente precisa poiché per esempio non specifica la quantità di sale da utilizzare

12 Correttezza Un algoritmo è corretto se esso perviene alla soluzione del compito per cui è stato sviluppato, senza difettare di alcun passo. Se nellalgoritmo di montaggio di un mobile viene omessa listruzione di stringere una vite, il risultato finale può non essere corretto perché il mobile molto probabilmente non starà in piedi (dipende dalla importanza della vite!!) Sarà necessario individuare lerrore e ripararlo per avere il risultato corretto Un algoritmo non deve tralasciare nessun passo per giungere alla soluzione, altrimenti il suo risultato potrebbe essere non corretto.

13 Efficienza minor tempo minima quantità di risorse Un algoritmo dovrebbe raggiungere la soluzione nel minor tempo possibile e/o utilizzando la minima quantità di risorse, compatibilmente con la sua correttezza. efficienza in tempo e spazio (risorse) Es. Non è efficiente un algoritmo in cui prima viene regolato un dispositivo e poi, nel passo successivo, tale impostazione viene modificata, se poi deve essere riportata a come era prima ottimizzare Un buon informatico cercherà di ottimizzare un algoritmo il più possibile per cercare di arrivare alla soluzione il più veloce possibile e con lutilizzo di minor risorse possibili

14 Complessità

15 La bontà di un programma viene valutata analizzando due fattori: Quanto è veloce il programma (complessità in tempo) Quante risorse richiede il programma (complessità in spazio) Solitamente, a meno di richieste esagerate, la complessità spaziale non è un problema, quindi si guarda molto più a quanto tempo ci vuole per eseguire un compito

16 Complessità La complessità temporale di un programma però non è di semplice valutazione Tempo di esecuzione: dipende dalla potenza della macchina su cui è eseguito dipende dalla dimensione dellinput (molti dati molto più tempo) La soluzione è stata di assegnare un costo alle operazioni e calcolare quindi la complessità totale come costo complessivo: Loperazione di confronto ha costo uno Tutte le altre hanno costo nullo (non è vero, ma hanno un costo irrisorio rispetto al confronto)

17 Complessità Inoltre non è possibile dare una misura puntuale del costo di un algoritmo, poiché molte volte esso dipende dallo spazio dellinput: Si calcola quindi il costo in funzione della dimensione dellinput costo = f(n), dove n è la dimensione dei dati su cui andiamo a lavorare Infine è necessario calcolare il costo nel caso migliore e nel caso peggiore : o(f(n)) per la complessità nel caso migliore (costo minimo) O(f(n)) per la complessità nel caso peggiore Il limite inferiore (o() ) non ha molto significato

18 Complessità Es. Problema: Dati n numeri, trovare il più grande Algoritmo: MAXVAL = primo numero Per ogni numero seguente, se è maggiore di quello in MAXVAL, memorizzalo in MAXVAL Complessità: nel caso peggiore, il valore massimo si trova in ultima posizione; in questo caso sono necessari n-1 confronti. Quindi la complessità dellalgoritmo è O(n). Tale funzione (f(n)=n) è detta anche lineare, dato che cresce linearmente con n.

19 Complessità In realtà, lesempio precedente, avrebbe complessità O(n-1), ma poiché per n >>1 O(n-1)~O(n), viene utilizzato O(n) come valore di complessità Questo vale per tutte le funzioni f(n) indicanti la complessità di un algoritmo: O(2n+4), per n>>1 O(n) O(2n 2 +n+4), per n>>1 O(n 2 ) Ecc.. cioè viene individuata la funzione maggiorante

20 Complessità La complessità dà unidea della fattibilità dellalgoritmo. Supponiamo il costo di una operazione pari ad 1 secondo, il tempo necessario per un algoritmo con le varie complessità è il seguente: Come si vede, un problema con complessità polinomiale diventa molto velocemente intrattabile Logaritmica O(log(n)) Lineare O(n) Quadratic aO(n 2 ) Polinomiale O(2 n ) 103 sec10 sec1,5 min17 min sec20 sec7 min291 giorni 305 sec30 sec15 min191 anni complessità n

21 Esempio: un algoritmo più complicato Gestione di una biblioteca: Ogni libro si trova su uno scaffale e può essere preso in prestito ed in seguito restituito. La biblioteca è dotata di uno schedario ed ogni scheda contiene: Nome,cognome dellautore (o autori) Titolo Data di pubblicazione Numero scaffale in cui si trova Numero dordine della posizione Le schede sono disposte in ordine alfabetico in base allautore ed al titolo.

22 La biblioteca Algoritmo di ricerca di un libro in biblioteca: 1. Ricerca della scheda nello schedario 2. Lettura del numero di scaffale e posizione del libro 3. Ricerca dello scaffale indicato 4. Prelievo del libro e scrittura sulla scheda delle informazioni sul prestito: data,nome ecc.. Ogni passo può (deve) essere descritto più dettagliatamente: sotto- algoritmo Passo1: 1.1Lettura della prima scheda dello schedario 1.2 Se il nome dellautore ed il titolo coincidono, ricerca conclusa 1.3 Si ripete il passo 2 fino trovare la scheda cercata o a terminare le schede 1.4 Se non viene trovata la scheda, la ricerca termina in modo infruttuoso

23 La biblioteca Il sotto-algoritmo per il passo 1, è corretto ma non efficiente: Ha una complessità lineare con il numero di schede, ossia nel caso peggiore tutte le schede nello schedario verranno analizzate E possibile definire un altro algoritmo per il passo 1 più efficiente: 1.1Se lo schedario è vuoto ricerca infruttuosa 1.2Lettura scheda centrale 1.3Se è quella cercata, ricerca conclusa con successo 1.4Se il nome dellautore e/o titolo è precedente, si ripete lalgoritmo sulla prima metà dello schedario 1.5Se il nome dellautore e/o titolo è successivo, si ripete lalgoritmo sulla seconda metà dello schedario Nel caso peggiore, questo algoritmo analizza log 2 (n) schede: schede alg1 = confronti schede alg1 = confronti schede alg2 = 14 confronti schede alg2 = 14 confronti

24 La biblioteca Nel secondo algoritmo presentato per automatizzare il passo 1, lalgoritmo richiama se stesso su un differente set di dati fino ad una terminazione positiva o negativa Un algoritmo di questo tipo si dice ricorsivo: ovvero il problema viene scomposto in sottoproblemi uguali ma su set di dati più piccoli, fino ad arrivare o alla soluzione o alla impossibilità di proseguire condizioni di stop: Il sottoinsieme su cui viene richiamato lalgoritmo è vuoto (1.1) La ricerca è andata a buon fine (1.3) Vedremo più avanti in dettaglio quali tipi di algoritmi esistono

25 Livelli di astrazione e storia dei linguaggi di programmazione

26 Programmazione di basso livello Al livello più basso le istruzioni di un computer sono estremamente elementari. Il processore esegue le istruzioni macchina: Sequenze di bytes i codici delle operazioni e le locazioni in memoria Le CPU di fornitori diversi (Intel, SUN, Mac) hanno insiemi differenti di istruzioni macchina: Instruction set

27 Linguaggio binario Scrivere programmi direttamente in linguaggio macchina però risulta essere molto complicato: Sequenze di numeri con poca espressività Es - Carica il contenuto della posizione di memoria 40 - Carica il valore - Se il primo valore è maggiore del secondo continua con listruzione in posizione

28 Assembler Il primo passo fu di assegnare nomi abbreviati ai comandi: iLoad carica un numero intero bipush inserisci una costante numerica if_icmpgt se il numero intero è maggiore Lesempio può essere riscritto così: iLoad 40 bipush100 if_icmpgt 240 Similmente in seguito furono assegnati dei nomi anche alle locazioni di memoria (variabili): iLoad intRate bipush100 if_icmpgt intError ASSEMBLER

29 Assemblatore Tale metodo di programmazione richiedeva quindi un software che traducesse il codice dal linguaggio ASSEMBLER al linguaggio macchina: ASSEMBLATORE La nuova forma di programmazione continua ad avere corrispondenza uno-a-uno con il linguaggio macchina Ad ogni istruzione ASSEMBLER corrisponde una istruzione del Instruction Set Rimane però molto difficile sviluppare grandi programmi utilizzando istruzioni di così basso livello: If_cmpgt, iLoad, ecc… lavorano su indirizzi in memoria, su registri della CPU ed anche un semplice confronto tra due variabili richiede una sequenza di alcune istruzioni assembler

30 Linguaggi di alto livello Furono teorizzati allora linguaggi di programmazione che astraevano dalla architettura del calcolatore (indipendenza dall Instruction Set) e permettevano costrutti molto più vicini al pensiero umano: se EXPR allora FAI QUESTO, altrimenti FAI QUESTALTRO ripeti L1 fino a che NON ACCADE QUESTO Questi costrutti permettevano di racchiudere una sequenza di istruzioni macchina in una descrizione molto più vicina allessere umano

31 La compilazione Questi linguaggi richiedono luso di un compilatore che traduca il codice di alto livello in codice macchina: Ogni linguaggio ha quindi il suo compilatore (o più di uno) Inoltre, dato che ciò che il compilatore genera è il codice macchina e questultimo è strettamente dipendente dalla macchina (Hardware/SO) su cui gira, la compilazione traduce il codice da un linguaggio astratto in una forma dedicata e strettamente dipendente dalla macchina che si sta adoperando

32 La compilazione Quindi: È il compilatore che si occupa di tradurre un dato linguaggio (es. C) nel codice macchina adatto alla macchina su cui dovrà essere eseguito Se un programma C viene compilato su ambiente Windows, sarà creato un programma che girerà solo su ambienti windows; similarmente, se lo stesso codice viene compilato su Linux, sarà possibile eseguire lapplicazione solo su una macchina Linux Un linguaggio di programmazione di alto livello deve rispettare RIGOROSE convenzioni affinché sia interpretabile dal compilatore

33 Linguaggi alto livello If (intRate>100) printf(Error) C se il tasso di interesse è maggiore di 100 scrivi a video un messaggio di errore Sviluppo Compilazione

34 La compilazione Il processo di compilazione però peggiora le prestazioni rispetto ad un programma scritto direttamente in linguaggio macchina: Il tempo richiesto dal traduttore per generare il codice macchina Il codice oggetto generato dal compilatore tipicamente è meno ottimizzato rispetto a quello generato direttamente dallessere umano In realtà: Anche considerando il tempo di traduzione del compilatore, il tempo necessario allo sviluppo del programma rimane comunque decisamente inferiore a quello necessario a sviluppare direttamente in codice oggetto Il codice generato dal compilatore è sì difficile che sia ottimizzato, ma comunque la perdita di prestazione risulta essere accettabile: Oggi inoltre tutti i compilatori hanno la possibilità di ottimizzare il codice E comunque non è detto che un essere umano riuscirebbe a generare codice ottimizzato per programmi molto complicati come quelli che si possono scrivere utilizzando linguaggi di alto livello

35 Un po di storia dei linguaggi di alto livello A seguito di queste teorie nacquero i primi linguaggi di alto livello: FORTRAN (FORmula TRANslator): il primo linguaggio di alto livello sviluppato per descrivere formule matematiche COBOL (Common Business Oriented Language): il primo linguaggio orientato alle applicazioni gestionali Per molto tempo questi due linguaggi rimasero i linguaggi più diffusi fino a quando non fu studiata una categoria di linguaggi più rigorosamente basati su uno studio dei principi della programmazione: Il capostipite di questa famiglia fu ALGOL60 (ALGOrithmic Language) che pur non essendo mai stato utilizzato nella pratica, è famoso per questo motivo.

36 Un po di storia dei linguaggi di alto livello Dopo lALGOL60, furono sviluppati molti nuovi linguaggi di programmazione orientati ad una programmazione generale : PASCAL, oggi molto diffuso per la didattica C,tra i più potenti linguaggi di programmazione, divenuto molto popolare grazie al fatto che il sistema Unix fu interamente sviluppato utilizzando questo linguaggi di programmazione ADA, Basic, Perl, Phyton, ecc…. Anche questi però ad un certo punto furono superati da un nuovo paradigma: la programmazione ad oggetti C++, C# e Java derivati dal C Eiffel e Delphi derivati dal Pascal VisualBasic ecc…

37 Programmazione Procedurale

38 Linguaggio procedurale La programmazione classica segue il paradigma di programmazione procedurale o imperativo Cosa significa: Il problema è scomposto in molti sottoproblemi che possono essere risolti in maniera semi-indipendente per poi essere aggregati per ottenere il risultato finale Ogni sotto-problema viene assegnato ad un sottoprogramma che si occupa di eseguire le operazioni per restituire il risultato (o effettuare il compito per cui è stato creato) Lapplicazione stessa è vista come un sottoproblema: Assemblare i sotto-problemi individuati in maniera da ottenere il risultato

39 Linguaggio procedurale Quindi seguendo questa idea, il programma è costituito da diverse funzioni (procedure o subroutines) ognuna disegnata per uno scopo Il corpo del programma è esso stesso una funzione, con la particolarità però di avere una forma (prototipo) standard: la funzione main() Quando un programma procedurale è compilato ed avviato, ciò che viene lanciato è la procedura main: Tutto ciò che deve essere eseguito dal programma deve essere implementato nella procedura main, ricorrendo a tutte le chiamate di funzione e dati necessari

40 Linguaggio procedurale int main(int,int) funzione1 funzione2 funzioneM Dato A Dato B Dato N avvio dellapplicazione … …

41 Variabili e tipi di dato

42 Variabili Una variabile rappresenta unarea di memoria riservata dal compilatore per memorizzare dati Questarea di memoria è riferita tramite un nome univoco: Questo nome permette di accedere alla memoria per leggere/scrivere informazioni In fase di programmazione, quando viene definita ed utilizzata la variabile non ha un valore, ma rappresenta tutti i valori che ciò che essa rappresenta potrà assumere Il valore è assegnato a run-time

43 Nomi di variabili Ogni variabile (e funzione che vedremo in seguito) è riferita tramite un nome Un nome in un linguaggio di programmazione è inteso come una etichetta che rispetta alcune regole fissate dal linguaggio stesso: Deve essere costituita da un singolo token. Etichette formate da più parole separate non sono accettate (il compilatore non può sapere che le due parole vanno interpretate insieme!!) Non può essere una delle parole riservate del linguaggio (if, else, while, return ecc…) Deve contenere solo caratteri alfa-numerici più il carattere _: questa condizione implica la prima poiché il carattere spazio non è permesso Non può comunque iniziare con un carattere numerico

44 Dichiarazione di variabili Dichiarare una variabile significa avvertire il compilatore che riservi unarea di memoria perché in futuro essa verrà utilizzata con quel nome: Processo di allocazione della memoria Ogni variabile, prima di essere utilizzata, deve essere dichiarata: In caso contrario il compilatore genera un errore, non trovando la corrispondenza del nome con un area di memoria nella tabella simboli Quando una variabile viene dichiarata, deve perciò essere specificato sia il nome della variabile, sia il tipo di dato che tale variabile potrà assumere: Se una variabile viene dichiarata di tipo T, necessariamente essa potrà memorizzare valori di tipo T

45 Tipi di dato I tipi di dato si distinguono in tipi semplici e tipi strutturati: tipi di dato semplici – sono tipi di dato a cui può essere associato un singolo valore (numerico o stringa) ed un riferimento alla variabile è un riferimento al contenuto tipi di dato strutturati – sono tipi di dato composti da più campi, da cioè uno o più altri tipi di dato a loro volta semplici o strutturati Ogni linguaggio di programmazione mette a disposizione una serie di tipi di dati predefiniti: Tipi di dato semplici bool, int, char, double, ecc… Tipi di dato strutturati array

46 Tipi semplici predefiniti Tutti i linguaggi di programmazione mettono a disposizione un numero di tipi di dato semplice built-in: boolValori logici: true e false char Interi su 8 bit utilizzati per rappresentare numeri e caratteri (ASCII): signed/unsigned intInteri su 16 bit: signed/unsigned longInteri su 32 bit: signed/unsigned floatNumeri in virgola mobile a 32 bit doubleNumeri in virgola mobile a 64 bit

47 Operatori Per questi tipi di dato semplici sono forniti dal linguaggio anche gli operatori: Assegnamento (=) Addizione (+) Sottrazione (–) Moltiplicazione (*) Divisione (/) Modulo (%), ritorna il resto della divisione. Es. 5%3 = 2 Uguaglianza (==) e disuguaglianza (!=), ritornano vero o falso Minore o maggiore (, >=), ritornano vero o falso

48 Il tipo char Il tipo char è un tipo semplice particolare: Il nome deriva dallabbreviazione di character ed infatti è utilizzato per rappresentare linsieme finito dei caratteri (la tabella ASCII) Ogni carattere ha un codice numerico compreso tra 0 e 127 (7 bit), più un extended set compreso tra 128 e 255 (8° bit)

49 Il tipo char e le sequenze di escape Alcuni codici sono riservati per i caratteri di controllo e solitamente sono indicati utilizzando il carattere \ come carattere di escape: Molte volte è necessario utilizzare particolari sequenze di caratteri per rappresentare caratteri che altrimenti non sarebbe possibile visualizzare Es: Le stringhe sono sequenze di caratteri delimitate da. Se vogliamo inserire un carattere allinterno di una stringa dobbiamo utilizzare la sequenza di escape \ per informare il compilatore che non delimita la fine della stringa Di solito le sequenze di escape sono utilizzate per i caratteri di controllo: new line \n, tab \t, ecc….

50 Caratteri di controllo escapeASCIIcodiceDescrizione 0NULLCarattere nullo ………… \b8BSBack Space \t9HTHorizontal Tab \n10LFLine Feed ………… \r13CRCarriage Return ………… La rappresentazione dei caratteri tramite un codice numerico permette un ordinamento tra gli stessi caratteri e quindi la possibilità di operazioni

51 Valori esadecimali È inoltre possibile assegnare valori in base 16 utilizzando la convenzione: 0xNNNN dove NNNN è il numero in base 16 Es int y=0x0A; assegna 10 alla variabile y

52 Definizione di nuovi tipi semplici Oltre ai tipi di dato semplici predefiniti, è possibile definirne di nuovi : Ridefinizione typedef : il nuovo tipo di dato è solamente un alias di uno già esistente. Per esempio, listruzione C typedef int T1; definisce un tipo di dato T1 che altro non è che un altro nome per un intero (int) Enumerazione enum : il nuovo tipo di dato può assumere solamente uno dei valori (numerici o meno) contenuti in una lista di enumerazione. Per esempio, listruzione C enum{A,B,C} D1; definisce una variabile D1 che può assumere solamente i valori A,B, o C. In questo esempio il tipo di dato è enum{….} ; per avere un tipo di dato T1 è necessario utilizzare il costrutto typedef: typedef enum{A,B,C} T1; tipo di dato T1 T1 D1; crea una variabile D1 di tipo T1

53 Tipi strutturati predefiniti I linguaggi di programmazione forniscono anche dei tipi strutturati predefiniti: array questo tipo di dato corrisponde ad una sequenza di celle consecutive di memoria di lunghezza fissata (e fornita al momento della dichiarazione della variabile di quel tipo). Tutte le celle devono necessariamente contenere lo stesso tipo di dato. Per esempio, in C, listruzione int list[20]; dichiara la variabile list come una sequenza di 20 interi I recenti linguaggi di programmazione forniscono altri dati strutturati predefiniti per esempio: date, record, ecc…

54 Creazione di nuovi tipi strutturati Oltre ai tipi built-in è possibile create nuovi tipi di dato strutturati tramite appositi costrutti del linguaggio: Es. in C (o C++) struct { int giorno; int mese; int anno; } D1; Lesempio mostra una situazione tipica in cui è preferibile utilizzare un dato strutturato: La data infatti può essere vista come una tripla di interi, ma gestire tre variabili intere separate per tutto il programma può essere contro- intuitivo per il programmatore. Risulta migliore, come si può immaginare, creare un dato D1 come aggregazione dei tre interi. Anche in questo esempio per definire un tipo di dato T1 si può utilizzare la funzione typedef

55 Dichiarazione ed inizializzazione Una volta dichiarata, la variabile può essere utilizzata nel programma: La dichiarazione crea una istanza di quel tipo di dato con il suo relativo nome (normalmente si parla di variabile istanziata) Notare che la dichiarazione non inizializza la variabile ad un valore, ma semplicemente riserva uno spazio in memoria per quella variabile: un successivo accesso alla variabile non inizializzata, può restituire un valore non valido Linizializzazione di una variabile è fatta con un istruzione di assegnamento: Può essere fatta al momento della dichiarazione Può essere fatta in un secondo momento

56 Cast Se memorizziamo un dato in una variabile di tipo diverso, è solitamente avviene unoperazione di casting, ovvero il compilatore traduce automaticamente il dato nella forma richiesta dal tipo della variabile: int double ( 54 54,00) – non si ha perdita di precisione double int (54, ) – si perde la parte decimale signed char unsigned char (-1 255) – vengono utilizzati tutti i 2 8 valori positivi generabili con 8 bit. Infatti: signed char [-2 7,2 7 -1], di cui 1xxxxxxx sono i valori negativi [-128,-1] [ , ] se non consideriamo il segno, e quindi nel passaggio da signed ad unsigned (e viceversa)

57 Cast Il cast avviene in maniera implicita se è previsto dal linguaggio di programmazione (int char, double int, ecc…) altrimenti è necessario farlo in maniera esplicita definendo unoperatore di casting Per i tipi di dato semplici predefiniti il casting è quasi sempre fornito dal linguaggio di programmazione stesso

58 Dichiarazione delle costanti Una dichiarazione di costante associa permanentemente un valore ad un identificatore La dichiarazione di una costante è praticamente la dichiarazione con assegnamento di una variabile con laggiunta della parola riservata const davanti, a specificare il fatto che quella cella di memoria conterrà il valore assegnato senza possibilità di modifica ES: const int numAlunni = 25; numALunni conterrà 25 per tutto il suo ciclo di vita const char nome[16]=leonardo nome conterrà leonardo senza possibilità di modifiche const date data = {5,10,2005} data conterrà 5/10/2005

59 Istruzioni e controllo del flusso

60 Istruzioni Le istruzioni sono frasi proprie del linguaggio di programmazione delimitate dal simbolo ; Es. read(x); è una istruzione – senza il simbolo terminatore, il compilatore non capirebbe dove termina listruzione Molte volte è necessario che sequenze di istruzioni vengano eseguite sequenzialmente: Corpo del programma, corpo di una funzione, corpo di un ciclo Una lista di istruzioni è delimitata dai simboli { e }: { inizia una lista di istruzioni } termina la lista E possibile innestare più liste di comandi

61 Struttura di un semplice programma Un programma è organizzato in maniera sequenziale (non è più tanto vero) La prima parte è lintestazione, ovvero una etichetta seguita da una coppia di parentesi tonde () Il significato di queste parentesi sarà spiegato in seguito A seguire inizia una lista di istruzioni, delimitate quindi da { e } Ma che tipo di istruzioni prevede un linguaggio?

62 Istruzione di assegnamento Questa istruzione fondamentale viene utilizzata per assegnare a una variabile il valore di una espressione: Consiste nel simbolo = preceduto dal nome della variabile a cui deve essere assegnato il valore Alla destra del simbolo = viene specificate lespressione che genera il risultato da assegnare alla variabile Lespressione può essere costituita da un valore costante (numero), identificatori di altre variabili, espressioni aritmetico/logiche di combinazioni di valori e variabili e funzioni: x =23; w=a; y =z; alfa = x + y; r3=(alfa * 43 –xgg)*(delta-32ijj); x = x + 1;

63 Istruzione di assegnamento Lesecuzione di una istruzione di assegnamento comporta la valutazione della espressione a destra del simbolo = e la sostituzione del valore nella cella di memoria individuata dal nome della variabile a sinistra del simbolo = Si noti che un valore cotante può essre un numero ma anche un carattere: Nellesempio w=a, assegna il carattere a alla variabile w

64 Controllo del flusso Il flusso delle istruzioni può cambiare a seconda della valutazione di una espressione: Può essere necessario per esempio effettuare una lista di operazioni L1 se una condizione è vera, una lista L2 altrimenti Oppure può essere necessario ripetere una lista di istruzioni fino a che non è verificata una condizione Le istruzioni che permettono queste scelte sono dette di controllo del flusso e come si evince dai due esempi qui sopra sono divise in due gruppi: Istruzioni condizionali Istruzioni iterative

65 Istruzione condizionale Consente di valutare una espressione e di eseguire due sequenze distinte di operazioni a seconda del risultato ritornato dalla espressione Sintassi: if (E1) {L1} else {L2} Se E1 è vera verrà eseguita la lista di istruzioni L1, altrimenti sarà eseguita la lista L2 Il cosiddetto ramo else non è obbligatorio e può essere omesso nel caso in cui non vi sia alcuna lista L2 Se L1 e L2 sono composta da ununica istruzione anche le parentesi graffe possono essere omesse: Es. if (E1) I1; else I2;

66 Castello di if Se è necessario verificare più espressioni in maniera innescata, è necessario fare attenzione a quale if fa riferimento ogni else if (E1) { if (E2) { L2; } else { if (E3) { L3; } } chiude lelse di E2 } else { L1; } chiude lelse di E1

67 Istruzione iterativa while Consente di ripetere la lista di istruzioni (iterare) più volte fino a che non risulta vera una condizione Sintassi: while (E1) {L1} Lespressione E1 viene valutata e se è vera, viene eseguito il corpo del ciclo. Terminato il corpo, viene di nuovo valutata E1, e se continua ad essere vera, il corpo è di nuovo eseguito. Questo è ripetuto fino a che la valutazione di E1 non ritorna false È possibile utilizzare allinterno del ciclo while listruzione break che interrompe incondizionatamente il ciclo, come se la condizione E1 fosse verificata

68 Istruzioni derivate: strutture di controllo Teorema di Bohem-Jacopini: Le istruzioni di controllo viste finora if … else … e while sono equivalenti alle strutture di controllo del linguaggio assemblatore basate sulla diretta e condizionale manipolazione del registro contatore di programma: tramite esse è possibile esprimere ogni qualsiasi altra struttura di controllo di ogni qualsiasi altro linguaggio di programmazione Questo teorema ci dice che sebbene sia possibile pensare a molte altre possibili strutture di controllo di alto livello, per ognuna di esse si può sempre trovare una forma equivalente in termini di if…else… o di ciclo while: La specifica di nuove strutture di controllo rimane comunque utile per fornire ai programmatori strumenti più vicini ai casi concreti

69 Istruzioni condizionali derivate: else if Quando sono necessarie valutazioni di più espressioni su di una variabile è possibile utilizzare il costrutto else if: if (E1) { L1 } else if (E2) { L2 } …. else if (En-1) { Ln-1 } else { Ln } In alcuni linguaggi else if è stato condensato in un comando unico elseif

70 Istruzioni condizionali derivate: switch…case Molti linguaggi per evitare enormi castelli di if, mettono a disposizione un costrutto derivato: switch … case Sintassi: switch (E1) { case A: L A ; break; case B: L b ; break; …. default: L default ; }

71 Istruzioni condizionali derivate: switch…case Listruzione switch-case, valuta lespressione E1 e suppone che siano possibili vari casi Per ognuno dei casi viene fornita una lista di istruzioni, seguita dal comando break Nel caso si verifichi una condizione non specificata da alcun case, viene fornita una scelta di default Il comando break non è obbligatorio, serve solamente per evitare che nel caso A, il programma prosegua effettuando anche le istruzioni relative al caso B, caso C, e così via fino al caso di default. Se allinterno di un particolare algoritmo è richiesto un tale comportamento, il comando break può essere rimosso NB: il costrutto switch-case lavora solamente su valori numerici La valutazione di E1 deve ritornare quindi un valore numerico

72 Istruzioni iterative derivate: for Consente di ripetere la lista di istruzioni (iterare) un numero prefissato di volte Sintassi: for (START; EXPR; STEP) {L1;} START è la condizione iniziale da cui partire; EXPR è la espressione che viene valutata ad ogni iterazione per decidere se interrompere o meno il ciclo; STEP è la modifica che viene effettuata ad ogni passo Es. for (i=0;i

73 Istruzioni iterative derivate: for Listruzione for è in realtà un modo diverso per scrivere la forma while. Essa può essere infatti sostituita con: i=0; while (i

74 Istruzione iterative derivate: do…while Così come listruzione for può essere vista come un caso particolare del più generale ciclo while, così molte altre istruzioni condizionali sono state proposte nei vari linguaggi di programmazione do … while do {L1;} while (E1) – ripete la lista di istruzioni fino a che E1 non è verificata; a quel punto esce dal ciclo La differenza importante dal ciclo while è il punto in cui E1 viene valutata: - nel ciclo while viene valutata prima di eseguire L1, - nel ciclo do-while è valuata dopo aver eseguito almeno una volta il corpo del ciclo

75 Istruzione iterative derivate: goto Molti linguaggi hanno mantenuto tra le proprie strutture di controllo istruzioni di salto direttamente ispirate alle istruzioni macchina dei processori: Istruzione goto Sintassi: goto LABEL; … LABEL: L1 Listruzione goto permette di saltare lesecuzione del programma alla linea di codice individuata dalla etichetta LABEL. Sono molto poco utilizzate ed anzi i teorici della programmazione sostengono che se siamo costretti ad utilizzare un istruzione goto, abbiamo sicuramente sbagliato qualcosa nellimpostare lalgoritmo

76 Funzioni e procedure

77 Sottoprogrammi Nella creazione di un programma può accadere che problemi simili appaiano in mole parti diverse La soluzione più ovvia è quella di replicare il codice in ogni parte dove è necessario, ma questa soluzione non è certamente la più friendly: Se il codice ha un errore, infatti, esso sarà replicato in tutte le parti in cui quel codice è stato copiato Se quella parte di codice viene aggiornata, sarà necessario aggiornare tutte le parti in cui è stato copiato Molto poco flessibile e manutenibile: uso di sottoprogrammi

78 Sottoprogrammi Sottoprogrammi: parti di codice separate dal flusso principale del programma che possono essere richiamate dove necessario senza replicare il codice ogni volta Un sottoprogramma deve essere innanzitutto definito, cioè deve essere dichiarato da qualche parte ciò che esso esegue, su cosa lo esegue e cosa restituisce (se restituisce qualcosa) Una volta definito, esso deve essere implementato, cioè scritto il corpo del sottoprogramma, con le variabili e le istruzioni che permettono di eseguire il compito per cui è stato progettato Infine, una volta dichiarato e implementato, esso può essere richiamato ogni qualvolta è richiesta una operazione di quel tipo

79 Funzioni e procedure Ma cosa sono di preciso i sottoprogrammi? Ogni sottoprogramma è un blocco più o meno lungo di codice che riceve in ingresso alcuni parametri (anche nessuno) e può restituire un valore: I parametri che riceve in ingresso sono i valori che il chiamante passa alla procedura: sono quindi variabili in cui a run-time vengono memorizzati i valori attuali dei parametri Il sottoprogramma utilizza questi parametri per eseguire il suo compito ed eventualmente restituisce a sua volta al chiamante un valore

80 Funzioni e procedure Formalmente due tipologie di sottoprogrammi sono state individuate: Le funzioni, che in analogia con le funzioni matematiche, ricevono alcuni valori in ingresso e restituiscono un valore in uscita Le procedure, che ricevuti alcuni dati in ingresso, eseguono un compito che non può concettualmente essere associato ad una funzione (es. stampa, aggiornamento ecc…) Diciamo subito che la distinzione è abbastanza formale e che chiamare entrambi funzioni o procedure non comporta alcun problema Le procedure, in efetti possono essere assimilate a funzioni che non restituiscono alcun valore, ma eseguono semplicemente un compito

81 Funzioni e procedure I parametri che la funzione utilizza per ricevere dati dallesterno sono variabili locali: Ovvero esistono localmente alla funzione Come tali devono avere un tipo ed un nome (univoco allinterno della funzione) Anche il valore di ritorno della funzione è un dato di un certo tipo, quindi nella descrizione della funzione sarà necessario specificare che tipo di dato essa ritorna

82 Le funzioni Una funzione è una parte di programma individuato da una etichetta che riceve in ingresso dei parametri e ritorna un risultato, terminando sempre il suo compito I parametri dingresso sono chiamati semplicemente parametri della funzione Il valore di uscita è detto valore di ritorno della funzione Come accennato in precedenza una funzione deve essere prima definita, ovvero deve venire specificato la struttura della funzione stessa: il nome univoco della funzione i parametri che riceve in ingresso il tipo di dato che essa restituisce

83 Dichiarazione di funzione Prima di implementare una funzione è necessario dichiarare la sua interfaccia con lesterno, il modo cioè con cui essa è invocata ed il tipo di risultato che ritorna: La dichiarazione di una funzione specifica il prototipo della funzione Es: double potenza( int x, int y) è una funzione che riceve due parametri di tipo int (x e y) e restituisce un double Come la dichiarazione di una variabile non inizializzava la variabile, così la dichiarazione di una funzione non realizza la funzione: In questo modo per ora è stato dichiarata solamente la interfaccia della funzione

84 Dichiarazione di funzione La dichiarazione di una funzione (prototipo) è utile per far capire al compilatore che la funzione esiste e che forma ha: La funzione poi, potrebbe essere implementata in qualsiasi altro file del progetto e compilata dopo il file principale, ma limportante è che nel file principale sia riportato il prototipo della funzione che viene utilizzata altrimenti il compilatore non può continuare Questo metodo permette di scomporre il progetto in più file in cui implementare le varie procedure, organizzandole in maniera più intelligente: E importante che quando una funzione viene utilizzata in qualche file, sia specificato in quel file il prototipo di quella funzione Il linker si occupa di collegare il corpo della funzione nei punti in cui la funzione è chiamata

85 Dichiarazione di funzione La sintassi comune di una dichiarazione di funzione è la seguente: [tipo_di_dato] [nome_della_funzione] ([lista di parametri]) Dove [lista di parametri] = lista di dichiarazioni di variabili visibili dallinterno e dallesterno della funzione Es. double potenza( int x, int y); Questa riga di codice dice che da qualche parte esiste una funzione potenza che riceve in ingresso due interi, esegue qualcosa e ritorna un double.

86 Dichiarazione di funzione Se una funzione (o procedura) non ritorna alcun valore, si utilizza un tipo di dato apposito void : void stampa(String file); È possibile infine avere funzioni che non richiedono parametri, poiché quello che devono fare è indifferente da valori esterni: void stampa(); In molti linguaggi (C++) è possibile specificare valori di default ai parametri della funzione: Valori che se non forniti alla funzione

87 Valori di default Se un parametro normalmente assume un certo valore (=K) e solo poche volte varia, è possibile inserire il valore K come valore di default: se quel parametro non è fornito, la funzione utilizza il valore di default double potenza( int x, int y); Ad esempio: double ris = potenza(4,2) 16 NB: se un parametro ha un valore di default, allora tutti i parametri seguenti devono avere un valore di default, altrimenti durante la chiamata con valore di default il compilatore non sa più a quale parametro fare riferimento: double potenza( int x=2, int y); se chiamiamo int r=potenza(4); il compilatore non sa se associare 4 ad x o a y.

88 Implementazione della funzione La funzione deve comunque essere implementata (cioè realizzata) da qualche parte nel progetto: La definizione del prototipo infatti non specifica cosa deve fare la funzione Limplementazione di una funzione avviene dichiarando il prototipo della funzione stessa ed iniziando immediatamente dopo un blocco di istruzioni: double potenza( int x, int y) { Lista di istruzioni; } Il blocco nellesempio qui sopra è delimitato dalle parentesi graffe, come avviene nel C, C++ e JAVA.

89 Implementazione della funzione Allinterno del corpo di una funzione, possono essere dichiarate nuove variabili, utilizzando anche nomi di variabili utilizzati in altre funzioni: Ogni variabile interna ad una funzione ha infatti una visibilità limitata a quella funzione ed una volta usciti dalla funzione le variabili vengono deallocate Le varibili allocate allinterno di una funzione sono chiamate variabili locali, per distinguerle dalle variabili globali che sono variabili dichiarate fuori da ogni altra funzione, compresa la principale e sono quindi visibili sempre ed ovunque: Se allinterno di una funzione viene utilizzato un nome di variabile globale, ogni riferimento a quel nome viene reindirizzato alla variabile locale

90 Istruzione return Quando la funzione deve ritornare un valore è necessario al suo interno contenga una istruzione di ritorno valore che termina la funzione e restituisce un dato conforma al prototipo: il valore viene restituito tramite listruzione return Sintassi: return [VALORE o VARIABILE]; Il tipo di valore restituito deve coincidere con il tipo di valore specificato nella dichiarazione della funzione, altrimenti il compilatore ritorna errore.

91 Istruzione return Il valore di ritorno può essere una valore costante oppure il contenuto di una variabile: Nel secondo caso la variabile dovrà essere dello stesso tipo del dato da ritornare ES double potenza( int x, int y) { double ris=1; for (int i=0;i

92 Istruzione return Quando la funzione non deve ritornare alcun valore listruzione return non è richiesta: è però possibile inserirlo senza specificare il dato ritornato: void stampa(String file) { L1; return; }

93 Istruzione return Listruzione return può essere utilizzata anche per interrompere lesecuzione della funzione senza attendere la fine della lista di istruzioni: ad esempio può accadere che se una condizione è verificata, la funzione ritorna un valore X, altrimenti esegue altre operazioni e ritorna un valore Y In questo caso avremo due istruzioni return, ma eseguite in maniera esclusiva (o luna o laltra) ES. double potenza( int x, int y) { if (y==0) { return 1; } double r=(calcola la potenza x y ); return r; }

94 Chiamate alle funzioni Una volta dichiarata ed implementata, una funzione può essere richiamata laddove è necessario la chiamata di una funzione è semplicemente il nome della funzione seguito dai parametri da passare: Es. double r=potenza( 2, 4) in questo caso r assume il valore ritornato dalla funzione potenza con i parametri x=2 e y=4 È possibile passare anche variabili invece che valori numerici costanti: Es. int a=2; int b=4; double r=potenza(a,b);

95 Chiamate alle funzioni Il prototipo deve necessariamente essere rispettato: double potenza( int x, int y) double potenza( 2, 3) se viene omesso qualche parametro, il compilatore dà errore: il compilatore cerca una funzione con prototipo diverso e non la trova

96 Passaggio di parametri

97 Cosa succede quando si passa una variabile come parametro ad una funzione? Ossia, se allinterno della funzione modifico il valore di quel parametro, il valore della variabile originaria viene modificato ? La risposta è: dipende !! Esistono due modi per il passaggio dei parametri: Per valore Per riferimento

98 Passaggio di parametri per valore Quando è richiesto che i valori delle variabili non siano modificati dal corpo della funzione, quello che avviene è che il valore della variabile viene copiato nella variabile-parametro della funzione. In questo modo: ogni modifica alla variabile locale non si riflette sulla variabile esterna ogni modifica fatta alle variabili locali, però, va persa alluscita dalla funzione e solo il risultato ritornato è visibile dallesterno della funzione Questo metodo di passaggio dei parametri va sotto il nome di passaggio per copia o per valore

99 Passaggio di parametri per riferimento Quando invece è richiesto che le variabili interne alla funzione siano non una copia di quelle esterne, ma la stessa cosa, si parla di passaggio per riferimento: In questo caso, la variabile locale assume il riferimento in memoria alla variabile esterna Quindi ogni modifica ad essa è una modifica alla cella di memoria originale e permane anche alla chiusura dalla funzione Normalmente questa situazione è richiesta quando si ha un sottoprogramma che deve effettuare delle operazioni su un dato e non ritorna alcun valore: ES. aggiornamento del fatturato totale, ecc… in questo caso il fatturato totale, una volta aggiornato deve rimanere tale, quindi è necessario operare laggiornamento sulla variabile originale e non su una copia del valore

100 Passaggio di parametri Notiamo che il passaggio per valore richiede la copia della variabile nella nuova variabile locale, mentre il riferimento ha un costo costante, viene copiato comunque un indirizzo di memoria Se stiamo passando dati strutturati particolarmente complessi, array di grandi dimensioni, ecc… il passaggio per valore può essere molto più lento del passaggio per riferimento E anche vero però che il passaggio per valore è molto più safety rispetto allaltro, dato che qualsiasi modifica, anche quella non voluta ed erronea non si riflette sul dato originario

101 Passaggio di parametri Il metodo utilizzato per il passaggio dei parametri cambia da linguaggio a linguaggio: Il C e C++ utilizzano il passaggio per valore, ma forniscono dei tipi di dato che permettono di realizzare il passaggio per riferimento (puntatori e riferimenti) Il JAVA utilizza il passaggio per riferimento. Il passaggio per valore può comunque essere effettuato costruendo ogni volta una copia del parametro e passare quella alla funzione.

102 Funzioni e procedure La differenza sul metodo di passaggio dei parametri stabilisce la principale differenza tra una funzione ed una procedura: Funzione: lo scopo principale è quello di utilizzare alcuni valori in ingresso per produrre un valore di uscita, senza modificare nullaltro passaggio per valore Procedura: lo scopo principale è quello di operare delle operazioni sui parametri in ingresso che abbiano una semantica tale da poterle racchiudere in un corpo unico passaggio per riferimento Comunque la differenza è solamente formale: In C++ o in JAVA non esistono metodi per dichiarare una funzione piuttosto che una procedura. In realtà il primo prevede solamente funzioni, il secondo procedure.

103 Passaggio di parametri per valore Programma chiamante Funzione chiamata Spazio di memoria del chiamato par1 Spazio di memoria del chiamante int a copia chiamata di funzione valore di ritorno modifica locale

104 Passaggio di parametri per riferimento Programma chiamante Funzione chiamata Spazio di memoria del chiamato par1 Spazio di memoria del chiamante int a riferimento chiamata di funzione valore di ritorno modifica modifica locale

105 La funzione main

106 Il corpo del programma Adesso che abbiamo acquisito il concetto di funzione è possibile descrivere la struttura completa di un programma (procedurale) Un programma procedurale è composto da: Dichiarazioni di variabili Dichiarazioni di funzioni (prototipi) Implementazioni delle funzioni (anche su più file) Corpo del programma Ma cosa sintende per corpo del programma ? Il corpo del programma è a sua volta una funzione globale particolare che utilizzando tutte le altre funzioni ed i dati a disposizione, assembla il tutto per eseguire il compito ellalgoritmo

107 La funzione main In definitiva, un programma non è altro che una funzione dal prototipo particolare e fissato: int main(int argc,char[] argv) Questa funzione accetta due parametri in ingresso e restituisce un valore intero in uscita: I due parametri in ingresso permettono di leggere eventuali parametri passati da linea di comando allapplicazione Es. il comando copy in windows accetta due parametri copy file_a file_b argc=2, argv=[file_a,file_b] Il valore di ritorno permette di stabilire se lesecuzione del programma è andata a buon fine o meno. Solitamente un programma ritorna 0 se esce correttamente, 1 se vi sono stati errori C e C++

108 Variabili globali e locali La dichiarazione di una variabile normalmente può avvenire in qualsiasi punto del programma, sempre però prima che la variabile sia utilizzata per la prima volta In particolare, la variabile può essere dichiarata fuori da qualsiasi funzione oppure allinterno di una funzione (o procedura : Nel primo caso, ambito globale, la variabile esiste in ogni parte del programma (con le debite eccezioni), a partire dal momento in cui il compilatore legge la riga di dichiarazione e la istanzia Nel secondo caso, ambito locale, invece, la variabile esiste solamente allinterno della funzione in cui è stata dichiarata e sarà deallocata alluscita della funzione stessa

109 Visibilità di variabili (scope) Ma cosa accade se una variabile locale ha lo stesso nome di una variabile globale? Quando la funzione è chiamata, con quel nome a quale variabile ci riferiamo, quella globale o quella locale? Risposta: la variabile più vicina, è quella a cui viene fatto riferimento con il nome (nesting rule) Il campo di visibilità di una variabile (scope) è sempre quello ultimo in cui la variabile è stata dichiarata, anche se due variabili portano lo stesso nome Quindi durante lesecuzione di tutta la procedura, ogni riferimento alla variabile A sarà risolto con un riferimento alla variabile A locale alla procedura. Tale riferimento cessa non appena la procedura e lo scope relativo ad essa terminano

110 Lo spazio di memoria globale Per tutto quello che si è detto, anche le variabili dichiarate nella funzione principale sono variabili locali alla funzione main: Un programma allora è composto da più spazi di memoria distinti, di cui solamente uno, lo spazio di memoria globale è condiviso in tutto il progetto Lo spazio di memoria globale è composto dalle variabili dichiarate fuori da ogni funzione, compresa la funzione principale (il main())

111 fnfn Schema di un programma procedurale int main(int,int) f2f2 f1f1 Spazio di memoria delle funzioni … Spazio di memoria del main Spazio di memoria globale Chiamate di funzione Accessi alla memoria globale

112 Le funzioni nella CPU

113 Cambio di contesto Ma cosa succede a basso livello quando viene chiamata una funzione ? La chiamata di una funzione genera un cambio di contesto (context switch) nella CPU: Lo stato attuale della CPU deve essere salvato da qualche parte affinchè una volta eseguita la procedura sia possibile riprendere correttamente lesecuzione delle istruzioni Anche i registri devono essere salvati dato che le istruzioni della procedura non devono assolutamente modificare i valori eterni alla procedura stessa Lo stato del processo viene allora salvato sullo stack di sistema

114 Lo stack Una parte di memoria centrale viene gestita come stack di sistema (pila) Alla chiamata di una procedura: il contesto attuale viene inserito in cima allo stack: operazione push Prepara la CPU ad eseguire la nuova procedura: Setta listruzione da eseguire, il registro dati e quello indirizzi con i parametri passati alla procedura La procedura esegue le istruzioni Quando la procedura termina, il contesto in cima allo stack viene ripristinato: Operazione pop Lo stack memorizza le chiamate a procedura e le richiama in ordine inverso di memorizzazione

115 Stack – 1 Alla chiamata della funzione f, il contesto della funzione in esecuzione attualmente viene memorizzato in cima allo stack main() f1 f2 f4 CPU stack f3 main() f1 f2 f3 stack funzione in esecuzione push

116 Stack – 2 Quando poi la funzione termina, viene ripristinato lultimo contesto inserito nello stack, la funzione f(t-1): main() f1 f2 stack CPU f f3 Funzioni chiamate e terminate funzione in esecuzione pop

117 Stack – 3 Quando anche la funzione f(t-1) termina, viene ripristinato il contesto di f(t-2), e così via… f3 main() f1 f2 stack CPU f Funzioni chiamate e terminate funzione in esecuzione pop

118 Stack – 4 Lultimo contesto nello stack sarà quello relativo alla funzione main(). Quando questo termina, lapplicazione termina. f3 main() f1 f2 stack CPU f Funzioni chiamate e terminate funzione in esecuzione pop empty

119 La ricorsione

120 Si parla di ricorsione quando allinterno di una funzione P, viene richiamata P stessa, normalmente su un diverso set di dati La chiamata ricorsiva può essere sia esplicita che implicita: Esplicita – la funzione P è richiamata esplicitamente allinterno del corpo di P stesso Implicita – la funzione P è richiamata attraverso la chiamata ad unaltra funzione Q che contiene la chiamata a P A prima vista la cosa può apparire sorprendente dato che una funzione (o procedura) è dedita a risolvere un sottoproblema: La ricorsione è come dire che per risolvere il sottoproblema P è necessario risolvere il sottoproblema P stesso !!

121 La ricorsione In realtà la programmazione ricorsiva a profonde radici concettuali derivanti dal fatto che per molti problemi la soluzione per un caso generico può essere ricavata sulla base della soluzione di un caso più semplice dello stesso problema: Lordinamento di un array, il calcolo del fattoriale, ecc… sono tipici problemi di questo tipo

122 Es. somma come ricorsione La ricorsione è nascosta in moltissime forme del ragionamento umano. Per esempio la somma tra due numeri può essere vista come ricorsione delloperatore successore(): successore(x)=numero successivo ad x Algoritmo ricorsivo per laddizione x+0 = x; x+y = x+successore(y-1) = successore(x+y-1) 1+3 = successore(1+2) = successore(sucessore(1+1)) = = successore(successore(successore(1+0))) = = successore(successore(successore(1))) = = successore(successore(2)) = successore(3) = 4

123 Es. 1 – Pesate Problema: date un gruppo di palline di forma identica e tutte di peso identico tranne una di peso maggiore delle altre, trovare la pallina più pesante utilizzando una bilancia ed il minor numero di pesate (supponiamo di avere un numero di palline multiplo di 3) Algoritmo: 1. Dividi il gruppo in tre gruppi di egual numero di palline e pesa due di questi gruppi 2. Se i due gruppi hanno lo stesso peso scartarli entrambi, altrimenti scartare il gruppo non pesato e quello sulla bilancia con il peso minore (la pallina cercata si troverà nel gruppo con peso maggiore) 3. Dividi il gruppo rimasto in tre sottogruppi e ripeti il procedimento finché i sottogruppi non contengano una sola palline: in tal caso lultima pesata indica quale delle tre palline rimaste è quella cercata

124 Es. 1 – Pesate Notiamo come lultimo punto sia la ripetizione dellalgoritmo su un set ridotto di dati fino a che non ci troviamo di fronte ad un caso notevole, di cui possiamo dare una immediata soluzione: tre gruppi di una pallina ciascuno Per avere una visione più informatica dellalgoritmo: 1. Se il gruppo di palline consiste di una sola pallina, la pallina è la soluzione cercata, altrimenti … 2. Dividi il gruppo di palline in tre sottogruppi e poni due sottogruppi sulla bilancia 3. Se i due gruppi hanno peso uguale scartarli entrambi, altrimenti scartare il gruppo non pesato e quello di peso minore 4. Applica lalgoritmo pesate al sottogruppo individuato

125 Es. 2 – Successione di Fibonacci La successione di numeri di Fibonacci fu definita dal matematico come modello di crescita dei conigli in allevamento: f 0 =0 f 1 =1 f n =f n-1 + f n-2, per n> 1 Es. calcolando f per i primi 6 numeri: f 0 =0 f 1 =1 f 2 =f 1 +f 0 =1+0=1 f 3 =f 2 +f 1 =1+1=2 f 4 =f 3 +f 2 =2+1=3 f 5 =f 4 +f 3 =3+2=5 …

126 Es. 3 – Fattoriale Il fattoriale di un numero è la produttoria di tutti i numeri fino al numero stesso, escluso lo 0: 5! = 5 x 4 x 3 x 2 x 1 = 120 Il fattoriale può essere espresso in forma ricorsiva: 0! = 1 n! = n x (n-1)! Infatti: 5!=5 x (4 x 3 x 2 x 1) = 5 x 4! Anche il fattoriale è un tipico esempio di algoritmo ricorsivo

127 Es. 4 – ricerca in un vettore Un esempio tipico di algoritmo di ricerca dati in un vettore ordinato è il seguente: Se il vettore è composto da un solo elemento, confronta lelemento con D e se è uguale ritorna D altrimenti ritorna non trovato Seleziona lelemento centrale P del vettore Confronta il dato D da cercare con lelemento centrale P Se D

128 Es. 5 – Sommatoria La sommatoria di una sequenza di n numeri può essere vista come la somma numero n-esimo alla sommatoria dei rimanenti (n-1) numeri: La sommatoria comunque può essere risolta semplicemente con un ciclo, risparmiando così il carico aggiuntivo della ricorsione

129 Caso generale Si può generalizzare il concetto di ricorsione. Affinché un algoritmo sia scrivibile in forma ricorsiva, è necessario che: lalgoritmo abbia una soluzione banale, f(0)=0 lalgoritmo al passo n possa essere espresso come funzione del valore al passo (n-1) La condizione banale è necessarie per evitare una ricorsione infinita: Se infatti non esiste una condizione in cui siamo in grado di dare la soluzione, le iterazioni non terminano mai In questo caso, solitamente si verifica un errore di stack overflow, ovvero pila piena

130 Ricorsione e stack La ricorsione viene realizzata mantenendo nello stack la sequenza delle chiamate alla procedura che non possono essere risolte fino al caso banale Una volta trovata la soluzione banale questa viene passata a ritroso a tutte le istanze dellalgoritmo sospese nello stack fino alla istanza finale che restituisce il risultato finale

131 Successione di Fibonacci Scriviamo lalgoritmo ricorsivo per la successione di Fibonacci: int fibonacci(int n) { int ris=0; if (n==0) return 0; else if (n==1) return 1; else ris = fibonacci(n-1) + fibonacci(n-2); }

132 Fattoriale e sommatoria Scriviamo lalgoritmo ricorsivo per il calcolo del fattoriale: int fatt(int n) { int ris=0; if (n==0) return 1; else ris = n * fatt(n-1); return ris; } Scriviamo lalgoritmo ricorsivo per la sommatoria: int sum(int a[]) { int ris=0; if (vuota(a)==true) return 0; else ris = testa(a) + coda(a); return ris; }

133 Fattoriale – 1 y=fatt(5) fatt(5) fatt(4) fatt(3) fatt(1) CPU stack fatt(2) fatt(5) fatt(4) fatt(3) fatt(2) stack funzione in esecuzione push

134 Fattoriale – 2 fatt(5) fatt(4) fatt(3) stack CPU fatt(1)=1 fatt(2) Funzioni chiamate e terminate funzione in esecuzione pop y=fatt(5)

135 Fattoriale – 3 fatt(2)=2 fatt(5) fatt(4)=24 fatt(3)=6 stack CPU fatt(1)=1 Funzioni chiamate e terminate funzione in esecuzione pop empty y=fatt(5)

136 Programmazione ad oggetti

137 Paradigma ad oggetti Come detto, i linguaggi di programmazione procedurali sono stati quasi tutti sostituiti dalle rispettive versioni ad oggetti (tranne nei casi in cui la programmazione ad oggetti non è consigliata, ad esempio programmazione di basso livello, S.O., programmazione PLC ecc…) Ma cosè questo diverso tipo di programmazione? In cosa differisce dalla programmazione classica?

138 Object Oriented Programming (OOP) La maggior parte dei programmi utili non manipola soltanto numeri e stringhe, ma gestisce dati più complessi e più aderenti alla realtà: Conti bancari, informazioni sugli impiegati, forme grafiche… Lidea della programmazione ad oggetti è di rappresentare questi dati così come sono anche nel linguaggio di programmazione: gli oggetti o classi La programmazione ad oggetti estende al massimo lidea di dato strutturato presente nella programmazione classica: una classe (o oggetto) è un tipo di dato strutturato a cui sono associate variabili e funzioni

139 Object Oriented Programming (OOP) Il programmatore individua degli oggetti nella applicazione che deve progettare: Conto corrente, client e server, documento, ecc… Ogni oggetto ha delle proprietà e delle capacità (o funzioni) Lapplicazione è poi costruita facendo interagire correttamente gli oggetti esistenti nell ambiente che si va a progettare Ma la funzione main() che fine ha fatto ? Se non si usano più le procedure/funzioni globali, come si rende eseguibile un programma? Cioè qual è il corpo principale dellapplicazione? Due filosofie differenti per rispondere a queste domande: il C++ ed il JAVA

140 Object Oriented Programming(OOP) Il C++ ha mantenuto comunque una certa compatibilità con i linguaggi procedurali: in C++ è ancora possibile infatti definire funzioni globali, variabili globali e quindi la funzione main() rimane una funzione definita globalmente e cercata dal compilatore per rendere eseguibile un programma Il JAVA ha seguito un altro approccio: In JAVA non esistono funzioni globali, cioè esterne da qualsiasi oggetto, ma ogni funzione o variabile deve appartenere ad un oggetto (o meglio classe): è possibile però definire una funzione main() per ogni classe e rendere eseguibile la classe stessa In compilazione, vengono creati degli oggetti eseguibili (non un unico programma). La scelta di cosa far partire viene fatta dallutente selezionando loggetto eseguibile desiderato.

141 Object Oriented Programming(OOP) Es di oggetti JAVA Normalmente quello che accade in JAVA è che viene creato un oggetto che rappresenta lapplicazione e che contiene la funzione main(): Eseguire lapplicazione significa lanciare il main() di tale oggetto ObjectA: -datoY -funzione1 -funzione2 -main() ObjectB: -datoX -funzione1 -funzione2 -funzione3 -main()


Scaricare ppt "Introduzione alla programmazione Dott. Ing. Leonardo Rigutini Dipartimento Ingegneria dellInformazione Università di Siena Via Roma 56 – 53100 – SIENA."

Presentazioni simili


Annunci Google