Prof. S. Masetta [email protected] 328 6563830 Agg. 16/11/2015 www.sezioneb.com P Prrooggrraam mm maa ddii IInnffoorrm maattiiccaa C Cllaassssee 33°° B B IITTIIS S E E.. TToorrrriicceellllii –– S S.. A Aggaattaa M Miilliitteelllloo ((M ME E)) SOMMARIO 1 1.1 MODELLI DELL’INFORMATICA _________________________________________ 4 Introduzione al concetto di algoritmo ________________________________________________________ 4 1.2 Esempi _________________________________________________________________________________ 5 1.2.1 Esempio: fare la somma di due numeri _____________________________________________________ 5 1.2.2 Esempio: fare la somma di due numeri con incrementi successivi. _______________________________ 6 1.2.3 Esempio: numeri pari e numeri dispari _____________________________________________________ 6 1.2.4 Esempio: Divisione intera tra due numeri positivi ____________________________________________ 7 1.2.5 Esempio: Dato un numero N generato a caso trovarlo e contare il numero dei tentativi ___________ 7 1.2.6 Esempio: calcolo delle tasse _____________________________________________________________ 7 1.2.7 Esempio: Dire se in un vettore sono di più i valori maggiori di MAX o quelli minori _________________ 8 1.2.8 Esempio: Elevazione a potenza __________________________________________________________ 8 1.2.9 Esempio: Macchinetta del caffè __________________________________________________________ 9 1.2.10 Esempio: media dei numeri pari caricati in un vettore ________________________________________ 10 1.2.11 Esempio: Flow Char elevazione a potenza _________________________________________________ 10 1.2.12 Esempio: matrice 2 * n ________________________________________________________________ 11 1.2.13 Esempio: Cicli fissi ___________________________________________________________________ 11 1.3 Soluzione dei problemi: processi algoritmici _________________________________________________ 13 1.4 Proprietà degli algoritmi: costrutti fondamentali, complessità __________________________________ 13 1.5 Costrutti di base ________________________________________________________________________ 14 1.6 Algoritmi notevoli: ordinamento, ricerca, fusione _____________________________________________ 1.6.1 Ottimizzazione dei Programmi: _________________________________________________________ 1.6.2 Algoritmi e complessità _______________________________________________________________ 1.6.3 Elenco degli algoritmi di ordinamento (wikipedia) ________________________________________ 1.6.3.1 Ricerca __________________________________________________________________________ 1.6.3.1.1 Introduzione ___________________________________________________________________ 1.6.3.1.2 Hash _________________________________________________________________________ 1.6.3.1.2.1 Introduzione ______________________________________________________________ 1.6.3.1.2.2 Gestione degli overflow ____________________________________________________ 1.6.3.2 Algoritmi di ordinamento ____________________________________________________________ 1.6.3.3 Algoritmi di ricerca _________________________________________________________________ 2 14 14 15 16 17 17 17 17 18 18 20 STRUTTURE DATI ___________________________________________________ 21 2.1 Tipi di dati _____________________________________________________________________________ 21 2.2 I vettori (ARRAY) ______________________________________________________________________ 23 2.3 Le matrici _____________________________________________________________________________ 24 3 PROGRAMMAZIONE E LINGUAGGI ____________________________________ 24 pag 1 3.1 Introduzione alla programmazione _________________________________________________________ 3.1.1 Le funzioni _________________________________________________________________________ 3.1.2 Tipi di linguaggi _____________________________________________________________________ 3.1.3 Linguaggi imperativi __________________________________________________________________ 3.1.4 Stile funzionale ______________________________________________________________________ 3.1.5 Programmazione ad oggetti ____________________________________________________________ 3.1.6 Programmazione concorrente ed a eventi __________________________________________________ 24 25 27 27 28 28 29 3.2 dati e procedure, linguaggi e tecniche di programmazione secondo i diversi paradigmi: _____________ 3.2.1 Metodo Top Down ___________________________________________________________________ 3.2.2 Metodo Bottom Up ___________________________________________________________________ 3.2.3 Programmazione strutturata ____________________________________________________________ 29 29 29 30 3.3 Metodologia di costruzione dei programmi. Modularità _______________________________________ 31 3.4 Ingegneria del software, tecniche di documentazione e di manutenzione dei programmi. ____________ 31 4 LABORATORIO _____________________________________________________ 32 4.1 cicli ___________________________________________________________________________________ 32 4.2 caricamento di vettore con numeri casuali ___________________________________________________ 33 4.3 Media, massimo e minimo dei valori di una matrice __________________________________________ 33 4.4 Uso delle funzioni _______________________________________________________________________ 33 4.5 Calcolo del fattoriale _____________________________________________________________________ 34 4.6 Algoritmo di Euclide (MCD) ______________________________________________________________ 34 4.7 programma che dice se x è il fattoriale di qualche numero ______________________________________ 35 4.8 Uso degli operatori logici _________________________________________________________________ 35 4.9 Uso della shell per rinominare files _________________________________________________________ 35 4.10 programma di conversione da decimale a binario _____________________________________________ 36 4.11 operazioni sui files _______________________________________________________________________ 36 4.12 funzione INPUTBOX ____________________________________________________________________ 36 4.13 Calcolo di polinomio _____________________________________________________________________ 36 4.14 Shift di un vettore _______________________________________________________________________ 36 4.15 Ordinamento di un vettore ________________________________________________________________ 36 4.16 Generazione di tabella della verità _________________________________________________________ 36 4.17 Calcolatrice ____________________________________________________________________________ 37 4.18 Numeri di fibonacci _____________________________________________________________________ 37 4.19 Codifica di un testo per sostituzione ________________________________________________________ 37 4.20 Operazioni sulle stringhe _________________________________________________________________ 38 4.21 Campo Minato _________________________________________________________________________ 38 pag 2 5 VARIE _____________________________________________________________ 39 5.1 Appendici ______________________________________________________________________________ 39 5.1.1 Programmazione strutturata secondo il teorema di Jacopini-Böhm __________________________ 39 5.2 Uso delle TABELLE _____________________________________________________________________ 43 5.3 Bibbliografia ___________________________________________________________________________ 45 pag 3 1 MODELLI DELL’INFORMATICA 1.1 Introduzione al concetto di algoritmo Immaginiamo che dobbiamo formalizzare il pensiero dell’uomo al fine di poter descrivere in un modo preciso ( cioè formale, con una forma ben definita) come risolvere un problema. Tale pensiero schematizzato, con passi ben precisi, prende il nome di ALGORITMO. La prima definizione di Algoritmo è stata data da Alan Turing (un algoritmo ha un inizio e una fine, risolve tutti i problemi dello stesso tipo, non è unico per risolvere un problema, ogni passo determina univocamente il successivo, è formato da un numero finito di passi) Argomenti trattati: definizione formale di algoritmo ESEMPIO: calcolare la divisione di un numero per 5. Questo si può fare in due modi Primo algoritmo) RIS = X / 5 Secondo algoritmo) Y = X / 10 (che corrisponde a spostare a sinistra la virgola di una posizione) RIS = Y * 2 Questi due procedimenti sono la soluzione formale al problema del calcolo di un qualunque numero per 5. ESEMPIO: Algoritmo per il calcolo del fattoriale Introduzione al concetto di ottimizzazione degli algoritmi “ “ di bontà “ “ NOTA: vedasi i concetti di programmazione strutturata pag 4 1.2 1.2.1 o o o o o o o o o Esempi Esempio: fare la somma di due numeri Inizio A è un numero B è un numero Leggi A da tastiera Leggi B da tastiera S=A+B Scrivi S su video Scrivi S su stampante Fine pag 5 1.2.2 Esempio: fare la somma di due numeri con incrementi successivi. 1.2.3 Esempio: numeri pari e numeri dispari Se si vuole sviluppare un algoritmo per capire se un dato numero N è pari o dispari possiamo procedere ad eliminare da N stesso tante volte 2 fino a quando N diventa 0 oppure 1. Se resta 1 è dispari altrimenti è pari: Vediamo 1) acquisire dall’utente il valore di N 2) se N = 0 allora era pari -> Fine programma pag 6 3) se N = 1 allora era dispari -> Fine programma 4) N = N -2 (cioè ad N togli il valore 2) 5) torna al punto 2 Questo algoritmo ha un difetto. Funziona solo con i numeri positivi. Infatti se N = -3 va in un ciclo infinito in quanto continua a decrementare N non raggiungendo mai né 0 né 1. Infatti se N è negativo deve sommare 2 e non sottrarre. Allora il nuovo programma diventa 1) 2) 3) 4) 5) 6) acquisire dall’utente il valore di N se N = 0 allora era pari -> Fine programma se N = 1 allora era dispari -> Fine programma se N > 0 allora N = N - 2 (cioè ad N togli il valore 2) se N < 0 allora N = N +2 (cioè ad N aggiungi il valore 2) torna al punto 2 1.2.4 Esempio: Divisione intera tra due numeri positivi 1.2.5 Esempio: Dato un numero N generato a caso trovarlo e contare il numero dei tentativi 1.2.6 Esempio: calcolo delle tasse Laboratorio: colcolo delle tasse in base alle aliquote seguenti Imposta sul reddito delle persone fisiche IRPEF L'Irpef è un'imposta diretta, personale, progressiva per scaglioni che si applica al reddito complessivo netto. E' regolata dal testo unico dell'imposta sui redditi (DPR n. 917/1986) ed è l'imposta con il numero maggiore di contribuenti e al primo posto per quanto riguarda il gettito fiscale dello Stato (circa un terzo). Le nuove aliquote IRPEF - Finanziaria 2007 Nuove Aliquote IRPEF Scaglione di reddito 23% fino a 15.000 Euro 27% da 15.000 a 28.000 Euro 38% da 28.000 a 55.000 Euro 41% da 55.000 a 75.000 Euro 43% Per redditi superiori a 75.000 euro Esercitazione: Si deve sviluppare un diagramma di flusso che dato X, totale fatturato, ci calcola quanto dobbiamo pagare di tasse in base alla tabella di cui sopra. Per esempio, se abbiamo fatturato 16.000 € allora si paga il 23% sui primi 15.000 € e il 27% sui 1000 rimanenti per un totale di 3450 + 270 € = 3720 € pag 7 Nel caso di fatturato 30000 euro si paga il 23% sui primi 15000 + il 27% dei successivi 13.000 + il 38% degli ultimi 2000 € 1.2.7 Esempio: Dire se in un vettore sono di più i valori maggiori di MAX o quelli minori 1.2.8 Esempio: Elevazione a potenza Se si vuole sviluppare un algoritmo per implementare X Y (Cioè X elevato Y) possiamo pensare di farlo prendendo una variabile R (Risultato) alla quale moltiplichiamo per Y volte il valore di X . Infatti X Y è la stessa cosa di fare per Y volte 1) 2) 3) 4) 5) 6) 7) X* X * X* ... *X acquisire dall’utente il valore di X acquisire dall’utente il valore di Y R=X Y = Y -1 se Y = 0 allora hai finito R=R *X torna a 4 pag 8 1.2.9 Esempio: Macchinetta del caffè Si deve creare l’algoritmo per programmare una macchinetta che fa caffè e the. Questa ha la caratteristica di avere due pulsanti (A e B ) per scegliere Caffè oppure The, ed una fessura C per prendere i soldi e dare il resto. Un caffè costa 1 Euro mentre un the costa 1,5 Euro. Si vuole che inseriti i soldi (qualunque cifra) la macchinetta sappia come A comportarsi (cioè capire se fare il caffè o il the, se i soldi immessi bastano oppure se deve tornare il resto). B C Start Inserire ancora 1 – C Euro Leggi C C<1 Leggi X Controlla X X=A X=B C=1 Controlla C Fai caffè C>1 Sottoprogramma The Dare resto C - 1 Euro LEGENDA: leggi C = acquisisci i soldi leggi X = acquisisci la scelta (A o B) NOTARE: il programma non ha una fine. non abbiamo gestito altri tipi di input abbiamo introdotto il concetto di sottoprogramma possiamo creare delle variabili per gestire i prezzi pag 9 1.2.10 Esempio: media dei numeri pari caricati in un vettore 1.2.11 Esempio: Flow Char elevazione a potenza pag 10 1.2.12 Esempio: matrice 2 * n Avendo una matrice MAT 12 15 22 55 22 34 18 14 5 44 M F F M F M M M F F dove la prima riga indica una età mentre il corrispondente di sotto (nella seconda riga) indica il sesso di una persona (ESEMPIO: MAT(0,2) indica una persona di 22 anni MAT(1,2) indica che è di sesso femminile), sviluppare un algoritmo che calcoli la media delle età di tutti i maschi. Fatto questo calcolare anche la percentuale delle femmine presenti nella lista. 1.2.13 Esempio: Cicli fissi caricamento vettore no start Input N Input V(I) I= 0 I = (n-1) I= I+1 si ------------------------------------------------------------------------------------------------------ordinamento vettore v(I) >= v(J) J=I+1 no I=0 J=N no Si si I=I+1 I = (n-1) Sostituisci V(I) , V(J) si pag 11 J=J+1 end pag 12 1.3 Soluzione dei problemi: processi algoritmici . Costrutti di base •if then else •repeat until •while do •selezione •case Rappresentazione: •Flow Chart •codifiche •struttogramma e prog. strutturata Definizione Documentazione •ciclo di vita •progettaz. •ISO 9000 Algoritmi Sviluppo: •top down •bottom up •progr. Strutt. •Stile imperativo •Stile funzionale Tipi di programmazione: •funzionale •imperativa •oggetti •ad eventi 1.4 Approccio: •top down •bottom up •progr. Strutt. Bontà : •complessità •carico comput. •Velocità •uso delle risorse •garbage collection •threads •ottimizzazione Ottimizzazione: •velocità •leggibilità •uso risorse •uso memoria Proprietà degli algoritmi: costrutti fondamentali, complessità Introdurre i concetti che ci permettono di valutare la bontà degli algoritmi. algoritmo, definizione Def: Diamo una definizione formale di Algoritmo Un qualsiasi procedimento eseguibile in modo meccanico è detto algoritmo se : 1. 2. 3. 4. 5. è formato da un numero finito di passi ogni passo determina univocamente il successivo rappresenta la soluzione a tutti i problemi dello stesso tipo è interpretabile dall’ esecutore ha un inizio e una fine flow chart e codifiche pag 13 struttogramma e programmazione strutturata Fare vedere come sia difficile mettere su carta un algoritmo che prevede salti, e come sia ancora più difficile gestire un programma che abbia salti (fa fatica pure un programmatore esperto a districarsi nella logica di un programma così fatto) e soprattutto come non si possa ottimizzare il codice che prevede i salti (meglio i cicli for) oltre alla inutilità dell’utilizzo delle cache. complessità carico computazionale ottimizzazione algoritmi che implementano e sfruttano al massimo i threads 1.5 Costrutti di base I costrutti di base principali sono: 1.6 if --- then --- else repeat --- until while --- do do --- case for --- next Algoritmi notevoli: ordinamento, ricerca, fusione 1.6.1 Ottimizzazione dei Programmi: Ottimizzazione dei programmi e dell’ordinamento come mezzo per effettuare delle ricerche più performanti Ottimizzare la leggibilità Ottimizzare la Velocità: Si può fare in molti modi che dipendono spesso anche dal tipo di SO e di linguaggio che si sta usando (ES: un algoritmo che usa una FORK, e quindi processi concorrenti su una piattaforma che non supporta il multitasking). Molte regole valgono sempre. Per esempio conviene usare i cicli FOR NEXT invece di usare dei GOTO. Questo in quanto quando inizia un ciclo viene memorizzato dal SO l’indirizzo di memoria in cui questo si trova e dove quindi deve ritornare. Con la GOTO invece devono essere lette tutti i numeri di riga fin quando non si trova quella giusta. I cicli for next invece vengono trattati con lo stack allocato ogni volta che uno di questi inizia. Lo spazio sullo stack viene cancellato quando il ciclo finisce. Per questo motivo il NEXT chiude sempre e solo l’ultimo FOR aperto ! Inoltre le GOTO vanificano l’uso delle cache memory. Le subroutine vengono trovate anch’esse per scansione, per cui quelle utilizzate più spesso è meglio metterle in cima al programma. In genere vengono scritte in questo ordine: Inizializzazioni Subroutine di servizio Sottoprogramma 1 Sottoprogramma 1 … pag 14 Sottoprogramma 1 Programma Principale (MAIN) Riutilizzare le stesse variabili ove possibile, nel caso che non servano più per altri scopi (ES: una variabile temp) Il valore delle variabili viene trovato in apposite tabelle scandite in maniera sequenziale per cui quelle dichiarate prima sono anche trovate prima -> dichiarare prima quelle più usate. Ottimizzare lo spazio in memoria: Vedi [ei1 81] garbage collection. Viene utilizzata per deallocare dalla memoria le variabili che sappiamo già che non vengono utilizzate nel resto del codice. Ricorsione di coda come metodo di risparmio dello stack. Tenere presente che una procedura è detta ricorsiva quando richiama se stessa ! ESEMPIO: fattoriale di N con la ricorsione Procedura P(N) IF N = 1 THEN RETURN N ELSE RETURN N* P(N-1) con un ciclo for 10 Input N 20 RIS= N 30 if N=1 then print RIS else 50 40 end 50 RIS = RIS * N 60 N = N –1 70 goto 30 1.6.2 Algoritmi e complessità qui NOTA: la bontà di un algoritmo dipende da tanti fattori che non possono essere determinati a priori. Se ad esempio stiamo studiando un algoritmo che deve funzionare su una memoria in un cellulare o su un dispositivo elettronico molto piccolo allora è importante che occupi poca memoria. invece se consideriamo un algoritmo che funzioni in rete e soddisfa le esigenze di tanti utenti allora deve essere performante (magari a discapito della memoria che occupa). In genere quindi deve essere il progettista a capire come valutare gli algoritmi per le sue necessità. Negli algoritmi che considereremo tenere presente … … che consideriamo per semplicità sempre liste di numeri interi da ordinare in senso crescente … quali sono i criteri di valutazione. Cioè a) numero di righe del codice b) quanta memoria occupano c) complessità (intesa come l’esponente di N nel calcolo relativo) d) applicazione dell’algoritmo (su pochi elementi , su molti su liste preordinate) … le curve delle prestazioni dei vari algoritmi (bubble e shell si incontrano verso i 55 – 60 elementi da ordinare) … che si può dimostrare che: N + (N-1) + (N-2) + . . . = N (N + 1) / 2 pag 15 1.6.3 Elenco degli algoritmi di ordinamento (wikipedia) Vi sono varie classi di algoritmi di ordinamento, i più noti ed utilizzati sono gli algoritmi di ordinamento per confronto (comparison sort algorithms), ma esistono altre classi caratterizzate da un tempo di esecuzione nel caso peggiore inferiore a O(nlogn). Nella tabella seguente sono elencati alcuni algoritmi di ordinamento, riportandone la complessità al caso Migliore, Medio e Peggiore, la memoria aggiuntiva richiesta, e la stabilità. Nome Migliore Medio Peggiore Memoria Stabile In place Avl sort — — — — — — Bubble sort O(n) O(n ) O(n ) O(1) Sì Sì 2 2 B sort — — — — — — B-Tree sort — — — — — — B-Tree sort — — — — — — Bitonic sort — — — — — — Btm sort — — — — — — Bucket sort O(n+m) O(n+m) O(n+m) O(m) — No Comb sort — — — — — — Counting sort O(n+k) O(n+k) O(n+k) O(n+k) Sì Sì — — Gnome sort Heap sort O(nlog n) O(nlog n) — — — — O(nlog n) O(1) No Sì Insertion sort O(n) O(n + d) O(n ) O(1) Sì Sì Integer sort O(n + k) O(n + k) O(n + k) O(1) Sì Sì Intro sort — — — — — — — — Jump sort Merge sort O(nlog n) O(nlog n) 2 — — — — O(nlog n) O(n) Sì No Ms sort — — — — — — Oet sort — — — — — — O(n2) O(log n) No — — — O(n2) O(log n) No Sì Partition sort Plasel sort O(nlog n) O(nlog n) — — — — Quicksort O(nlog n) O(nlog n) Radix sort O(n·k/s) O(n·k/s) O(n·k/s) O(n) Sì — Ripple sort — — — — — — Selection sort O(n ) O(n ) O(n ) O(1) No Sì Several unique sort — — — — — — Shaker sort O(n) — O(n2) O(1) Sì Sì Shear sort — — — — — — Simple sort — — — — — — Shell sort — — O(n1.5) O(1) No Sì Smooth sort — — — — — — Stupid sort O(1) O(n × n!) illimitato O(1) No Sì O(nlog n) O(n) No — O(nlog(3)/log(1.5)) — — Sì Sunrise sort Trippel sort 2 2 O(nlog n) O(nlog n) — — 2 1 QuickSort nel caso peggiore impiega O(n2), ma utilizzando Quickselect per la selezione del pivot, il tempo diventa O(nlogn) pag 16 1.6.3.1 Ricerca 1.6.3.1.1 Introduzione Ci sono vari algoritmi di ricerca. Questi li possiamo fondamentalmente organizzare (vedi [russo 64]) nei modi seguenti (si assume di ricercare un elemento su n): ricerca lineare Si intende la ricerca più semplice. Si scandisce dall’inizio alla fine tempi che sono proporzionali a n (statisticamente si fanno n/2 accessi) ricerca binaria E’ migliore del precedente, però prevede che la lista sia ordinata. Complessità nell’ordine di log2 di n. per calcolo E’ ancora più efficiente. Si tratta di avere una rappresentazione numerica della chiave (con qualunque algoritmo). fatto questo interpretando il valore che la chiave assume si posiziona in un indirizzo ben preciso. In pratica si fa un accesso per associazione. Tra i metodi di ricerca per calcolo descriviamo il metodo Hash e quello B-Tree. 1.6.3.1.2 1.6.3.1.2.1 Hash Introduzione Questo sistema per organizzare gli indici fornisce una funzione H(K) la quale applicata alla chiave K mi permette di rintracciare l’area di memoria in cui si trova le informazioni che cerco sulla chiave. H(K) = indirizzo Tenere presente che : la struttura è organizzata in blocchi di informazioni dette bucket (in cui mettiamo le registrazioni logiche) ogni bucket contiene al massimo C chiavi un bucket può coincidere con una pagina dati, una pagina virtuale, un insieme di pagine. Questo non interessa in quanto è un dettaglio implementativo ho m bucket (0, 1, ... , m-1) la funzione di hashing restituisce il buchet (quindi un valore compreso tra 0 e m-1) la funzione di hash serve sia per inserire che per reperire le chiavi ogni bucket fisicamente ha una dimensione fissa. Quindi sapendo quanto è grande (in byte) e quanto occupa una chiave è facile determinare C se si riempie un bucket si va in overflow e deve essere prevista una politica per contenere le chiavi in più due chiavi che finiscono nello stesso bucket sono dette sinonime. per N intendo tutte le chiavi possibili che devo trattare (gestire) introduco il concetto di fattore di caricamento Si intende con questo termine il valore: d = N / (C*m) cioè il rapporto tra le chiavi che devo gestire e quelle che entrano nella struttura che predispongo. pag 17 Se questo valore è basso avrò tendenzialmente pochi overflow. Se si avvicina a 1 o lo supera vuol dire che la struttura è sottodimensionata. Detto quanto sopra appare chiaro il funzionamento di una struttura ad hash. Prendo la chiave, la traduco in numerica, vi applico la funzione di hash e scopro in quale bucket andare a cercare. questo riduce gli accessi in maniera tanto migliore quanto maggiore è il numero dei bucket. Resta solo da definire la funzione di hash cercando di risolvere il problema della equidistribuzione. Infatti se le chiavi le distribuisco bene allora avrò anche pochi overflow. La funzione che è stato dimostrato rispondere a questi requisiti è: H(K) = K * costante mod m Infatti questa fornisce sempre valori compresi tra 0 e m-1, e si dimostra che tanto più grande è la costante più i valori dati sono equiprobabili. Cioè la probabilità che data una chiave trovo un preciso bucket è 1/m. 1.6.3.1.2.2 Gestione degli overflow E’ ovvio che in fase di progettazione si debba fare in modo che ci siano pochi overflow. In ogni caso devono essere previste le gestioni. [da1 pg 130] Abbiamo principalmente due tecniche. Supponiamo per semplicità di avere C=2 (gestiamo due chiavi per bucket) e due bucket in tutto : i e j. a) Soluzione a liste circolari In questo caso se H(K) di tre chiavi mi da sempre j allora le prime due le metto nel bucket j e la terza in i. scandendo le liste finché non trovo spazio. Implica più scansioni anche per la ricerca. Va bene se prevedo pochissimi overflow. b) Soluzione a catene distinte Prevede di allocare delle liste in più in caso di overflow. Le liste sono ordinate in maniera che se ci finisco evito di controllarle tutte. NOTA: Proposte più avanzate introducono l’Hashing dinamico, dove cioè il numero dei bucket non è determinato a priori. In questo caso si devono implementare delle politiche di riorganizzazione. 1.6.3.2 Algoritmi di ordinamento Sono importanti in quanto certi algoritmi di ricerca si basano su liste già ordinate. Negli ordinamenti il problema è quello di minimizzare il numero di operazioni medie necessarie. Ci sono anche in [corso pg 96 ] . In particolare si potrebbe fare qualche esempio su uso di struttogramma e sul calcolo della complessità Fare delle considerazioni sul modo di calcolare le prestazioni: tempo di swap parametri di calcolo ordine di complessità pag 18 Tipi di algoritmi: 1. A cicli fissi [ei1 88] Questo metodo consiste nel confrontare il primo elemento con tutti gli altri ed eventualmente mettere al primo posto quello più piccolo. Dopo si procede partendo dal secondo elemento e confrontarlo con tutti i rimanenti, e così via. Il vantaggio è che l’algoritmo si scrive in 5 linee (immaginiamo l’utilità su un cellulare !!). Lo svantaggio è che se la lista è già ordinata si faranno sempre lo stesso numero di scansioni (appunto i cicli sono fissi e sono N-1, il primo per il primo numero , il secondo per il secondo numero, …) Programma: FOR I =1 TO N-1 FOR J = I+1 TO N IF A(I) >A(J) THEN SWAP (I,J) NEXT NEXT Struttogramma FOR I = 1 FOR J = I+1 IF A(I) >A(J) no si SWAP (I,J) N N – 1 2. Metodo Bubble Sort [ei1 89] Molti dicono che l’unico vantaggio del metodo bubble sort sia nel nome accattivante e che per il resto non sia buono. TEST = TRUE I= 1 REPEAT IF A(I) > A(I+1) THEN SWAP(I,J) : TEST = FALSE I=I+1 IF I = N THEN I =1 ; TEST = TRUE UNTIL TEST = TRUE La complessità è dell’ordine N* (N+1)/2 ~ N² pag 19 1.6.3.3 Algoritmi di ricerca Si può guardare in [ei1 86]. Il problema è quello di ottimizzare il tempo medio di confronto tra i valori ( Vedasi [ei1 87]) che in genere è T=Tm*N/2 dove Tm è il tempo medio di un confronto. Ricerca Lineare: Se A è una costante di confronto con il metodo utilizzato allora il tempo medio è Tm=A* N. Ricerca Binaria: Se B è una costante di confronto con il metodo utilizzato allora il tempo medio è Tm=B* Log2(N) NOTA: per un numero N di elementi minore di 50 .. 100 allora conviene il metodo lineare altrimenti quello binario. NOTA: Vi vedano le tecniche di ricerca nella sezione relativa. pag 20 2 STRUTTURE DATI Le strutture dati servono come supporto per programmi complessi 2.1 Tipi di dati Tipo di dati Spazio su disco Intervallo Byte 1 byte Da 0 a 255 Boolean 2 byte True o False Le variabili che includono informazioni di tipo vero/falso, sì/no e on/off possono essere dichiarate come tipo Boolean. Il valore predefinito di Boolean è False. Nell'esempio seguente, blnRunning è una variabile Boolean in cui è memorizzata un'impostazione di tipo sì/no. Dim blnRunning As Boolean ' Verifica che il nastro stia avanzando. If Recorder.Direction = 1 Then blnRunning = True End if pag 21 Integer 2 byte Da -32.768 a 32.767 Long (intero lungo) 4 byte Da -2.147.483.648 a 2.147.483.6477 Single (virgola mobile a precisione semplice) 4 byte Da -3,402823E38 a -1,401298E-45 per valori negativi; da 1,401298E-45 a 3,402823E38 per valori positivi Double (virgola mobile a precisione doppia) 8 byte Da -1,79769313486232E308 a -4,94065645841247E-324 per valori negativi; da 4,94065645841247E-324 a 1,79769313486232E308 per valori positivi. Currency (intero diviso) 8 byte Da -922.337.203.685.477,5808 a 922.337.203.685.477,5807 Decimal 14 byte +/-79.228.162.514.264.337.593.543.950.335 senza virgola; +/-7,9228162514264337593543950335 con 28 decimali; il numero minore diverso da zero è +/-0,0000000000000000000000000001 Date 8 byte Dall'1 gennaio 100 al 31 dicembre 9999 Object 4 byte Qualsiasi riferimento Object String (lunghezza variabile) 10 byte + lunghezza stringa Da 0 a circa 2 miliardi Le variabili in cui vengono sempre memorizzate stringhe e mai valori numerici possono essere dichiarate come tipo String: Private S As String È quindi possibile assegnarvi stringhe e gestirle tramite funzioni stringa: S = "Database" Variant (con numeri) 16 byte Variant (con caratteri) 22 byte + lunghezza stringa Qualsiasi valore numerico fino all'intervallo di un Double pag 22 2.2 I vettori (ARRAY) Un elemento di un array e’ uno dei valori contenuti nell’array e ad esso si accede attraverso un indice che individua la sua posizione all’interno dell’array. pag 23 Nome del vettore a[0] -45 a[1] 6 a[2] 0 a[3] 72 a[4] 1543 a[5] -89 a[6] 0 a[7] 62 a[8] -3 a[9] 1 a[10] 6453 a[11] 78 Indice dell’elemento nel vettore 2.3 3 Le matrici PROGRAMMAZIONE E LINGUAGGI 3.1 Introduzione alla programmazione Gli argomenti rilevanti di questo campo possono essere divisi in due: da una parte c’è la discussione del concetto di algoritmo e della sua rappresentazione, la macchina di Turing, cioè del "cosa fare e come farlo", dall’altra quella dei linguaggi di programmazione, cioè del "come esprimerlo". Le due cose nella realtà sono andate ovviamente di pari passo, interagendo tra loro, ma in una discussione concettuale è bene tenerle distinte. Nella prima parte è centrale il concetto di algoritmo, utilizzato in matematica da sempre, dal famoso algoritmo di Euclide tanto per fare un esempio, ma precisato concettualmente solo in questo secolo dal pag 24 matematico inglese Alan Turing, inventore della famosa "macchina". Qui la ferraglia non c’entra nulla, essendo questa macchina solo una specifica delle poche, limitatissime cose che essa sa fare. Eppure questa limitatissima versione di un calcolatore è, dal punto di vista della capacità di portare a termine un programma, esattamente potente quanto il più sofisticato elaboratore attuale, l’unica differenza rilevante essendo la velocità di esecuzione. Seguono poi i modelli astratti di calcolatore inventati per darne una rappresentazione più realistica: la macchina di Von Neumann. 3.1.1 Le funzioni Procedure e funzioni Un programma puo' certamente essere realizzato scrivendo una sequenza di istruzioni che termina con il programma stesso. La soluzione non e' malvagia, ma si presta ad innumerevoli svantaggi. Il primo fra tutti e' l'impossibilita' di gestire progetti di grosse dimensioni: finche' si tratta di correggere o di modificare codice di un migliaio di righe non ci sono problemi, ma quando il codice raggiunge dimensioni dell'ordine di 100.000 righe ed oltre... Tutti i linguaggi ad alto livello consentono di programmare in modo piu' elegante ed efficace attraverso le astrazioni mediante procedure e funzioni. Queste astrazioni consentono di creare unita' ben distinte all'interno di un programma raggruppando una sequenza di istruzioni, dandole un nome e stabilendo le modalita' di comunicazione con il resto del programma. Queste istruzioni, generalmente, vengono raggruppate tenendo conto del compito da svolgere, anche se qualsiasi criterio e' permesso. Il programmatore, per usare questa sequenza di istruzioni, avra' bisogno solamente di indicarne il nome, al resto pensera' il compilatore o il traduttore del linguaggio. Si ha un'astrazione della nozione di istruzione (la procedura) e di operatore (la funzione). Infatti, la procedura non e' altro che una istruzione complessa che puo' essere utilizzata ovunque possa una istruzione semplice: il compito principale di una procedura e' il modificare il contenuto di locazioni di memoria. La funzione, invece, puo' essere utilizzata in qualunque valutazione di espressione: ha il compito di fornire un valore, anche se non e' escluso che, nel fornirlo, assuma pure i comportamenti della procedura. Come le istruzioni e gli operatori, anche le procedure e le funzioni possono accettare degli argomenti (parametri). Nel paragrafo successivo verra' mostrato come. Infine, e' stato detto che la funzione e' un'astrazione del concetto di operatore. Ma un operatore, la maggior parte delle volte, e' sovraccarico, cioe' e' definito su piu' tipi di dato diversi, piu' o meno simili. Purtroppo, non tutti i linguaggi di programmazione ad alto livello, permettono il sovraccarico delle funzioni (al posto di sovraccarico si parla overloading delle funzioni), cioe', per alcuni linguaggi, una funzione non puo' essere definita piu' volte all'interno di un programma per diversi tipi di argomenti. Passaggio parametri Come visto nella sezione precedente, per la sequenza di istruzioni che realizza una procedura o una funzione, viene stabilita la modalita' di comunicazione con il resto del programma. I parametri servono alla comunicazione esplicita, ed interna, di dati e risultati fra procedura o funzione (chiamato) ed il resto del programma (chiamante, puo' essere anche un'altra funzione e procedura). Dal lato del codice "chiamato", si parla di parametri formali. Dal lato "chiamante", si parla di parametri attuali. pag 25 I parametri formali sono degli identificatori speciali dichiarati localmente al chiamato. L'essere speciale deriva dall'associazione che viene fatta tra essi ed i parametri attuali, nel meccanismo di passaggio dei parametri. I parametri attuali possono rappresentare dei valori o degli identificatori esterni al chiamato. Vediamo un esempio. void A(int x, int y) { ... } void B() { ... int h=4; ... A(h,5); ... } Nel codice mostrato, la funzione B chiama la A. In questo processo di chiamata, "h" e "5" sono i parametri attuali, mentre invece "x" ed "y" sono i parametri formali. Il meccanismo di passaggio non sempre e' lo stesso: esistono diverse tecniche. Passaggio per valore: E' il modo piu' semplice per passare parametri: i parametri attuali vengono valutati (ne viene calcolato ed estratto il valore) ed i valori sono associati ai parametri formali. Nel codice precedente, "x" assumera' il valore di "h" ed "y" assumera' il valore "5". Saranno comunque tutte entita' diverse, cioe' i valori dei parametri attuali non verranno alterati in alcun modo. Passaggio per riferimento (o per indirizzo): Viene passato l'indirizzo della memoria contenente i valori dei parametri attuali. Quindi, quest'ultmi, condivideranno l'indirizzo con i parametri formali. Ogni modifica fatta ai formali, si riflettera' sugli attuali. In alcuni linguaggi, se il parametro passato per riferimento non e' un identificatore ma una espressione (puramente numerica o composta da identificatori), allora viene valutata l'espressione ed il valore calcolato viene posto in una nuova locazione di memoria. L'indirizzo di questa nuova locazione di memoria sara' quello passato. Metodo "Copia-Ripristina" E' un ibrido dei due metodi precedenti. L'associazione e' identica al passaggio per valore, quindi i parametri formali verranno modificati indipendentemente da quelli attuali. Alla fine della procedura o della funzione, pero', il valore dei parametri formali viene copiato in quelli attuali. Questo metodo potrebbe essere utile nella programmazione parallela, dove una funzione continua ad accedere ad un identificatore mentre viene modificato da una procedura appena chiamata. Finche' il chiamato non avra' terminato il proprio lavoro, il chiamante usera' il valore vecchio e non uno inconsistente. Ricorsione La ricorsione e' una tecnica altamente inefficiente in termini di memoria utilizzata e di tempo pag 26 impiegato nell'esecuzione, a causa delle modalita' di implementazione nei vari linguaggi. Ha il notevole vantaggio, pero', di introdurre nei programmi eleganza, naturalezza descrittiva, facilita' di espressione per problemi particolari, sinteticita'. In matematica, una funzione e' definita ricorsivamente quando, nella sua definizione, chiama se stessa. Un esempio puo' essere la funzione fattoriale f(x)=x! (es. 5!=5*4*3*2*1=120). Per definizione il fattoriale vale 1 se x=0, vale x*f(x-1) se x>0 (5!=5*4!=5*4*3!=5*4*3*2!=5*4*3*2*1!=5*4*3*2*1). Come si puo' osservare, la definizione avviene in due passi distinti: si ha un passo base ed un passo induttivo. Il passo base consiste nella "condizione di blocco" della funzione. Nella funzione precedente, come in tutte le altre funzioni ricorsive, il passo base consiste nel dare un valore indipendente dalla variabile, sotto certe condizioni. L'utilita' del passo base sta nell'evitare alla funzione di essere applicata infinite volte (oppure, nel caso del fattoriale, di arrivare ad un punto in cui la funzione non e' definita, rendendo senza senso la formula). Il passo induttivo consiste nella definizione della funzione nel caso generale (in termini della funzione stessa). Come ulteriore esempio, vediamo la realizzazione, in Java, della funzione che calcola l'elevamento a potenza (positiva) di una base (positiva anch'essa). La funzione matematica e' f(x,y)=x^y. Puo' essere definita ricorsivamente come 1 per y=0 e x*f(x,y-1) per y>0. Il seguente codice realizza la funzione: long Eleva(int x, int y) { if (y==0) return 1; else return x*Eleva(x,y-1); } 3.1.2 Tipi di linguaggi NOTA: Fare una introduzione sui linguaggi classificati in senso verticale (macchina, C, … , linguaggi ad alto livello) e in senso orizzontale (imperativi, ad oggetti , …) Questi argomenti sono in stretto contatto con l’architettura reale dei calcolatori e con il loro linguaggio macchina. Questo tipo di linguaggi costituisce quindi il primo e più elementare "stile" per un linguaggio di programmazione. Alcuni dei linguaggi di cosiddetto alto livello in realtà possono essere visti come l’astrazione dalla particolare architettura della macchina "fisica" attraverso l’uso delle istruzioni di una macchina "logica" definita dallo stesso linguaggio. Piuttosto che programmare nell’Assembler della macchina fisica si programma per esempio in C, che non a caso è stato definito con il termine autocontraddittorio di "assembler multipiattaforma", con questo intendendo che le sue istruzioni sono elementari al punto di poter pensare di costruire una "macchina C" reale che le esegua direttamente come fossero parte di un vero Assembler. 3.1.3 Linguaggi imperativi Un poco più su come livello di astrazione ci sono i cosiddetti linguaggi imperativi, in cui l’idea di fondo è che: il programmatore ha l’obbligo di definire in tutti i dettagli l’algoritmo che sta costruendo, ha a disposizione strumenti che richiamano più un linguaggio naturale che la macchina che lo esegue. pag 27 Importante è qui l’idea di programmazione strutturata, con il famoso teorema di Jacopini-Böhm, che dice sostanzialmente che si può programmare senza i "goto", sostituiti dai concetti di blocco, alternativa ciclo. Importanti sono anche i concetti di procedura e programmazione ricorsiva, di struttura dati con puntatore, . . . i quali, assieme al controllo di tipo, sono i primi rudimentali elementi del concetto di tipo di dati astratto (ADT, Abstract Data Type) della programmazione ad oggetti. In questa categoria si possono far rientrare, pur con la difficoltà che tutte le classificazioni comportano, tutti i linguaggi più noti, a cominciare dal Fortran per proseguire con tutti i suoi eredi più o meno diretti, cioè Algol, Pascal, Modula e Ada da una parte, BCPL e C dall’altra. Rientra in questa categoria anche il Basic, il quale, chissà perché, viene sempre ignorato nelle storie dei linguaggi di programmazione, pur avendo un peso notevole, se non altro per chi si avvicina alla programmazione attraverso i PC. 3.1.4 Stile funzionale Lo stile imperativo non è l’unico possibile, infatti sono noti da molto tempo linguaggi come Lisp e Prolog, rappresentanti rispettivamente dello stile funzionale e dello stile logico di programmazione. In comune hanno l’eliminazione dell’assegnamento di valore ad una variabile, sostituita dall’uso di funzioni e relazioni. La loro espressività è molto diversa da quella dei programmi imperativi, tanto da essere linguaggi di elezione nei campi dell’intelligenza artificiale. 3.1.5 Programmazione ad oggetti NOTA:Vedi “Appendice A : Oggetti” alla fine. Un altro tipo di evoluzione dei linguaggi è quello verso la cosiddetta programmazione ad oggetti (OOP, Object Oriented Programming), settore che attualmente attrae il maggior numero di studi ed attività, promettendo un rivoluzione nella capacità di costruire programmi per sistemi complessi. Una caratteristica dei linguaggi imperativi che li rende inadatti a questo scopo è infatti quella che con essi il programmatore ha sempre l’impressione di dover partire da zero, anche quando riutilizza parti di programmi scritti da altri. Il disagio provocato da questa situazione è stato ben espresso da R.W.Hamming: "laddove Newton poteva dire di aver visto un po’ più avanti degli altri perché era potuto salire "sulle spalle di giganti", io sono costretto a dire che oggi noi stiamo uno sui piedi dell’altro". La capacità di utilizzare il lavoro di altri viene, nei linguaggi OO, dai concetti di incapsulamento, ereditarietà polimorfismo. Questi permettono di dare al linguaggio che li implementa, sia la potenza dell’astrazione matematica che la flessibilità del linguaggio naturale. Queste due cose sono spesso in contraddizione e non a caso esistono diversi tipi di linguaggi OO, che propendono verso uno o l’altro dei due versanti. I linguaggi OO sono derivati tutti dal capostipite Simula, da cui direttamente discendono Beta ed Eiffel. Simula ha però trovato una reinterpretazione terminologica e quasi "filosofica" in Smalltalk, che ha finito per trovarsi al centro dell’attenzione, come fosse il progenitore della specie. pag 28 NOTA: Nella realtà però, la purezza di impostazione che caratterizza i linguaggi appena citati non ha trovato gran riscontro, forse perché le idee innovative faticano sempre ad affermarsi. Così in giro si sente parlare prevalentemente di linguaggi "misti", che altro non sono che linguaggi imperativi a cui sono stati sovrapposti i costrutti tipici della OOP: dal C sono derivati Objetive-C, C++ e Java, dal Pascal l’Object Pascal (Delphi), dal Modula-2 Modula-3 e da Ada Ada 95. Qualcuno di questi concetti è filtrato addirittura nella struttura del Basic. 3.1.6 Programmazione concorrente ed a eventi Parallelamente a queste divisioni di stile, anche se sarebbe meglio dire perpendicolarmente, si sviluppano i concetti della programmazione concorrente e ad eventi. Il primo riguarda le idee di esecuzione contemporanea di più processi o sottoprocessi (task e thread), con i problemi di sincronizzazione ed attesa, con i concetti di semafori e risorse (e deadlock). Il secondo riguardo il concetto di interruzione del flusso di programma e salto alla procedura di gestione dell’evento. Entrambi devono fare i conti con i problemi delle cosiddette sezioni critiche, cioè parti di programma non interrompibili. Tutti questi argomenti sono fondamentali per i sistemi operativi e sono strettamente connessi ai dispositivi hardware che permettono di gestirli. 3.2 dati e procedure, linguaggi e tecniche di programmazione secondo i diversi paradigmi: Una volta definito il progetto e fatta l’analisi (e anche la scelta del linguaggio che si deve adoperare) si deve procedere alla ricerca dell’algoritmo e alla sua definizione approccio top-down o bottom-up. [ei1 75] NOTA: il medoto deduttivo partiva da una idea sintetica e generale per arrivare a conseguenze logiche sempre più dettagliate. Quello induttivo invece partiva dal particolare per arrivare agli aspetti generali. La trasposizione in informatica di questi due concetti a portato ai metodi top-down e bottom-up [ei1 75]. 3.2.1 Metodo Top Down E’ quello che va per la maggiore in quanto i linguaggi moderni sono orientati alla programmazione strutturate che si avvicina di più a questo modello [ei1 76]. NOTA: Vedere la parte in [ei1 76 ] Si parte dalla stesura dei concetti generali per sviluppare in seguito i dettagli in maniera ricorsiva. In questo modo risulta facile rimanere aderenti alla realtà anche in mancanza di una analisi ferrea. Se l’esito della fase di test dovesse risultare negativo allora si scende di un livello (nella particolarità) fino a quando non si individua l’errore. Questa fase di ricerca di un errore è più macchinosa che non nel metodo Bottom Up in quanto si deve salire e scendere continuamente di livello mentre nell’altra metodologia si rimane sempre allo stesso livello. 3.2.2 Metodo Bottom Up Vedere la parte in [ei1 75]. NOTA: Con questo metodo è difficile rimanere aderenti alle specifiche generali se queste non sono state rigorosamente definite. Infatti partendo dalla definizione di tanti particolari si può incorrere nel rischio di metterli insieme e arrivare a dei risultati indesiderati nati appunto dal fatto che la progettazione è stata fatta male. Infatti niente è più facile che partendo dalla definizione di tante piccole parti poi alla fine si arrivi, mettendole insieme, ad avere un risultato che non è perfettamente quello desiderato. pag 29 3.2.3 Programmazione strutturata Vedi [ei1 76] ed [ei6 123]. NOTA: prima che si introducesse la programmazione strutturata si usavano linguaggi in cui si facevano i salti. Questo implicava che la bravura di un programmatore dipendeva dalla sua capacità di non perdersi tra di questi. NOTA: La documentazione dei programmi è una voce di costo che si possono sorbire solo i CED. Questo implica che per poter fare una buona manutenzione dei programmi devono essere più chiari possibile. Se si usa un approccio strutturato questo viene più semplice. La programmazione strutturata si basa su seguenti regole: Il programma deve essere composto da tanti moduli indipendenti [ei1 77]. Ogni modulo ha un punto di ingresso e uno di uscita (istruzione END). La dichiarazione di variabili fatta ai livelli inferiori risulta invisibile ai livelli superiori per cui si evita così anche la confusione sull’uso delle variabili. ciascun programma è sviluppato usando solo i costrutti fondamentali: Sequenza, Selezione, Iterazione NOTA: Questi costrutti hanno un solo punto di ingresso e uno solo di uscita. Ogni programma deve essere commentato e indentato NOTA: E’ possibile costruire programmi strutturati anche con programmi che non sono orientati a questo (come il BASIC) in quanto le regole usate sono molto semplici, ma si complica molto il codice e anche la leggibilità (vedi esempi di [ei1 79]) Riassumendo: a) La programmazione strutturata si sposa molto bene con la metodologia Top-Down (che inoltre consente sempre di avere uno sguardo sull'’intero progetto senza mai perdersi) b) Il programma deve essere modulare possibilmente di piccole dimensioni (si consiglia che un modulo stia in una pagina) c) ci deve essere un uso delle sole tre strutture fondamentali: selezione, sequenza, iterazione Selezione Iterazione Sequenza IMPORTANTE: Bohm e Jacopini hanno dimostrato che con questi tre costrutti si può costruire qualunque programma che è rappresentabile mediante un diagramma a blocchi in maniera equivalente [ei6 123]. pag 30 Alcuni propongono di aggiungere a queste tre strutture anche un’altra che faccia da CASE e un’altra da DOTEST (che differisce solo per il fatto che il controllo lo fa alla fine). In ogni caso sono delle derivate in quanto si possono ricostruire con gli altri tre costrutti. 3.3 Metodologia di costruzione dei programmi. Modularità NOTA: Sarebbe interessante fare un collegamento con il concetto di ereditarietà dei linguaggi OO. Si può parlare, in ambito di programmazione ad oggetti, di 3.4 package classi istanze importazione Ingegneria del software, tecniche di documentazione e di manutenzione dei programmi. Come documentare un programma è trattato [ei6 122]. In particolare si descrivono i concetti principali per fare una buona documentazione del software: a) Un buon programma non è completo senza documentazione (anche se funziona). Infatti dovrebbe avere almeno il manuale utente. b) La documentazione deve essere scritta dalle prime fasi della progettazione del software e non alla fine in quanto si perderebbero dei concetti importanti. Fanno parte della documentazione di un progetto (NOTA: fare parallelismo con il ciclo di vita del software, fare esempio di analisi sbagliate): a) la descrizione della analisi del problema b) la descrizione delle procedure per risolverlo (magari mediante diagrammi a blocco, senza dettagli implementativi ma solo di I/O) c) la descrizione di come i moduli sono collegati tra di loro d) la descrizione degli INPUT e OUTPUT e) la descrizione dei tracciati delle tabelle f) la descrizione di tutte le variabili usate (o almeno delle più significative) con un occhio di riguardo a quelle che causano delle scelte. g) la descrizione di tutti i casi di prova (infatti in questo caso se si dovessero fare delle modifiche al programma sarebbe molto facile verificare se il programma è ancora valido rieffettuando tutte le prove già superate -> infatti spesso per risolvere un problema se ne causa un’altro). Tale elenco dovrà essere arricchito ogni volta che si studiano nuovi casi o che si facciano delle modifiche. h) un manuale per l’utente è uno per l’amministratore (sviluppatore) del programma Infine bisogna tenere presente che una ottima documentazione se non aggiornata ogni qualvolta si fanno delle modifiche ad un programma diventa inutile. NOTA: Un’altra utile osservazione si deve fare su come fare per rendere rintracciabile la documentazione fatta in quanto si può incorrere nel pericolo che si scriva tanto ma nessuno sappia più dove andare a trovare pag 31 le parti interessanti soprattutto quando si parla di moduli che possono essere utilizzati in tanti programmi (vedi la tecnica per mettere in relazione molti moduli a molti programmi e anche di come lo standard Qualità ISO 9000 nella parte che riguarda il software tratta la gestione della documentazione 4 LABORATORIO 4.1 cicli Questi cicli visualizzano i primi 10 numeri: ESEMPIO 01 for i = 1 to 10 msgbox(“ numero : “ & i ) next ESEMPIO 02 i=1 do msgbox(“ numero : “ & i ) i=i+1 loop while (I < 11) ESEMPIO 03 i=1 do msgbox(“ numero : “ & i ) i=i+1 loop while (I =< 10) ESEMPIO 04 i=1 do msgbox(“ numero : “ & i ) i=i+1 loop until ( i> 10) Questi cicli visualizzano i primi 10 numeri tranne il 7 ESEMPIO 01 for i = 1 to 10 if (i <> 7) then msgbox(“ numero : “ & i ) end if next ESEMPIO 02 pag 32 i=1 do msgbox if (i <> 7) then msgbox(“ numero : “ & i ) end if i=i+1 loop while (I =< 10) 4.2 caricamento di vettore con numeri casuali Public Sub carica() '---------------------------------------------''- procedura di caricamento '---------------------------------------------Text1.Text = "" For i = 0 To 9 v(i) = Int((Rnd) * 100) '--- genera numeri casuali Text1.Text = Text1.Text & " " & v(i) Next End Sub 4.3 Media, massimo e minimo dei valori di una matrice 4.4 Uso delle funzioni pag 33 4.5 Calcolo del fattoriale Il fattoriale di un numero N si indica con: fattoriale = N ! e coincide con : N ! = N * (N – 1) * (N – 2) * (N – 3) . . . * 1 ESEMPIO 1 : Fattoriale di 4 4 ! = 4 * 3 * 2 * 1 = 24 ESEMPIO 2 : Fattoriale di 5 5 ! = 5 * 4 * 3 * 2 * 1 = 24 Da notare che il fattoriale di 5 è uguale a 5 * (fattoriale di 4) NOTA: il fattoriale si può implementare in maniera iterativa o ricorsiva 4.6 Algoritmo di Euclide (MCD) Questo algoritmo serve per calcolare il Massimo Comun Divisore con un algoritmo inventato da Euclide (questo è uno dei primi algoritmi della storia) Si basa su un concetto semplice. Se abbiamo A e B allora il MCD di questi è uguale al MCD di B e R, dove R è il resto della divisione tra A e B. ES: A = 32 e B = 12 R = A / B = 32 / 12 = 2 con il resto di 8 (quindi R = 8 ) adesso A = B e B = R (quindi A = 12 e B = 8) si ripete R = A / B = 12 / 8 = 1 con il resto di 4 adesso A = B e B = R (quindi A = 8 e B = 4) si ripete R = A / B = 8 / 4 = 2 con il resto di 0 l’ultimo valore di B è 4 quindi il MCD è 4 Infatti 32 = 2^4 (2 elevato alla quarta) mentre 12 = 2^2 * 3 , quindi il Massimo dei comun divisori è 4 L’algoritmo è semplice: siano A e B assegnati pag 34 si calcola R se R = 0 allora B è il MCD altrimenti si assegnano ad A il valore di B e a B il valore di R e si ricomincia CODICE: Private Sub Command1_Click() Dim x As Integer Dim y As Integer x = InputBox("dammi il numero") y = InputBox("dammi l'altro numero") R = x Mod y If R = 0 Then MsgBox ("il M.C.D. è " & y) Else Do MsgBox (" X = " & x & " Y = " & y) x=y y=R R = x Mod y Loop Until R = 0 MsgBox ("il M.C.D. è " & y) End If End Sub 4.7 programma che dice se x è il fattoriale di qualche numero Dato un numero N intero calcolare di quale numero è il fattoriale (se lo è di qualche numero) Esempio: dato N = 24 si deve calcolare che è il fattoriale di 4 (infatti F(4) = 24 ) 4.8 Uso degli operatori logici 4.9 Uso della shell per rinominare files Il seguente esempio rinomina il file c:\q\c.txt in d.txt (sempre nella stessa cartella Private Sub Command1_Click() Shell "cmd.exe /c rename c:\q\c.txt d.txt" End Sub pag 35 4.10 programma di conversione da decimale a binario Fare un programma che dato un numero decimale ritorna la sua conversione in binario. 4.11 operazioni sui files Vedi il progetto completo 4.12 funzione INPUTBOX 4.13 Calcolo di polinomio Si può caricare un vettore con i valori della funzione di n-esima potenza: caricare n = grado del polinomio (es: 3) caricare i valori v0, v1, v2, v3 allora si deve calcolare risultato = v0 + v1* X + v2*X² + v3*X³ 4.14 Shift di un vettore spostamento in un vettore in avanti di n posizioni. spostamento a partire dalla cella x e finire alla Y fare ordinamento per inserimento usando questi criteri. 4.15 Ordinamento di un vettore 4.16 Generazione di tabella della verità pag 36 Dato in input il numero di bit di cui calcolare le combinazioni si deve visualizzare a video la tabella della verità per l’ OR o per l’ AND Esempio: Se N = 2 e si deve calcolare l’ AND: ______AND_____ 00 0 01 0 10 0 11 1 Esempio: Se N = 3 e si deve calcolare l’ AND: ______AND_____ 000 0 001 0 010 0 011 0 100 0 101 0 110 0 111 1 Esempio: Se N = 3 e si deve calcolare l’ OR: ______OR_____ 000 0 001 1 010 1 011 1 100 1 101 1 110 1 111 1 4.17 Calcolatrice 4.18 Numeri di fibonacci La serie di fibonacci dice che ogni numero è dato dalla somma dei due precedenti. 4.19 Codifica di un testo per sostituzione Dato un testo fare attraverso una chiave segreta la sostituzione delle lettere con numeri (compreso lo spazio) al fine di crittarlo pag 37 4.20 Operazioni sulle stringhe 4.21 Campo Minato pag 38 5 VARIE 5.1 5.1.1 Appendici Programmazione strutturata secondo il teorema di Jacopini-Böhm NOTA: mancano le immagini Come abbiamo visto quando si è trattato della macchina di Turing, un algoritmo è essenzialmente costituito dai seguenti elementi: un numero finito di operazioni, o meglio funzioni, da applicare ai dati per trasformarli; la distinzione tra un numero finito di "stati di computazione " nel programma e la capacità di saltare da uno all'altro tramite l'istruzione "goto" (vai a ..); una "struttura di controllo" capace di distinguere la situazione del momento e agire di conseguenza (cioè applicare operazioni e salti), che non è altro che la if .. then .. (se .. allora ..). Se noi abbiamo delle operazioni e le applichiamo una di seguito all'altra sempre nello stesso modo inventiamo solo un altro tipo di operazione. Quello che rende gli algoritmi capaci di fare molto di più di questo è che ad ogni passo possono scegliere, a seconda dello stato in cui sono giunti, come eseguire l'operazione presente e quale passo eseguire dopo. La situazione è ben rappresentata da un grafico in cui ci siano un numero finito di "nodi", rappresentanti gli stati di computazione, e da ognuno di essi escano delle frecce verso alcuni altri nodi. Il progredire del calcolo può essere visualizzato così: si parte dal nodo iniziale, eseguendo le operazioni lì specificate, e si passa al nodo successivo seguendo una delle frecce, quella corrispondente alla situazione in cui ci si trova; qui giunti si ripete, eseguendo le operazioni del nodo e scegliendo il successivo; e così via finché si trova l'istruzione di termine del calcolo. L'aspetto grafico della computazione sarà il cosiddetto "piatto di spaghetti", con un groviglio di linee tra un punto e l'altro. Quando l'arte della programmazione muoveva i primi passi i programmatori consideravano dimostrazione di bravura programmare con il minor numero di istruzioni possibili, e questo faceva sì che si tendesse a riutilizzare pezzi di codice semplicemente saltando al loro inizio. Ne risultavano complicati collegamenti tra le varie parti del programma, non facilmente intelleggibili a chi non lo avesse scritto. NOTA: Il problema è che così facendo il programma risulta difficilmente comprensibile anche al suo estensore, specie quando è grande o quando questo gli pone mano dopo una lunga pausa. Inoltre, pur se questo modo di lavorare è ancora praticabile da un singolo programmatore, diventa del tutto inconcepibile per un lavoro di gruppo, quando è necessario che il codice sia facilmente capito anche da persone che non lo hanno scritto. Si è arrivati dunque, verso la fine degli anni sessanta, a cambiare completamente la visione di cosa sia un programma ben scritto, e a non considerare più la capacità di districarsi in mezzo ad aggrovigliate matasse di goto come principale caratteristica del buon programmatore. Lo schema che alla fine è emerso, detto "programmazione strutturata", si propone di dare l'aspetto di un flusso ordinato tra un inizio ed una fine a programmi che di per sé sarebbero intricati. Il modello è il "programma sequenziale", nel quale si applicano le varie operazioni una di seguito all'altra, in modo ordinato, senza alternative possibili. Abbiamo detto però che la semplice sequenza non può esprimere tutta pag 39 la potenza degli algoritmi poiché questa è essenzialmente contenuta nella capacità di scegliere tra due alternative. Come si possono dunque conciliare queste due esigenze? L'idea è semplicissima: basta imitare il linguaggio naturale. Il goto è concepibile solo per una macchina i cui "pensieri" hanno dei ben determinati indirizzi, mentre in un linguaggio naturale un algoritmo è già espresso in una forma strutturata che corrisponde allo "svolgimento temporale" di una particolare computazione, quello cioè che si chiama un "processo". In un linguaggio naturale le costruzioni utilizzate sono "fai questo ... , dopo fai quello ... ", "se ... fai questo ... altrimenti fai quello ... ", "finché ... fai così ... " oppure "ripeti 5 volte questo ... ", che sono proprio le costruzioni di "controllo del flusso", sequenza, alternativa e ciclo, che saranno al centro di questa discussione. Possiamo dire che i linguaggi vicini alla macchina, come i programmi di Turing, i linguaggi Assembler o i più vecchi linguaggi di alto livello tendono naturalmente ad avere una struttura "a ragnatela spaziale", creata dai goto, mentre i linguaggi naturali e quelli di più alto livello tra i linguaggi di programmazione hanno una struttura temporale data dal "seguire lo svolgimento del processo". Questo fatto è visibile nel Fortran, che come primo linguaggio di livello superiore a quello della macchina ha ancora strutture di controllo molto primitive, specie nelle prime versioni. La tendenza ad imitare il linguaggio naturale è iniziata con Algol (1960), linguaggio che era appunto noto per la sua verbosità e che ha dato in eredità le sue strutture di controllo a quasi tutti i suoi successori. Non si può però dire che abbia inventato niente di straordinario, semplicemente perché queste strutture sono solo l'espressione, in inglese, delle frasi viste sopra, "if ... then ... else ... " ecc. . L'utilizzo delle forme di controllo di flusso "alla Algol", cioè l'alternativa, nella quale il flusso si divide in due rami per poi riunirsi, e il ciclo, in cui il flusso gira in tondo come in un gorgo, poneva il problema se tutti i programmi potessero essere scritti solo con essi, evitando l'ormai famigerato goto. La cosa è evidente per un programma pensato fin dall'inizio in termini naturali, lo è molto meno se si richiede di riscrivere in forma strutturata un programma pieno di salti tra un punto e l'altro. La cosa fu comunque risolta teoricamente nel 1966 dal teorema di Jacopini-Böhm: ogni programma di Turing può essere espresso con sequenze, alternative o cicli di blocchi di istruzioni. Il seguito sarà dedicato a chiarire i termini della questione, spesso non esposti accuratamente, soprattutto riguardo al fondamentale concetto di blocco. Esprimeremo i concetti in un quasi-C (misto a Basic e Pascal), non perché il C sia espressione di programmazione strutturata, cosa non vera in quanto, come molti altri linguaggi, mantiene l'istruzione goto, ma perché dispone di un simbolo, la parentesi graffa, che rende facilmente visibile il concetto, fondamentale per la comprensione del teorema, di blocco di istruzioni. Le caratteristiche dei vari termini quindi non si riferiscono al C reale. Il blocco di istruzioni è un sottoprogramma, cioè un insieme di istruzioni in sé completo, che non ha bisogno di far riferimento ad altro. E' importante notare che questo implica che il blocco ha un'unica uscita, la fine del blocco, e tutti i processi che eseguono istruzioni del blocco terminano qui. Se infatti ci fossero delle "uscite laterali" tramite delle istruzioni di salto, queste dovrebbero far riferimento ai nomi (etichette) dei punti di programma a cui saltare, per cui il sottoprogramma non sarebbe più autonomo, facendo riferimento a queste etichette. Inoltre si vuole che il blocco sia "opaco", nel senso che si possa utilizzare la sua capacità di elaborazione complessiva senza però vederne e poterne usare le singole parti (è un blocco, in fin dei conti!). Dall'esterno non si può quindi saltare ad una particolare sua istruzione che sia diversa dal semplice inizio del blocco. Queste due caratteristiche fanno sì che i processi esecutivi, attraversando un blocco, trovino un'unica entrata (l'inizio del blocco), un'unica uscita (la fine del blocco) e nessuna "uscita laterale". Questa, anche se di solito poco sottolineata, è la caratteristica principale della programmazione strutturata, perché prima di parlare del controllo del flusso (canali che si dividono o uniscono) bisogna sottolineare le caratteristiche di "impermeabilità" del tubo che porta questo flusso, che sono appunto una sola entrata, una sola uscita e niente perdite laterali. Dal punto di vista simbolico le parentesi del C sono il modo migliore di descrivere i blocchi: { ..... } L'inizio di un blocco corrisponde al simbolo "{", la fine al simbolo "}", e i blocchi possono essere annidati uno dentro l'altro come sono i livelli di parentesi in matematica. Un blocco può essere trattato (e pensato) a tutti gli effetti come una singola istruzione, e quindi la combinazione più semplice è la sequenza di blocchi: pag 40 { B1 } { B2 } ..... { BN } Vediamo invece come si esprimono alternative e cicli usando i blocchi. Prima di iniziare bisogna far notare che queste istruzioni condizionali si legano ad un particolare blocco, e quindi formano con esso un nuovo blocco che si dovrebbe indicare con due parentesi. Questo però renderebbe la notazione pesante, per cui è meglio pensare alle parole chiave, introdotte in sovrabbondanza rispetto al vero C (if, then, else, do, while, until, loop, ecc.), come a collanti tra le varie parti. Nel seguito per chiarezza si userà distinguere tramite colori blocchi di questo tipo, cioè non delimitati da parentesi. In alcuni casi le parentesi si rendono comunque necessarie. Per l'alternativa l'espressione (quasi C) è: if (A) then { B1 } else { B2 } Qui si ha la valutazione di una condizione, A; se questa è vera si esegue il primo blocco altrimenti si esegue il secondo. E' da notare che i termini "then" ed "else" non sono necessari per la costruzione, servono solo per chiarezza linguistica. Rispetto alle tipiche rappresentazioni grafiche dei diagrammi di flusso, qui adottiamo per le istruzioni decisionali dei cerchi invece dei soliti rombi. Questi ultimi sono usati perché sono sottointese due possibili risposte alternative, sì o no, corrispondenti a due vertici del rombo. Qui però pensiamo nel modo più generale, con scelta non tra due sole possibilità, ma tra quante si vuole (multibranch), per cui usiamo dei cerchi. Nello sforzo di essere minimali si può notare che la prima parte delle costruzione è già sufficiente scrivendo, in quasi C, senza "then" ma con un "do": if (A) do { B } Questa, che potremmo chiamare "esecuzione opzionale" perché si limita ad eseguire il blocco seguente, B, se è vera la condizione A, può infatti esprimere l'alternativa completa nel seguente modo: if (A) do { B } if (not A) do { B } dove "not A" è la condizione opposta ad A, e B rappresenta due volte lo stesso blocco. A stretto rigore bisogna però prima "congelare" la condizione iniziale: {A1 = A} if (A1) do { B } if (not A1) do { B } cioè osservare il valore della condizione A all'inizio, scrivendolo in una nuova variabile A 1 che non sia modificata nel passaggio intermedio. La valutazione di condizioni come la if ( ... ) infatti hanno l'importante caratteristica di non modificare nessuna variabile (osservano solo la situazione), cosa che però non è garantita da un semplice blocco di istruzioni. Riguardo ai cicli, il più facile da descrivere è quello che richiede di eseguire un certo numero N di volte un blocco di istruzioni. Esprimendoci in questo caso quasi alla Pascal (con però un "do" in più) perché la costruzione C è poco trasparente: for i=1 to N do { ..... } Questo tipo di ciclo però è facilmente esprimibile usando quello generale, che valuta una condizione per l'esecuzione. Di questo tipo di ciclo ci sono due forme (la prima è scritta con un "do" in più rispetto al C): while (A) do { B } oppure: do { B } while (A) Nel secondo si esegue il blocco una prima volta, ed alla fine si valuta la condizione e, nel caso sia vera, si ripete finché la condizione non diventi falsa. Nel primo invece la condizione viene valutata all'inizio, quindi il blocco potrebbe anche non essere mai eseguito, se la condizione è subito falsa. Oltre a queste due forme si trovano costruzioni equivalenti che usano la condizione in modo opposto, cioè se è vera non eseguono il ciclo. Abbiamo così la costruzione del Pascal: pag 41 repeat { ..... } until (A) e le costruzioni Basic: do until (A) { ..... } loop do { ..... } loop until (A) che è: che è: do while (not A) { ..... } loop do { ..... } loop while (not A) Queste comunque implicano solo il cambio di condizione, da "A" a "not A", nelle costruzioni precedenti e non sono quindi nulla di nuovo. L'importante è che i due tipi di cicli while sono equivalenti e possono esprimere il ciclo for. Per questo infatti basta scrivere: {i = 1} while (i = < N) do { ..... i = i + 1} in cui si mette una istruzione di inizializzazione, i = 1, si aggiunge alla fine del blocco l'istruzione di incremento, i = i +1, con i = < N come condizione di ripetizione. Per gli altri due, "do ... while" può essere espresso con "while ... do" semplicemente ripetendo il blocco una prima volta, prima del ciclo, cioè: do { B } while (A) che è: { B } while (A) do { B } A rigore invece il "while ... do", che può avere zero esecuzioni, non può essere espresso con il "do ... while" che di esecuzioni ne fa almeno una. Se però si utilizza anche la "if ... do" questo è possibile, pur se contorto: while (A) do { B } che è: (A) do { do { B } while (A) } Riassumendo quindi il contenuto del teorema di Jacopini-Böhm si può dire che ogni programma di Turing può essere espresso organizzandolo in blocchi uno contenuto nell'altro, oppure in sequenza uno di seguito all'altro, con eventuali blocchi alternativi (if ... then ... else ...) o blocchi da ripetere in ciclo (while ... do ...). Graficamente si hanno dei flussi che scendono dall'alto verso il basso, con i blocchi rappresentati da rettangoli e le istruzioni di decisione da cerchi. I blocchi sono organizzati in "piani", e le frecce passano da un piano a quello inferiore, suddividendosi all'uscita delle istruzioni di decisione: l'unica eccezione è che una freccia può risalire di un piano per rieseguire un blocco, nel qual caso si ha una ripetizione ciclica. All'interno di un blocco tutta questa struttura può ripetersi rispetto a dei sottoblocchi. Si può dare una rappresentazione più simmetrica, anche se non di uso pratico, del teorema introducendo due costruzioni di salto all'inizio dei blocchi seguente o precedente, nel seguente modo: 1) istruzione "ometti-se", che chiamiamo "skip-if": 2) skip-if (A) { B } che è: if (not A) do { B } che, se è vera A, salta all'inizio del blocco successivo. 2) istruzione "ripeti-se", che chiamiamo "repeat-if": { B } repeat-if (A) che è: do { B } while (A) che, se è vera A, salta all'inizio del blocco precedente (B). In questo caso si deve pensare ad una sequenza di blocchi: ... { B-1 } { B0 } { B+1 } ... con le istruzioni skip-if o repeat-if davanti ad un blocco, in questo caso il blocco B0: ... { B-1 } istruzione-salto { B0 } { B+1 } ... Si hanno i casi: 1) niente istruzione > sequenza: viene eseguito B0; pag 42 2) istruzione skip-if 2) istruzione skip-if > > > > > omissione: viene eseguito B+1; ripetizione: viene eseguito B-1. Si vede quindi che si ha una notevole simmetria, e il teorema di Jacopini-Böhm può essere riespresso dicendo che un programma "if ... goto ..." può essere organizzato in blocchi contenuti gerarchicamente uno nell'altro oppure messi in sequenza con possibilità di salti condizionali di ± 1 posizioni, cioè rispettivamente con omissioni o ripetizioni condizionali. In questa formulazione si vede che i salti "qualsiasi" permessi dalle "if ... goto ..." vengono regolamentati. Intanto si ha una struttura sequenziale di istruzioni, come quella tipica dell'Assembler o della numerazione presente nelle prime versioni di Fortran e Basic; queste istruzioni sono riunite in blocchi che sono contenuti uno nell'altro o uno di seguito all'altro (in ogni caso non possono intersecarsi); le istruzioni di salto possono saltare in modo molto limitato, cioè solo all'inizio del blocco successivo o precedente (non servono quindi nemmeno etichette del target); quindi, visto che i blocchi non si intersecano, le "traiettorie" dei salti non si possono incrociare, e quindi non si ha il groviglio tipico del "piatto di spaghetti". Dal punto di vista "visuale" c'è qualcosa da dire sul significato delle tre costruzioni della "if", la "if ... goto ...", la "if ... do ..." e la "if ... then ... else ...". Nella prima la "if" è semplicemente una istruzione condizionale e l'informazione di salto è tutta contenuta nel "goto". Questa è l'istruzione dei programmi non strutturati, come quelli di Turing. La seconda, da chiamare "esecuzione opzionale", presuppone un ordinamento sequenziale dei blocchi, come quello di cui abbiamo appena parlato, perché fa esplicito riferimento al "blocco successivo". La terza, nominata "alternativa" e usata in tutti i linguaggi superiori oltre che nella formulazione originale del teorema di Jacopini-Böhm, fa riferimento all'immagine del "diagramma di flusso" o "diagramma a blocchi", e l'abbiamo vista più sopra. In questi il flusso esecutivo si divide in due, senza alcuna idea di salto, segue paritariamente le due alternative e si torna a riunire. Dal punto di vista logico non ha nemmeno importanza che le alternative siano due, come si vede dalle istruzioni "multibranch" che si trovano in C (switch), Pascal (case of) e anche Basic (select case). Per descrivere questa struttura si può dire che si ha un ordinamento "discendente" (dall'inizio alla fine) con dei punti di diramazione del flusso che si riunisce tutto in un punto "nuovo", cioè non già attraversato in precedenza. Il ciclo ha anche in questa rappresentazione lo stesso significato di ritorno all'inizio del blocco appena eseguito. Questi diagrammi di flusso hanno in sé più chiarezza della struttura sequenziale con omissioni e ripetizioni. Riguardo ai goto che permangono in molti linguaggi, bisogna dire che esiste un loro uso particolare che non contravviene alla strutturazione del programma, cioè non distrugge la chiarezza espositiva. Questo è il caso in cui il goto è usato per uscire dai cicli annidati, allo stesso modo delle istruzioni tipo "break" ed "exit" per un singolo ciclo. Questo uso residuale "buono" del goto può essere del tutto sostituito, come in Java, da un "break etichettato" per uscire da cicli annidati a più livelli. Per terminare, un accenno alle idee alla base della dimostrazione del teorema. Sostanzialmente si tratta di replicare il codice invece che riutilizzarlo "saltando indietro". Inoltre si eliminano i salti "a valle" costringendo l'alternativa che salta a seguire il flusso normale, senza però subire le computazioni di questo. Questa cosa si può fare introducendo delle variabili booleane ausiliarie con valori diversi per il flusso normale e quello "saltante", e testando prima di ogni esecuzione il loro valore, omettendo l'esecuzione nel caso del flusso saltante. Si vede quindi che il programma strutturato sarà più lungo, ripetitivo e, potremmo dire, ridondante dell'originale. Questo è il prezzo da pagare alla chiarezza e all'ordine, e non è troppo alto considerati i vantaggi che porta con sé. 5.2 Uso delle TABELLE si usa <table> che serve a gestire la tabella su più colonne, affiancare immagini e persino i rientri e i margini. tener presente che una tabella si devono definire cella per cella pag 43 <tab> e </tab> servono per definire la tabella mentre le righe si gestiscon con <tr> (cioè table row). Le celle si gestiscono con <td> associato al parametro width: es <td width=200> crea una cella larga 200 pixel il bordo e il colore si possono gestire con <table border=1 bgcolor=green > con <th> si gestisce l’intestazione di una cella <html> <head> <title> prove della mia prima pagina html </title> </head> <SCRIPT language="VBscript"> sub prova() msgbox("CIAO da Salvatore") end sub </SCRIPT> <body bgcolor=black link=red vlink=yellow> <br><br> <img src="c:\masetta\images\striscia.gif"> <br> <font size=4 color=red face=Arial,Helvetica> Toti Masetta Home Page <font> <a href="javascript:prova()">collegamento a una sub </a> <p> <font size=+2 color=#aaf884> Questa è la mia prima Home <b> Page </b> </font> <br> <br> <a href="/masetta/html/prova2.htm">punta ad una seconda pagina </a> <table border=1 bgcolor=green > <tr> <td width=40 align=left valign=top> PRIMA <td width=200> dfd <td width=200> <font size=1> PROva </font> <tr> <th width=200> <color=red> INTESTAZIONE <td width=200> dsf </table> <br> <img src="c:\masetta\images\striscia.gif" width=180 > <p> <a href="mailto:[email protected]"> contattatemi !!! </a> </body> </html> pag 44 5.3 Bibbliografia CODICE TESTO [ag] Appunti universitari su automi e grammatiche [ce] Appunti di Comunicazioni elettriche dell’università [corso] Corso di Informatica 1 [da1] appunti di documentazione automatica [dev59] rivista DEV n*59 di gennaio 99 [ei1] fotocopie di Elettronica e Informatica Volume 1 [ei2] fotocopie di Elettronica e Informatica Volume 2 [ei9] fotocopie di Elettronica e Informatica Volume 9 [fon] libro “Fondamenti di informatica” della bibblioteca comunale [hal] libro di TNA di Fred Halsal [java1] libro di java 1.1 della Hoepli [linux01] Mensile “Linux & Co” N° 1 [rose] Libro di testo di Marshall Rose [russo] "Progettazione basi relazionali" di Nino Russo [sis1] appunti di Sistemi I [sis2] appunti di sistemi II [tna] appunti di TNA [comp] libro “COMPUTER” pag 45 INDICE A algoritmo, definizione..............................................................................................................................................................................9 Algoritmo, definizione di ........................................................................................................................................................................9 B blocco ....................................................................................................................................................................................................19 Bubble Sort ..........................................................................................................................................................................................14 bucket ....................................................................................................................................................................................................12 C caricamento vettore.................................................................................................................................................................................7 caricamento,fattore di ............................................................................................................................................................................12 cicli ........................................................................................................................................................................................................24 ciclo .......................................................................................................................................................................................................19 complessità ............................................................................................................................................................................................10 D Decimal .................................................................................................................................................................................................16 dell’array ..............................................................................................................................................................................................17 E ereditarietà ...........................................................................................................................................................................................20 F fattoriale ..................................................................................................................................................................................................3 Flow.........................................................................................................................................................................................................6 I incapsulamento ....................................................................................................................................................................................20 INPUT ..................................................................................................................................................................................................23 Integer ..................................................................................................................................................................................................16 ISO 9000 ...............................................................................................................................................................................................24 L Long ......................................................................................................................................................................................................16 M macchina di Turing .............................................................................................................................................................................18 matrice .....................................................................................................................................................................................................7 memoria .................................................................................................................................................................................................11 multitasking ...........................................................................................................................................................................................10 O operatori logici ......................................................................................................................................................................................26 P package ..................................................................................................................................................................................................23 polimorfismo ........................................................................................................................................................................................20 polinomio ...............................................................................................................................................................................................27 potenza ....................................................................................................................................................................................................4 programmazione strutturata ..............................................................................................................................................................19 pag 46 R ricerca binaria ........................................................................................................................................................................................12 Ricerca Binaria ......................................................................................................................................................................................15 ricerca lineare ........................................................................................................................................................................................12 S Single ....................................................................................................................................................................................................16 Sottoprogramma ....................................................................................................................................................................................10 struttogramma ........................................................................................................................................................................................13 swap.......................................................................................................................................................................................................13 T Tipi di dati .............................................................................................................................................................................................16 Turing .....................................................................................................................................................................................................3 V vettori ....................................................................................................................................................................................................17 pag 47