calcolatore, sistema di elaborazione elettronico ... - Studio SIP.
calcolatore, sistema di elaborazione elettronico ... - Studio SIP.
calcolatore, sistema di elaborazione elettronico ... - Studio SIP.
You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
INDICE<br />
Introduzione allo sviluppo del sw e manuale Turbo Pascal versione 1.0 Giugno 2005<br />
Alcune definizioni importanti: <strong>calcolatore</strong>, <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong> <strong>elettronico</strong>,<br />
programmabilità, soluzioni hardware e soluzioni software.<br />
Pag. 1<br />
Logica cablata e logica programmata: soluzioni hardware e soluzioni software a<br />
confronto.<br />
Pag. 2<br />
Dal problema al programma: ciclo <strong>di</strong> sviluppo (semplificato) del software Pag. 3<br />
Stu<strong>di</strong>o della situazione reale, analisi dei requisiti Pag. 3<br />
Analisi dei dati Pag. 4<br />
Algoritmo risolutivo; definizione, caratteristiche; Pag. 5<br />
Definizione <strong>di</strong> programma, linguaggi <strong>di</strong> programmazione Pag. 6<br />
Fase <strong>di</strong> co<strong>di</strong>fica Pag. 6<br />
Figura dell’analista, <strong>di</strong>stinzione tra risolutore ed esecutore Pag. 7<br />
Fase <strong>di</strong> test (alpha, beta e gamma test) Pag. 7<br />
Fase <strong>di</strong> debug Pag. 8<br />
Release e manutenzione Pag. 8<br />
Dismissione Pag. 8<br />
Architettura hardware e software <strong>di</strong> un <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong> (cpu, principali<br />
<strong>di</strong>spositivi interni quali la ALU, i registri, decoder)<br />
Pag. 9<br />
Ciclo <strong>di</strong> funzionamento <strong>di</strong> una CPU (fetch – decode – execute) Pag. 11<br />
Rappresentazione interna delle informazioni, giustificazione della scelta <strong>di</strong>gitale Pag. 14<br />
Rappresentazione delle istruzioni Pag. 15<br />
Evoluzione dei linguaggi <strong>di</strong> programmazione (co<strong>di</strong>ce macchina, assembly,<br />
assemblatori, cenni ai <strong>di</strong>agrammi <strong>di</strong> flusso)<br />
Pag. 16<br />
Linguaggi ad alto livello Pag. 18<br />
Differenza tra interpreti e compilatori Pag. 19<br />
La catena della programmazione (e<strong>di</strong>torIDE e programma sorgente) Pag. 20<br />
Compilazione e sue fasi: controllo lessicale, controllo sintattico, co<strong>di</strong>ce oggetto Pag. 21<br />
Il linker e le librerie Pag. 21<br />
L’eseguibile finale Pag. 22<br />
Struttura <strong>di</strong> un programma pascal, intestazione ed identificatori Pag. 23<br />
Sezione <strong>di</strong>chiarativa; constanti e vantaggi del loro uso Pag. 24<br />
Variabili Pag. 24<br />
Tipi <strong>di</strong> dati semplici e loro dominio, forma esponenziale dei floating point, co<strong>di</strong>ce<br />
ASCII<br />
Pag. 25<br />
Sezione esecutiva Pag. 26<br />
Assegnamento, compatibilità <strong>di</strong> tipo Pag. 26<br />
Uso delle parentesi Pag. 27<br />
Principali operatori e operatori relazionali utilizzabili sud<strong>di</strong>visi per tipo <strong>di</strong> dato, tipi <strong>di</strong><br />
errore (onverflow, underflow, operazione illegale)<br />
Pag. 27<br />
Tabella con l’or<strong>di</strong>ne <strong>di</strong> precedenza <strong>di</strong> tutti gli operatori visti Pag. 29<br />
Coman<strong>di</strong> <strong>di</strong> ingresso/uscita (readln e write/ln) Pag. 29<br />
Coman<strong>di</strong> per il controllo del flusso <strong>di</strong> esecuzione, programmazione strutturata Pag. 30<br />
Selezione ad una via (if … then) Pag. 31<br />
Selezione a due vie (if … then … else) Pag. 33<br />
Uso <strong>di</strong> con<strong>di</strong>zioni composte con i connettivi logici Pag. 33<br />
Selezione a molte vie (case … of) Pag. 34<br />
Esercizi riepilogativi sulla struttura selettiva Pag. 36<br />
Struttura iterativa enumerativa (for … do) Pag. 48<br />
Esercizi riepilogativi sul ciclo for Pag. 50<br />
Struttura iterativa indefinita repeat … until Pag. 61<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 1
Introduzione allo sviluppo del sw e manuale Turbo Pascal versione 1.0 Giugno 2005<br />
Struttura iterativa indefinita while … do Pag. 63<br />
Esercizi riepilogativi sul repeat e sul while Pag. 64<br />
Approfon<strong>di</strong>mento sui flow chart Pag. 68<br />
I sottoprogrammi (procedure e funzioni) Pag. 83<br />
Regole <strong>di</strong> visibilità e durata Pag. 108<br />
Gli array Pag. 110<br />
I record Pag. 126<br />
I files Pag. 131<br />
La ricorsione Pag. 152<br />
Limiti della memoria allocata staticamente Pag. 159<br />
Allocazione <strong>di</strong>namica della RAM (implementazione completa del tipo <strong>di</strong> dato astratto<br />
lista semplice)<br />
Pag. 160<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 2
Calcolatore? Non solo …<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 1<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Nel nostro corso useremo spesso la parola computer (<strong>calcolatore</strong>). Ed anche se questa è entrata ormai a far parte del<br />
linguaggio comune, è assai riduttiva. Questo termine sarebbe infatti appropriato per quelle "macchinette" che tenete<br />
negli astucci e che sono in grado <strong>di</strong> svolgere giusto le quattro operazioni elementari, l'estrazione <strong>di</strong> ra<strong>di</strong>ce e poco più<br />
(sto volutamente ignorando le cosiddette calcolatrici programmabili che, <strong>di</strong> fatto, sono dei personal computer in<br />
miniatura, anche se con funzionalità limitate).<br />
Si dovrebbe infatti parlare <strong>di</strong> <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong> <strong>elettronico</strong> delle informazioni programmabile.<br />
Soffermiamoci su ogni termine:<br />
• Sistema Insieme <strong>di</strong> componenti, ognuno con la sua specifica funzione, ma con uno scopo comune: elaborare<br />
(si parla <strong>di</strong> ‘processing’) dati forniti in ingresso (si parla <strong>di</strong> ‘input’) e fornire risultati (si parla <strong>di</strong> ‘output’)<br />
adeguatamente presentati.<br />
In un moderno <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong> <strong>elettronico</strong> possiamo in<strong>di</strong>viduare tra i componenti: il microprocessore<br />
(ad esempio Intel Pentium, AMD Athlon), la memoria <strong>di</strong> lavoro RAM, il <strong>di</strong>sco fisso (hard <strong>di</strong>sk), il monitor, la<br />
stampante ecc.<br />
• Elaborazione Operazione (tra cui i calcoli) che trasforma uno o più dati/informazioni in altri dati/informazioni.<br />
Questi dati possono insomma essere sì tra loro sommati, sottratti ecc. (se sono numeri); ma possono anche<br />
essere confrontati tra loro, spostati o copiati da un punto all'altro della memoria, inviati ad un <strong>di</strong>spositivo per la<br />
loro visualizzazione (ad esempio il monitor) o stampa.<br />
NOTA: un dato è una misurazione <strong>di</strong> un aspetto della realtà e <strong>di</strong>venta informazione solo quando sappiamo<br />
dare un significato ad esso (ecco allora che un numero da anonimo <strong>di</strong>venta un peso, un'altezza, un punto <strong>di</strong><br />
un'immagine sul video ecc.<br />
• Elettronico Un’<strong>elaborazione</strong> può avvenire anche in modo manuale (come quando con carta e penna si mette in<br />
or<strong>di</strong>ne alfabetico un elenco <strong>di</strong> nomi).<br />
Se l'<strong>elaborazione</strong> avviene senza l'intervento umano si parla allora <strong>di</strong> <strong>elaborazione</strong> automatica ( ad esempio le<br />
macchine per lo smistamento della posta ).<br />
E se infine i <strong>di</strong>spositivi automatici non hanno parti meccaniche movimento ma sono costituiti da circuiti elettrici<br />
si parla <strong>di</strong> <strong>elaborazione</strong> elettronica (il microprocessore che somma due numeri).<br />
• Programmabile Con le macchinette calcolatrici non potrete fare altro che i calcoli previsti dal costruttore. Non<br />
c'è modo infatti <strong>di</strong> istruire quel piccolo congegno a svolgere calcoli <strong>di</strong>versi. Allo stesso modo in cui non potete<br />
ottenere altro da una lavatrice che le sequenze (programmi) <strong>di</strong> lavaggio previste dal costruttore! E così come<br />
con una lavatrice potrete lavare solo panni, allo stesso modo con una calcolatrice potrete usare solo numeri!<br />
Un computer, invece, è dotato <strong>di</strong> una memoria <strong>di</strong> lavoro elettronica (RAM, Random Access Memory, Memoria<br />
ad accesso casuale) in cui possono essere rappresentati numeri, lettere, immagini e suoni.<br />
Non solo: la memoria contiene anche la sequenza delle istruzioni che il microprocessore deve eseguire per<br />
svolgere un certo compito (il programma)! È sufficiente caricare (dall'hard <strong>di</strong>sk, dal CD, dal DVD ecc.) una<br />
<strong>di</strong>versa sequenza <strong>di</strong> istruzioni per avere una macchina elettronica in grado <strong>di</strong> svolgere un compito anche<br />
completamente <strong>di</strong>verso dal precedente.<br />
Tutto sommato, il ciclo <strong>di</strong> funzionamento <strong>di</strong> un <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong> <strong>elettronico</strong> programmabile (ehm,<br />
computer d'ora in avanti e solo per como<strong>di</strong>tà) è assai semplice: prelievo dalla RAM dalla prossima istruzione da<br />
eseguire, interpretazione dell'istruzione (cosa deve essere fatto? Quali altri componenti devono essere attivati<br />
ed in che modo?) esecuzione (attivare nella giusta sequenza i componenti elettronici coinvolti). Si parla <strong>di</strong> ciclo<br />
<strong>di</strong> fetch (prelievo), decode (deco<strong>di</strong>fica) ed execute (esecuzione).<br />
NOTA: approfon<strong>di</strong>rete questi argomenti nel corso parallelo <strong>di</strong> ‘sistemi’
Logica cablata e logica programmata<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 2<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Quando un <strong>di</strong>spositivo <strong>elettronico</strong> viene costruito in modo da poter funzionare senza un programma (i circuiti sono<br />
scelti e collegati per dare sempre la stessa gamma <strong>di</strong> risposte) viene definito in logica cablata (dalla parola inglese<br />
cable, cavo, filo).<br />
Un altro esempio (avevamo già visto quello della lavatrice) è rappresentato da un orologio <strong>di</strong>gitale <strong>di</strong> prima<br />
generazione (che <strong>di</strong>fferenza della lavatrice non presenta parti elettromeccaniche). In un orologio <strong>di</strong> questo tipo non è<br />
presente alcun microprocessore e nessun programma: i circuiti sono stati stampati per reagire in modo prefissato alla<br />
pressione dei tasti (reset, regolazione orario/data ecc.) e per incrementare l’orario/data.<br />
O ancora: una PlayStation costruita (per assurdo) in logica cablata vi consentirebbe <strong>di</strong> giocare a quel solo gioco<br />
corrispondente a quella particolare pre<strong>di</strong>sposizione <strong>di</strong> <strong>di</strong>spositivi elettronici e relativi collegamenti elettrici.<br />
Quando invece è presente un microprocessore che esegue istruzioni prelevate da una memoria si parla <strong>di</strong> logica<br />
programmata. Spesso la memoria può essere riscritta (come avviene in tutti i personal computer) e il programma è<br />
cambiato piacere. Ecco allora che il computer può <strong>di</strong> volta in volta <strong>di</strong>ventare l'equivalente <strong>elettronico</strong> <strong>di</strong> una macchina<br />
da scrivere (Word), <strong>di</strong> una super calcolatrice (Excel), simulatore <strong>di</strong> calcio (FIFA e simili!) ecc.<br />
E le soluzioni che sfruttano una logica cablata vengono dette soluzioni hardware (dove con questo termine si<br />
in<strong>di</strong>cano le parti fisiche <strong>di</strong> un <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong>; in inglese il termine significa letteralmente ‘ ferraglia’). Quelle<br />
che sfruttano invece una logica programmata sono dette soluzioni software (i dati e le istruzioni memorizzate<br />
sottoforma <strong>di</strong> un segnale elettrico chiaramente impalpabile, morbido, soft (che significa appunto ‘ soffice’).<br />
La soluzione software è più flessibile: se viene trovato un errore è sufficiente cancellare qualche istruzione e la<br />
memoria e sostituirle con quelle corrette. Lo stesso accade nel caso si decida <strong>di</strong> apportare migliorie o adattare il<br />
programma a causa, per esempio, <strong>di</strong> una legge cambiata. In un circuito, invece, se viene trovato un errore questo<br />
può comportare lo scarto dell'intero circuito stesso e la realizzazione <strong>di</strong> un circuito completamente nuovo!<br />
La rigi<strong>di</strong>tà delle soluzioni hardware sembrano relegare queste ultime ad un ruolo <strong>di</strong> secondo piano rispetto alle<br />
soluzioni software, ma hanno almeno un grosso pregio che potrebbe essere determinante: la rapi<strong>di</strong>tà nel fornire la<br />
risposta! Mancando infatti il microprocessore e non essendoci co<strong>di</strong>ci d'istruzione da prelevare nella RAM né<br />
deco<strong>di</strong>fiche da effettuare, i dati in input sono trasformati in quelli <strong>di</strong> output ad una velocità molto superiore rispetto<br />
ad un programma che compie la stessa <strong>elaborazione</strong>.<br />
Nel nostro corso ci occuperemo <strong>di</strong> trovare soluzioni software ai problemi che affronteremo. E l'attività <strong>di</strong><br />
programmazione non è l'unica da mettere in gioco per risolvere un problema.
1<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 3<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Dal problema al programma – ciclo <strong>di</strong> sviluppo (semplificato) del software<br />
La scrittura del programma è solo una delle fasi del processo <strong>di</strong> sviluppo <strong>di</strong> un'applicazione informatica. Tutto inizia<br />
con l'esigenza <strong>di</strong> risolvere un problema con un <strong>sistema</strong> informatico. La parola problema deve essere intesa in modo<br />
ampio: gestire la contabilità <strong>di</strong> un'azienda, usare il personal computer per scrivere documenti, per una simulazione <strong>di</strong><br />
guida, per svolgere calcoli complessi, controllare un processo industriale, pilotare un robot eccetera.<br />
Per quanto ci riguarda, i problemi saranno espressi in forma testuale. Ecco il testo dell'esame <strong>di</strong> Stato del 1998:<br />
Una galleria d'arte ha deciso <strong>di</strong> creare un <strong>sistema</strong> che consenta ai suoi clienti <strong>di</strong> consultare da<br />
casa il catalogo: dei quadri, tramite accesso a una pagina web che la galleria può creare<br />
presso un fornitore <strong>di</strong> servizi telematici.<br />
Per ogni quadro è compilata una scheda che riporta l'autore, il titolo, la tecnica (olio, tempera<br />
ecc.), le <strong>di</strong>mensioni, il prezzo. La consultazione del catalogo: può avvenire o semplicemente<br />
scorrendo avanti e in<strong>di</strong>etro le schede in or<strong>di</strong>ne alfabetico oppure cercando uno specifico<br />
autore.<br />
Il can<strong>di</strong>dato, fatte le ipotesi aggiuntive che ritiene necessarie,<br />
1) proponga una soluzione per la creazione del <strong>sistema</strong> illustrandone la struttura a blocchi e<br />
in<strong>di</strong>cando una soluzione <strong>di</strong> principio per ciascun blocco;<br />
2) proponga e illustri una struttura per il Data Base dei quadri,<br />
3) sviluppi in dettaglio la soluzione per almeno una delle seguenti funzioni, co<strong>di</strong>ficandone un<br />
segmento con uno strumento software <strong>di</strong> sua conoscenza:<br />
a) creazione del Data Base,<br />
b) creazione <strong>di</strong> una semplice pagina web della galleria,<br />
c) interfaccia per consentire al cliente la consultazione del catalogo: e la visione delle singole<br />
schede,<br />
4) facoltativamente proponga una soluzione <strong>di</strong> principio per realizzare un <strong>sistema</strong> che<br />
consenta <strong>di</strong> mostrare al cliente non solo la scheda <strong>di</strong> catalogo, ma anche una fotografia del<br />
quadro.<br />
Il testo va dapprima ha stu<strong>di</strong>ato per evidenziare parti poco chiare (sulle quali sarà necessario prendere delle<br />
decisioni) ed eventualmente fare ipotesi aggiuntive su aspetti per i quali il testo non <strong>di</strong>ce come comportarsi.<br />
Ad esempio, nel testo si parla <strong>di</strong> prezzo del quadro ma non viene in<strong>di</strong>cata la valuta da<br />
utilizzare. Una galleria d'arte spesso a clienti stranieri, per cui la soluzione <strong>di</strong> proporre il<br />
prezzo in lire (siamo nel ‘98... e l'euro non esisteva ancora e neppure l'obbligo <strong>di</strong> esporre il<br />
doppio prezzo in euro ed in lire) forse non è ottimale. Ecco allora la prima ipotesi<br />
aggiuntiva (o, se preferite, un primo chiarimento): i prezzi verranno in<strong>di</strong>cati sia in lire che<br />
in dollari.<br />
Questa fase viene chiamata stu<strong>di</strong>o della situazione reale. La letteratura informatica fa riferimento a questa fase<br />
anche con il nome <strong>di</strong> analisi (termine però non corretto da un punto <strong>di</strong> vista matematico). Naturalmente noi<br />
inizieremo con problemi e testi assai più semplici. Ad esempio: calcolare la spesa in euro per una settimana <strong>di</strong> viaggi<br />
andata e ritorno da casa a scuola. Le uniche cose da chiarire potrebbero essere: è necessario percorrere strade con<br />
pedaggi? Il numero <strong>di</strong> chilometri del percorso in andata è identico a quello del ritorno?
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 4<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Vengono anche presi in considerazione i requisiti (cosa deve fare il programma, con quali vincoli <strong>di</strong> velocità,<br />
occupazione <strong>di</strong> memoria, hardware e software a <strong>di</strong>sposizione, se deve funzionare in rete, se deve sapere interoperare<br />
con altri software magari su piattaforme hardware <strong>di</strong>verse, se deve funzionare in real time, se deve essere portabile<br />
in altri ambienti hardware/software, , che grado <strong>di</strong> robustezza, che grado <strong>di</strong> sicurezza, che tipo <strong>di</strong> periferiche deve<br />
supporatare ecc.)<br />
Devono poi essere in<strong>di</strong>viduate tutte le informazioni che è necessario gestire. Queste spesso verranno memorizzate in<br />
una banca dati (data base). Il risultato <strong>di</strong> questa fase viene <strong>di</strong> solito sintetizzato con uno schema che evidenzia i<br />
cosiddetti insiemi entità e le relazioni tra esse.<br />
Ad esempio, è possibile in<strong>di</strong>viduare l'insieme entità dei quadri e quello dei pittori:<br />
2<br />
Co<strong>di</strong>ce<br />
pittore<br />
nome<br />
Pittori<br />
Data <strong>di</strong><br />
nascita<br />
Per ogni insieme entità vengono anche in<strong>di</strong>cati gli attributi che descrivono un esemplare <strong>di</strong> quell’ insieme. Ad<br />
esempio, per l'insieme entità dei pittori si decide che ogni pittore verrà descritto tramite un co<strong>di</strong>ce, un nome ed una<br />
data <strong>di</strong> nascita. Ogni quadro verrà invece descritto da un co<strong>di</strong>ce e da un titolo. La freccia che da pittori e raggiunge<br />
quadri sta ad in<strong>di</strong>care che per ogni pittore esiste un certo numero <strong>di</strong> quadri. Nella banca dati verrà creata una tabella<br />
‘pittori’ che conterrà su ogni sua riga i dati <strong>di</strong> un pittore e similmente per i quadri. Ogni quadro potrebbe essere<br />
associato al suo pittore in<strong>di</strong>cando nel quadro il co<strong>di</strong>ce del pittore.<br />
Questa fase viene chiamata analisi dei dati.<br />
Co<strong>di</strong>ce<br />
quadro<br />
Quadri<br />
Titolo<br />
Anche per questa fase inizieremo con situazioni molto semplici. Proseguendo con l'esempio <strong>di</strong> problema presentato al<br />
punto uno, non è <strong>di</strong>fficile convincersi che i dati <strong>di</strong> cui abbiamo bisogno sono: costo <strong>di</strong> un litro <strong>di</strong> carburante, numero<br />
<strong>di</strong> chilometri tra la casa e la scuola, numero <strong>di</strong> chilometri che il mezzo utilizzato compie con un litro <strong>di</strong> carburante,<br />
costo <strong>di</strong> eventuali pedaggi.<br />
Per queste semplici situazioni uno schema come quello appena visto è esagerato. Ci accontenteremo <strong>di</strong> elencare i<br />
cosiddetti dati in ingresso (input); il programma li elabora (processing) per produrre i risultati, i dati in uscita<br />
(output).<br />
INPUT<br />
costo <strong>di</strong> un litro <strong>di</strong> carburante<br />
chilometri tra casa e scuola<br />
chilometri / litro del mezzo<br />
costo <strong>di</strong> eventuali pedaggi.<br />
ELABORAZIONE<br />
Elaborazione svolta dal<br />
programma<br />
OUTPUT<br />
costo <strong>di</strong> un litro <strong>di</strong> carburante<br />
chilometri tra casa e scuola<br />
chilometri / litro del mezzo<br />
costo <strong>di</strong> eventuali pedaggi.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 5<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Deve essere molto chiara una cosa: per dati in input si intendono quelli in<strong>di</strong>spensabili, quelli cioè che il computer<br />
non può calcolare o derivare in altro modo. Facciamo un esempio: se ad un certo punto in un programma abbiamo<br />
<strong>di</strong>sposizione una quantità espressa in ore e per proseguire è necessario esprimerla i secon<strong>di</strong>, non ce<strong>di</strong>amo alla<br />
pigrizia chiedendo a chi sta usando il programma <strong>di</strong> inserire questo valore usando la tastiera, pretendendo che sia lui<br />
a fare la conversione! Sarà invece il programma a calcolare autonomamente il valore richiesto moltiplicando per 3600<br />
il numero delle ore...<br />
NOTA: non è raro il caso <strong>di</strong> alunni in <strong>di</strong>fficoltà al momento <strong>di</strong> in<strong>di</strong>viduare i dati in input. Intanto <strong>di</strong>ciamo che non è<br />
necessario avere la certezza <strong>di</strong> averli in<strong>di</strong>viduati proprio tutti per poter proseguire: è normale, dopo avere in<strong>di</strong>viduato<br />
i più importanti ed evidenti, iniziare la fase successiva (trovare un 'modo' <strong>di</strong> utilizzare i dati in input per giungere alla<br />
risultato); si ad un certo punto ci si accorge che manca qualche dato per poter proseguire lo si aggiungerà<br />
semplicemente ai dati <strong>di</strong> input.<br />
È arrivato il momento <strong>di</strong> descrivere il modo in cui i dati <strong>di</strong> input devono essere utilizzati per ottenere i risultati. Questa<br />
'descrizione' è chiamata algoritmo.<br />
3<br />
Da ‘Wikipe<strong>di</strong>a’, l’enciclope<strong>di</strong>a libera (http://it.wikipe<strong>di</strong>a.org), con qualche piccolo adattamento:<br />
Il termine (algoritmo) deriva dal nome del (grande) matematico arabo Al-Khwarizmi, che pubblicò,<br />
tra gli altri, il libro dal quale prende le origini la parola Algebra (ora sapete chi o<strong>di</strong>are). Nei suoi libri<br />
ne scrive anche i proce<strong>di</strong>menti per portare a termine alcuni tipi <strong>di</strong> calcolo: questi proce<strong>di</strong>menti<br />
presero il nome <strong>di</strong> algoritmi.<br />
Nella sua definizione più semplice ed intuitiva un algoritmo è una sequenza or<strong>di</strong>nata <strong>di</strong> passi<br />
semplici che hanno lo scopo <strong>di</strong> portare a termine un compito più complesso (una ricetta da cucina,<br />
ad esempio, può essere considerata come un algoritmo che partendo da un insieme <strong>di</strong> singoli<br />
alimenti <strong>di</strong> base ed eseguendo una sequenza <strong>di</strong> passi, produce come risultato un piatto composto).<br />
In un modo più formale, possiamo quin<strong>di</strong> definire l'algoritmo come una<br />
sequenza or<strong>di</strong>nata e finita <strong>di</strong> istruzioni che, dato uno od una serie <strong>di</strong><br />
elementi in input, produce uno od una serie <strong>di</strong> risultati in output .<br />
Sequenza or<strong>di</strong>nata significa che esiste un or<strong>di</strong>ne preciso in base al quale vengono eseguite le<br />
istruzioni (d'altronde sarebbe ben <strong>di</strong>fficile prima sbattere un uovo e poi rompere il guscio!).<br />
Sequenza finita significa che le istruzioni possono essere anche veramente tante, ma non in<br />
numero limitato; inoltre il numero <strong>di</strong> volte che globalmente queste istruzioni vengono eseguite non<br />
può essere illimitato.<br />
La sequenza delle operazioni deve essere chiara, mai ambigua, deve avere un or<strong>di</strong>ne ben preciso, e<br />
deve giungere a termine per ogni input. Tutte le istruzioni devono comportare delle azioni tra<br />
quelle che l’esecutore è in grado <strong>di</strong> svolgere. Il risultato <strong>di</strong> un algoritmo deve essere sempre uguale<br />
in<strong>di</strong>pendentemente da chi lo esegue.<br />
Se, come visto, una ricetta da cucina rappresenta un <strong>di</strong>screto esempio <strong>di</strong> algoritmo <strong>di</strong>rettamente<br />
eseguibile da un essere umano, l'istruzione "aggiungere sale quanto basta" <strong>di</strong>fficilmente sarà<br />
comprensibile per una macchina (ma anche tra gli umani stessi quel ‘ quanto basta’ verrebbe<br />
sicuramente interpretato in tanti mo<strong>di</strong> <strong>di</strong>versi!).<br />
Un passo <strong>di</strong> un algoritmo può essere definito anche tramite un altro algoritmo (chiamato in questo<br />
caso sottoalgoritmo), che sud<strong>di</strong>vide il compito in compiti ancora più elementari. Facciamo un<br />
esempio: l'algoritmo "va dal salotto alla cucina" si compone in realtà delle seguenti istruzioni:<br />
•esci dal salotto<br />
•curva a sinistra<br />
•prosegui per il corridoio fino all'ultima porta sulla sinistra<br />
•attraversa la porta a sinistra<br />
Questo è certamente fin troppo esplicito per un operatore umano (al quale già il problema originale<br />
"va dal salotto alla cucina" sembra probabilmente abbastanza elementare da non richiedere, in<br />
apparenza, sud<strong>di</strong>visioni), ma nel caso <strong>di</strong> un robot richiederebbe <strong>di</strong> specificare i passi con ben altra<br />
(minore) complessità.
L'algoritmo "attraversa la porta a sinistra" si compone <strong>di</strong>:<br />
•controlla se la porta è aperta<br />
•nel caso che la porta sia aperta salta il passo seguente<br />
•apri la porta<br />
•avanza <strong>di</strong> un metro<br />
L'algoritmo "apri la porta", compreso nel precedente, a sua volta si compone <strong>di</strong>:<br />
•proten<strong>di</strong> il braccio<br />
•afferra la maniglia<br />
•rotea la mano <strong>di</strong> 30 gra<strong>di</strong> in <strong>di</strong>rezione antioraria<br />
•applica una pressione alla maniglia <strong>di</strong>retta <strong>di</strong> fronte a te<br />
•...<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 6<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Un modo dettagliato <strong>di</strong> rappresentare l'algoritmo "attraversa la porta a sinistra" è allora il seguente:<br />
•controlla se la porta è aperta<br />
•nel caso che la porta sia aperta salta il passo seguente<br />
•apri la porta<br />
•proten<strong>di</strong> il braccio<br />
•afferra la maniglia<br />
•rotea la mano <strong>di</strong> 30 gra<strong>di</strong> in <strong>di</strong>rezione antioraria<br />
•applica una pressione alla maniglia <strong>di</strong>retta <strong>di</strong> fronte a te<br />
•...<br />
•avanza <strong>di</strong> un metro<br />
Una breve analisi dell'esempio sopra, porta a delineare alcune caratteristiche essenziali <strong>di</strong> un<br />
algoritmo:<br />
•non ambiguo: le istruzioni devono essere univocamente interpretabili;<br />
•eseguibile: ogni istruzione deve terminare in tempo finito.<br />
•Inoltre, in informatica, si richiede generalmente che un algoritmo sia finito, ovvero termini per<br />
ogni insieme <strong>di</strong> dati <strong>di</strong> ingresso.<br />
Un algoritmo non è tale se risolve in un caso particolare <strong>di</strong> un problema: deve essere utile per la soluzione <strong>di</strong><br />
un'intera classe <strong>di</strong> problemi. Facciamo un esempio: il proce<strong>di</strong>mento che serve a calcolare l'area del triangolo<br />
la cui base misura 3 m e l'altezza 5 m, e solo l'area <strong>di</strong> questo triangolo, non può definirsi un algoritmo. Il<br />
proce<strong>di</strong>mento invece che descrive come calcolare l'area <strong>di</strong> un qualsiasi triangolo nota la misura della base e<br />
dell'altezza, risolve un'intera classe <strong>di</strong> problemi (è una soluzione generale) e può definirsi algoritmo.<br />
Un programma è la traduzione <strong>di</strong> un algoritmo in un blocco <strong>di</strong> istruzioni eseguibili automaticamente da un<br />
<strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong> <strong>elettronico</strong>.<br />
Arrivati a questo punto dobbiamo confrontarci con l'assoluta inadeguatezza del linguaggio parlato (il<br />
cosiddetto linguaggio naturale) per descrivere un algoritmo. Banalizzo con un classico esempio:<br />
la vecchia porta la sbarra<br />
Quale significato deve essere dato questa frase? Si tratta forse <strong>di</strong> un'anziana signora china sotto il peso <strong>di</strong><br />
una pesante sbarra? O si sta parlando <strong>di</strong> uscita sbarrata da una vecchia porta? Questa ambiguità è<br />
inaccettabile per un computer: esso deve sapere esattamente come comportarsi e deve produrre sempre gli<br />
stessi risultati se gli vengono sottoposti gli stessi dati in input.<br />
È necessario servirsi <strong>di</strong> linguaggi formali, cioè rigorosamente definiti. Questi tipi <strong>di</strong> linguaggio sono <strong>di</strong> solito<br />
molto meno ricchi <strong>di</strong> vocaboli e <strong>di</strong> regole sintattiche ma hanno il grosso pregio <strong>di</strong> non essere ambigui (ogni<br />
istruzione è chiara, ha un solo significato e produce sempre lo stesso risultato).<br />
4<br />
Questi linguaggi sono chiamati linguaggi <strong>di</strong> programmazione. La figura professionale che si occupa<br />
della scrittura dei programmi è il programmatore. la fase <strong>di</strong> scrittura <strong>di</strong> un programma è detta <strong>di</strong><br />
co<strong>di</strong>fica (il programmatore scrive il co<strong>di</strong>ce del programma)
5<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 7<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Non è detto che sia il programmatore a stu<strong>di</strong>are il problema e ad ideare l'algoritmo risolutivo: la figura professionale<br />
specializzata in questi compiti preliminari e fondamentali è chiamato analista. E’ certamente vero che il ruolo<br />
dell'analista e del programmatore possano essere svolti dalla stessa persona. Per compiere una buona analisi è<br />
necessaria molta esperienza, ed è per questo che spesso si nasce 'semplici' programmatori per poi <strong>di</strong>ventare analisti<br />
o analisti-programmatori.<br />
il ruolo del programmatore ' puro' è quello allora <strong>di</strong> ricevere dall'analista la descrizione dell'algoritmo per provvedere<br />
alla co<strong>di</strong>fica (cioè scrittura) <strong>di</strong> quest'ultimo usando un linguaggio <strong>di</strong> programmazione.<br />
Torneremo presto sulla questione della descrizione degli algoritmi: è il<br />
tema portante <strong>di</strong> quest'anno scolastico!<br />
NOTA: è corretto fare <strong>di</strong>stinzione tra il risolutore <strong>di</strong> un problema (colui che ha ideato l’algoritmo che lo risolve) e<br />
l’esecutore materiale dei passi dell’algoritmo. Nel nostro caso il risolutore è sempre un uomo/donna e l’esecutore è<br />
il computer.<br />
Terminata la scrittura del programma inizia la fase <strong>di</strong> test. Sottoporre a test un programma significa provarlo<br />
con tutte le configurazioni <strong>di</strong> dati in input normali e particolari. Di nuovo, facciamo un semplice esempio<br />
immaginando <strong>di</strong> avere scritto un programma che, forniti due numeri in input, calcola che percentuale è il primo<br />
rispetto al secondo; ad esempio se il primo numero fosse 50 ed il secondo 150, il risultato fornito dovrebbe<br />
essere 33,3 % perio<strong>di</strong>co (50 è infatti un terzo <strong>di</strong> 150...). Non è <strong>di</strong>fficile convincersi che nel programma la formula<br />
usata è:<br />
(primo numero/secondo numero)*100<br />
Fare il test <strong>di</strong> questo programma con configurazioni <strong>di</strong> dati in input 'normali', significa provare il programma con<br />
coppie <strong>di</strong> numeri tipo (10,20) (50,150) eccetera. Poi ci si potrebbe domandare se il programma fornisce risultati<br />
corretti anche quando il primo numero è maggior dal secondo: (240,60); e scopriremo che la risposta è sì: otterremo<br />
come valore 400% (in effetti, 240 è il quadruplo <strong>di</strong> 60). E se usassimo numeri negativi? Nessun problema...<br />
Ok, è arrivato il momento <strong>di</strong> essere cattivi: e se usassimo numeri decimali? Tipo (10.2, 97.5) ? E se il primo numero<br />
fosse zero: (0, 34)? Anche con queste configurazioni <strong>di</strong> valori in input il programma continua a fornire risultati<br />
corretti. Giunti a questo punto, il programmatore inesperto (o pigro) potrebbe concludere che il programma funziona<br />
bene in tutti i casi possibili. Purtroppo, la matematica c'insegna che non è possibile <strong>di</strong>videre per zero: inserendo una<br />
configurazione <strong>di</strong> input con il secondo uguale a zero, come in (72,0), il programma andrebbe letteralmente tilt! Gli<br />
informatici in questi casi usano un'espressione assai colorita: il programma va in crash!<br />
Il caso dello zero come secondo numero è un cosiddetto caso limite: ogni programma dovrebbe essere testato in<br />
tutti i casi limite che potrebbero presentarsi, anche se con probabilità molto bassa!<br />
La fase <strong>di</strong> test viene <strong>di</strong> solito sud<strong>di</strong>visa a sua volta in:<br />
• alpha test: è quello svolto <strong>di</strong>rettamente dal programmatore che ha scritto il co<strong>di</strong>ce o comunque da personale<br />
interno alla <strong>di</strong>tta che commercializzerà il software; potremmo <strong>di</strong>re che in questa fase vengono trovati gli errori<br />
più grossolani;<br />
• beta test: quando il software viene ritenuto sufficientemente stabile viene <strong>di</strong>stribuito, <strong>di</strong> solito gratuitamente,<br />
ad un numero ristretto <strong>di</strong> utenti che, in cambio del beneficio <strong>di</strong> poter <strong>di</strong>sporre in anteprima del prodotto quasi<br />
finito, comunicheranno secondo protocolli stabiliti una descrizione degli errori che capitano durante l'utilizzo; se<br />
il prodotto è particolarmente complesso, il numero dei beta tester, può essere elevato: ad esempio, quando la<br />
Microsoft rilascia per il beta test una nuova versione <strong>di</strong> Windows lo fa anche a decine <strong>di</strong> migliaia <strong>di</strong> utenti!<br />
• gamma test: a volte viene definito un ulteriore livello che si <strong>di</strong>fferenzia dal precedente solo del fatto che<br />
dovrebbe essere quasi esente da errori
8<br />
6<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 8<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
La fase <strong>di</strong> test ha lo scopo <strong>di</strong> evidenziare gli errori durante il funzionamento del programma, il che non vuol <strong>di</strong>re<br />
però automaticamente sapere quale istruzione ha causato il problema.<br />
La fase <strong>di</strong> debug in<strong>di</strong>ca invece l'attività del programmatore volta ad in<strong>di</strong>viduare esattamente la porzione <strong>di</strong><br />
co<strong>di</strong>ce che contiene l'errore in modo da poter eliminare.<br />
Ogni volta che si scopre un errore è necessario ritornare alla fase <strong>di</strong> co<strong>di</strong>fica per mo<strong>di</strong>ficare il co<strong>di</strong>ce.<br />
ATTENZIONE!! Una delle cattive abitu<strong>di</strong>ni più comuni è quella <strong>di</strong> non testare nuovamente il programma dopo<br />
l'eliminazione <strong>di</strong> un errore. Purtroppo l'esperienza insegna che, non così raramente come si potrebbe pensare, le<br />
mo<strong>di</strong>fiche apportate per correggere un errore ne introducono altri!<br />
Finalmente il co<strong>di</strong>ce può essere immesso sul mercato! Si parla <strong>di</strong> rilascio (release). Ma non è finita qui! Anzi,<br />
rapportato a 100, il totale dell'impegno rappresentato da queste fasi viene stimato da molti come non superiore al<br />
40%<br />
Ed il resto ? Siate sinceri: quante volte vi è capitato <strong>di</strong> acquistare un video game senza essere costretti ad applicare<br />
una cosiddetta patch (letteralmente pezza, correzione). Quante volte come utenti siamo rimasti in attesa<br />
dell'ennesimo service pack (un kit che contiene molte patch ‘in un colpo solo’) <strong>di</strong> Windows o <strong>di</strong> Office o <strong>di</strong> altri<br />
software? Ma anche senza errori, ogni anno vengono immesse sul mercato nuove versioni (qualcuno riesce a contare<br />
quelle della serie FIFA? o <strong>di</strong> Final Fantasy?).<br />
Il 60% che manca <strong>di</strong> tutto il costo per lo sviluppo del software viene fagocitato dalla cosiddetta manutenzione. Ci<br />
sono tre tipi <strong>di</strong> manutenzione:<br />
7<br />
• correttiva: anche dopo aver superato tutte le fasi <strong>di</strong> test, è quasi impossibile che un software non contenga<br />
ancora almeno un errore; perio<strong>di</strong>camente, quin<strong>di</strong>, vengono rilasciate versioni che si spera siano <strong>di</strong> volta in volta<br />
meno affette da errori;<br />
• perfettiva o migliorativa: in<strong>di</strong>ca tutti quegli interventi che non servono a togliere degli errori ma a<br />
migliorare in qualche modo il prodotto; ad esempio a renderlo più veloce, a <strong>di</strong>minuire le sue esigenze <strong>di</strong><br />
spazio sul <strong>di</strong>sco, a renderlo capace <strong>di</strong> riconoscere nuove periferiche, a compiere funzioni prima non<br />
previste eccetera;<br />
• adattiva o adattativi: nessun errore da togliere, nessun miglioramento da portare; ma un cambiamento nel<br />
contesto in cui il software deve funzionare costringe ad apportare delle mo<strong>di</strong>fiche: il caso classico che si cita in<br />
questa situazione è la mo<strong>di</strong>fica <strong>di</strong> una legge che forza la software house a rispettarla, a costo <strong>di</strong> pesanti<br />
mo<strong>di</strong>fiche del co<strong>di</strong>ce!<br />
Infine, quando un'applicazione non è più utile (perché superata da altre, perché le mo<strong>di</strong>fiche richieste sono<br />
troppe, perché un evento imprevisto la rende obsoleta eccetera) se ne può anche decretare la morte, la<br />
<strong>di</strong>smissione.<br />
Riassumendo:<br />
1<br />
Stu<strong>di</strong>o della<br />
situazione reale<br />
2<br />
Analisi dei dati<br />
3<br />
Algoritmo/i risolutivo<br />
Questo tipo <strong>di</strong> modello è detto, per<br />
evidenti motivi, a cascata.<br />
E’ doveroso sottolineare che questo processo nel quale io ho in<strong>di</strong>viduato<br />
otto sta<strong>di</strong> non è l'unico modello riconosciuto per lo sviluppo del software<br />
(e a <strong>di</strong>re la verità l'ho semplificato rispetto quelli che trovate nella<br />
letteratura informatica). È però facile da capire e si adatta bene alla<br />
maggior parte delle stazioni che ci troveremo ad affrontare. Di tanto in<br />
tanto ritorneremo su uno degli sta<strong>di</strong> per aggiungere maggiore dettaglio.<br />
4<br />
Co<strong>di</strong>fica<br />
5<br />
test<br />
6<br />
debug<br />
7<br />
Manutenzione<br />
8<br />
Dismissione
Architettura hardware e software <strong>di</strong> un <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong><br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 9<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Per capire cosa sia un linguaggio <strong>di</strong> programmazione e come utilizzarlo per scrivere programmi, è prima necessaria<br />
una panoramica che mostri a gran<strong>di</strong> linee il funzionamento <strong>di</strong> un <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong>.<br />
Periferiche <strong>di</strong><br />
input (tastiera)<br />
Periferiche <strong>di</strong><br />
output (monitor)<br />
Periferiche <strong>di</strong><br />
input / output<br />
(modem)<br />
SCHEMA A BLOCCHI DI UN SISTEMA DI ELABORAZIONE<br />
Nota: tecnicamente parlando, i supporti <strong>di</strong> memorizzazione <strong>di</strong> massa<br />
sono ancora periferiche <strong>di</strong> input/output. La loro importanza è però<br />
tale da meritare un trattamento a parte.<br />
Il cuore del <strong>sistema</strong> è la CPU (Central Processing Unit, Unità centrale <strong>di</strong> processo). Nei personal computer la CPU è<br />
rappresentata da un singolo chip (il microprocessore): Pentium, Celeron, Athlon, Sempron, PowerPC, non vi <strong>di</strong>cono<br />
nulla questi nomi??<br />
La CPU è il ‘cervello’ del <strong>sistema</strong>. Il software <strong>di</strong> base (il BIOS che controlla <strong>di</strong>rettamente l’hardware, windows/linux<br />
cioè i sistemi operativi, gli strumenti <strong>di</strong> programmazione ed altre utilità <strong>di</strong> <strong>sistema</strong>) ed anche quello applicativo<br />
(programmi <strong>di</strong> video scrittura, contabilità, giochi ecc.) è formato da istruzioni, molte istruzioni. Per avere un idea della<br />
complessità, pensate che un software applicativo come Word è formato da milioni <strong>di</strong> istruzioni ... Queste in<strong>di</strong>cano ciò<br />
che va fatto per consentire all’utente <strong>di</strong> scrivere un documento usando il computer. Ecco, sinteticamente, come<br />
funziona l’intero meccanismo:<br />
Usando l’interfaccia che il <strong>sistema</strong> operativo mette a <strong>di</strong>sposizione, l’utente comanda il caricamento delle istruzioni (il<br />
programma) dai supporti <strong>di</strong> memorizzazione <strong>di</strong> massa: nel caso <strong>di</strong> Windows si fa doppio click, ad esempio, sull’icona<br />
<strong>di</strong> Word ...).<br />
Le istruzioni (se non tutte almeno quelle che servono in un primo momento) sono trasferite dai lenti supporti <strong>di</strong><br />
memorizzazione <strong>di</strong> massa nella velocissima memoria elettronica (RAM): da quest’ultima saranno a <strong>di</strong>sposizione della<br />
CPU in tempi molto brevi.<br />
La CPU preleva la prima istruzione del programma dalla RAM (Random Access Memory, memoria ad accesso<br />
casuale).<br />
La CPU ‘capisce’ cosa gli chiede l’istruzione.<br />
LA CPU invia i necessari coman<strong>di</strong> a tutti i <strong>di</strong>spositivi interessati da quell’istruzione, eseguendola.<br />
Se l’istruzione non è quella finale, si ritorna al punto 3 prelevando la successiva istruzione e così via ...<br />
CPU<br />
R<br />
A<br />
M<br />
Supporti <strong>di</strong> memorizzazione<br />
<strong>di</strong> massa (<strong>di</strong>schi, nastri, CD)
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 10<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Durante l’esecuzione delle istruzioni, la CPU può usare la parte della RAM rimasta libera per registrare dati interme<strong>di</strong>:<br />
è sempre per motivi <strong>di</strong> velocità che si preferisce la memoria elettronica a quella <strong>di</strong> massa (naturalmente si dovrà<br />
prima o poi provvedere anche alla registrazione dei dati sui supporti <strong>di</strong> massa, permanenti, pena la per<strong>di</strong>ta dei dati<br />
stessi allo spegnimento del computer). Ad esempio, i documenti creati con Word sono prima registrati nella RAM e<br />
solo con un comando esplicito <strong>di</strong> registrazione (File / Salva) sono memorizzati sui <strong>di</strong>schi.<br />
Alcune istruzioni chiederanno alla CPU <strong>di</strong> controllare l’uso <strong>di</strong> periferiche <strong>di</strong> input o <strong>di</strong> output. Sempre pensando a<br />
Word, non è <strong>di</strong>fficile convincersi che le sue istruzioni debbano fare in modo che la CPU rimanga in attesa fino a che<br />
chi sta usando il programma non preme un tasto qualsiasi o usa il mouse. Non appena l’utente preme un tasto, le<br />
istruzioni comandano la sua visualizzazione sul video; se con il mouse viene scelto un comando dal menu, le<br />
istruzioni chiederanno alla CPU <strong>di</strong> comportarsi in modo appropriato.<br />
In realtà tutto questo avviene con la collaborazione del <strong>sistema</strong> operativo (Windows o Lilnux, ad esempio):<br />
quest’ultimo mette a <strong>di</strong>sposizione <strong>di</strong> ogni programma una serie <strong>di</strong> servizi che possono essere invocati; ad esempio se<br />
un carattere deve essere fatto apparire sullo schermo, tutte le operazioni coinvolte non sono gestite al massimo<br />
dettaglio dal programmatore <strong>di</strong> word (per fortuna!); quello che accade è che il programmatore che ha scritto Word<br />
‘chiede’ a Windows <strong>di</strong> fare questo per lui. Sono tantissimi i servizi messi a <strong>di</strong>sposizione da un <strong>sistema</strong> operativo<br />
(gestione <strong>di</strong> tutte le periferiche, gestione della ram, del file system, della rete ecc.). Quando i sistemi operativi non<br />
esistevano ancora od erano molto primitivi, la vita del programmatore era molto più dura (doveva programmare<br />
TUTTI i dettagli). Approfon<strong>di</strong>rete questi aspetti nel corso <strong>di</strong> sistemi.<br />
Esaminiamo ora in dettaglio il funzionamento <strong>di</strong> una CPU. Ecco un ingran<strong>di</strong>mento:<br />
C<br />
P<br />
U<br />
bus dati esterno: prelevo un dato dalla RAM o lo registro dalla RAM<br />
ALU<br />
Aritmetic<br />
Logic<br />
Unit<br />
registri<br />
Deco<strong>di</strong>ficatore<br />
CU<br />
Control Unit<br />
In<strong>di</strong>rizzi per leggere<br />
o scrivere la RAM<br />
s<br />
Per comandare la<br />
lettura o la scrittura<br />
Non spaventatevi: è più semplice <strong>di</strong> quello che sembra a colpo d’occhio. Nella zona in grigio della RAM immaginiamo<br />
essere presenti un blocco <strong>di</strong> istruzioni da eseguire (potrebbero essere quelle <strong>di</strong> un programma come Word).<br />
La RAM: pensiamola come una sequenza <strong>di</strong> celle, chiamate byte. In ogni byte può essere memorizzato un<br />
dato, come un istruzione od un carattere (vedremo dopo come ed in che forma). Nel <strong>di</strong>segno qui sopra<br />
sono stati evidenziati in grigio quattro byte. Ogni cella (byte) viene in<strong>di</strong>cata con il suo in<strong>di</strong>rizzo, cioè la<br />
posizione a partire dall’inizio. Visto che si comincia a contare da zero, quest’ultimo è l’in<strong>di</strong>rizzo della prima<br />
cella in alto nella RAM (si parla anche <strong>di</strong> byte zero). La seconda cella dall’alto ha allora in<strong>di</strong>rizzo uno e così<br />
via... I byte in grigio iniziano all’in<strong>di</strong>rizzo cinque e terminano quattro byte più avanti, all’in<strong>di</strong>rizzo otto<br />
(considerando il cinque sono appunto quattro in<strong>di</strong>rizzi).<br />
R<br />
A<br />
M
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 11<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
I registri: sono celle <strong>di</strong> memoria speciali, interne alla CPU, usate per memorizzare valori per <strong>di</strong>versi<br />
scopi: la CPU impiega pochissimo a trovare o scrivere un dato nei registri, perché sono molto vicini ad<br />
essa e molto veloci. E’ quin<strong>di</strong> assai conveniente tenere qui i risultati interme<strong>di</strong> delle operazioni e tutto<br />
ciò che si dovrebbe continuamente rileggere o scrivere nella più lenta RAM. Purtroppo la spazio<br />
all’interno della CPU è veramente minimo: c’è spazio solo per alcune decine <strong>di</strong> registri ... Chiaro che <strong>di</strong><br />
volta in volta si terrà, della RAM, solo ciò che serve alle istruzioni del momento, poi si dovrà sostituire<br />
il contenuto dei registri con un altra parte della RAM. Pur con queste limitazioni, lavorare con i registri dà grossi<br />
benefici rispetto all’uso della sola RAM.<br />
Alcuni registri hanno poi una funzione speciale: uno, chiamato contatore <strong>di</strong> programma (program counter) in<strong>di</strong>ca a<br />
quale in<strong>di</strong>rizzo della RAM si trova il dato che rappresenta la prossima istruzione da eseguire.<br />
Un altro è chiamato registro in<strong>di</strong>rizzi: è qui che va depositato un in<strong>di</strong>rizzo ogni volta che si vuole leggere o scrivere un<br />
byte della RAM (quando si vuole leggere il co<strong>di</strong>ce della prossima istruzione da eseguire, il contatore <strong>di</strong> programma<br />
viene infatti copiato qui).<br />
Un altro è chiamato registro istruzioni e contiene il co<strong>di</strong>ce numerico dell’istruzione letta nella RAM, che deve essere<br />
eseguita (infatti ogni possibile istruzione è rappresentata da un co<strong>di</strong>ce numerico).<br />
ALU: è l’Unità Aritmetico Logica. Essa svolge le operazioni aritmetiche (matematiche)<br />
elementari ed i confronti logici tra due dati (sa <strong>di</strong>re se sono uguali, se uno è più grande o<br />
più piccolo dell’altro ecc.).<br />
Control Unit: è l’unità <strong>di</strong> controllo. E’ lei che invia a tutti gli altri <strong>di</strong>spositivi segnali elettrici con<br />
cui comanda tutte le micro operazioni che possono essere svolte. Ad esempio può comandare<br />
l’invio del contenuto <strong>di</strong> un registro in un altro registro, oppure la lettura <strong>di</strong> un byte della RAM ed<br />
il trasferimento del suo contenuto in un registro (o il contrario), il trasferimento alla ALU <strong>di</strong> due<br />
dati da sommare o da sottrarre (ed il comando che fa eseguire la somma e la sottrazione). Può<br />
inviare segnali <strong>di</strong> comando anche a <strong>di</strong>spositivi esterni alla CPU: ad esempio può comandare allo<br />
scanner <strong>di</strong> inviare il prossimo blocco <strong>di</strong> dati del documento che sta leggendo. Insomma è il vero regista che regola il<br />
funzionamento <strong>di</strong> tutti gli altri <strong>di</strong>spositivi interni od esterni.<br />
Bus: si è parlato <strong>di</strong> invio <strong>di</strong> coman<strong>di</strong> dalla control unit, <strong>di</strong> trasportare byte dalla RAM ai registri e<br />
viceversa, <strong>di</strong> comunicare in<strong>di</strong>rizzi per usare la RAM. Come viaggiano tutte queste informazioni ?<br />
Semplice, usano il bus ! Battute a parte, il termine in<strong>di</strong>ca i canali fisici lungo i quali si spostano i segnali<br />
elettrici che rappresentano dati, in<strong>di</strong>rizzi e coman<strong>di</strong>. Ci sono tre tipi <strong>di</strong> bus:<br />
Bus dati: come suggerisce il nome, trasporta dati (il contenuto <strong>di</strong> una cella <strong>di</strong> RAM, <strong>di</strong> un registro o un byte da o<br />
verso una periferica. Esiste un bus dati interno, sul quale viaggiano i dati all’interno della CPU, ed uno esterno che<br />
permette lo scambio dei dati tra la CPU e <strong>di</strong>spositivi esterni (e viceversa) o tra <strong>di</strong>spositivi esterni stessi. Ho<br />
rappresentato i bus dati con una linea a tratti piccoli. Per non appesantire il <strong>di</strong>segno non ho collegato tutti i <strong>di</strong>spositivi<br />
che si possono scambiare dati grazie al bus interno. Le frecce in<strong>di</strong>cano naturalmente il senso <strong>di</strong> percorrenza.<br />
Nelle moderne architetture esistono bus specializzati per scambiare dati con periferiche che consumano questi ultimi<br />
ad un tasso elevatissimo. Un esempio è rappresentato dalla scheda grafica: essa sfrutta bus de<strong>di</strong>cati (connessione<br />
AGP, oggi in via <strong>di</strong> sostituzione con i collegamenti Serial Ata).<br />
Bus in<strong>di</strong>rizzi: ogni volta che la CPU legge o scrive un dato dalla RAM deve prima impostare il relativo in<strong>di</strong>rizzo ;<br />
pensate al bus in<strong>di</strong>rizzi come ad un bus dati specializzato nel trasportare in<strong>di</strong>rizzi. Ho in<strong>di</strong>cato il bus in<strong>di</strong>rizzi con una<br />
linea a tratti larghi.<br />
Bus controlli: è l’insieme delle linee su cui viaggiano i coman<strong>di</strong> (segnali elettrici) che la Control Unit invia a <strong>di</strong>spositivi<br />
esterni; serve anche ai <strong>di</strong>spositivi esterni per segnalare eventi particolari alla CPU: avete presente cosa accade<br />
quando finisce la carta della stampante ? O quando il <strong>di</strong>sco che tentate <strong>di</strong> scrivere è protetto ? O quando non c’è più<br />
spazio sul <strong>di</strong>sco ? L’ho in<strong>di</strong>cato nel <strong>di</strong>segno con le tre linee continue che puntano verso il basso. La control Unit può<br />
attivare o <strong>di</strong>sattivare anche i componenti interni grazie a delle linee <strong>di</strong> selezione: le ho in<strong>di</strong>cate con delle linee<br />
continue senza frecce: quando la Control Unit vuole attivare un <strong>di</strong>spositivo, invia lungo una linea <strong>di</strong> selezione un<br />
segnale apposito.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 12<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Ora che sono stati descritti tutti i componenti, ve<strong>di</strong>amoli all’opera. Immaginiamo che le istruzioni da eseguire siano<br />
contenute nei byte in grigio:<br />
Il registro program counter, abbiamo detto, contiene l’in<strong>di</strong>rizzo dell’istruzione da eseguire (in questo caso l’in<strong>di</strong>rizzo<br />
del primo byte in grigio). Per poterla leggere dalla RAM è necessario che tale in<strong>di</strong>rizzo sia contenuto nel registro<br />
in<strong>di</strong>rizzi. L’unità <strong>di</strong> controllo comanda allora la copia del contenuto del program counter in questo registro.<br />
L’unità <strong>di</strong> controllo comanda quin<strong>di</strong> al <strong>di</strong>spositivo <strong>di</strong> lettura/scrittura della RAM (non evidenziato nel <strong>di</strong>segno per<br />
semplicità) <strong>di</strong> leggere il byte all’in<strong>di</strong>rizzo contenuto ora nel registro in<strong>di</strong>rizzi; il suo valore (il co<strong>di</strong>ce dell’istruzione)<br />
viene trasferito lungo il bus dati esterno e da lì sul bus dati interno, fino a depositarlo nel registro istruzioni.<br />
L’unità <strong>di</strong> controllo comanda a questo punto al deco<strong>di</strong>ficatore <strong>di</strong> leggere dal registro istruzioni il co<strong>di</strong>ce numerico<br />
dell’istruzione e <strong>di</strong> ‘capire’ che cosa è richiesto.<br />
Sulla base della ‘risposta’ del deco<strong>di</strong>ficatore l’unità <strong>di</strong> controllo attiva in sequenza i <strong>di</strong>spositivi interessati<br />
dall’istruzione; ad esempio (semplificando) se si dovesse calcolare A = B + C<br />
prelevare B dalla RAM (in<strong>di</strong>rizzo <strong>di</strong> B in registro in<strong>di</strong>rizzi e successiva lettura);<br />
prelevare C dalla RAM (in<strong>di</strong>rizzo <strong>di</strong> C in registro in<strong>di</strong>rizzi e successiva lettura);<br />
trasferire alla ALU gli operan<strong>di</strong> e comandare la somma<br />
copiare il risultato nella posizione dove in RAM si trova ‘C’ (in<strong>di</strong>rizzo <strong>di</strong> C in registro in<strong>di</strong>rizzi e scrittura)<br />
Nel frattempo il program counter è stato automaticamente incrementato e in<strong>di</strong>ca<br />
l’in<strong>di</strong>rizzo della prossima istruzione da eseguire. Si può allora ricominciare dal punto 1,<br />
fino a che ... il computer viene spento ! In realtà, anche dopo che il programma termina,<br />
ad esempio chiudendo Word, un altro rimane in funzione: quello che già funzionava<br />
prima <strong>di</strong> Word, Windows in persona. Infatti il <strong>sistema</strong> operativo, esso stesso un insieme<br />
<strong>di</strong> programmi, è all’opera dal momento dell’accensione del computer fino allo<br />
spegnimento. Quando l’utente comanda, usando l’interfaccia grafica, la partenza <strong>di</strong><br />
un’applicazione, le istruzioni <strong>di</strong> quest’ultima affiancano in RAM quelle del <strong>sistema</strong><br />
operativo ed il program counter viene fatto ‘puntare’ alla prima istruzione<br />
dell’applicazione, che inizia così ad essere eseguita. A programma terminato si riprende<br />
ad eseguire il <strong>sistema</strong> operativo. Durante il suo funzionamento l’applicazione può<br />
richiamare parti del <strong>sistema</strong> operativo (richiesta <strong>di</strong> servizi): il punto a cui si era arrivati con l’applicazione viene prima<br />
memorizzato, per poter riprendere, dopo che il <strong>sistema</strong> operativo ha esau<strong>di</strong>to la richiesta, il programma esattamente<br />
dall’istruzione a cui era stato interrotto.<br />
La fase in cui si provvede a prelevare il co<strong>di</strong>ce numerico dell’istruzione da eseguire è chiamata fase <strong>di</strong> fetch<br />
(prelievo).<br />
La fase in cui il deco<strong>di</strong>ficatore interpreta l’istruzione, determinando le microoperazioni che verranno poi eseguite<br />
dall’unità <strong>di</strong> controllo è chiamata fase <strong>di</strong> decode (deco<strong>di</strong>fica).<br />
La fase in cui le microoperazioni sono effettivamente portate a termine, attivando nella giusta sequenza i <strong>di</strong>spositivi<br />
interessati è chiamata fase <strong>di</strong> execute (esecuzione).<br />
Il ciclo fetch, decode ed execute viene ripetuto a velocità incre<strong>di</strong>bili: oggi miliar<strong>di</strong><br />
<strong>di</strong> milioni <strong>di</strong> volte al secondo (1 volta al secondo = 1 Hertz = 1Hz; un milione <strong>di</strong><br />
volte al secondo = 1 Mega Hertz = 1Mhz; 1Ghz= un miliardo). Le fantastiche<br />
capacità <strong>di</strong> un <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong> sono tutte qui: una velocità pazzesca ed<br />
una memoria perfetta nell’eseguire istruzioni semplicissime (sposta un byte <strong>di</strong> qua<br />
e mettilo <strong>di</strong> là ...) ! E’ come se un muratore usasse mattoni gran<strong>di</strong> come pezzi del<br />
lego (le istruzioni semplicissime) ma lo facesse ad una velocità incre<strong>di</strong>bile, costruendo palazzi meravigliosi, ma solo se<br />
qualcuno lo guida passo passo (il programma) ... Qualcuno ha infatti definito il computer uno stupido molto veloce !
RAPPRESENTAZIONE INTERNA DELLE INFORMAZIONI<br />
Rimane da sciogliere ancora un nodo importante: come sono rappresentate le informazioni<br />
nella RAM e nei registri ? E le istruzioni ? Essenzialmente devono essere trattati numeri e<br />
caratteri. Le prime macchine calcolatrici (attenzione: non sto ancora parlando <strong>di</strong> computer<br />
elettronici!) erano gran<strong>di</strong> grovigli <strong>di</strong> ingranaggi: pur costruite con una precisione<br />
ammirabile, gli attriti ed i tempi <strong>di</strong> funzionamento <strong>di</strong> tutte queste parti meccaniche<br />
costituivano un limite serio per la velocità <strong>di</strong> <strong>elaborazione</strong>.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 13<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Mentre un ingranaggio impiega un certo tempo per funzionare, un segnale elettrico va alla velocità della luce<br />
(300.000 Km al secondo) ! Come <strong>di</strong>re, più <strong>di</strong> sette volte il giro della terra in un secondo ! Sono stati ideati dei<br />
<strong>di</strong>spositivi (circuiti elettronici) in grado sfruttare i segnali elettrici per rappresentare le informazioni ed elaborarle. Si è<br />
anche capito che il miglior modo <strong>di</strong> procedere sarebbe stato quello <strong>di</strong> rappresentare tutto sotto forma <strong>di</strong> numeri<br />
binari. Cerchiamo <strong>di</strong> arrivare a questa conclusione per gra<strong>di</strong>.<br />
I segnali elettrici non sono perfetti: un segnale elettrico, mentre viaggia su un filo, è soggetto a<br />
<strong>di</strong>sturbi ed attenuazioni <strong>di</strong> vario tipo che tendono a <strong>di</strong>storcerlo: in partenza, cioè, possiede certe<br />
caratteristiche (forma, potenza ecc.) ; all’arrivo non sono più esattamente le stesse. Per cui se<br />
scelgo due segnali abbastanza simili tra loro e li trasmetto, potrebbe capitare che chi li riceve<br />
scambi l’uno per l’altro (errore). Questa possibilità cresce con il numero dei tipi <strong>di</strong> segnale usati.<br />
Questo spiega perché i prototipi <strong>di</strong> <strong>calcolatore</strong> che cercavano <strong>di</strong> usare tanti segnali <strong>di</strong>versi per<br />
rappresentare le cifre numeriche ed i caratteri dell’alfabeto non ebbero molto successo:<br />
semplicemente la probabilità <strong>di</strong> errore era troppo alta ! Certo se i tipi <strong>di</strong> segnale fossero <strong>di</strong> meno,<br />
<strong>di</strong>minuirebbe la probabilità <strong>di</strong> confonderli ... Fin dove possiamo arrivare ? Un solo segnale non<br />
basta: sarebbe sempre uguale a se stesso e non porterebbe informazione, ma due ... Due è proprio<br />
risultato il numero ottimale: usando solo due segnali il più possibile <strong>di</strong>versi tra loro, è ben <strong>di</strong>fficile che un segnale<br />
venga <strong>di</strong>storto al punto tale da confonderlo per l’altro. E’ nata la logica <strong>di</strong>gitale (binaria), basata cioè sull’uso <strong>di</strong> due<br />
soli simboli (che possiamo far corrispondere alle cifre 0 ed 1). Tutte le informazioni devono essere co<strong>di</strong>ficate<br />
(rappresentate) con combinazioni <strong>di</strong>verse <strong>di</strong> 0 ed 1. Queste cifre, dette binarie, sono anche chiamate bit. Un bit può<br />
essere 0 od 1.<br />
I circuiti <strong>di</strong>gitali sono facili da costruire: <strong>di</strong>gitali, così sono chiamati i circuiti in grado <strong>di</strong> trattare segnali <strong>di</strong> due tipi, per<br />
confrontarli e combinarli. Purtroppo non è possibile neanche accennare alla realizzazione dei circuiti <strong>di</strong>gitali. Vi basti<br />
sapere che è relativamente semplice costruire circuiti per memorizzare, sommare, sottrarre, moltiplicare, <strong>di</strong>videre,<br />
confrontare ecc. due numeri binari. Questo perché le regole dell’aritmetica binaria (come si fanno le somme,<br />
sottrazioni, moltiplicazioni ecc.) sono ad<strong>di</strong>rittura più semplici <strong>di</strong> quelle che usiamo noi con la nostra aritmetica<br />
decimale (le cifre dallo 0 al 9) ! Beh, avete un intero corso <strong>di</strong> elettronica per togliervi certe voglie!<br />
Il <strong>sistema</strong> binario è equivalente a quello decimale: come <strong>di</strong>re che ogni numero espresso con la nostra<br />
notazione decimale è esprimibile anche in binario ed in modo biunivoco. Il termine significa che, dato<br />
un qualsiasi numero espresso in decimale, lo si può convertire ottenendo un numero in forma binaria;<br />
viceversa se si parte da questo numero binario e lo si converte in decimale, si ritorna al numero <strong>di</strong><br />
partenza. Questo è molto importante: significa poter continuare per noi a fornire dati in decimale<br />
all’elaboratore. Questi saranno convertiti in binario ed elaborati. Il risultato sarà <strong>di</strong> nuovo, per noi,<br />
convertito in binario, con la certezza <strong>di</strong> non commettere equivoci. Equivalentemente, significa che<br />
tutte le operazioni matematiche possibili in decimale lo sono anche in binario e che se una certa<br />
operazione in decimale dà un certo risultato, lo stesso verrà fornito in binario dall’operazione corrispondente.<br />
Potremmo <strong>di</strong>re, volgarmente, che il <strong>sistema</strong> binario è ‘potente’ quanto quello decimale.<br />
Anche le informazioni non numeriche sono comunque rappresentabili sotto forma <strong>di</strong> numeri. Consideriamo caso per<br />
caso:<br />
Alfanumeriche: le singole cifre numeriche, le lettere dell’alfabeto ed altri caratteri come la punteggiatura: basta far<br />
corrispondere ad ogni carattere un ben preciso co<strong>di</strong>ce numerico. Uno tra i co<strong>di</strong>ci più <strong>di</strong>ffusi è l’ASCII: secondo<br />
questo co<strong>di</strong>ce, ad esempio la lettera ‘A’ è co<strong>di</strong>ficata con il numero 65, la ‘B’ con 66 e così via (i numeri precedenti il<br />
65 sono usati per altri caratteri). Naturalmente l’elaboratore saprà quando interpretare un numero come tale o come<br />
la rappresentazione <strong>di</strong> un carattere. Nota: in fondo al libro troverai un appen<strong>di</strong>ce con l’intera tabella ASCII.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 14<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Grafiche: se il <strong>di</strong>segno è <strong>di</strong> tipo tecnico memorizzo le coor<strong>di</strong>nate numeriche <strong>di</strong> ogni elemento geometrico ed altre<br />
caratteristiche (coor<strong>di</strong>nate del centro <strong>di</strong> una circonferenza e la misura del raggio, le coor<strong>di</strong>nate degli angoli <strong>di</strong> un<br />
rettangolo o degli estremi <strong>di</strong> una retta ecc.). Se il <strong>di</strong>segno è pittorico, cioè fatto per punti (pixel), si memorizza per<br />
ogni pixel il valore dell’intensità <strong>di</strong> rosso, verde e blu che lo caratterizza.<br />
Animazioni e filmati: è un caso particolare del precedente. Infatti ogni sequenza <strong>di</strong> animazione o filmato è formata<br />
da tanti fotogrammi, ognuno dei quali può essere considerato un <strong>di</strong>segno statico, ricadendo nel caso precedente. Un<br />
a tecnica molto usata per risparmiare memoria è quella <strong>di</strong> memorizzare solo alcuni fotogrammi chiave (key frame) e<br />
<strong>di</strong>re per ogni fotogramma interme<strong>di</strong>o cosa cambia a livello <strong>di</strong> pixel (compressioni mpeg / <strong>di</strong>vx).<br />
Suoni: ogni suono può essere scomposto, determinando le caratteristiche delle onde sonore che lo costituiscono.<br />
Questi caratteristiche sono rappresentate da numeri ... Esistono dei formati compressi anche per l’au<strong>di</strong>o (MP3, WMA).<br />
RAPPRESENTAZIONE DELLE ISTRUZIONI:<br />
Si adotta anche in questo caso una rappresentazione numerica. Ecco qui sotto il formato <strong>di</strong> una generica istruzione:<br />
co<strong>di</strong>ce operazione operando 1 (o suo in<strong>di</strong>rizzo) operando 2 (o suo in<strong>di</strong>rizzo) in<strong>di</strong>rizzo risultato<br />
Ogni possibile istruzione è in<strong>di</strong>cata da un co<strong>di</strong>ce numerico. Ad esempio la somma potrebbe essere in<strong>di</strong>cata con 001<br />
(ricor<strong>di</strong>amo che i numeri sono binari, ed usano solo lo 0 e l’1), la <strong>di</strong>fferenza con 002 ecc. Dopo aver specificato il tipo<br />
<strong>di</strong> istruzione è necessario in<strong>di</strong>care gli operan<strong>di</strong> (ad esempio i numeri da sommare). Qualche volta nell’operazione si<br />
mettono <strong>di</strong>rettamente i valori da usare, altre volte si specifica l’in<strong>di</strong>rizzo dove trovare in RAM i valori che servono.<br />
Così si potrebbe chiedere <strong>di</strong> sommare <strong>di</strong>rettamente 0010 (2 in binario) e 0100 (4 in binario) oppure intendere che<br />
0010 è l’in<strong>di</strong>rizzo in RAM in cui trovare il primo numero da sommare e che 0100 è l’in<strong>di</strong>rizzo dove trovare in RAM il<br />
secondo numero da sommare. A seconda del co<strong>di</strong>ce dell’istruzione la CPU sa come comportarsi. Se l’istruzione<br />
produce un risultato è anche possibile in<strong>di</strong>care a quale in<strong>di</strong>rizzo in RAM depositare il risultato. Ecco come potrebbe<br />
apparire un’istruzione completa:<br />
0001 0010 0100 1100<br />
Noterete che sono necessari 16 bit, cioè due byte, per rappresentarla. Infatti, anche se le celle della RAM contengono<br />
un byte, spesso sono considerati a gruppi. Il deco<strong>di</strong>ficatore riceve proprio la sequenza completa <strong>di</strong> bit, la scompone<br />
in co<strong>di</strong>ce operativo, operan<strong>di</strong> e in<strong>di</strong>rizzo risultato e comunica alla Control Unit le sequenze (microistruzioni) <strong>di</strong><br />
coman<strong>di</strong> da attivare. Alcune istruzioni, semplici, occupano un solo byte, altre molti. La CPU non fa in questo caso<br />
confusione a causa delle <strong>di</strong>verse lunghezze: il co<strong>di</strong>ce operativo (il primo ad essere letto nella RAM) chiarisce subito <strong>di</strong><br />
che tipo <strong>di</strong> istruzione si tratta e <strong>di</strong> quanti byte necessita, per ogni sua parte. E’ per questo che il program counter può<br />
essere automaticamente aumentato del giusto numero <strong>di</strong> byte per ‘puntare’ alla posizione in RAM in cui si trova la<br />
prossima istruzione.<br />
OSSERVAZIONE IMPORTANTE. Sembrerebbe che qualsiasi aspetto della realtà sia perfettamente rappresentabile e<br />
trattabile da un elaboratore ... ma non è esattamente così ! Dobbiamo convincerci che ciò che viene costruito nelle<br />
memorie <strong>di</strong> un elaboratore è solo un modello (una rappresentazione semplificata, parziale, mai perfetta) della realtà.<br />
Certo, quando scriviamo una lettera con Word è esattamente quello che si voleva fare: in questo caso il modello è<br />
praticamente perfetto. Ma in altri, per motivi pratici, sono stati imposti dei limiti. Pensiamo ai numeri interi. Più sono<br />
gran<strong>di</strong> e più bit sono necessari per rappresentarli, questo è intuitivo. Per motivi <strong>di</strong> efficienza e praticità si è deciso <strong>di</strong><br />
de<strong>di</strong>care sempre lo stesso numero <strong>di</strong> bit per rappresentare un intero: sono usati cioè gli stessi bit per un numero<br />
grande che per uno piccolo. E’ un po’ come scrivere 00128 che è ancora 128: stessa cosa in binario (i bit <strong>di</strong> più ed<br />
inutili sono messi a 0). Già, ma quanti bit ? Se ne vengono usati pochi allora non sarà possibile rappresentare numeri<br />
gran<strong>di</strong>. Se ne vengono usati tanti, tutte le volte che si rappresenta un numero piccolo ne vengono ‘sprecati’ un certo<br />
numero ... Qualunque sia la scelta, la conclusione è sempre la stessa: fissato il numero <strong>di</strong> bit da usare,<br />
automaticamente è fissato anche il più grande ed il più piccolo numero intero (positivo e negativo) che si può<br />
rappresentare. Stessa cosa per i numeri reali (quelli ‘con la virgola’): in questo caso, oltre ai limiti esiste anche un<br />
problema <strong>di</strong> precisione. Infatti un numero può anche essere piccolo ma con un numero infinito <strong>di</strong> cifre decimali
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 15<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
(pensate al pi greco): se si fissa il numero <strong>di</strong> bit, più <strong>di</strong> tante cifre decimali non potranno essere rappresentate. Ecco<br />
allora che un numero come 3,145672943456 sarà invece memorizzato come 3,145673, arrotondando la sesta cifra.<br />
Ai fini pratici, per la stragrande maggioranza delle applicazioni, la questione è praticamente ininfluente, ma è giusto<br />
sapere che questi limiti esistono e che ciò che viene rappresentato all’interno <strong>di</strong> un <strong>sistema</strong> <strong>di</strong> <strong>elaborazione</strong> può<br />
essere soggetto ad approssimazioni. Attenzione: poiché ogni informazione è rappresentata in forma numerica,<br />
questo significa che ogni tipo <strong>di</strong> informazione è soggetto ad approssimazioni (scegliendo, ad esempio, <strong>di</strong> usare pochi<br />
bit per le intensità dei colori <strong>di</strong> un immagine, la gamma dei colori risultanti sarà povera e <strong>di</strong>scorde da quella reale).<br />
Evoluzione dei linguaggi <strong>di</strong> programmazione<br />
Ok, tutto questo <strong>di</strong>scorso per avere perfettamente chiaro questo concetto: nella sua forma più primitiva un<br />
programma è una sequenza <strong>di</strong> valori numerici memorizzati in altrettanti byte della RAM; il formato <strong>di</strong> memorizzazione<br />
<strong>di</strong> questi valori è quello binario. Un programma in questa forma è imme<strong>di</strong>atamente comprensibile al deco<strong>di</strong>ficatore e<br />
<strong>di</strong> linguaggio in cui è espresso è chiamato linguaggio macchina. Si parla anche <strong>di</strong> linguaggio basso livello<br />
perché è vicino alla macchina e lontano dal nostro linguaggio naturale.<br />
I primi programmatori non avevano<br />
molta scelta: non esisteva la tastiera,<br />
non esistevano i supporti <strong>di</strong><br />
memorizzazione <strong>di</strong> massa ... Ogni<br />
istruzione ed ogni dato veniva<br />
comunicato al computer usando le<br />
cosiddette schede perforate (ormai<br />
reperti archeologici !). Sono tessere <strong>di</strong><br />
carta, dalle <strong>di</strong>mensioni <strong>di</strong> una grossa<br />
banconota, su cui i numeri binari sono<br />
rappresentati bucando o no piccoli<br />
rettangoli <strong>di</strong>sposti in file verticali: un<br />
buco all’interno del rettangolino<br />
significa 1, niente buco 0. Una fila<br />
verticale <strong>di</strong> rettangolini corrisponde ad un byte: quin<strong>di</strong>, l’uno <strong>di</strong> fianco all’altro, sulla stessa scheda possono stare<br />
anche più byte (file <strong>di</strong> rettangolini). Il programmatore, usando un <strong>di</strong>spositivo chiamato perforatrice, preparava pacchi<br />
<strong>di</strong> schede, corrispondenti ai programmi che voleva far eseguire. Le schede perforate venivano poi lette da un lettore<br />
meccanico: questo scan<strong>di</strong>va ogni scheda rilevando i fori e comunicando 1 o 0 al computer, che a sua volta<br />
memorizzava i byte nella RAM.<br />
A parte la scomo<strong>di</strong>tà fisica <strong>di</strong> questo meccanismo (era facilissimo spiegazzare una scheda, con conseguente<br />
inceppamento del perforatore o del lettore, oppure invertire la posizione <strong>di</strong> due schede ecc.), la realizzazione e la<br />
manutenzione <strong>di</strong> un programma non era pane per i denti <strong>di</strong> tutti:<br />
• bisognava <strong>di</strong>ventare esperti (in materia <strong>di</strong> programmazione non sono ammesse mezze misure) <strong>di</strong> quel<br />
particolare linguaggio riconosciuto dalla CPU usata, fatto <strong>di</strong> intricate sequenze <strong>di</strong> zeri e <strong>di</strong> uno: provate ad<br />
immaginare la fatica <strong>di</strong> trovare un piccolo errore in una lista <strong>di</strong> centinaia <strong>di</strong> migliaia <strong>di</strong> co<strong>di</strong>ci binari ...<br />
• bisognava essere esperti <strong>di</strong> aritmetica binaria, visto che tutto è espresso in quella forma;<br />
• dato che le istruzioni sono così a basso livello, vicino all’hardware, era necessario conoscere alla perfezione<br />
anche quest’ultimo; se cambia l’hardware i programmi dovevano essere pesantemente mo<strong>di</strong>ficati; se cambiava<br />
il microprocessore (la CPU), e con esso l’insieme delle istruzioni accettate, l’intero programma andava riscritto;<br />
Per tutti questi motivi era <strong>di</strong>fficile trovare buoni programmatori e lo sviluppo del software era costoso e lento.<br />
Nonostante queste <strong>di</strong>fficoltà i primi abbozzi <strong>di</strong> sistemi operativi e le prime applicazioni sono stati scritti proprio così ...<br />
<strong>di</strong>rettamente linguaggio macchina!<br />
Si parla <strong>di</strong> linguaggio perché come ogni linguaggio parlato ha un suo vocabolario <strong>di</strong> ‘parole’ (l’insieme dei co<strong>di</strong>ci delle<br />
istruzioni che si possono usare) ed una sua sintassi, cioè le regole con cui si possono formare le ‘frasi’ (le istruzioni e<br />
le sequenze <strong>di</strong> istruzioni). Ogni frase ha poi il suo ‘significato’ (l’effetto che produce).<br />
Un primo miglioramento (oltre alla <strong>di</strong>ffusione <strong>di</strong> <strong>di</strong>spositivi come le tastiere ed i <strong>di</strong>schi) è stato introdotto con l’uso <strong>di</strong><br />
co<strong>di</strong>ci mnemonici al posto delle sequenze <strong>di</strong> 1 e <strong>di</strong> 0. Il concetto è assai intuitivo: è molto più semplice ricordare
falso<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 16<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
qualche cosa del tipo ‘SUM 15 21’ che non ‘100100101011’, per in<strong>di</strong>care che vanno sommati 15 e 21. Il<br />
programmatore può ora scrivere il programma usando un linguaggio un po’ più lontano da quello macchina ma più<br />
vicino al suo modo <strong>di</strong> esprimersi. Questo linguaggio è chiamato assembly. Naturalmente, prima <strong>di</strong> sottoporre il<br />
programma al computer è necessario un processo <strong>di</strong> traduzione: i co<strong>di</strong>ci mnemonici ed i valori espressi in decimale<br />
non sono comprensibile dalla CPU ! Un programma apposito chiamato assemblatore (assembler) , scritto<br />
ovviamente in linguaggio macchina, traduce ogni co<strong>di</strong>ce mnemonico e valore decimale nella corrispondente sequenza<br />
<strong>di</strong> 1 e <strong>di</strong> 0. I linguaggi assembly devono essere considerati, come linguaggio macchina, <strong>di</strong> basso livello.<br />
Qui a lato, un esempio <strong>di</strong> programma<br />
scritto in un linguaggio assembly.<br />
Esso calcola la somma <strong>di</strong> una<br />
sequenza <strong>di</strong> numeri letti (istruzione<br />
IN 1) dalla periferica 1 (che potrebbe<br />
essere la tastiera) fino a che non<br />
viene specificato lo zero come<br />
numero da sommare. Quando viene<br />
inserito lo zero si termina il ciclo <strong>di</strong><br />
lettura e si invia il risultato (istruzione<br />
OUT 2) alla periferica 2 (che potrebbe<br />
essere il video).<br />
NOTA: non sforzatevi <strong>di</strong> comprendere<br />
i dettagli (è ancora troppo presto!)<br />
Beh, certamente meglio <strong>di</strong> una sfilza <strong>di</strong> bit, ma ancora piuttosto <strong>di</strong>fficile da leggere! Inoltre questo stesso programma<br />
è inservibile con un'altra CPU... Non solo: un programmatore abituato a programmare per un'altra CPU (e quin<strong>di</strong> con<br />
un altro linguaggio assembly) potrebbe non capirci nulla!<br />
inizio<br />
totale
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 17<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
I linguaggi assembly sono, infatti, ancora troppo scomo<strong>di</strong> e soffrono, in fondo, ancora <strong>di</strong> tutti i problemi elencati per<br />
il linguaggio macchina: hanno reso solo un poco più lieve la vita al programmatore, ma sono ancora molto legati alla<br />
CPU usata. Il passaggio successivo è stato lo sviluppo <strong>di</strong> linguaggi ancora più lontani dal linguaggio macchina e, <strong>di</strong><br />
conseguenza, più vicini al nostro modo <strong>di</strong> esprimerci. Sono nati i linguaggi ad alto livello. Questi sono<br />
caratterizzati da istruzioni molto potenti, con descrizioni facili da ricordare, che corrispondono anche a centinaia <strong>di</strong><br />
semplici istruzioni in linguaggio macchina o assembly.<br />
Program somma;<br />
var totale, dato: integer;<br />
begin<br />
totale :=0;<br />
repeat<br />
read(dato);<br />
totale := totale + dato<br />
until dato=0;<br />
write(totale).<br />
end.<br />
Ed ecco qui a lato la versione dello stesso algoritmo co<strong>di</strong>ficata con il<br />
linguaggio <strong>di</strong> programmazione Pascal (per l'esattezza il suo ‘ <strong>di</strong>aletto’ Turbo<br />
Pascal).<br />
È molto leggibile, compatto. Dopo un'intestazione che assegna un nome al<br />
programma, il programmatore <strong>di</strong>chiara i simboli che vorrà utilizzare<br />
in<strong>di</strong>cando anche che sono dei numeri interi (integer).<br />
Il ‘begin’ in<strong>di</strong>ca appunto l'inizio del programma. Come prima istruzione si<br />
azzera il totale e poi si ripetono (repeat) due istruzioni fino a che (until)<br />
non viene introdotto come dato il valore zero: la lettura (read) del dato e la<br />
sua aggiunta al totale. Terminato il ciclo si provvede alla scrittura del totale<br />
calcolato.<br />
Siamo chiaramente ad un livello più astratto: il programmatore non deve conoscere i meccanismi hardware della<br />
stampante o della CPU: ad esempio, deve solo ricordare che il comando <strong>di</strong> stampa è ‘write’ e che deve specificare tra<br />
parentesi ciò che vuole sia stampato. Cambia la stampante ? Il comando rimane sempre questo ! Cambia la CPU ? Il<br />
comando rimane sempre questo ... Cambia ad<strong>di</strong>rittura il tipo <strong>di</strong> computer? Non devo mo<strong>di</strong>ficare o riscrivere tutti i<br />
programmi: il Pascal rimane Pascal...<br />
Ricor<strong>di</strong>amo qualche nome <strong>di</strong> linguaggio ad alto livello tra i più famosi: Fortran (<strong>di</strong>ffusissimo in ambito scientifico e<br />
storicamente il primo linguaggio ad alto livello), il ‘C’ (<strong>di</strong>ffusissimo in molti ambiti; la sua capacità <strong>di</strong> interagire ancora<br />
facilmente con l’hardware della macchina gli vale la definizione in effetti <strong>di</strong> linguaggio <strong>di</strong> me<strong>di</strong>o livello; molti sistemi<br />
operativi, anche per l’efficienza dei programmi che i compilatori ‘C’ sono in grado <strong>di</strong> produrre, sono per questo motivo<br />
in gran parte sviluppati con questo linguaggio), Cobol (<strong>di</strong>ffusissimo in ambito gestionale e commerciale), il mitico<br />
Basic (il linguaggio che ha aperto, per la sua semplicità, la porta della programmazione a milioni <strong>di</strong> utenti anche non<br />
esperti <strong>di</strong> informatica), il Pascal (usatissimo negli ambienti scolastici), il Lisp ed il Prolog (usati per sviluppare<br />
programmi <strong>di</strong> intelligenza artificiale), il C++ (il successore del C). Conclu<strong>di</strong>amo con un linguaggio che sta<br />
rapi<strong>di</strong>ssimamente conquistandosi i favori dei programmatori perché molto adatto a sviluppare programmi funzionanti<br />
su Internet: Java (che molto ha in comune con il C++).<br />
Ad ognuno il suo. Ma se questa è solo una scelta dei principali linguaggi, quanti sono in tutto ? Il numero preciso non<br />
è noto, ma è certo che superi qualche centinaio ... Come mai ? Per lo stesso motivo per cui esistono in fondo tanti<br />
linguaggi anche all’interno del nostro: esiste un linguaggio matematico (osereste negarlo, pensando all’algebra, alle<br />
formule matematiche ecc. ?), un linguaggio filosofico, uno giuri<strong>di</strong>co ecc. A seconda dell’ambito applicativo può<br />
convenire usare un linguaggio specializzato. Il Fortran deve il suo nome al termine ‘Formula Translator’, cioè<br />
traduttore <strong>di</strong> formule: il suo <strong>di</strong>zionario è ricco <strong>di</strong> coman<strong>di</strong> per calcoli matematici complessi. Il Cobol l’opposto: è ricco<br />
però <strong>di</strong> coman<strong>di</strong> per gestire efficientemente archivi sui <strong>di</strong>schi, che è proprio ciò che occorre per sviluppare programmi<br />
<strong>di</strong> gestione aziendale. Attenzione: non è che con un linguaggio si possano fare certe cose ma non altre, solo ... più<br />
semplicemente. Provate a spiegare le equazioni senza usare termini matematici ... E’ possibile, ma che fatica ... Per<br />
lo stesso motivo sarebbe anche possibile scrivere in Cobol un programma per il calcolo dell’orbita <strong>di</strong> un satellite, ma<br />
che fatica ... Certo, alcuni linguaggi sono più specializzati <strong>di</strong> altri. Alcuni sono invece ad uso generale: il Basic ed il ‘C’<br />
od il C++ non eccellono in nessun campo in particolare ma sono più che adeguati per ogni compito. E’ il mercato che<br />
decide la fortuna <strong>di</strong> un linguaggio.<br />
Ovviamente anche per i linguaggi a me<strong>di</strong>o/alto livello è necessaria una traduzione in linguaggio macchina prima <strong>di</strong><br />
poterli mandare in esecuzione!<br />
NOTA: tutti i linguaggi <strong>di</strong>versi dal co<strong>di</strong>ce macchina sono chiamati simbolici perché hanno sostituito dei simboli<br />
0.mnemonici alle sequenze <strong>di</strong> 1 e
Esistono due tipi <strong>di</strong> traduttori per linguaggi a me<strong>di</strong>o/alto livello:<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 18<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
1. Interpreti. Il programmatore scrive le istruzioni usando un programma <strong>di</strong> video scrittura (e<strong>di</strong>tor). Quando si<br />
comanda la partenza del programma, l’interprete legge le istruzioni una alla volta, non opera una vera traduzione ma<br />
si limita a riconoscere ciò che le istruzioni vogliono ed a compiere l’azione corrispondente; otteniamo quin<strong>di</strong> subito,<br />
senza attese apprezzabili, il risultato dell'esecuzione <strong>di</strong> quell'istruzione ma non viene prodotto in modo permanente<br />
nessun co<strong>di</strong>ce binario: se questa istruzione deve essere eseguita <strong>di</strong> nuovo, anche per 1000 volte, deve essere<br />
reinterpretata.<br />
2 Compilatori: questi sono dei veri traduttori. Come prima, il programmatore scrive le istruzioni usando un<br />
programma <strong>di</strong> video scrittura(e<strong>di</strong>tor). Ma, ogni volta che mo<strong>di</strong>fica anche un solo carattere del testo del programma<br />
e ne richiede l'esecuzione, il programmatore deve comandare ad uno specifico programma (compilatore) la<br />
traduzione in linguaggio macchina <strong>di</strong> tutte le istruzioni che compongono il programma. Come approfon<strong>di</strong>remo meglio<br />
in seguito, il risultato della compilazione prima <strong>di</strong> poter essere mandato in esecuzione deve essere elaborato anche<br />
da un altro programma (linker) che, per così <strong>di</strong>re, completa l'opera <strong>di</strong> traduzione iniziata dal compilatore.<br />
Interpreti e compilatori a confronto<br />
Interpreti Compilatori<br />
Minori tempi <strong>di</strong> attesa durante lo sviluppo: per vedere il<br />
risultato in esecuzione <strong>di</strong> una mo<strong>di</strong>fica ad un'istruzione o<br />
della giunta <strong>di</strong> una nuova istruzione, si deve attendere<br />
solo l'interpretazione delle istruzioni che portano a quella<br />
che interessa; tutto il resto del programma è come se<br />
venisse ignorato.<br />
Maggiori tempi totali nel fornire il risultato: l'esecuzione<br />
è rallentata dal continuo bisogno <strong>di</strong> interpretare le righe<br />
del programma, anche se sono già state interpretate<br />
molte volte. In certe situazioni il tempo <strong>di</strong> esecuzione <strong>di</strong><br />
un programma interpretato può essere anche 100 volte<br />
superiore a quello del corrispondente programma<br />
compilato …<br />
Dipendenza del programma dall’interprete: l’interprete<br />
deve sempre essere già pronto e <strong>di</strong>sponibile in memoria.<br />
Relativamente semplici da scrivere.<br />
Possibili lunghi tempi <strong>di</strong> attesa durante lo sviluppo:<br />
prima <strong>di</strong> potere e stare un'istruzione qualsiasi <strong>di</strong> un<br />
programma, è necessario che tutto il programma venga<br />
tradotto in linguaggio macchina; se le istruzioni sono<br />
poche (qualche centinaio) le potenze elaborative e la<br />
velocità degli o<strong>di</strong>erni hard <strong>di</strong>sk (che a volte non vengono<br />
ad<strong>di</strong>rittura utilizzati perché sono sufficienti le ormai assai<br />
capienti e molto più veloci RAM) la velocità della<br />
compilazione è tale da sembrare che si stia utilizzando<br />
un interprete! Se il progetto consta invece <strong>di</strong> migliaia,<br />
centinaia <strong>di</strong> migliaia, <strong>di</strong> milioni <strong>di</strong> istruzioni (30 milioni<br />
per Windows 98!) prima <strong>di</strong> veder partire il programma in<br />
seguito ad una mo<strong>di</strong>fica potrebbero rendersi necessari<br />
tempi <strong>di</strong> attesa anche <strong>di</strong> parecchi minuti...<br />
Esecuzione veloce: tempo per la compilazione a parte, il<br />
co<strong>di</strong>ce binario prodotto dalla compilazione+link può<br />
essere eseguito a grande velocità senza bisogno <strong>di</strong><br />
ulteriori traduzioni.<br />
In<strong>di</strong>pendenza del programma dal compilatore: una volta<br />
tradotto in binario il programma, il compilatore non<br />
serve più.<br />
Più complessi da realizzare rispetto agli interpreti.<br />
Volendo sintetizzare vantaggi e svantaggi, <strong>di</strong>ciamo che con un interprete risulta velocizzata la fase <strong>di</strong> sviluppo: non ci<br />
sono i tempi <strong>di</strong> compilazione e <strong>di</strong> link che costringono a ritradurre l’intero programma anche se si sbaglia,<br />
letteralmente, una virgola. Un compilatore produce un programma autonomo che garantisce il massimo della velocità<br />
in esecuzione (anche decine <strong>di</strong> volte più veloce): mentre un interprete è perfetto per lo sviluppo del programma, il<br />
compilatore serve a generare prodotto finale. Per la verità, queste considerazioni erano molto più vere qualche anno<br />
fa: i computer erano molto più lenti nel far funzionare i compilatori (CPU meno potenti e <strong>di</strong>schi molto più lenti): era<br />
allora molto conveniente usare un interprete nelle fasi iniziali (in cui si commettono molti errori) ed il compilatore per<br />
i ritocchi finali. Oggi i tempi <strong>di</strong> compilazione e link sono rapi<strong>di</strong>ssimi e l’interprete come strumento è molto meno<br />
appetibile.<br />
Sviluppare software, oggi sempre più complicato, è molto costoso. Essere produttivi è in<strong>di</strong>spensabile per avere<br />
successo come sviluppatori <strong>di</strong> software. Gli o<strong>di</strong>erni compilatori e linker sono integrati in ambienti <strong>di</strong> sviluppo<br />
sofisticatissimi, dove alcune parti standard <strong>di</strong> un programma sono scritte automaticamente ! Ad esempio, mentre un<br />
tempo, per gestire una finestra come quelle <strong>di</strong> Windows, era necessario scrivere decine <strong>di</strong> istruzioni, oggi il<br />
programmatore <strong>di</strong>segna la finestra con il mouse (come fareste in Paintbrush per un rettangolo) e le istruzioni<br />
corrispondenti sono generate in automatico. Lo stesso per molte finestre <strong>di</strong> <strong>di</strong>alogo, i bottoni, i menu, le caselle con
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 19<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
gli elenchi, le schede <strong>di</strong> inserimento dati, i pannelli <strong>di</strong> ricerca <strong>di</strong> un file per l’apertura o la registrazione <strong>di</strong> un archivio<br />
ecc. E’ sempre <strong>di</strong>sponibile anche un potente strumento per la ricerca degli errori (debugger). Questi sono solo alcuni<br />
dei moltissimi bonus che un moderno ambiente per lo sviluppo dei programmi è in grado <strong>di</strong> offire ...<br />
La catena della programmazione<br />
Il programmatore inizia con lo scrivere le istruzioni (nel linguaggio <strong>di</strong> programmazione scelto) con un programma<br />
chiamato e<strong>di</strong>tor. Questo termine inglese significa redattore cioè colui che cura la scrittura <strong>di</strong> un testo, ed ha quin<strong>di</strong><br />
un significato abbastanza generico. Molto semplicemente, pensate all'e<strong>di</strong>tor come ad un programma <strong>di</strong> videoscrittura<br />
specializzato per la stesura delle istruzioni. Il file scritto con l'e<strong>di</strong>tor prende il nome <strong>di</strong> co<strong>di</strong>ce sorgente. (source<br />
code). I primi e<strong>di</strong>tor erano molto semplici e permettevano solo <strong>di</strong> scrivere il co<strong>di</strong>ce (il testo corrispondente alle<br />
istruzioni) e <strong>di</strong> salvarlo su <strong>di</strong>sco: spettava poi al programmatore mandare in esecuzione, manualmente, il compilatore<br />
ed il linker.<br />
Oggi gli e<strong>di</strong>tor sono <strong>di</strong>ventati ‘ intelligenti’:<br />
• colorano, o evidenziano automaticamente in altro modo (usando il grassetto, il corsivo, la sottolineatura ecc) le<br />
<strong>di</strong>verse parti <strong>di</strong> un'istruzione per facilitare la lettura (si parla <strong>di</strong> sintax highlightining, cioè evidenziazione della<br />
sintassi; e così anche decisamente più <strong>di</strong>fficile commettere certi errori (tipo <strong>di</strong>menticarsi <strong>di</strong> chiudere delle<br />
parentesi aperte);<br />
• sono in grado <strong>di</strong> completare automaticamente la scrittura delle istruzioni (code completion): il programmatore<br />
inizia a scrivere un comando e l'e<strong>di</strong>tor può proporre un elenco <strong>di</strong> tutti i coman<strong>di</strong> che iniziano con le stesse<br />
lettere; a questo punto programmatore può inserire rapidamente il comando che voleva scrivere senza <strong>di</strong>gitare<br />
il resto dei caratteri; può anche usare delle abbreviazioni <strong>di</strong> pochissime lettere per far inserire un'istruzione<br />
composta anche da molte righe che poi verrà completata;<br />
un'altra caratteristica utilissima è rappresentata dai cosiddetti tooltip (suggerimenti): mentre si sta completando<br />
la scrittura <strong>di</strong> una istruzione complessa sopra il punto <strong>di</strong> inserimento dei caratteri appare un piccolo riquadro<br />
che contiene informazioni sull'istruzione stessa; questo meccanismo consente <strong>di</strong> risparmiare tantissimo tempo<br />
che una volta venire impiegato per andare a consultare un manuale;<br />
• sono integrati con un <strong>sistema</strong> <strong>di</strong> aiuto (help in linea) ipertestuale (cioè navigabile facendo clic sui collegamenti<br />
che portano ad approfon<strong>di</strong>menti, argomenti correlati, esempi); a volte l'help è ad<strong>di</strong>rittura iperme<strong>di</strong>ale (si<br />
possono richiamare filmati, animazioni, commenti vocali ecc.).<br />
• permettono, ad<strong>di</strong>rittura, <strong>di</strong> ‘<strong>di</strong>segnare’ parte del programma invece <strong>di</strong> scriverlo: usando il mouse il<br />
programmatore <strong>di</strong>spone componenti preconfezionati su quella che <strong>di</strong>venterà la finestra del programma in<br />
esecuzione: caselle in cui inserire del testo, bottoni su cui fare clic, menù, tavolozze che appariranno quando si<br />
vorrà far scegliere un colore all'utente, finestre <strong>di</strong> <strong>di</strong>alogo che permetteranno <strong>di</strong> cercare un file sul <strong>di</strong>sco quando<br />
l'utente comanderà il caricamento <strong>di</strong> un documento o il suo salvataggio eccetera; per ogni componente scelto<br />
l'e<strong>di</strong>tor inserirà automaticamente il co<strong>di</strong>ce corrispondente! Quando un e<strong>di</strong>tor è in grado <strong>di</strong> funzionare in questo<br />
modo è detto visuale.<br />
• sono in grado <strong>di</strong> richiamare automaticamente il compilatore ed il linker e se uno <strong>di</strong> questi due programmi trova<br />
degli errori mostrare a video i relativi messaggi spostando il punto <strong>di</strong> inserimento del testo sulla riga del primo<br />
errore; se invece il programma non contiene errori può anche essere mandato automaticamente in esecuzione<br />
per provarlo: al termine dell'esecuzione si verrà riportati nell'ambiente <strong>di</strong> lavoro dell'e<strong>di</strong>tto. In questo modo il<br />
programmatore è in grado <strong>di</strong> controllare tutti i passaggi senza mai abbandonare le e<strong>di</strong>tor. È per queste<br />
caratteristiche che oggi, più che <strong>di</strong> semplici e<strong>di</strong>tor, si preferisce parlare <strong>di</strong> IDE, Integrated Development<br />
Envinronment (ambienti integrati <strong>di</strong> sviluppo).<br />
I file dei co<strong>di</strong>ci sorgenti vengono registrati sul <strong>di</strong>sco con un'estensione (la parte del nome del file dopo il punto) che<br />
aiuta a riconoscere il linguaggio <strong>di</strong> programmazione utilizzato. Ecco le principali estensioni: .pas (Pascal), .c (C), .cpp<br />
(c ++), .asm (assembly), .asp (ASP), .bas (Basic).
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 20<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Il compilatore trasforma il co<strong>di</strong>ce sorgente nel cosiddetto co<strong>di</strong>ce oggetto (object code); ma questo processo <strong>di</strong><br />
traduzione potrebbe interrompersi per vari motivi:<br />
• viene scoperto un simbolo che non appartiene al cosiddetto <strong>di</strong>zionario delle parole chiave (keyword) proprie<br />
del linguaggio e neppure all’insieme dei simboli aggiunti dal programmatore: ad esempio se il linguaggio<br />
prevede un comando write per scrivere qualche cosa sullo schermo, il programmatore potrebbe sbagliare e<br />
scrivere wrote; questa parola viene non verrebbe riconosciuta come appartenente al linguaggio; viene allora<br />
cercata nell’insieme degli simboli (si parla più correttamente <strong>di</strong> identificatori) introdotti dal programmatore;<br />
quest’ultimo potrebbe aver <strong>di</strong>chiarato <strong>di</strong> voler usare un simbolo chiamato area per memorizzare un valore con<br />
la virgola in cui far calcolare la superficie del cerchio:<br />
area := raggio * raggio * 3.14; dai al simbolo area il valore della formula in<strong>di</strong>cata (* significa moltiplicato)<br />
naturalmente raggio è un altro simbolo introdotto dal programmatore per memorizzare il dato corrispondente<br />
alla misura del raggio …<br />
se un identificatore non viene trovato neppure nell’insieme definito dal programmatore, allora il compilatore<br />
decide che ha trovato un errore lessicale; questa fase viene infatti chiamata analisi lessicale e quella parte<br />
del compilatore che la svolge analizzatore lessicale.<br />
• Errori sintattici ed analizzatore sintattico. Anche se viene superata la fase <strong>di</strong> analisi lessicale ci<br />
potrebbero essere ancora errori <strong>di</strong> tipo sintattico, cioè che violano una regola sintattica <strong>di</strong> quel linguaggio; le<br />
regole sintattiche servono a costruire frasi corrette, al <strong>di</strong> là del fatto <strong>di</strong> aver usato singoli termini riconosciuti.<br />
Nulla <strong>di</strong> sorprendente: anche con la lingua italiana dovete rispettare delle regole (soggetto, verbo,<br />
complemento oggetto ecc. ricordate?), solo che quelle <strong>di</strong> un linguaggio <strong>di</strong> programmazione sono decisamente<br />
più semplici! Ed anche in italiano, pur usando tutte parole presenti sul <strong>di</strong>zionario, è facilissimo scrivere frasi<br />
senza senso!<br />
Facciamo un esempio: molti linguaggi hanno la regola che pretende che ogni istruzione sia terminata con un<br />
punto e virgola:<br />
write(‘Questa istruzione contiene un errore!’)<br />
Mentre dal punto <strong>di</strong> vista dell’analizzatore lessicale non ci sono errori, quello sintattico si accorge della<br />
mancanza della virgola alla fine dell’istruzione.<br />
Controllo lessicale e sintattico non esauriscono il compito <strong>di</strong> un compilatore: esiste anche un controllo chiamato<br />
semantico che non è però il caso <strong>di</strong> approfon<strong>di</strong>re in questa sede.<br />
Nota: i co<strong>di</strong>ce oggetto hanno <strong>di</strong> solito come estensione .obj<br />
Il co<strong>di</strong>ce oggetto, risultato della compilazione, non è ancora in una forma del tutto eseguibile dalla CPU: alcune parti<br />
del co<strong>di</strong>ce sorgente non possono essere tradotte dal compilatore perché rappresentano dei riferimenti ( link) a dei<br />
coman<strong>di</strong> esterni che non fanno parte <strong>di</strong>rettamente del linguaggio <strong>di</strong> programmazione in uso ma sono resi <strong>di</strong>sponibili<br />
in raccolte chiamate librerie. (library).<br />
Queste ultime sono un preziosissimo strumento per la programmazione: una volta che un problema è stato risolto il<br />
co<strong>di</strong>ce corrispondente (già tradotto in linguaggio macchina) può essere depositato in una raccolta (la libreria) dalla<br />
quale potrà essere estratto per essere incorporato in altri programmi che ne hanno bisogno.<br />
Ad esempio: immaginiamo <strong>di</strong> avere scritto un comando che <strong>di</strong>segna sullo schermo una circonferenza note le sue<br />
coor<strong>di</strong>nate sullo schermo del centro e la misura del raggio; il co<strong>di</strong>ce sorgente viene compilato e tradotto in co<strong>di</strong>ce<br />
oggetto (da notare che anche in questo caso ci potrebbero essere della parti ancora non tradotte…) ed aggiunto con<br />
un programma apposito ad una libreria (immaginiamo che si chiami grafica.lib, dove .lib è naturalmente l’estensione<br />
con cui sono <strong>di</strong> solito in<strong>di</strong>viduate su <strong>di</strong>sco le librerie). Deci<strong>di</strong>amo <strong>di</strong> chiamare crf (abbreviazione <strong>di</strong> circonferenza) il<br />
comando in questione; le coor<strong>di</strong>nate del centro e la misura del raggio della circonferenza dovranno essere in<strong>di</strong>cate<br />
(come in quasi tutti i linguaggi <strong>di</strong> programmazione) tra parentesi tonde al momento dell’uso del comando stesso,
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 21<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Immaginiamo ora <strong>di</strong> scrivere un programma <strong>di</strong> geometria e <strong>di</strong> avere la necessità <strong>di</strong> <strong>di</strong>segnare alcune circonferenze.<br />
Ecco come richiameremmo il comando:<br />
programma geometria;<br />
USES grafica;<br />
…<br />
crf( 12 , 61 , 30);<br />
…<br />
crf( 7, 49 , 22);<br />
Il programmatore utilizza in questo esempio due volte il comando per <strong>di</strong>segnare<br />
una circonferenza; il centro della prima ha come coor<strong>di</strong>nate 12, 61 ed un raggio<br />
pari a 30; similmente per la seconda.<br />
E’ molto importante che il programmatore specifichi dove è possibile trovare il<br />
co<strong>di</strong>ce oggetto che corrisponde al comando crf! Questa informazione viene<br />
fornita con ‘USES grafica’.<br />
Il compilatore non è in grado <strong>di</strong> tradurre il comando crf (non appartiene a quelli standard del pascal) ed in mancanza<br />
<strong>di</strong> altre in<strong>di</strong>cazioni dovrebbe interrompersi ed emettere un messaggio <strong>di</strong> errore (tipo ‘simbolo non trovato’ in<br />
corrispondenza delle righe in cui si richiama il comando crf).<br />
Trovando però l’in<strong>di</strong>cazione <strong>di</strong> utilizzo della libreria grafica, lascia nel co<strong>di</strong>ce oggetto un cosiddetto collegamento (link)<br />
non risolto, cioè una specie <strong>di</strong> segnalibro per ricordare che in quel punto c’è da completare la traduzione.<br />
E’ compito del programma linker analizzare tutti i collegamenti irrisolti dei programmi oggetto che gli vengono<br />
sottoposti al fine <strong>di</strong> produrre un unico eseguibile (estensione .exe) e cercare nelle librerie in<strong>di</strong>cate il co<strong>di</strong>ce mancante;<br />
quest'ultimo verrà estratto in copia ed incorporato nei punti richiesti. Ecco uno schema che esemplifica il<br />
meccanismo:<br />
Co<strong>di</strong>ce Sorgente<br />
(Geometria.pas)<br />
Program geometria;<br />
USES grafica;<br />
…<br />
crf( 12 , 61 , 30);<br />
…<br />
crf( 7, 49 , 22);<br />
Geometria.obj<br />
compilatore Co<strong>di</strong>ce<br />
oggetto<br />
Il co<strong>di</strong>ce della crf<br />
viene estratto dalla<br />
libreria ed<br />
incorporato nel<br />
programma finale.<br />
La libreria <strong>di</strong><br />
solito contiene il<br />
co<strong>di</strong>ce <strong>di</strong> molti<br />
coman<strong>di</strong><br />
crf<br />
quadrato<br />
linker<br />
Co<strong>di</strong>ce binario<br />
+ del comando crf =<br />
Programma eseguibile finale<br />
traduzione in linguaggio macchina<br />
delle istruzioni scritte dal<br />
programmatore.<br />
Abbiamo ora tutte le nozioni necessarie per esaminare in dettaglio il proce<strong>di</strong>mento <strong>di</strong> scrittura <strong>di</strong> un programma.<br />
….<br />
….<br />
….<br />
….<br />
….<br />
Ecc.<br />
LIBRERIA GRAFICA.LIB<br />
Geometria.exe<br />
+<br />
co<strong>di</strong>ce della crf prelevato dalla<br />
libreria
Struttura <strong>di</strong> un programma Pascal<br />
Ogni programma Pascal è costituito da tre sezioni:<br />
Intestazione<br />
Program ;<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 22<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
E’ una riga che inizia con la parola riservata (keyword) program seguita dal nome che si vuol dare al programma.<br />
Le parole riservate sono alcune decine ed il programmatore non può usare identificatori con lo stesso nome <strong>di</strong> una <strong>di</strong><br />
esse. Ad esempio, non possiamo chiamare un programma proprio program. Il nome da dare al programma è invece<br />
un esempio <strong>di</strong> identificatore, un nome scelto dal programmatore.<br />
Ci sono alcune regole da rispettare quando scegliamo un identificatore:<br />
• il suo primo carattere non può essere una cifra numerica (il compilatore penserebbe che in quel punto sta<br />
iniziando un numero); ad esempio 1arrivato non va bene; arrivato1 invece sì;<br />
• non può contenere spazi; totale iva non va bene; totaleIva o TotaleIva o totale_iva invece sì; notate in<br />
particolare l'uso del carattere <strong>di</strong> sottolineatura _ che consente <strong>di</strong> ‘staccare’ le parole come se avessimo inserito<br />
uno spazio;<br />
• non può contenere caratteri strani: ad esempio !#?&%$” eccetera; come regola pratica potreste ricordare che<br />
possono essere usate solo lettere (maiuscole e minuscole), cifre numeriche ed il carattere <strong>di</strong> sottolineatura;<br />
alcuni linguaggi fanno <strong>di</strong>fferenza tra minuscole e maiuscole (e sono detti case sensitive) per cui gli identificatori<br />
totale e Totale sono considerati <strong>di</strong>versi; altri linguaggi, tra cui Pascal, non <strong>di</strong>stinguono invece minuscole<br />
maiuscole (e sono detti case insensitive);<br />
• hanno una lunghezza massima, <strong>di</strong> solito piuttosto ampia (quasi sempre almeno 32 caratteri); attenzione<br />
perché è superata la lunghezza massima non è detto che il compilatore segnali errore ma potrebbe<br />
semplicemente considerare uguali due identificatori che iniziano con la stessa sequenza <strong>di</strong> caratteri pari a<br />
lunghezza massima; se questa fosse otto, ad esempio, i due identificatori costoTotale e costoTot potrebbero<br />
essere considerati lo stesso identificatore, causando situazioni d'errore <strong>di</strong>fficili da scoprire;<br />
Dopo il nome del programma deve essere messo un punto e virgola. Il punto e virgola è molto usato in Pascal (ed in<br />
genere da molti linguaggi <strong>di</strong> programmazione): serve a separare due istruzioni; questo significa che se dopo<br />
un'istruzione dev'essere scritta una parola riservata che <strong>di</strong> per sé non viene considerata un’ istruzione il punto e<br />
virgola può anche essere omesso. In ogni caso, almeno all'inizio, potreste ricordare come regola semplificata quella<br />
<strong>di</strong> mettere un punto e virgola alla fine <strong>di</strong> un'istruzione. Ecco allora qualche esempio <strong>di</strong> intestazione corretta:<br />
program prova; program primoProgramma; program primo_programma;<br />
program capitolo1;
Sezione <strong>di</strong>chiarativa<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 23<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Qui il programmatore assegna un nome (identificatore) ai dati che devono essere memorizzati durante<br />
l'<strong>elaborazione</strong>.<br />
Costanti<br />
Se un identificatore viene descritto nella sezione introdotta dalla parola riservata const (abbreviazione <strong>di</strong> constant,<br />
costante) deve anche essere in<strong>di</strong>cato dopo il simbolo dell'uguaglianza (=) il suo valore che non potrà mai variare<br />
durante l'esecuzione del programma. Ad esempio il valore del pi greco può essere introdotto in un programma in<br />
questo modo:<br />
Program geometria;<br />
const<br />
PI_GRECO = 3.14159265;<br />
In un programma possono essere introdotte tutte le costanti che si vogliono. Il tipo del dato (un numero reale, real in<br />
Pascal) per una costante viene dedotto dal valore posto dopo il simbolo dell'uguaglianza. Ecco alcuni altri esempi <strong>di</strong><br />
costanti:<br />
GIORNI_SETTIMANA = 7; esempio <strong>di</strong> costante <strong>di</strong> tipo intero (integer in Pascal)<br />
RISPOSTA_AFFERMATIVA = ‘S’; esempio <strong>di</strong> costante <strong>di</strong> tipo carattere; gli apici sono obbligatori!<br />
MESSAGGIO_ERRORE = ‘Sbagliato !!’ ; esempio <strong>di</strong> costante <strong>di</strong> tipo stringa (sequenza <strong>di</strong> caratteri racchiusi tra apici,<br />
string in Pascal)<br />
Vantaggi dell'uso delle costanti:<br />
• aumentano la leggibilità <strong>di</strong> un programma: è più facile riconoscere nomi simbolici che numeri complessi;<br />
• aumentano la flessibilità <strong>di</strong> un programma: se vi è la necessità <strong>di</strong> cambiare il valore associato ad una costante<br />
sarà sufficiente farlo solamente nella sezione const ed automaticamente il nuovo valore verrà utilizzato in ogni<br />
punto del programma in cui è stata usata la costante:<br />
• migliorano la sicurezza: il programmatore non potrà neppure per sbaglio cambiare il valore della costante<br />
perché il compilatore glielo impe<strong>di</strong>rà emettendo un messaggio <strong>di</strong> errore<br />
NOTA: la sezione delle costanti può anche non essere presente.<br />
Variabili<br />
Quando invece il valore <strong>di</strong> un dato può cambiare durante l'esecuzione deve essere memorizzato in una cosiddetta<br />
variabile. Le variabili devono essere <strong>di</strong>chiarate in una sezione, in<strong>di</strong>viduata dalla parola chiave var, posta dopo la<br />
sezione const:<br />
Program geometria;<br />
const<br />
PI_GRECO = 3.14159265;<br />
var<br />
raggio : integer;<br />
Nel resto del programma invece <strong>di</strong> essere costretti a ricordare questa scomoda<br />
sequenza <strong>di</strong> cifre decimali sarà possibile utilizzare la costante PI_GRECO.<br />
Nota: è abitu<strong>di</strong>ne dei programmatori usare le maiuscole per gli identificatori che<br />
rappresentano una costante.<br />
In Pascal le variabili non possono ricevere un valore <strong>di</strong> partenza <strong>di</strong>rettamente nella sezione <strong>di</strong>chiarativa ma dovrà<br />
essere usato un apposito comando (assegnamento) nella sezione esecutiva. Come certamente immaginate questo<br />
stesso comando non può essere invece utilizzato con le costanti. Dopo l’identificatore del nome <strong>di</strong> una variabile deve<br />
essere messo il simbolo ‘:’ (due punti) e poi l'identificatore <strong>di</strong> un tipo, ed infine il punto e virgola.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 24<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Più variabili dello stesso tipo possono essere <strong>di</strong>chiarate insieme separando gli identificatori con una virgola:<br />
var<br />
base, altezza: integer;<br />
Il tipo in<strong>di</strong>vidua l’insieme dei possibili valori che possono essere assunti da una costante o da una variabile (si parla<br />
anche <strong>di</strong> dominio). Ogni tipo ha infatti i suoi limiti . Ecco la tabella che riassume la situazione per i principali tipi del<br />
Pascal:<br />
INTERI<br />
Tipo da a<br />
Byte 0 255<br />
Shortint -128 127<br />
Integer -32768 32767<br />
Word 0 65535<br />
Longint -2147483648 2147483647<br />
REALI<br />
Tipo da a<br />
Real 2.9x10 -38 1.7x10 38<br />
Single 1.5x10 -45 3.4x10 38<br />
Double 5.0x10 -324 1.7x10 308<br />
extended 1.9x10 -4951 1.1x10 4932<br />
Esempi <strong>di</strong> numeri reali: 3.14 4.761E-3;<br />
Il secondo numero è espresso in forma esponenziale: la lettera E seguita da un numero<br />
equivale a moltiplicare (o <strong>di</strong>videre se il numero dopo la E è negativo) per quella potenza del <strong>di</strong>eci.<br />
Quin<strong>di</strong> 4.761E-3 = 4.76 x 10 -3 = 4.76 / 1000 = 0.00476; 2.45E+4 = 2.45 x 10 4 = 24500<br />
N OTA: ci si riferisce a questo tipo <strong>di</strong> dato anche con la <strong>di</strong>citura floating point, a virgola mobile.<br />
TIPO CARATTERE (char). Il tipo char si usa per il singolo carattere (lettera alfabetica, cifra numerica da 0 a 9 o<br />
qualsiasi altro simbolo rappresentabile con il P.C. come la punteggiatura, ma anche le lettere dell’alfabeto greco,<br />
alcuni caratteri semigrafici tipo cuoricini o faccine, lettere straniere come âåë o simboli matematici). L’elenco<br />
completo forma quella che viene chiamata la tabella ASCII (American Standard Code form Information<br />
Interchange) che esiste in forma orginale (128 simboli) o estesa (256 simboli). I caratteri vanno racchiusi tra due<br />
apici semplici come in ‘a’, ‘A’, ‘]’,’4’.<br />
Attenzione a non confondere una cifra numerica con il carattere corrispondente: 1 è <strong>di</strong>verso da ‘1’ ! Anche se in un<br />
documento <strong>di</strong> testo i numeri vengono memorizzati con la sequenza dei co<strong>di</strong>ci ascii corrispondenti, questo formato<br />
risulta molto inefficiente per svolgere calcoli numerici. Numeri interi e reali vengono rappresentati in modo assai<br />
<strong>di</strong>verso ma questi argomenti verranno affrontati in parallelo nel corso <strong>di</strong> sistemi.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 25<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
TIPO BOOLEANO (boolean). I dati boolean possono assumere solo due valori: vero (true) e falso (false).<br />
TIPO STRINGA (string) Un dato string è un insieme <strong>di</strong> caratteri racchiusi tra apici. La lunghezza massima è 255<br />
caratteri.<br />
Esempi <strong>di</strong> string: ‘Marco’, ‘12/03/1998’;<br />
Questi appena visti sono dati semplici o non strutturati: non sono ulteriormente scomponibili. Più avanti nella<br />
<strong>di</strong>spensa verrà introdotta la possibilità <strong>di</strong> usare dati complessi o strutturati. Vedremo anche come sia possibile per il<br />
programmatore aggiungere nuovi dati (user defined type, tipi definiti dall’utente) basandosi su quelli nativi del pascal<br />
(standard type, tipi standard).<br />
NOTA: la sezione delle costanti può anche non essere presente.<br />
Procedure e Funzioni<br />
Dopo la sezione delle variabili può essere presente quella dei cosiddetti sottoprogrammi. Si rimanda la <strong>di</strong>scussione <strong>di</strong><br />
questo argomento ad un momento più appropriato, molto più avanti nella <strong>di</strong>spensa… Per il momento possiamo<br />
comportarci come se questa sezione non esistesse.<br />
Sezione esecutiva (corpo principale del programma)<br />
E’ la parte veramente operativa del programma! Corrisponde in pratica alla traduzione dell’algoritmo. La parte<br />
<strong>di</strong>chiarativa è infatti solo preparatoria e serve al compilatore per essere più efficiente (ad esempio se conosce in<br />
anticipo i tipi delle variabili può organizzarle più efficientemente nella RAM) e controllare il resto del programma<br />
per alcuni tipi <strong>di</strong> errore (ad esempio tentare <strong>di</strong> mo<strong>di</strong>ficare una costante: come potrebbe se non avessimo <strong>di</strong>chiarato in<br />
anticipo quali identificatori considerare associati a valori immutabili?).<br />
Questa sezione inizia con la parola riservata begin e termina con la parola riservata end seguita da un punto:<br />
program Esempio; Intestazione<br />
const<br />
…<br />
Sezione <strong>di</strong>chiarativa<br />
var<br />
…<br />
begin<br />
<br />
Sezione esecutiva<br />
end;<br />
TIPI DI ISTRUZIONE CHE SI POSSONO METTERE NELLA SEZIONE ESECUTIVA<br />
assegnamento: memorizza in una variabile una costante oppure il valore <strong>di</strong> un'altra variabile oppure il risultato <strong>di</strong><br />
un calcolo (espressione); in Pascal il simbolo dell'assegnamento è questo: :=<br />
X := 8; costante X := Y; variabile X := Y*2 +4; espressione<br />
program EsempiDiAssegnamento;<br />
var<br />
n1, n2: integer; x: real; s: string;<br />
begin<br />
n1 := 100; n1 := n2; n1 := 3*(34 –<br />
n2);<br />
x := 2; x := 3.14; x := n1; s := ‘ciao<br />
come stai?’;<br />
end.<br />
program AssegnamentiCompatibili;<br />
var<br />
c: char; x: real; s: string;<br />
begin<br />
x := 3 + 7 (* equivale a 10.0 *);<br />
c := ‘A’;<br />
s := c; (* un carattere ‘sta’ in una<br />
Il valore che viene assegnato ad una variabile deve essere <strong>di</strong> tipo compatibile.. Questo<br />
non significa per forza identico ma che (almeno) il risultato dell'espressione alla destra<br />
del simbolo <strong>di</strong> assegnamento sia convertibile nel tipo della variabile in<strong>di</strong>cata alla<br />
sinistra.<br />
Come regola pratica ricordate questa: un tipo più grande può accoglierne uno più<br />
‘piccolo’ ma non il viceversa.<br />
Ad esempio, se X è una variabile reale: x := 7 corrisponde a x := 7.0 e x:=3+7<br />
corrisponde a x := 10.0 (gli interi 7 e l'intero risultato dell'espressione 3+7 possono<br />
essere convertiti nel corrispondente numero reale senza problemi). Ma se x fosse una<br />
variabile intera, l'assegnamento x := 1.5, ammesso che fosse accettato, dovrebbe<br />
trasformare il numero reale 1.5 o nel numero intero uno o nel numero intero due con<br />
una per<strong>di</strong>ta <strong>di</strong> precisione inaccettabile e tale da richiedere un comando esplicito ( x :=<br />
trunc(1.5) ad esempio tronca il numero reale restituendo un intero.
stringa *)<br />
end.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 26<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 27<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
E’ anche possibile calcolare espressioni facenti uso <strong>di</strong> parentesi. Un computer non ha però necessità <strong>di</strong> facilitarsi la<br />
lettura <strong>di</strong> un'espressione utilizzando <strong>di</strong>versi tipi <strong>di</strong> parentesi: quadre e graffe non devono essere usate! Bisogna usare<br />
invece più livelli <strong>di</strong> parentesi tonde:<br />
Come siamo abituati noi Come dev'essere scritto con un linguaggio <strong>di</strong> programmazione<br />
(5 + 4) (5 + 4)<br />
(4 -2) * (23 +9) (4 -2) * (23 +9)<br />
[ (4 + 6) / 3] * (23 + 7) ( (4 + 6) / 3 ) * (23 + 7)<br />
{ [ (4 + 6) / 3] * (23 + 7) } / (67 – 9) ( ( (4 + 6) / 3) * (23 + 7) ) / (67 – 9)<br />
Principali operatori predefiniti ed operatori relazionali utilizzabili per i principali tipi <strong>di</strong> dato<br />
CHAR<br />
Ord( carattere ) Co<strong>di</strong>ce ascii corrispondente: ord(‘A’) 65; ord(‘a’) 97; ord(‘1’) 49<br />
Chr( co<strong>di</strong>ce ascii) Carattere corrispondente: chr(65) ‘A’; chr(97) ‘a’<br />
Upcase( carattere) Trasforma il carattere in maiuscolo: upcase(‘a’) ‘A’; upcase(‘A’) ‘A’<br />
Confronti (pre<strong>di</strong>cati): =, < (minore), > (maggiore), (<strong>di</strong>verso), = (maggiore od uguale).<br />
NOTA: sono tutti da intendersi in senso alfabetico.<br />
INTERI<br />
+ - * Somma, sottrazione, moltiplicazione<br />
a DIV b Quoziente intero tra a e b: 12 <strong>di</strong>v 4 3; 14 <strong>di</strong>v 4 3;<br />
a MOD b Resto intero della <strong>di</strong>visione tra a e b<br />
Succ( numero ) Successore del numero in<strong>di</strong>cato: succ(4) 5<br />
Pred( numero ) Predecessore del numero in<strong>di</strong>cato: pred(5) 4<br />
Dec( numero ) Toglie uno a numero: dec(7) 6<br />
Dec(numero, quantita) Toglie quantità a numero: dec(12,5) 7<br />
Inc( numero ) Aggiunge uno a numero: inc(7) 8<br />
Inc(numero, quantita) Aggiunge quantità a numero: inc(12,5) 17<br />
Abs( numero ) Valore assoluto (o modulo) <strong>di</strong> numero<br />
Sqr(x) Quadrato<br />
-x Opposto <strong>di</strong> x<br />
Possibili errori: overflow e <strong>di</strong>visione per zero (<strong>di</strong>vision by zero).<br />
Confronti (pre<strong>di</strong>cati): =, < (minore), > (maggiore), (<strong>di</strong>verso), = (maggiore od uguale).
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 28<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
REALI - ricordare che sono rappresentati nella forma x = mantissa * 10 caratteristica come in 5.28 * 10 6<br />
+ - * / Somma, sottrazione, moltiplicazione, <strong>di</strong>visione<br />
Sqrt (x) Ra<strong>di</strong>ce quadrata<br />
Sqr(x) Quadrato<br />
Sin(x), cos(x), tan(x), Seno, coseno, tangente, arcotangente <strong>di</strong> x<br />
arctan(x)<br />
Abs( x ) Valore assoluto (o modulo) <strong>di</strong> x<br />
-x Opposto <strong>di</strong> x<br />
Trunc(x) Tronca la parte frazionare <strong>di</strong> x<br />
Round(x) Arrotondamento <strong>di</strong> x<br />
Possibili errori: overflow (caratteristica troppo grande per essere rappresentata), underflow (caratteristica troppo<br />
piccola per essere rappresentata), <strong>di</strong>visione per zero, argomento illegale (illegal argument, come in sqrt(-10)).<br />
Problemi <strong>di</strong> precisione: è limitata dal numero <strong>di</strong> bit de<strong>di</strong>cati alla mantissa<br />
Problemi <strong>di</strong> approssimazione: sono dovuti alla conversione tra decimale e binario; ad esempio 0.1 (in decimale) può<br />
essere rappresentati in binario solo come numero perio<strong>di</strong>co (0.0001 0001 0001…) che una volta convertito in<br />
decimale può dare origine a nome approssimati come 0.0999999 o 9.99999E-2<br />
Confronti (pre<strong>di</strong>cati): =, < (minore), > (maggiore), (<strong>di</strong>verso), = (maggiore od uguale).<br />
STRINGHE<br />
+ Concatenazione: ‘ciao ‘ + ‘a tutti’ ‘ciao a tutti’<br />
Copy(s, inizio,<br />
quanti)<br />
Restituisce della stringa s la sotto stringa che inizia alla posizione inizio e continua per quanti caratteri<br />
Confronti (pre<strong>di</strong>cati): =, < (minore), > (maggiore), (<strong>di</strong>verso), = (maggiore od uguale).<br />
NOTA: sono tutti da intendersi in senso alfabetico.<br />
ALTRI COMANDI UTILI<br />
Length(s) Restituisce alla lunghezza (numero <strong>di</strong> caratteri) della stringa s<br />
Pos(s1, s2) Restituisce la posizione della stringa s1 all'interno della stringa s2; se s1 non viene trovata restituisce zero<br />
Val(s, x, errore) Se possibile trasforma la stringa s nel numero corrispondente e memorizza il risultato nella variabile X; s<br />
la conversione riesce nella variabile intera errore e ha messo il valore zero; se la conversione non riesce i<br />
errore viene emesso un valore <strong>di</strong>verso da zero<br />
Str(x, s) Converte il numero x nella stringa corrispondente memorizzandola nella variabile stringa s<br />
Incremento/decremento del valore <strong>di</strong> una variabile.<br />
x := x + 1; x assume il valore attuale incrementato <strong>di</strong> uno (x <strong>di</strong>venta x incrementato <strong>di</strong> uno)<br />
ATTENZIONE: non confondetelo con la scrittura x = y + 1 (test <strong>di</strong> confronto tra il valore contenuto nella variabile x<br />
e quello della variabile y incrementato <strong>di</strong> uno.<br />
x := x + 5.2; x viene incrementata <strong>di</strong> 5.2 (evidentemente il tipo <strong>di</strong> x è real)<br />
x := x -10; x viene incrementata <strong>di</strong> 10
not<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 29<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
PRINCIPALI OPERATORI IN ORDINE DECRESCENTE DI PRECEDENZA<br />
Negazione logica (booleana); esempio: not x=y (espressione vera se il valore della variabile x è<br />
<strong>di</strong>verso da quello <strong>di</strong> y);<br />
Negazione bit a bit (bitwise): not 10010 01101<br />
* Moltiplicazione aritmetica<br />
/ Divisione (tra numeri reali)<br />
<strong>di</strong>v Divisione intera<br />
mod Modulo (il resto <strong>di</strong> una <strong>di</strong>visione intera) 5 mod 2 1 18 mod 5 3<br />
and<br />
And booleano: (x>4) and (x Test maggiore <strong>di</strong><br />
Or booleano: (x12) (espressione vera se x ha un valore esterno all’intervallo (4,12)<br />
Or bitwise: 1001 and 1011 1011<br />
Test <strong>di</strong> non uguaglianza (<strong>di</strong>verso da); x y (espressione vera se il valore della variabile x è <strong>di</strong>verso<br />
da quello <strong>di</strong> y)<br />
= Test maggiore o uguale <strong>di</strong><br />
coman<strong>di</strong> <strong>di</strong> ingresso/uscita: servono per acquisire dati dalla tastiera o per visualizzare risultati numerici, messaggi<br />
<strong>di</strong> testo, caratteri semigrafici, primitive geometriche (rette, circonferenze), suoni, stampare ecc.<br />
uscita (scrittura)<br />
write(espressione1, espressione2, ): mostra sullo schermo la sequenza dei valori delle espressioni in<strong>di</strong>cate tra<br />
parentesi; espressione1/2ecc. può essere praticamente qualsiasi cosa (una costante numerica,una costante stringa,<br />
una variabile <strong>di</strong> tutti i principali tipi, un'espressione che verrebbe prima calcolata ed il cui valore sarebbe quin<strong>di</strong><br />
visualizzato; nota: non va a capo automaticamente; per cui il successivo comando <strong>di</strong> visualizzazione su video<br />
continuerà sulla stessa linea dello schermo (a meno che si sia già arrivati all'ultima posizione, nel qual caso si va<br />
comunque a capo);<br />
writeln(espressione1, espressione2, ): come il comando write ed in più si posiziona automaticamente sulla<br />
prossima linea dello schermo.<br />
Esempi: write(12); write(‘a’); write(‘ciao mondo’); write(‘Gianni ha ‘, eta, ‘ anni’);<br />
writeln(‘In un secolo ci sono ‘, 60*60*24*365*100.0, ‘secon<strong>di</strong>’);<br />
NOTA: se non moltiplicassimo per 100.0 (ci avevate badato?) otterremmo un messaggio <strong>di</strong> errore perché il risultato<br />
supererebbe il più grande numero intero (longint) rappresentabile con il turbo Pascal. In<strong>di</strong>cando invece un numero<br />
reale il calcolo viene effettuato nel dominio dei numeri reali.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 30<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Il formato del risultato usa la notazione esponenziale: 3.1536000000E+09. E’ però possibile in<strong>di</strong>care degli<br />
in<strong>di</strong>catori <strong>di</strong> ampiezza: detto in altre parole possiamo scegliere quante posizioni sul video utilizzare per le cifre<br />
prima e dopo il punto decimale:<br />
writeln(60*60*24*365*100.0:10:0) 3153600000 :10:0 significa 10 posizioni in tutto, <strong>di</strong> cui<br />
0 (nessuna) per le cifre dopo il punto decimale<br />
writeln(60*60*24*365*100.0:13:2) 3153600000.00 :13:2 significa 13 posizioni in tutto, <strong>di</strong> cui 2 per le<br />
cifre dopo il punto decimale (bisogna contare<br />
anche quest'ultimo)<br />
ingresso (lettura)<br />
readln( variabile): il programma si mette in attesa <strong>di</strong> un valore che deve essere <strong>di</strong>gitato usando la tastiera; il<br />
tipo del dati inserito deve corrispondere a quello della variabile; un quadratino od un trattino lampeggiante<br />
in<strong>di</strong>cano lo stato <strong>di</strong> attesa; il dato si intende <strong>di</strong>gitato completamente quando viene premuto il tasto invio (enter<br />
sulle tastiere americane); il valore inserito viene memorizzato nella variabile in<strong>di</strong>cata: da questo momento in<br />
avanti <strong>di</strong>venta <strong>di</strong>sponibile nel programma.<br />
Coman<strong>di</strong> per il controllo del flusso <strong>di</strong> esecuzione<br />
Tutti i coman<strong>di</strong> visti fino ad ora sono del tutto insufficienti per implementare (realizzare sul computer) algoritmi<br />
anche molto semplici. Immaginiamo ad esempio un programma che chiede a chi lo sta usando <strong>di</strong> inserire l'età; il<br />
programma dovrebbe rispondere con un messaggio appropriato a seconda che ci si trovi <strong>di</strong> fronte ad un maggiorenne<br />
piuttosto che a un minorenne.<br />
Non c'è modo scrivendo semplicemente un'istruzione dopo l'altra <strong>di</strong> riuscire in questo intento! Per ora, infatti, siamo<br />
solo in grado <strong>di</strong> in<strong>di</strong>care in sequenza una serie <strong>di</strong> coman<strong>di</strong>. Questo è certamente utile ed in<strong>di</strong>vidua la prima delle<br />
cosiddette strutture fondamentali della programmazione. Ne mancano due: la selezione e l'iterazione. La<br />
selezione corrisponde la possibilità <strong>di</strong> far eseguire un blocco <strong>di</strong> istruzioni piuttosto che un altro a seconda del<br />
risultato <strong>di</strong> un confronto. L'iterazione corrisponde invece la possibilità <strong>di</strong> far ripetere un blocco <strong>di</strong> istruzioni fino al<br />
verificarsi <strong>di</strong> una certa con<strong>di</strong>zione.<br />
Due stu<strong>di</strong>osi hanno <strong>di</strong>mostrato che con sequenza, selezione e l'iterazione possono essere scritti tutti i possibili<br />
programmi che un microprocessore è in grado <strong>di</strong> eseguire. Si parla anche <strong>di</strong> programmazione strutturata.<br />
NOTA: siete caldamente invitati a consultare l'approfon<strong>di</strong>mento sui <strong>di</strong>agrammi <strong>di</strong> flusso per vedere come sono<br />
rappresentate le strutture fondamentali della programmazione.<br />
Ci sono tre tipi <strong>di</strong> struttura selettiva: ad una via, a due vie (detta anche binaria), a molte vie.
SELEZIONE AD UNA VIA<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 31<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Fa eseguire una singola od un blocco <strong>di</strong> istruzioni (delimitate in questo caso dalla coppia <strong>di</strong> parole chiave begin<br />
end) solo se la con<strong>di</strong>zione in<strong>di</strong>cata e vera (true). Se la con<strong>di</strong>zione è falsa l’istruzione (o il blocco <strong>di</strong><br />
istruzioni) viene semplicemente ignorato. Ecco la sintassi (a sinistra il caso <strong>di</strong> singola istruzione ed a destra<br />
quello con più <strong>di</strong> una istruzione:<br />
if con<strong>di</strong>zione then<br />
istruzione;<br />
ma non sarebbe sbagliato:<br />
if con<strong>di</strong>zione then<br />
begin<br />
istruzioneSingola<br />
end<br />
if con<strong>di</strong>zione then<br />
begin<br />
istruzione1;<br />
istruzione2;<br />
…<br />
istruzioneN<br />
end;<br />
Dopo istruzioneN non è stato <strong>di</strong>menticato un punto e virgola: prima <strong>di</strong> un end può essere omessa in quanto il ;<br />
serve a separare due istruzioni e l’end non può essere considerato un’istruzione ma un delimitatore.<br />
con<strong>di</strong>zione è un espressione che una volta valutata deve generare un valore booleano (vero/falso); le con<strong>di</strong>zioni<br />
si esprimono con gli operatori relazionali:<br />
OPERATORE ESEMPI<br />
< minore X < 4 x+y < z a < b+c Ord(c) < 67 (1) Parola1 < Parola2 (2)<br />
= b+c X >= sqrt(z) (3) Parola1 >= Parola2<br />
> maggiore Somma>100 x+y > z a > b+c X > sqr(z) Length(cognome)>12 (4)<br />
<strong>di</strong>verso x y x+y z a > b+c X sqr(z) Cognome1 Cognome2<br />
(1) c è un carattere; la funzione ord restituisce il valore del suo co<strong>di</strong>ce ASCII che viene confrontato con il<br />
valore 67.<br />
(2) Parola1 e Parola2 sono due stringhe; il confronto deve essere inteso in senso lessicografico, cioè<br />
considerando la posizione che ciascuna parola occuperebbe sul vocabolario; per cui ‘cane’ < ‘ca in anni<br />
libri, e mi sa’; il tutto si applica anche quando la stringa contiene più parole come in ‘bel cane’ < ‘bel gatto’<br />
(3) Il valore della variabile X viene confrontato con quello della ra<strong>di</strong>ce quadrata del valore contenuto nella<br />
variabile z<br />
(4) Si controlla se la lunghezza in caratteri della stringa cognome supera 12<br />
Immaginiamo <strong>di</strong> voler applicare uno sconto al prezzo <strong>di</strong> un prodotto solo ai clienti abituali...<br />
program ApplicaSconto;<br />
var<br />
costo: real; tipoCliente: string; percentualeSconto:<br />
integer;<br />
begin<br />
writeln(Inserisci costo prodotto: ); readln( costo );<br />
writeln(Inserisci eventuale % <strong>di</strong> sconto: );<br />
readln(percentualeSconto );<br />
writeln(Inserisci il tipo <strong>di</strong> cliente: ); readln(<br />
tipoCliente );<br />
if tipoCliente=abituale then<br />
costo := costo (costo/100) * percentualeSconto;<br />
NOTA: in molti degli esempi che verranno proposti non ci si<br />
preoccuperà <strong>di</strong> controllare la correttezza dei dati inseriti<br />
tramite la tastiera perché il co<strong>di</strong>ce si appesantirebbe e<br />
<strong>di</strong>venterebbe molto più <strong>di</strong>fficile da seguire.<br />
In riferimento al programma qui a lato, ad esempio, l'utente<br />
potrebbe inserire un costo pari a zero o negativo, una<br />
percentuale <strong>di</strong> sconto senza senso ed anche una descrizione<br />
per il tipo del cliente che potrebbe ‘ mettere in crisi’ il test<br />
fatto con l’if: ‘Abituale’ con la A maiuscola non verrebbe<br />
riconosciuto invalido per l'applicazione dello sconto.<br />
writeln(Questo cliente paga :, costo):<br />
readln; (* un readln senza variabile attende fino a che non viene premuto il tasto INVIO)
end.<br />
Altri esempi.<br />
program selezione;<br />
var<br />
a,b,c: integer;<br />
s1,s2: string;<br />
c1,c2: char;<br />
begin<br />
a:=1; b:=2; c:=1;<br />
s1:='rossi'; s2:='mario';<br />
c1:='a';<br />
c2:='b';<br />
if a=b then<br />
writeln('a uguale b');<br />
if a>3 then<br />
writeln('a maggiore 3');<br />
if b=4 then<br />
writeln('I n. maggiore uguale II');<br />
(* il seguente confronto segue l'or<strong>di</strong>ne alfabetico (a2 then<br />
Begin<br />
Istruzione;<br />
Istruzione;<br />
Istruzione<br />
End;<br />
Istruzione;<br />
If x>2 then<br />
begin<br />
Istruzione1;<br />
Istruzione2;<br />
Istruzione3<br />
end;<br />
Istruzione<br />
If x>2 then<br />
Begin<br />
Istruzione;<br />
Istruzione;<br />
Istruzione<br />
End;<br />
Istruzione;
SELEZIONE A DUE VIE<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 33<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Rispetto a quella ad una via viene aggiunta la parte da eseguire quando la con<strong>di</strong>zione falsa:<br />
if con<strong>di</strong>zione then<br />
istruzioneSeVera<br />
else<br />
istruzioneSeFalsa;<br />
Qui sopra sono stati esemplificati tutti i possibili casi che si possono presentare a seconda che sia presente una<br />
sola istruzione da eseguire (nella parte then o nella parte else) piuttosto che un blocco <strong>di</strong> due o più istruzioni che<br />
vanno quin<strong>di</strong> racchiuse tra un begin ed un end. Nel primo esempio c'è una sola istruzione da eseguire sia che la<br />
con<strong>di</strong>zione sia vera sia che sia falsa. Nel secondo esempio ci sono N istruzioni da eseguire quando la con<strong>di</strong>zione<br />
vera ed una sola quando la con<strong>di</strong>zione falsa. Nel terzo esempio c'è una sola istruzione da eseguire quando la<br />
con<strong>di</strong>zione vera ed N istruzioni quando la con<strong>di</strong>zione falsa. Nell'ultimo esempio più <strong>di</strong> un'istruzione da eseguire<br />
sia nel caso la con<strong>di</strong>zione sia vera sia nel caso sia falsa.<br />
Se la con<strong>di</strong>zione è vera viene eseguita l'istruzione (o il blocco <strong>di</strong> istruzioni tra begin end) dopo il then e viene<br />
invece ignorata la parte else. Viceversa, se la con<strong>di</strong>zione è falsa viene ignorata l'istruzione (o il blocco <strong>di</strong><br />
istruzioni tra begin end) dopo il then e viene eseguita la parte else.<br />
Con<strong>di</strong>zioni composte<br />
if con<strong>di</strong>zione then<br />
begin<br />
Istruzione1SeVera;<br />
Istruzione2SeVera;<br />
…<br />
IstruzioneNSeVera;<br />
end<br />
else<br />
istruzioneSeFalsa;<br />
Le con<strong>di</strong>zioni usate fino ad ora sono definite semplici; usando i connettivi logici and, or e not possiamo<br />
comporne <strong>di</strong> più complesse (composte).<br />
La con<strong>di</strong>zione composta che usa and è vera quando lo sono CONTEMPORANEAMENTE tutte le con<strong>di</strong>zioni<br />
coinvolte; se ANCHE UNA SOLA <strong>di</strong> esse e' falsa, la con<strong>di</strong>zione intera risulterà falsa. Ogni con<strong>di</strong>zione deve<br />
essere racchiusa tra parentesi tonde.<br />
if (a>=1) and (a=18) then<br />
writeln('rossi e'' maggiorenne, maschio o femmina');<br />
if con<strong>di</strong>zione then<br />
IstruzioneSeVera<br />
else<br />
begin<br />
Istruzione1SeFalsa;<br />
Istruzione2SeFalsa;<br />
…<br />
IstruzioneNSeFalsa;<br />
end;<br />
ERRORE COMUNE: mettere il ; prima dellelse. In pascal l’istruzione if then else<br />
è considerata in<strong>di</strong>visibile e, lo sapete, il ; serve a separare due istruzioni.<br />
(* le con<strong>di</strong>zioni possono anche essere piu'' <strong>di</strong> 2 ... devono sempre essere vere<br />
CONTEMPORANEAMENTE *)<br />
if (s1='rossi') and (eta>18) and (s2='femmina') then<br />
writeln('rossi e'' maggiorenne, femmina');<br />
if con<strong>di</strong>zione then<br />
begin<br />
Istruzione1SeVera;<br />
Istruzione2SeVera;<br />
…<br />
IstruzioneNSeVera;<br />
end<br />
else<br />
begin<br />
Istruzione1SeFalsa;<br />
Istruzione2SeFalsa;<br />
…<br />
IstruzioneNSeFalsa;<br />
end;
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 34<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
La con<strong>di</strong>zione composta che usa or è vera quando lo è ALMENO UNA delle con<strong>di</strong>zioni coinvolte.; è falsa<br />
quando le con<strong>di</strong>zioni sono false TUTTE CONTEMPORANEAMENTE.<br />
If (mese=Gennaio) or (mese=Marzo) or (mese=Luglio) then<br />
Numero_giorni := 31;<br />
Prestate particolare attenzione al seguente esempio; va letto così: se oppure allora ..<br />
osservate le parentesi, OBBLIGATORIE: if ( ) or ( ( ) and ( ) )<br />
if (s1='bella') or ( (eta>60) and (s2='ricca') ) then<br />
writeln('la sposo in ogni caso ...');<br />
Variante sul tema … (più <strong>di</strong> una istruzione da eseguire)<br />
if (s1='bella') or ( (eta>60) and (s2='ricca') ) then<br />
begin<br />
writeln('la sposo in ogni caso ...');<br />
writeln('poi forse mi pentiro!')<br />
end<br />
else<br />
begin<br />
writeln('in questo caso niente dubbi ...');<br />
writeln('... meglio scapoli !!')<br />
end;<br />
IF ni<strong>di</strong>ficati: gli if possono contenere altri if; si parla <strong>di</strong> if ni<strong>di</strong>ficati:<br />
(* troviamo il massimo tra a b c *)<br />
if a>=b then<br />
if a>=c then<br />
writeln('il max e'' a')<br />
else<br />
writeln('il max e'' c')<br />
else<br />
if b>=c then<br />
writeln('il max e'' b')<br />
else<br />
writeln('il max e'' c');<br />
SELEZIONE A MOLTE VIE<br />
Cominciamo con il <strong>di</strong>re e non è una struttura in<strong>di</strong>spensabile ma solo una como<strong>di</strong>tà. È stata introdotta per sostituire la<br />
struttura degli if in cascata che rischia <strong>di</strong> <strong>di</strong>ventare poco leggibile. Ecco qui sotto le due strutture a confronto nel<br />
compito <strong>di</strong> determinare quanti giorni ci sono in un certo mese dell'anno: la variabile mese contiene un numero da 1 a<br />
12; la variabile anno il numero che corrisponde all'anno <strong>di</strong> riferimento.<br />
If mese=2 then<br />
If anno mod 4 = 0 then<br />
Giorni:=29 (* bisestile *)<br />
Else<br />
Giorni:=28<br />
Else<br />
If (mese=1) or (mese=3) or (mese=5)<br />
or (mese=7) or (mese=8) or (mese=10)<br />
or (mese=12) then<br />
Giorni:=31<br />
Else<br />
Giorni:=30<br />
IF in cascata:<br />
Case eta of<br />
1..11: writeln(‘ sei un poppante!’);<br />
12..15:<br />
begin<br />
writeln(‘OK, <strong>di</strong>mmi come ti chiami’);<br />
readln( nome );<br />
end;<br />
else<br />
begin<br />
writeln(‘Matusa non ammessi …’);<br />
writeln(‘Premi invio per continuare’);<br />
readln<br />
end<br />
end;<br />
if con<strong>di</strong>zione1 then<br />
…<br />
else<br />
if con<strong>di</strong>zione2 then<br />
…<br />
else<br />
if con<strong>di</strong>zione3 then<br />
….<br />
Else
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 35<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
La struttura <strong>di</strong> destra ‘si legge’ così: nel caso il valore <strong>di</strong> mese sia (case mese of …) 2 fai il controllo per il bisestile,<br />
nel caso invece il valore sia uno tra 1,3,5,7,8,10,12 i giorni sono 31, altrimenti sono 30. In pratica si elencano uno<br />
dopo l'altro i casi possibili (i valori assunti dalla variabile che si sta controllando) e per ciascun caso si in<strong>di</strong>cano le<br />
istruzioni da eseguire. La struttura che ne risulta è certamente più lineare e leggibile <strong>di</strong> quella ottenibile con una<br />
serie <strong>di</strong> if then else annidati e/o in cascata: e più sono i casi da considerare e più conviene usare questa<br />
nuova struttura. Ma ve<strong>di</strong>amone in dettaglio la sintassi:<br />
Case mese of<br />
2: If anno mod 4 = 0 then<br />
Giorni:=29 (* bisestile *)<br />
Else<br />
Giorni:=28;<br />
1,3,5,7,8,10,12: Giorni:=31;<br />
else<br />
Giorni:=30;<br />
end;<br />
Dopo la parola riservata case deve essere specificata la variabile da controllare: possiamo usare solo variabili <strong>di</strong> tipo<br />
integer e char (in realtà tutte le variabili <strong>di</strong> tipo or<strong>di</strong>nale).<br />
Per ogni caso che si presenta possiamo elencare:<br />
• un singolo valore (come il numero 2 che rappresenta febbraio nell'esempio)<br />
• più valori separati da virgola con l'idea che la variabile da controllare può assumere uno qualsiasi <strong>di</strong> questi<br />
valori per ricadere in questo caso<br />
• un intervallo <strong>di</strong> valori: 1..12 significa da 1a 12, ‘a’..’z’ significa dalla ‘a’ alla ‘z’<br />
• una situazione mista tra le due precedenti come in 1,3,5-12 che verrebbe interpretato come o il valore 1, o il<br />
valore 3, o un valore tra 5 e 12<br />
Dopo l'elenco dei valori che in<strong>di</strong>viduano un certo caso si deve mettere simbolo : (due punti) e poi o l'unica<br />
istruzione da eseguire o il blocco delle istruzioni da eseguire racchiuse tra begin e end. Ogni caso deve essere<br />
separato dal successivo con un punto e virgola.<br />
È possibile, ma non obbligatorio, inserire una parte else begin end con l'idea <strong>di</strong> in<strong>di</strong>care le istruzioni da eseguire<br />
nel caso il valore della variabile <strong>di</strong> controllo non trovi riscontro in nessuno dei casi primi elencati. Ad esempio nel<br />
controllo del valore della variabile mese se siamo certi che il suo valore è nell'intervallo 1..12, dopo aver controllato<br />
che non sia due e tutti i valori che corrispondono a mesi con 31 giorni, la parte else non può che corrispondere a casi<br />
in cui il mese è uno <strong>di</strong> quelli con 30 giorni. Naturalmente avremmo anche potuto usare la forma estesa senza la parte<br />
else:<br />
Case mese of<br />
2: If anno mod 4 = 0 then<br />
Giorni:=29 (* bisestile *)<br />
Else<br />
Giorni:=28;<br />
1,3,5,7,8,10,12: Giorni:=31;<br />
4,6,9,11: Giorni:=30;<br />
end;<br />
Case eta of<br />
1..11: writeln(‘ sei un poppante!’);<br />
12..15:<br />
begin<br />
writeln(‘OK, <strong>di</strong>mmi come ti chiami’);<br />
readln( nome );<br />
end;<br />
else<br />
begin<br />
Non è un errore!<br />
writeln(‘Matusa non ammessi …’);<br />
writeln(‘Premi invio per continuare’);<br />
readln<br />
end<br />
end;<br />
Il case va sempre concluso con un end. Un’ultima particolarità: prima dell’else <strong>di</strong> un case<br />
deve essere messo un punto e virgola perché altrimenti potrebbe essere confuso con la parte<br />
else <strong>di</strong> un if then else inserito nell'ultimo caso… Infatti nell'esempio qui sulla destra senza il<br />
punto e virgola dopo il 20, else che dovrebbe essere eseguito quando il soggetto ha più <strong>di</strong> 18<br />
anni (caso ’pigliatutto’ del case) verrebbe invece erroneamente associato con l’if che controlla<br />
le multe.<br />
Case eta of<br />
1..13: paghetta:=5;<br />
14-18: if multe=0 then<br />
paghetta:=20;<br />
else<br />
writeln(‘Vai a lavorare!’)<br />
end;
ESERCIZI E RIEPILOGATIVI SULLA SELEZIONE<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 36<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SEL1. <strong>di</strong>fficoltà: bassa Inserita un’età <strong>di</strong>re se siamo in presenza <strong>di</strong> un minorenne o maggiorenne.<br />
Program maggiorenni1;<br />
uses<br />
newdelay, crt;<br />
var<br />
eta: integer; (* variabile in cui verra' memorizzata l'eta' *)<br />
(* NOTA: non usare mai le lettere accentate per i<br />
nomi delle variabili ! *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Quanti hanni hai? -> ');<br />
readln(eta);<br />
if eta>=18 then<br />
writeln('OK, vedo che sei maggiorenne ...')<br />
else<br />
writeln('Sei ancora un poppante, torna tra ', 18 - eta, 'anni !!');<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
(* problemi aperti: cosa succede se chi usa il programma sbaglia ed inserisce<br />
un eta' negativa o troppo grande per essere vera ?<br />
mo<strong>di</strong>fica il programma per controllare queste possibilita' !!<br />
trovi la soluzione nel sorgente 'maggior2.pas' *)<br />
end.<br />
SEL2. <strong>di</strong>fficoltà: bassa Inserito un numero, <strong>di</strong>re se e' pari o <strong>di</strong>spari<br />
program pari<strong>di</strong>spari1;<br />
uses newdelay, crt;<br />
var<br />
numero: integer; (* variabile in cui verra' memorizzata il numero *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Inserisci un numero e ti <strong>di</strong>ro'' se e'' pari o <strong>di</strong>spari -> ');<br />
readln(numero);<br />
if numero mod 2 = 0 then<br />
writeln('e'' pari')<br />
else<br />
writeln('e'' <strong>di</strong>spari');<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.
SEL3. <strong>di</strong>fficoltà: bassa Inserito un carattere, <strong>di</strong>re se e' una vocale od una consonante.<br />
program consonanteVocale;<br />
uses<br />
newdelay, crt;<br />
var<br />
c: char; (* variabile in cui verra' memorizzata il singolo carattere *)<br />
(* commento: il tipo char può memorizzare un solo carattere; occupa la<br />
meta' dello spazio <strong>di</strong> una stringa lunga un solo carattere perché con le<br />
stringhe un byte supplementare serve a memorizzare la lunghezza della<br />
stringa stessa; questo non e' invece ovviamente necessario per una<br />
variabile carattere, la cui lunghezza e'' sempre 1 *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Inserisci un carattere -> ');<br />
readln(c);<br />
(* PRIMA SOLUZIONE *)<br />
writeln('PRIMA SOLUZIONE');<br />
if ( c='a' ) or ( c='e' ) or ( c='i' ) or ( c='o' ) or ( c='u' ) then<br />
writeln('e'' una vocale')<br />
else<br />
writeln('e'' una consonante');<br />
writeln('--------------------------');<br />
writeln;<br />
(* commento: questa soluzione non tiene conto delle vocali in maiuscolo *)<br />
(* SECONDA SOLUZIONE *)<br />
writeln('SECONDA SOLUZIONE');<br />
if ( c='a' ) or ( c='e' ) or ( c='i' ) or ( c='o' ) or ( c='u' ) or<br />
( c='A' ) or ( c='E' ) or ( c='I' ) or ( c='O' ) or ( c='U' )<br />
then<br />
writeln('e'' una vocale')<br />
else<br />
writeln('e'' una consonante');<br />
writeln('--------------------------');<br />
writeln;<br />
(* commento: provate con la A: la prima soluzione non fornisce un risultato<br />
corretto, la seconda si'; pero'' ci sono molti test ... ecco una soluzione<br />
piu'' efficiente *)<br />
(* TERZA SOLUZIONE *)<br />
writeln('TERZA SOLUZIONE');<br />
(* prima trasformiamo il carattere in maiuscolo e poi facciamo i test<br />
solo con le maiuscole; upcase e' una funzione che restituisce il<br />
maiuscolo del carattere fornito come parametro tra parentesi;<br />
se il carattere era gia' in maiuscolo, rimane in maiuscolo *)<br />
c := upcase( c );<br />
if ( c='A' ) or ( c='E' ) or ( c='I' ) or ( c='O' ) or ( c='U' ) then<br />
writeln('e'' una vocale')<br />
else<br />
writeln('e'' una consonante');<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 37<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004
writeln('--------------------------');<br />
writeln;<br />
(* QUARTA SOLUZIONE *)<br />
writeln('QUARTA SOLUZIONE');<br />
(* soluzione con if in cascata, poco leggibile e lunga *)<br />
c := upcase( c );<br />
if ( c'A' ) then<br />
if ( c'E' ) then<br />
if ( c'I' ) then<br />
if ( c'O' ) then<br />
if ( c'U' ) then<br />
writeln('e'' una consonante')<br />
else<br />
writeln('e'' una vocale (U)')<br />
else<br />
writeln('e'' una vocale (O)')<br />
else<br />
writeln('e'' una vocale (I)')<br />
else<br />
writeln('e'' una vocale (E)')<br />
else<br />
writeln('e'' una vocale (A)');<br />
(* commenti: questa soluzione, piuttosto pesante, consente pero' <strong>di</strong><br />
intraprendere azioni <strong>di</strong>fferenziate a seconda delle vocali incontrate;<br />
i blocchi ELSE sono tutti necessari! <strong>di</strong>versamente inserendo quella vocale<br />
non verrebbe stampato nulla! Prova a togliere l'ultimo blocco ed inserisci<br />
la A ... *)<br />
writeln('--------------------------');<br />
writeln;<br />
(* QUINTA SOLUZIONE *)<br />
writeln('QUINTA SOLUZIONE');<br />
(* la piu' efficiente, sfruttando una delle funzioni <strong>di</strong>sponibili<br />
per le stringhe: la funzione pos restituisce la prima posizione <strong>di</strong> una<br />
stringa all'interno <strong>di</strong> un altra; se non la trova restituisce zero;<br />
ad esempio pos('po','topolino') restituisce 3 perche' la stringa 'po'<br />
e' stata trovata a partire dalla posizione 3 nella stringa 'topolino' *)<br />
if pos(c,'aAeEiIoOuU') 0 then (* c trovata da qualche parte *)<br />
writeln('e'' una vocale')<br />
else<br />
writeln('e'' una consonante');<br />
writeln('--------------------------');<br />
writeln;<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 38<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 39<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SEL4. <strong>di</strong>fficoltà: bassa Inseriti A, B e C <strong>di</strong>re se B e' compreso tra A e C; in pratica si controlla se B appartiene<br />
all'intervallo [A,C]<br />
program intervallo;<br />
uses<br />
newdelay, crt;<br />
var<br />
a, b, c: real;<br />
(* variabili in cui verranno memorizzati i numeri *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Inserisci l''estremo sinistro dell''intervallo -> ');<br />
readln(a);<br />
write('Inserisci l''estremo destro dell''intervallo -> ');<br />
readln(c);<br />
(* se i numeri sono stati inseriti nell'or<strong>di</strong>ne sbagliato, stop *)<br />
if a>c then<br />
writeln('Intervallo impossibile.')<br />
else<br />
begin<br />
write('Inserisci un numero e ti <strong>di</strong>ro'' se appartiene all''intervallo -> ');<br />
readln(b);<br />
if ( b>=a ) and ( b=a ) or ( b=5 ) or ( 13=5 anche<br />
se e' falso che 13
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 40<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SEL5. <strong>di</strong>fficoltà: bassa Inserite due misure, una in pollici e l'altra in centimetri, <strong>di</strong>re qual è la maggiore<br />
program centimetriPollici;<br />
uses<br />
newdelay, crt;<br />
var<br />
cm: real; (* variabile in cui verra' memorizzate la misura in centimetri *)<br />
po: real; (* variabile in cui verra' memorizzate la misura in pollici *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Inserisci la misura in centimetri -> ');<br />
readln(cm);<br />
write('Inserisci la misura in pollici -> ');<br />
readln(po);<br />
(* ricordando che 1 pollice=2.54 cm ... *)<br />
if po * 2.54 > cm then<br />
writeln('La misura in pollici e'' maggiore (', po*2.54:4:2, 'cm)')<br />
else<br />
if po * 2.54 < cm then<br />
writeln('La misura in centimetri e'' maggiore')<br />
else<br />
writeln('Le due misure sono equivalenti');<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.<br />
SEL 6. <strong>di</strong>fficoltà: bassa Inserite le misure dei lati <strong>di</strong> 2 rettangoli <strong>di</strong>re quale dei due ha la superficie maggiore<br />
program confrontaAree;<br />
uses<br />
newdelay, crt;<br />
var<br />
b1, b2: real; (* variabile in cui verranno memorizzate le due basi *)<br />
h1, h2: real; (* variabile in cui verranno memorizzate le due altezze *)<br />
a1, a2: real; (* variabile in cui verranno memorizzate le due aree *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Inserisci la base del primo rettangolo -> ');<br />
readln(b1);<br />
write('Inserisci l''altezza del primo rettangolo -> ');<br />
readln(h1);<br />
write('Inserisci la base del secondo rettangolo -> ');<br />
readln(b2);
write('Inserisci l''altezza del secondo rettangolo -> ');<br />
readln(h2);<br />
(* PRIMA SOLUZIONE *)<br />
writeln('PRIMA SOLUZIONE');<br />
(* se non interessa memorizzare i valori delle due aree si puo' procedere<br />
al confronto <strong>di</strong>retto delle rispettive formule <strong>di</strong> calcolo *)<br />
if b1*h1 > b2*h2 then<br />
writeln('Il primo rettangolo ha la superficie maggiore')<br />
else<br />
if b1*h1 < b2*h2 then<br />
writeln('Il secondo rettangolo ha la superficie maggiore')<br />
else<br />
writeln('I due rettangoli hanno la stessa superficie');<br />
writeln('--------------------------');<br />
writeln;<br />
(* SECONDA SOLUZIONE (migliore) *)<br />
writeln('SECONDA SOLUZIONE');<br />
(* calcoliamo prima le due superfici e salviamo il risultato in a1 e a2 *)<br />
a1 := b1*h1;<br />
a2 := b2*h2;<br />
(* poi usiamo a1 e a2 per i confronti *)<br />
if a1>a2 then<br />
writeln('Il primo rettangolo ha la superficie maggiore')<br />
else<br />
if a1
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 42<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SEL7. <strong>di</strong>fficoltà: me<strong>di</strong>a Inserita un'età', <strong>di</strong>re se siamo in presenza <strong>di</strong> un maggiorenne o <strong>di</strong> un<br />
minorenne; controllare anche eventuali errori <strong>di</strong> inserimento da parte dell'utente<br />
program maggiorenni2;<br />
uses newdelay, crt;<br />
var<br />
eta: integer; (* variabile in cui verra' memorizzata l'eta' NOTA: non usare mai le lettere accentate per i<br />
nomi delle variabili ! *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
(* rispetto alla soluzione dell'esercizio maggior.pas in<strong>di</strong>chiamo nella richiesta dell'eta' anche l'intervallo accettato: da<br />
0 anni (neonato) a 120 *)<br />
write('Quanti hanni hai (0-120) ? -> ');<br />
readln(eta);<br />
(* SOLUZIONE 1 *)<br />
(* controlliamo per errori: eta' negative o superiori a 120 *)<br />
writeln('SOLUZIONE 1');<br />
if ( eta120 ) then<br />
writeln('ERRORE: l''eta'' deve essere compresa tra 0 e 120 !')<br />
else<br />
if eta>=18 then<br />
writeln('OK, vedo che sei maggiorenne ...')<br />
else<br />
writeln('Sei ancora un poppante, torna tra ', 18 - eta, 'anni !!');<br />
(* commenti: notate le parentesi OBBLIGATORIE quando in un if c'e' più <strong>di</strong> una con<strong>di</strong>zione; notate come sia<br />
possibile iniziare una parte ELSE subito con un altro test; questo tipo <strong>di</strong> struttura<br />
if ... then<br />
...<br />
else<br />
if ... then<br />
...<br />
else<br />
if ... then<br />
ecc. ecc.<br />
e' detta con if in cascata e permette <strong>di</strong> verificare in successione una sequenza <strong>di</strong> con<strong>di</strong>zioni<br />
La soluzione precedente non consente, in caso <strong>di</strong> errore, <strong>di</strong> capire se lo sbaglio e' relativo ad un'età' minore <strong>di</strong> zero<br />
o ad un'età' troppo grande perchè le due con<strong>di</strong>zioni sono testate insieme. Ve<strong>di</strong>amo come ottenere un controllo ancora<br />
piu' accurato rispondendo con messaggi <strong>di</strong> errore <strong>di</strong>fferenziati e piu' accurati*)<br />
(* SOLUZIONE 2 *)<br />
writeln('------------------------------');<br />
writeln('SOLUZIONE 2');<br />
if ( eta120 ) then<br />
writeln('ERRORE: l''eta'' non puo''essere maggiore <strong>di</strong> 120!')<br />
else<br />
if eta>=18 then<br />
writeln('OK, vedo che sei maggiorenne ...')<br />
else<br />
writeln('Sei ancora un poppante, torna tra ', 18 - eta, ' anni !!');<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 43<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SEL 8. <strong>di</strong>fficoltà: me<strong>di</strong>a Letto un carattere, <strong>di</strong>re se corrisponde ad una lettera maiuscola e se sì<br />
<strong>di</strong>re se e' una consonante od una vocale.<br />
program maiuscola;<br />
var<br />
c: char;<br />
begin<br />
write('Inserire un carattere -> ');<br />
readln(c);<br />
(* PRIMA SOLUZIONE: SENZA CASE *)<br />
(* se il co<strong>di</strong>ce ASCII del carattere letto e' compreso tra quelli <strong>di</strong> A e Z<br />
allora si tratta <strong>di</strong> una maiuscola ... *)<br />
writeln('SENZA ''CASE'' ...');<br />
if ( ord(c)>=ord('A') ) and ( ord(c)
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 44<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SEL 9. <strong>di</strong>fficoltà: me<strong>di</strong>a Disegna il <strong>di</strong>agramma <strong>di</strong> flusso e scrivi un programma che, letti tre numeri, li metta in<br />
or<strong>di</strong>ne crescente<br />
program or<strong>di</strong>na_tre;<br />
var a,b,c,temp: integer;<br />
begin<br />
write('Inserire il primo numero -> ');<br />
readln(a);<br />
write('Inserire il secondo numero -> ');<br />
readln(b);<br />
write('Inserire il terzo numero -> ');<br />
readln(c);<br />
(* e' necessario assicurarsi che tre numeri siano in or<strong>di</strong>ne *)<br />
(* prima or<strong>di</strong>na a e b tra loro *)<br />
if b
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 45<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SEL 10. <strong>di</strong>fficoltà: me<strong>di</strong>a Letti tre numeri da tastiera, determinare se possono appartenere ad una progressione<br />
aritmetica. TRACCIA: in una progressione aritmetica la <strong>di</strong>fferenza tra due numeri<br />
consecutivi e' costante. Ad esempio: 1 4 7 10 13 16 ecc. è la progressione in cui ogni numero si ottiene<br />
sommando 3 al precedente. La <strong>di</strong>fferenza tra due numeri consecutivi qualsiasi è pari a 3.<br />
program progressione;<br />
var a,b,c,temp: integer;<br />
begin<br />
write('Inserire il primo numero -> ');<br />
readln(a);<br />
write('Inserire il secondo numero -> ');<br />
readln(b);<br />
write('Inserire il terzo numero -> ');<br />
readln(c);<br />
(* e' necessario prima assicurarsi che i tre numeri siano in or<strong>di</strong>ne *)<br />
(* prima or<strong>di</strong>na a e b tra loro *)<br />
if b
SEL 11. <strong>di</strong>fficoltà: alta Verificare se una data inserita da tastiera e' corretta<br />
program dataCorretta;<br />
var gg,mm,aa: integer; (* gg=giorno, mm=mese, aa=anno *)<br />
ok_data: boolean;<br />
begin<br />
write('Inserire il giorno: ');readln(gg);<br />
write('Inserire il mese: ');readln(mm);<br />
write('Inserire l''anno: ');readln(aa);<br />
(* PRIMA SOLUZIONE SENZA CASE *)<br />
ok_data:=false; (* se data supera controlli ok_data verra' messo a true *)<br />
if (gg>0) and (gg0) and (mm0) then<br />
if mm=2 then (* febbraio *)<br />
begin<br />
if aa mod 4 = 0 then (* per semplicita': bisestili se multipli <strong>di</strong> 4 *)<br />
begin<br />
if gg
(* POI SOLUZIONE CON CASE *)<br />
ok_data:=false;<br />
if (gg>0) and (gg0) and (mm0) then<br />
case mm of<br />
2: if aa mod 4 = 0 then<br />
begin<br />
if gg
STRUTTURE ITERATIVE<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 48<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Esamineremo ora i tre tipi classici <strong>di</strong> struttura iterativa enumerativa (ciclo for do), indefinita con controllo in<br />
coda/uscita per vero (ciclo repeat until) e della indefinita con controllo in testa/uscita per falso (ciclo while).<br />
Premessa: in realtà potremo scrivere un qualsiasi programma utilizzando solo una delle due forme <strong>di</strong> struttura<br />
iterativa indefinita. Infatti la struttura iterativa enumerativa può essere ‘simulata’ con una delle altre due, ed è<br />
sempre possibile riscrivere un segmento <strong>di</strong> co<strong>di</strong>ce che utilizza una delle due forme <strong>di</strong> iterazione indefinita usando<br />
l'altra. Detto in altre parole tutte le nostre esigenze potrebbero essere sod<strong>di</strong>sfatte o utilizzando solo il ciclo repeat <br />
until o usando solo il ciclo while.<br />
Come ho avuto già modo <strong>di</strong> sottolineare per i costrutto case l'uso <strong>di</strong> forme <strong>di</strong>verse facilita la programmazione in<br />
quei casi in cui una delle forme <strong>di</strong> iterazione esistenti è particolarmente in<strong>di</strong>cata.<br />
Struttura iterativa enumerativa<br />
Si chiama così perché può essere usata solo se il numero <strong>di</strong> volte che l'istruzione od il blocco <strong>di</strong> istruzioni deve<br />
essere ripetuto è noto nel momento in cui il ciclo inizia, ed è per cui possibile far contare (cioè enumerare) al<br />
programma il numero <strong>di</strong> volte che deve ripetere le istruzioni. Ecco la sintassi:<br />
Oppure:<br />
For variabile_<strong>di</strong>_controllo := valore_inizio to valore_fine do<br />
Istruzione;<br />
For variabile_<strong>di</strong>_controllo := valore_inizio to valore_fine do<br />
begin<br />
Istruzione1;<br />
Istruzione2;<br />
<br />
IstruzioneN;<br />
end;<br />
La variabile <strong>di</strong> controllo deve essere <strong>di</strong> tipo or<strong>di</strong>nale (integer, longint o char anche se quest'ultimo caso è assai raro).<br />
Ad essa viene assegnato come valore iniziale quello in<strong>di</strong>cato dopo l'operatore <strong>di</strong> assegnamento. Se il valore iniziale<br />
è inferiore a quello in<strong>di</strong>cato come finale dopo la parola chiave to le istruzioni non sono ripetute neppure una volta.<br />
Diversamente il ciclo inizia e le istruzioni sono eseguite una prima volta; dopo che è stata eseguita l'ultima<br />
istruzione la variabile <strong>di</strong> controllo viene incrementata <strong>di</strong> una unità; se il valore è inferiore o uguale a quello in<strong>di</strong>cato<br />
come finale il ciclo viene ripetuto. Il ciclo termina quando il valore della variabile <strong>di</strong> controllo supera quello in<strong>di</strong>cato<br />
come valore finale.<br />
L'esempio che segue<br />
stampa per cinque<br />
volte il messaggio<br />
‘ciao!’:<br />
program cicli;<br />
var i: integer;<br />
begin<br />
for i:=1 to 5 do<br />
writeln(‘ciao!’);<br />
readln;<br />
end.<br />
In questo caso il ciclo<br />
viene ripetuto una<br />
volta<br />
program cicli;<br />
var i: integer;<br />
begin<br />
for i:=1 to 1 do<br />
writeln(‘ciao!’);<br />
readln;<br />
end.<br />
In questo caso il ciclo<br />
non viene ripetuto<br />
neanche una volta<br />
program cicli;<br />
var i: integer;<br />
begin<br />
for i:=1 to 0 do<br />
writeln(‘ciao!’);<br />
readln;<br />
end.<br />
Il valore d'inizio non deve essere per<br />
forza 1. Il ciclo che segue stamperà il<br />
messaggio sette volte (non<br />
<strong>di</strong>mentichiamo che deve considerare<br />
anche il valore 0 nella sequenza che qui<br />
riporto per intero -3 -2 -1 0 1 2 3 )<br />
program cicli;<br />
var i: integer;<br />
begin<br />
for i:= -3 to 3 do<br />
writeln(‘ciao!’);<br />
readln;<br />
end.
program cicli;<br />
var i, base, altezza, quanti: integer;<br />
begin<br />
writeln(‘Quanti triangoli vuoi esaminare?’);<br />
readln( quanti );<br />
for i:= 1 to quanti do<br />
begin<br />
writeln(‘Inserisci la misura della base del<br />
triangolo’);<br />
readln( base );<br />
writeln(‘Inserisci la misura dell’’ altezza<br />
corrispondente’);<br />
readln( altezza );<br />
writeln(‘La misura della superficie <strong>di</strong> questo triangolo è: ‘, base*altezza/2);<br />
readln;<br />
end; (* del for *)<br />
end.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 49<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Contare come i gamberi: esiste una forma leggermente mo<strong>di</strong>ficata del costrutto for che consente <strong>di</strong> considerare i<br />
valori della variabile <strong>di</strong> controllo in modo decrescente contando, per così <strong>di</strong>re, all'in<strong>di</strong>etro. L'esempio seguente<br />
stampa sul video i numeri da 10 a 1:<br />
program cicli;<br />
var i: integer;<br />
begin<br />
for i:= 10<br />
downto 1 do<br />
writeln( i );<br />
readln;<br />
end.<br />
Questo esempio chiarisce come non sia necessario conoscere l'esatto<br />
valore del numero delle volte che sarà ripetuto il ciclo al momento della<br />
scrittura del co<strong>di</strong>ce.<br />
È invece necessario che questo valore sia noto al momento in cui il ciclo<br />
dovrà essere eseguito: noto non significa sapere quale sarà<br />
effettivamente questo valore (è l'utente programma che decide quanti<br />
saranno i triangoli esaminati <strong>di</strong>gitando questo un valore quando il<br />
programma glielo chiede); significa invece sapere che questo valore è<br />
contenuto in una certa variabile (quanti) ed è quest'ultima che può essere<br />
utilizzata come valore terminale della variabile <strong>di</strong> controllo.<br />
Come vedete, è sufficiente in<strong>di</strong>care invece <strong>di</strong> to la parola chiave downto e come punto <strong>di</strong> inizio un valore<br />
più grande <strong>di</strong> quello in<strong>di</strong>cato per il punto <strong>di</strong> fine.<br />
Questo esempio è molto interessante anche per un altro motivo: <strong>di</strong>mostra come sia possibile utilizzare<br />
nelle istruzioni del ciclo il valore assunto in quel momento dalla variabile <strong>di</strong> controllo.
ESERCIZI E RIEPILOGATIVI SUL CICLO FOR<br />
ITE1. <strong>di</strong>fficoltà: bassa stampa dei primi N numeri naturali, con N letto da tastiera<br />
program numeri;<br />
uses ewdelay, crt;<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 50<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
var<br />
i: integer; (* contatore ciclo *) n: integer; (* qui viene memorizzato il numero letto da tastiera *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Fino a che numero devo arrivare? -> '); readln( n );<br />
for i := 1 to n do<br />
writeln( i );<br />
(* commenti: e' ancora materia <strong>di</strong> <strong>di</strong>sputa tra matematici se considerare lo<br />
zero un naturale; se lo si vuole considerare tale il co<strong>di</strong>ce <strong>di</strong>venta:<br />
for i := 0 to n-1 do<br />
writeln( i );<br />
infatti se chiedessimo i primi 5 naturali dovremmo ottenere: 0,1,2,3,4<br />
e sarebbe quin<strong>di</strong> necessario fermare il ciclo a 4, cioe' n-1<br />
*)<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.<br />
ITE2. <strong>di</strong>fficoltà: bassa stampa dei primi N numeri naturali, con N letto da tastiera; la stampa deve avvenire dal<br />
numero piu' grande al piu' piccolo<br />
program numeri;<br />
uses newdelay, crt;<br />
var<br />
i: integer; (* contatore ciclo *) n: integer; (* qui viene memorizzato il numero letto da tastiera *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Fino a che numero devo arrivare? -> '); readln( n );<br />
for i := n downto 1 do (* NOTATE IL DOWNTO INVECE DI TO !!!! *)<br />
writeln( i );<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 51<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
ITE3. <strong>di</strong>fficoltà: bassa stampa dei primi N numeri naturali, con N letto da tastiera; a fianco <strong>di</strong> ciascun numero<br />
in<strong>di</strong>care se e' pari o <strong>di</strong>spari<br />
program numeri;<br />
uses newdelay, crt;<br />
var<br />
i: integer; (* contatore ciclo *) n: integer; (* qui viene memorizzato il numero letto da tastiera *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Fino a che numero devo arrivare? -> '); readln( n );<br />
(* ricordo che l'operatore MOD calcola il resto della <strong>di</strong>visione tra il numero che viene messo alla sua sinistra e<br />
quello che viene messo alla sua destra; ad esempio 7 mod 3 calcola il resto della <strong>di</strong>visione tra 7 e 3, quin<strong>di</strong> 1; 13<br />
mod 8 -> 5; 19 mod 5 -> 4; quando il primo numero e' multiplo del secondo il resto e' zero: 4 mod 2 -> 0;<br />
15 mod 3 -> 0; 15 mod 5 -> 0; *)<br />
for i := 1 to n do<br />
begin<br />
write( i );<br />
(* i numeri pari <strong>di</strong>visi per due danno resto 0 *)<br />
if i mod 2 = 0 then<br />
writeln(' numero pari')<br />
else<br />
writeln(' numero <strong>di</strong>spari');<br />
end;<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.<br />
ITE4. <strong>di</strong>fficoltà: bassa Stampa dei numeri <strong>di</strong>spari minori o uguali a N, con N letto da tastiera<br />
program <strong>di</strong>spari;<br />
uses newdelay, crt;<br />
var<br />
i: integer; (* contatore ciclo *) n: integer; (* qui viene memorizzato il numero letto da tastiera *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Fino a che numero devo arrivare? -> '); readln( n );<br />
for i := 1 to n do<br />
begin<br />
if i mod 2 = 1 then<br />
writeln( i );<br />
end;<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 52<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
ITE5. <strong>di</strong>fficoltà: bassa Stampa dei primi N numeri naturali (N letto da tastiera) con a fianco<br />
i rispettivi quadrati e cubi<br />
program potenze;<br />
uses<br />
newdelay, crt;<br />
var<br />
i: integer; (* contatore ciclo *)<br />
n: integer; (* qui viene memorizzato il numero letto da tastiera *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Fino a che numero devo arrivare? -> ');<br />
readln( n );<br />
(* PRIMA SOLUZIONE *)<br />
writeln('PRIMA SOLUZIONE');<br />
for i := 1 to n do<br />
writeln( i, ' ', i*i, ' ', i*i*i );<br />
writeln('--------------------------');<br />
writeln;<br />
(* SECONDA SOLUZIONE (sfrutta l'operatore sqr che calcola il quadrato *)<br />
writeln('SECONDA SOLUZIONE');<br />
for i := 1 to n do<br />
writeln( i, ' ', sqr(i), ' ', sqr(i)*i );<br />
writeln('--------------------------');<br />
writeln;<br />
(* TERZA SOLUZIONE<br />
allinea i numeri usando gotoxy(colonna, riga) per posizionare il cursore alla colonna/riga dello schermo<br />
desiderata; whereY e' una funzione che restituisce il numero <strong>di</strong> riga dove si trova il cursore; quin<strong>di</strong> gotoxy(7,<br />
whereY) significa colonna 7, non muoverti dalla riga su cui ti trovi ...*)<br />
writeln('TERZA SOLUZIONE');<br />
for i := 1 to n do<br />
begin<br />
write( i );<br />
gotoxy(7,whereY); write( sqr(i) );<br />
gotoxy(14,whereY); writeln( sqr(i)*i )<br />
end;<br />
writeln('--------------------------');<br />
writeln;<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 53<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
ITE6. <strong>di</strong>fficoltà: bassa Stampa dei numeri interi relativi da -N a +N, con N letto da tastiera<br />
program numeri;<br />
uses<br />
newdelay, crt;<br />
var<br />
i: integer; (* contatore ciclo *)<br />
n: integer; (* qui viene memorizzato il numero letto da tastiera *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
write('Fino a che numero devo arrivare? -> ');<br />
readln( n );<br />
for i := -n to n do<br />
writeln( i );<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.<br />
ITE7. <strong>di</strong>fficoltà: bassa Stampa dei numeri da INIZIO a FINE, con INIZIO e FINE letti da tastiera.<br />
Controllare che INIZIO sia ');<br />
readln( inizio );<br />
write('Numero finale -> ');<br />
readln( fine );<br />
(* PRIMA SOLUZIONE *)<br />
writeln('PRIMA SOLUZIONE');<br />
if iniziofine) si esegue un ciclo con gli estremi invertiti; *)
(* SECONDA SOLUZIONE *)<br />
writeln('SECONDA SOLUZIONE');<br />
(* se inizio e fine sono invertiti, li scambio *)<br />
if inizio>fine then<br />
begin<br />
temp:=inizio;<br />
inizio:=fine;<br />
fine:=temp<br />
end;<br />
(* ora inizio e fine sono senza dubbio nell'or<strong>di</strong>ne giusto ... *)<br />
for i := inizio to fine do<br />
writeln( i );<br />
writeln('--------------------------');<br />
writeln;<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.<br />
ITE8. <strong>di</strong>fficoltà: bassa generazione <strong>di</strong> numeri casuali<br />
(* random.pas<br />
Con questo esercizio imparerete:<br />
*)<br />
- l'utilizzo della funzione random per generare numeri in modo casuale<br />
(utile per simulazioni, giochi ecc.<br />
- l'utilizzo della funzione randomize per avere sequenze <strong>di</strong> numeri casuali<br />
sempre <strong>di</strong>verse<br />
program prova_random;<br />
uses newdelay crt;<br />
var x,i,num: integer; (* non hanno un significato particolare *)<br />
begin<br />
clrscr;<br />
(* ESPERIENZA 1 *)<br />
(* random(N) restituisce un numero casuale compreso tra 0 e N-1 *)<br />
x:=random(5); (* x <strong>di</strong>venta un numero compreso tra 0 e 4 *)<br />
writeln('Esperienza 1: ', x);<br />
(* possiamo usare random <strong>di</strong>rettamente, senza memorizzare il risultato in x *)<br />
writeln('Esperienza 1: ',random(9));<br />
readln;<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 54<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004
(* ESPERIENZA 2 *)<br />
(* generiamo una sequenza <strong>di</strong> 10 numeri compresi tra 0 e 15 *)<br />
for i:=1 to 10 do<br />
writeln('Esperienza 2: ', random(16));<br />
readln;<br />
(* NOTA BENE: tutte le volte che si fa ripartire il programma, random genera<br />
la stessa sequenza <strong>di</strong> numeri (osservatelo con l'esperienza n. 2 facendo<br />
partire il programma piu' volte).<br />
Se si vuole iniziare ogni volta con una sequenza <strong>di</strong>versa, usare il comando<br />
randomize: esso cambia la sequenza basandosi sul tempo scan<strong>di</strong>to<br />
dall'orologio interno del computer *)<br />
(* ESPERIENZA 3 *)<br />
(* riscrivete il ciclo dell'esperienza due facendolo precedere dal comando<br />
randomize; fate ripartire piu' volte il programma e notate come la sequenza<br />
dell'esperienza 3 cambi tutte le volte *)<br />
randomize;<br />
for i:=1 to 4 do<br />
writeln('Esperienza 3: ', random(16));<br />
readln;<br />
(* ESPERIENZA 4 *)<br />
(* Invece <strong>di</strong> un numero possiamo usare come parametro per la random una<br />
variabile intera *)<br />
write ('Dimmi un numero: ');<br />
readln(num);<br />
writeln('Ora genero una sequenza <strong>di</strong> 10 numeri tra 0 e ',num - 1);<br />
for i:=1 to 10 do<br />
writeln('Esperienza 4: ', random(num));<br />
readln;<br />
(* PROVA DA SOLO ...<br />
1. Cosa accade in<strong>di</strong>cando 1 come parametro della random ?<br />
2. Cosa accade in<strong>di</strong>cando 0 come parametro della random ?<br />
3. Cosa accade in<strong>di</strong>cando un numero negativo come parametro della random ?<br />
4. Cosa accade in<strong>di</strong>cando un numero reale come parametro della random ?<br />
5. Cosa accade in<strong>di</strong>cando una variabile integer che contiene un valore<br />
negativo come parametro della random?<br />
*)<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 55<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
(* ESERCIZI SUGGERITI<br />
ES. 1: trovate un modo per generare numeri tra 1 e N;<br />
---------------------------------------------------------------------------<br />
ES. 2: sfruttando quanto scoperto nell'esercizio n. 1, generate una colonna<br />
<strong>di</strong> risultati <strong>di</strong> una sche<strong>di</strong>na del totocalcio;<br />
---------------------------------------------------------------------------<br />
ES. 3: generate in modo casuale la sequenza dei nomi per le interrogazioni <strong>di</strong> una classe con solo quattro alunni che<br />
si chiamano Primo, Secondo, Terzo, e Quarto;<br />
SUGGERIMENTI: il problema sara' non estrarre piu' volte lo stesso nome; fate corrispondere ogni nome ad un<br />
numero; quando viene estratto quel numero, stampate il nome corrispondente e poi 'cancellate' quel nome<br />
copiando nella variabile che lo memorizza una 'X' al posto del nome; se generate un numero casuale che corrisponde<br />
ad una X (nome gia' estratto) dovete provare con un altro numero; per sapere quando fermarsi gestite<br />
un contatore che aumenterete solo quando non troverete una 'X'<br />
----------------------------------------------------------------------------- *) end.
ITE9. <strong>di</strong>fficoltà: bassa utilizza la random per estrarre numeri da 1 a N<br />
program estrai_1_N;<br />
uses newdelay, crt;<br />
var i,n: integer;<br />
begin<br />
clrscr;<br />
(* dato che random(N) estrae numeri tra 0 ed N-1, e' sufficiente aggiungere<br />
1 al risultato per avere numeri tra 0+1=1 e N-1+1=N<br />
*)<br />
write('Che N vuoi usare? -> ');<br />
readln(n);<br />
(* estraiamo 20 numeri da 1 a n *)<br />
for i:=1 to 20 do<br />
writeln( random(n)+1 );<br />
(* CONCLUSIONI<br />
La formula che genera un numero casuale tra 1 e N e':<br />
RANDOM(N) + 1<br />
*)<br />
readln;<br />
end.<br />
ITE10. <strong>di</strong>fficoltà: bassa utilizza la random per estrarre numeri tra A e B<br />
program estrai_AB;<br />
uses newdelay, crt;<br />
var i,a,b: integer;<br />
begin<br />
clrscr;<br />
(* random(N) estrae numeri tra 0 ed N-1; immaginiamo <strong>di</strong> voler estrarre numeri<br />
tra 5 e 12: e' sufficiente aggiungere a 5 un numero casuale tra 0 e 7<br />
intuitivamente, infatti, 5+0=5 e 5+7=12; in generale dovremo aggiungere<br />
al primo estremo (A) un numero casuale tra 0 e B-A<br />
*)<br />
write('Che A vuoi usare? -> ');<br />
readln(a);<br />
write('Che B vuoi usare? -> ');<br />
readln(b);<br />
(* estraiamo 20 numeri da A a B *)<br />
(* NOTA: attenzione! random(B-A) estrarrebbe numeri tra 0 e (B-A) - 1<br />
e' quin<strong>di</strong> necessario in<strong>di</strong>care random (B-A+1) *)<br />
for i:=1 to 20 do<br />
writeln( A + random(B-A+1) );<br />
readln;<br />
end.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 56<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004
ITE11. <strong>di</strong>fficoltà: bassa generazione <strong>di</strong> una colonna <strong>di</strong> una sche<strong>di</strong>na del totocalcio<br />
program colonna_totocalcio;<br />
uses newdelay, crt;<br />
var i: integer; risultato: integer;<br />
begin<br />
clrscr;<br />
randomize;<br />
(* non potendo estrarre la X, useremo un numero al suo posto: il 3; generiamo<br />
allora 13 numeri da 1 a 3 ... *)<br />
for i:=1 to 13 do<br />
begin<br />
risultato:= random(3)+1;<br />
if risultato = 3 then<br />
writeln('X')<br />
else<br />
writeln(risultato) (* 1 o 2 *)<br />
end;<br />
readln;<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 57<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
(* proviamo con una sche<strong>di</strong>na piu' realistica che privilegia gli 1 rispetto alle X e le X rispetto ai 2; si estrae un n. da<br />
1 a 13 e se e' tra 1 e 6 e' come se fosse uscito l'1, tra 7 e 10 l'X e tra 11 e 13 il 2 *)<br />
for i:=1 to 13 do<br />
begin<br />
risultato:= random(13)+1;<br />
if risultato
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 58<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
ITE13 <strong>di</strong>fficoltà: bassa far scrivere sullo schermo 5 volte la frase 'come programma mi sento un po' stupido ...';<br />
in<strong>di</strong>care all'inizio <strong>di</strong> ogni riga il suo numero progressivo (1, 2, 3 ...)<br />
program messaggi;<br />
uses<br />
newdelay, crt;<br />
const<br />
var<br />
i: integer; (* contatore ciclo *)<br />
begin<br />
clrscr; (* cancello lo schermo *)<br />
(* PRIMA SOLUZIONE *)<br />
writeln('PRIMA SOLUZIONE');<br />
for i := 1 to QUANTE_VOLTE do<br />
writeln('N. riga: ', i ,' come programma mi sento un po'' stupido ...');<br />
writeln('--------------------------');<br />
writeln;<br />
(* spesso nelle istruzioni <strong>di</strong> un ciclo for e' utile usare la variabile <strong>di</strong> controllo del ciclo stesso; in questo esempio, al<br />
momento <strong>di</strong> scrivere ogni riga la variabile <strong>di</strong> controllo i ha proprio il valore che serve stampare come numero<br />
progressivo <strong>di</strong> riga; infatti quando si stampa per la prima volta il messaggio il suo valore e' uno, quando si stampa<br />
il messaggio per la seconda volta il suo valore e' due ecc. *)<br />
(* SECONDA SOLUZIONE *)<br />
writeln('SECONDA SOLUZIONE');<br />
for i := 1 to QUANTE_VOLTE do<br />
begin<br />
write('N. riga: ');(* non va a capo ... *)<br />
write(i); (* non va a capo ... *)<br />
writeln(' come programma mi sento un po'' stupido ...');<br />
end;<br />
writeln('--------------------------');<br />
writeln;<br />
(* commenti: la seconda soluzione e' equivalente alla prima; notate come sia INDISPENSABILE aggiungere un<br />
begin ed un end per racchiudere ed in<strong>di</strong>care in modo preciso l'inizio e la fine del blocco <strong>di</strong> istruzioni che deve<br />
essere ripetuto; se infatti avessimo scritto:<br />
for i := 1 to QUANTE_VOLTE do<br />
write('N. riga: ');<br />
write(i);<br />
writeln(' come programma mi sento un po'' stupido ...');<br />
writeln('--------------------------');<br />
il compilatore non potrebbe in alcun modo capire quali sono le istruzioni da ripetere (per quale motivo fermarsi al<br />
primo write piuttosto che al secondo?); l'aver allineato i coman<strong>di</strong> write/writeln serve solo a rendere piu' leggibile il<br />
co<strong>di</strong>ce per noi ... ma per il compilatore significa comprendere nel ciclo for SOLO LA RIGA SUCCESSIVA:<br />
for i := 1 to QUANTE_VOLTE do<br />
write('N. riga: ');<br />
write(i);<br />
writeln(' come programma mi sento un po'' stupido ...');<br />
writeln('--------------------------');<br />
prova a togliere il begin/end e sperimenta personalmente che il ciclo
stamperebbe per <strong>di</strong>eci volte la scritta 'N. riga:' e solo una volta il<br />
resto !!<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 59<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
A COSA SERVE ALLORA AVER COMPLICATO IL CODICE ??<br />
RISPOSTA: AD AVER MAGGIOR CONTROLLO, COME MOSTRATO NELLA 'TERZA SOLUZIONE' *)<br />
(* TERZA SOLUZIONE<br />
questa volta si 'pretende' che il messaggio che precede il numero <strong>di</strong> riga<br />
sia scritto in rosso, il numero <strong>di</strong> riga in giallo e la scritta in verde<br />
*)<br />
writeln('TERZA SOLUZIONE');<br />
for i := 1 to QUANTE_VOLTE do<br />
begin<br />
textcolor(RED); write('N. riga: ');(* non va a capo ... *)<br />
textcolor(YELLOW); write(i); (* non va a capo ... *)<br />
textcolor(GREEN); writeln(' come programma mi sento un po'' stupido ...');<br />
end;<br />
writeln('--------------------------');<br />
writeln;<br />
(* commenti: solo costruendo il messaggio una porzione alla volta usando il write e' possibile intervallare i coman<strong>di</strong><br />
per la selezione del colore *)<br />
writeln('Programma terminato. Premere INVIO per continuare...');<br />
readln; (* per dare il tempo <strong>di</strong> leggere il messaggio *)<br />
end.
STRUTTURA ITERATIVA INDEFINITA – il ciclo repeat until<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 60<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Serve a ripetere una o più istruzioni fino al momento in cui la con<strong>di</strong>zione specificata dopo la parola chiave until<br />
<strong>di</strong>venta vera. Questo momento non è preve<strong>di</strong>bile da cui la denominazione ‘indefinita’ per questo tipo <strong>di</strong> ciclo.<br />
Ovviamente il ciclo terminerà solo se le istruzioni che vengano ripetute avranno come effetto il raggiungimento<br />
della con<strong>di</strong>zione <strong>di</strong> uscita. Se questo non dovesse avvenire il ciclo si ripeterebbe all'infinito (in gergo si <strong>di</strong>rebbe che<br />
il programma è ‘andato in loop infinito’ o, semplicemente,’ andato in loop’)<br />
Nell'esempio che segue si chiede all'utente <strong>di</strong> inserire un valore maggiore o uguale 10. Se l'utente sbaglia (inserendo<br />
un valore minore <strong>di</strong> 10) riceve un avviso ed il dato viene richiesto. Ecco come viene programmata questa<br />
operatività:<br />
program prova;<br />
var valore: integer;<br />
begin<br />
repeat<br />
writeln(‘Inserire un numero maggiore od uguale 10: ‘);<br />
readln (valore );<br />
if valore=10;<br />
… resto del programma …<br />
end.<br />
Caratteristiche del ciclo repeat:<br />
• il controllo che decide se ripetere o meno il blocco delle istruzioni si trova in fondo al ciclo ed è per questo<br />
che il ciclo repeat viene detto con controllo in coda; questo comporta che almeno una volta le istruzioni del<br />
ciclo vengono eseguite; qualche volta questo è un vantaggio ma altre volte no (quando è necessario controllare<br />
prima una con<strong>di</strong>zione senza la quale potrebbe essere ad<strong>di</strong>rittura deleterio eseguire le istruzioni); se c'è questa<br />
necessità probabilmente è meglio utilizzare l'altro tipo <strong>di</strong> struttura iterativa indefinita: il ciclo while (ve<strong>di</strong> più<br />
avanti)<br />
• il ciclo termina quando la con<strong>di</strong>zione <strong>di</strong> uscita è vera: per questo motivo si parla anche <strong>di</strong> ciclo con uscita per<br />
vero<br />
Uscita dal ciclo repeat con contatore.<br />
Fondamentalmente si utilizza una variabile per contare quante volte è stato eseguito il ciclo. In pratica possiamo<br />
emulare il comportamento <strong>di</strong> un ciclo for ma con tanta flessibilità in più: non siamo ad esempio costretti a contare <strong>di</strong><br />
uno in uno ma possiamo scegliere <strong>di</strong> quanto aumentare la variabile <strong>di</strong> controllo ad ogni esecuzione del ciclo; non<br />
siamo neppure costretti ad incrementarla <strong>di</strong> valori interi: potremmo ad esempio optare per incrementi <strong>di</strong> millesimi <strong>di</strong><br />
unità per calcoli <strong>di</strong> tipo scientifico. Ve<strong>di</strong>amo qualche esempio.<br />
i:=0;<br />
repeat<br />
i := i+1;<br />
writeln( i );<br />
until i=10;<br />
Sarebbe impossibile programmare la stessa operatività<br />
utilizzando il ciclo for.<br />
Chiaramente è impossibile sapere quante volte sarà necessario<br />
ripetere le operazioni a causa <strong>di</strong> ripetuti errori da parte<br />
dell'utente!<br />
Questo ciclo stampa sul video i numeri da uno a 10. Tutti d'accordo sul fatto che in situazioni <strong>di</strong> questo tipo sia meglio<br />
utilizzare il ciclo for: con quest'ultimo non è il programmatore a doversi ricordare che è necessario inizializzare la<br />
variabile <strong>di</strong> controllo (i:=0), incrementarla ad ogni ciclo (i:=i+1) e verificare se è arrivato il momento <strong>di</strong> interrompere il<br />
ciclo, ma è tutto svolto in automatico secondo le in<strong>di</strong>cazioni date dal programmatore sulla prima riga della struttura<br />
fordo.
i:=0;<br />
repeat<br />
i := i+3;<br />
writeln( i );<br />
until i=21;<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 61<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Ma ecco una prima applicazione interessante: vengono stampati tutti i multipli <strong>di</strong> 3 fino al 21. Non è necessario utilizzare<br />
l'operatore MOD per testare la <strong>di</strong>visibilità per tre <strong>di</strong> ogni singolo valore della variabile <strong>di</strong> controllo come avremmo fatto<br />
con un ciclo for: invece <strong>di</strong> incrementare la variabile <strong>di</strong> una unità alla volta, si aggiunge tre in<strong>di</strong>viduando ogni volta a colpo<br />
sicuro un nuovo multiplo!
Uscita dal ciclo repeat con domanda all'operatore.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 62<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Da usare in quei casi in cui è necessario far inserire da tastiera una sequenza <strong>di</strong> dati ed è l'operatore che deve<br />
decidere quando sono finiti. Immaginiamo un programma funzionante all'ingresso <strong>di</strong> una mostra che deve servire a<br />
richiedere i dati a grafici <strong>di</strong> ogni visitatore ed a stampare per ciascuno un cartellino <strong>di</strong> riconoscimento. Non è<br />
ovviamente possibile utilizzare un ciclo for: impossibile, infatti, sapere a priori quanti saranno i visitatori… Per lo<br />
stesso motivo non è possibile utilizzare un ciclo repeat con uscita controllata da un contatore. È invece sufficiente<br />
organizzare un ciclo repeat in cui l'uscita venga decisa dalla risposta data dall'operatore ad una domanda posta dal<br />
computer come ultimo per azione del ciclo:<br />
repeat<br />
writeln(‘Inserire il nominativo del visitatore: ‘);<br />
readln(nome_cognome);<br />
writeln(‘Inserire la data <strong>di</strong> nascita del visitatore: ‘);<br />
readln(data_nascita);<br />
<br />
writeln(Sono terminati i visitatori?);<br />
readln( risp );<br />
until risp=;<br />
Uscita dal ciclo repeat con inserimento <strong>di</strong> un valore convenzionale.<br />
È un miglioramento della tecnica precedente. Quest'ultima, infatti, costringe l'operatore a rispondere anche molte<br />
volte alla stessa domanda per <strong>di</strong>re quando sono terminati i dati. Il ‘trucco’ consiste nello stabilire che il ciclo deve<br />
terminare quando alla richiesta <strong>di</strong> uno dei dati previsti viene inserito un valore speciale. Riprendendo l'esempio della<br />
mostra potremmo decidere che il ciclo termina quando come nominativo del visitatore viene inserita la parola<br />
NESSUNO. Ecco come potrebbe apparire il co<strong>di</strong>ce:<br />
repeat<br />
writeln(‘Inserire il nominativo del visitatore (NESSUNO per terminare): ‘);<br />
readln(nome_cognome);<br />
if nome_cognomeNESSUNO then<br />
begin<br />
writeln(‘Inserire la data <strong>di</strong> nascita del visitatore: ‘);<br />
readln(data_nascita);<br />
<br />
end;<br />
until nome_cognome=NESSUNO;<br />
NOTA: la con<strong>di</strong>zione <strong>di</strong> uscita da un ciclo repeat può essere composta, tipo quelle già viste per la struttura selettiva.<br />
L'esempio seguente richiede all'operatore un valore compreso nell'intervallo 1-100:<br />
repeat<br />
writeln(‘Inserire un valore compreso tra 1 e 100: ‘);<br />
readln(valore);<br />
until (valore>=1) and (valore
STRUTTURA ITERATIVA INDEFINITA – il ciclo while<br />
Si <strong>di</strong>stingue dal repeat per queste caratteristiche:<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 63<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
• il controllo che decide la terminazione ciclo è fatto prima delle istruzioni del ciclo stesso: per questo motivo<br />
viene anche in<strong>di</strong>cato come ciclo con controllo in testa; se la con<strong>di</strong>zione non è sod<strong>di</strong>sfatta fin dall'inizio il ciclo<br />
potrebbe non cominciare neppure, cioè le sue istruzioni non essere eseguite neanche una volta;<br />
• il ciclo termina quando la con<strong>di</strong>zione è falsa: si parla infatti <strong>di</strong> ciclo con uscita per falso;<br />
Ecco la sintassi:<br />
while con<strong>di</strong>zione do oppure while con<strong>di</strong>zione do<br />
istruzioneSingola begin<br />
istruzione1;<br />
istruzione1;<br />
…<br />
istruzioneN;<br />
end<br />
In generale potremmo <strong>di</strong>re che le tecniche per decidere la terminazione del ciclo viste per il ciclo repeat vanno bene<br />
anche per questo ciclo con i dovuti adattamenti resi necessari dalla <strong>di</strong>fferente logica:<br />
Stampa dei numeri da 1 a 10. Quando stampa il 10 la con<strong>di</strong>zione <strong>di</strong>venta falsa (10 non è minore <strong>di</strong> 10 ma uguale!)<br />
i:=0;<br />
while i
end;<br />
end (* del while *)<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 64<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 65<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
ESERCIZI RIEPILOGATIVI SUI CICLI REPEAT E WHILE<br />
ITE14 <strong>di</strong>fficoltà: me<strong>di</strong>a Calcolo delle prime N potenze <strong>di</strong> 2; deve essere 0
ITE15. <strong>di</strong>fficoltà: bassa Calcolo della me<strong>di</strong>a <strong>di</strong> N numeri inseriti dall'utente<br />
program me<strong>di</strong>aNnumeri;<br />
var<br />
n: integer; (* quanti numeri inserire *)<br />
numero: integer;<br />
somma: real ; (* la somma può superare 32767 ... *)<br />
me<strong>di</strong>a: real;<br />
i: integer; (* per i cicli *)<br />
begin<br />
writeln('CALCOLO MEDIA');<br />
writeln;<br />
(* continuo a chiedere un N finche' sod<strong>di</strong>sfa ... *)<br />
repeat<br />
writeln('quanti numeri vuoi inserire? (N>0): ');<br />
readln(n);<br />
if n0;<br />
(* VEDIAMO PRIMA LA SOLUZIONE CON IL REPEAT ...*)<br />
somma:=0; i:=1;<br />
writeln('SOLUZIONE CON IL REPEAT');<br />
repeat<br />
write('Inserire un numero (',i,') -> ' );<br />
readln(numero);<br />
somma:=somma+numero;<br />
i:=i+1;<br />
until i>n;<br />
me<strong>di</strong>a:=somma/n;<br />
writeln('me<strong>di</strong>a: ',me<strong>di</strong>a:6:2);<br />
readln;<br />
(* VEDIAMO POI LA SOLUZIONE CON IL WHILE ...*)<br />
somma:=0; i:=1;<br />
writeln('SOLUZIONE CON IL WHILE');<br />
while i
(* VEDIAMO INFINE LA SOLUZIONE CON IL FOR ...*)<br />
writeln('SOLUZIONE CON IL FOR');<br />
somma:=0;<br />
for i:=1 to n do<br />
begin<br />
write('Inserire un numero (',i,') -> ' );<br />
readln(numero);<br />
somma:=somma+numero;<br />
end;<br />
me<strong>di</strong>a:=somma/n;<br />
writeln('me<strong>di</strong>a: ',me<strong>di</strong>a:6:2);<br />
readln;<br />
end.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 67<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
ITE16. <strong>di</strong>fficoltà: me<strong>di</strong>a Dato in input un numero intero N, sommare i primi N numeri <strong>di</strong>spari e<br />
verificare che tale somma e' uguale al quadrato <strong>di</strong> N.<br />
program som_<strong>di</strong>sp;<br />
uses newdelay, crt;<br />
var<br />
i,quanti: integer; numero,somma: real;<br />
begin<br />
clrscr;<br />
(* il controllo che segue*sembra* efficace: provate con 35000;<br />
e poi con 70000: l'errore non viene rilevato; perche' ?? *)<br />
repeat<br />
writeln('Quanti numeri <strong>di</strong>spari vuoi considerare? (da 0 a ',MAXINT,')');<br />
readln(quanti);<br />
if (quantiMAXINT) then<br />
writeln('Errato, ripetere l''inserimento')<br />
until (quanti>=0) and (quanti
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 68<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
ITE17. <strong>di</strong>fficoltà: me<strong>di</strong>a Un giro turistico e' fatto <strong>di</strong> N tappe, delle quali si introducono da tastiera<br />
il nome della citta' <strong>di</strong> arrivo e i km percorsi. Calcolare il percorso totale e il percorso me<strong>di</strong>o delle tappe<br />
program tappe;<br />
uses newdelay, crt; var<br />
i,quante_tappe: integer;<br />
km_tappa,somma_km: real;<br />
citta: string;<br />
begin<br />
clrscr;<br />
repeat<br />
writeln('Quante sono le tappe? (da 0 a ',MAXINT,')');<br />
readln(quante_tappe);<br />
if (quante_tappeMAXINT) then<br />
writeln('Errato, ripetere l''inserimento')<br />
until (quante_tappe>=0) and (quante_tappe=0) and (km_tappa
COSA SONO I FLOW CHART<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 69<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
I flow chart sono schemi che descrivono visivamente come procede l’esecuzione <strong>di</strong> un programma. Essi non sono<br />
legati ad uno specifico linguaggio: dato un flow chart, il programmatore può poi usare un qualsiasi linguaggio <strong>di</strong><br />
programmazione (si tratta, per così <strong>di</strong>re, <strong>di</strong> un linguaggio visuale comprensibile a tutti i programmatori). Il flow<br />
chart aiuta anche il programmatore a descrivere correttamente un algoritmo (il proce<strong>di</strong>mento risolutivo <strong>di</strong> un<br />
problema).<br />
Ogni tipo <strong>di</strong> istruzione che si può inserire in un programma ha un suo simbolo ed ognuna delle tre strutture<br />
fondamentali della programmazione (sequenza, selezione ed iterazione) può essere rappresentata. Esistono anche<br />
simboli speciali (inizio programma, fine programma ecc.) che non rappresentano istruzioni vere e proprie ma che<br />
sono utili per la costruzione del flow chart.<br />
I PRINCIPALI SIMBOLI<br />
Inizio programma (i simboli sono equivalenti) Fine programma (i simboli sono<br />
equivalenti)<br />
puoi ricordare solo un simbolo a scelta … puoi ricordare solo un simbolo a<br />
scelta …<br />
inizio start S I<br />
fine end F<br />
‘S’ sta per Start, ‘i’ sta per Inizio ‘F’ sta per fine, ‘E’ sta per End<br />
Assegnamento o altre istruzioni generiche Scrittura <strong>di</strong> un valore sul video (i simboli<br />
sono equivalenti)<br />
(esempio: dai ad x il valore 8) puoi ricordare solo un simbolo a<br />
scelta …<br />
x 8 Scrivi ‘ciao’ S ‘ciao’<br />
(esempio <strong>di</strong> istruzione generica)<br />
attiva allarme<br />
‘S’ sta per scrivi, ‘W’ sta per Write<br />
Lettura <strong>di</strong> un valore scritto con la tastiera e memorizzato nella variabile in<strong>di</strong>cata<br />
(i simboli che seguono sono equivalenti: ricordane uno a tua scelta ..)<br />
Esempio: leggere dove abita una persona e memorizzare l’informazione nella variabile in<strong>di</strong>rizzo<br />
Leggi in<strong>di</strong>rizzo L in<strong>di</strong>rizzo R in<strong>di</strong>rizzo<br />
‘L’ sta per Leggi, ‘R’ sta per Read<br />
E<br />
W ‘ciao’
ALTRI ESEMPI<br />
x y x y - 1<br />
dai ad x lo stesso valore <strong>di</strong> y dai ad x il valore <strong>di</strong> y <strong>di</strong>minuito <strong>di</strong> 1<br />
x (5 – 2) * (4 + 3)<br />
dai ad x il valore dell’espressione (5 – 2) * (4 + 3)<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 70<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
I simboli visti fino ad ora servono per istruzioni singole. Ve<strong>di</strong>amo ora come si rappresentano le strutture<br />
fondamentali della programmazione (sequenza, selezione, iterazione).<br />
Sequenza<br />
S x, y<br />
scrivi sul video prima il valore della<br />
variabile x e poi quello della variabile y<br />
Per in<strong>di</strong>care che due istruzioni vanno eseguite una dopo l’altra si mettono i loro simboli uno sotto l’altro<br />
collegandoli con una freccia. Ecco un esempio.<br />
Calcoliamo quante settimane ci sono in un anno <strong>di</strong>videndo il<br />
numero dei giorni per 7.<br />
Memorizziamo prima il risultato della <strong>di</strong>visione nella<br />
variabile quante.<br />
Poi scriviamo un messaggio sul video che ‘annuncia’ il<br />
risultato<br />
Infine scriviamo il risultato, cioè il valore attuale della<br />
variabile quante.<br />
Il senso della freccia in<strong>di</strong>ca il flusso <strong>di</strong> esecuzione.<br />
L cognome, nome<br />
accetta dalla tastiera un primo valore che<br />
memorizzerai nella variabile ‘cognome’ e poi un<br />
secondo valore che memorizzerai nella variabile<br />
‘nome’<br />
quante
Selezione (decisione, scelta, alternativa)<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 71<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Il solo fatto <strong>di</strong> poter in<strong>di</strong>care un’istruzione dopo l’altra (sequenza) non ci consentirebbe <strong>di</strong> sviluppare programma<br />
interessanti. Ci sono innumerevoli situazioni in cui è necessario fare un ‘controllo’ ed agire <strong>di</strong> conseguenza.<br />
Esempi<br />
Se l’anno è bisestile allora considera febbraio con 29 giorni, altrimenti consideralo con 28.<br />
Se l’età è minore <strong>di</strong> 18 allora applica sconto, altrimenti applica prezzo pieno.<br />
Se la temperatura supera 37 C allora fai suonare l’allarme.<br />
Come avrete osservato, si controlla una con<strong>di</strong>zione (anno bisestile ?, età minore <strong>di</strong> 18 ?, temperatura supera 37 ?)<br />
che può risultare vera o falsa. In qualche caso (primi due esempi) ci sono operazioni (<strong>di</strong>verse) sia nel caso la<br />
con<strong>di</strong>zione risulti vera sia nel caso risulti falsa. E’ però possibile (terzo esempio) che nel caso la con<strong>di</strong>zione sia falsa<br />
non ci sia nulla da fare.<br />
Ecco come si rappresenta in un flow chart la struttura selettiva:<br />
Se l’anno è bisestile allora<br />
considera febbraio con 29 giorni<br />
altrimenti<br />
consideralo con 28<br />
questo tipo <strong>di</strong> selezione<br />
è detto a 2 vie<br />
La con<strong>di</strong>zione deve essere scritta all’interno del rombo. Il flusso<br />
<strong>di</strong> esecuzione si <strong>di</strong>vide a seconda del risultato del controllo<br />
per Vero e F sta per Falso) e solo la strada ‘a destra’ o ‘a<br />
sinistra’ viene percorsa.<br />
F<br />
(V sta<br />
Le istruzioni nella sezione ‘vera’ (o ‘falsa’) possono essere anche molte e non una sola come nel nostro esempio.<br />
Quando le istruzioni da eseguire a con<strong>di</strong>zione vera (o falsa) sono terminate il flusso si ricongiunge usando il simbolo<br />
chiamato connettore.<br />
Attenzione: nel rombo potete mettere solo con<strong>di</strong>zioni, non NO assegnamenti! !!<br />
Sarebbe quin<strong>di</strong> un errore grave il seguente:<br />
x
Se l’età è minore <strong>di</strong> 18 allora<br />
applica sconto<br />
altrimenti<br />
applica prezzo pieno.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 72<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Nota: nel flow chart immaginiamo che<br />
le variabili eta e prezzo abbiano già un<br />
valore valido e che i calcoli in<strong>di</strong>cati siano quin<strong>di</strong> fattibili. Lo sconto (10%) viene calcolato <strong>di</strong>videndo il prezzo pieno<br />
per 100 (calcolando così l’1%) e moltiplicando il risultato per 10 (ottenendo il 10%).<br />
Se la temperatura supera 37 C allora<br />
fai suonare l’allarme.<br />
Come potete vedere se non c’è nulla che deve essere fatto quando la<br />
con<strong>di</strong>zione è falsa, si congiunge la parte ‘falso’ <strong>di</strong>rettamente con il<br />
connettore.<br />
V F<br />
eta < 18<br />
sconto 37<br />
V<br />
attiva allarme<br />
F<br />
questo tipo <strong>di</strong> selezione<br />
è detto a una via<br />
NO !!<br />
ERRORE !!!<br />
V<br />
temp > 37<br />
V<br />
attiva allarme<br />
S prezzo<br />
F<br />
F
Selezione a molte vie<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 73<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Esiste anche un’utile variante che prevede molte vie a seconda del valore <strong>di</strong> una variabile. Immaginiamo che la<br />
variabile mese contenga un valore da 1 a 12 che rappresenta uno dei mesi dell’anno. Di nuovo, vorremmo sapere<br />
quanti giorni considerare. Vi ricordo che i mesi con 31 giorni sono 1 (gennaio), 3 (marzo), 5 (ecc.), 7, 8, 10, 12;<br />
quelli con 30 giorni sono 4 (aprile), 6, 9, 11; immaginiamo per semplicità che febbraio (2) ne abbia sempre 28.<br />
giorni
SEL2. Inserita un'età', <strong>di</strong>re se siamo in<br />
presenza <strong>di</strong> un maggiorenne o <strong>di</strong> un<br />
minorenne; controllare anche eventuali<br />
errori <strong>di</strong> inserimento da parte dell'utente<br />
Prima soluzione: ipotizzando un età<br />
massima <strong>di</strong> 120 anni, controlliamo che<br />
l’età inserita non sia al <strong>di</strong> fuori<br />
dell’intervallo 0 (neonato) – 120.<br />
NOTA: siate coerenti con l’utilizzo delle<br />
etichette ‘V’ e ‘F’: io ho scelto <strong>di</strong><br />
proseguire per vero (V) sempre a destra e<br />
per falso (F) sempre a sinistra.<br />
S 'minorenne'<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 74<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
eta>=18<br />
S 'maggiorenne'<br />
NOTA: è importante <strong>di</strong>fferenziare con un<br />
pallino-connettore separato le uscite dalle<br />
due strutture selettive. Il seguente flow<br />
chart è quin<strong>di</strong> sbagliato. Non riusciremmo<br />
infatti a tradurlo nel corrispondente<br />
programma Pascal (si accavallerebbero gli<br />
‘end’ dei blocchi ‘begin … end’ delle<br />
parti ‘then’ ed ‘else’ dei due ‘if’ !!<br />
S 'minorenne'<br />
L eta<br />
F V<br />
eta120<br />
eta>=18<br />
F<br />
F<br />
S 'maggiorenne'<br />
NO !!<br />
L eta<br />
F V<br />
eta120<br />
V<br />
V<br />
S 'errore'<br />
S 'errore'
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 75<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SEL2 bis. Con l’algoritmo precedente, quando l’utente sbaglia ad inserire un valore per l’età non si sa se per un<br />
valore negativo o maggiore <strong>di</strong> 120: si sa solo che ha commesso un errore ma non quale. Il seguente flow chart<br />
rappresenta un algoritmo che tiene conto anche <strong>di</strong> questo particolare, <strong>di</strong>fferenziando i controlli sui possibili errori:<br />
Struttura a ‘cascata’<br />
S 'minorenne'<br />
F<br />
eta>=18<br />
S 'maggiorenne'<br />
SEL3bis. Inserito un numero, <strong>di</strong>re se e' pari o <strong>di</strong>spari<br />
NOTA: si sta immaginando che in ogni linguaggio <strong>di</strong><br />
programmazione esista un operatore per il calcolo del<br />
modulo: MOD.<br />
E’ comunque accettabile (mi sto rivolgendo ai miei<br />
alunni) usare gli operatori del Pascal.<br />
V<br />
F<br />
eta>120<br />
V<br />
F<br />
S 'non puoi avere più!'<br />
'<strong>di</strong> 120 anni !!<br />
F<br />
S '<strong>di</strong>spari'<br />
L eta<br />
eta
SEL4. Inserito un carattere, <strong>di</strong>re se e' una vocale od una consonante.<br />
Soluzione 1 (non si fa <strong>di</strong>stinzione tra le vocali)<br />
F<br />
S 'consonante'<br />
L c<br />
c
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 77<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SEL5. Inseriti A, B e C <strong>di</strong>re se B e' compreso tra A e C; in pratica si controlla se B appartiene all'intervallo [A,C]<br />
(ripetizione, cicli)<br />
Iterazione<br />
Pur avendo a <strong>di</strong>sposizione sequenza e<br />
iterazione rimangono molte le situazioni ‘intrattabili’ con questi strumenti. Consideriamo infatti questo problema<br />
all’apparenza molto semplice: stampare i numeri da 1 a 10000. Certamente la soluzione <strong>di</strong> usare per 10000 volte<br />
l’istruzione <strong>di</strong> scrittura per stampare ogni numero non è molto praticabile …<br />
I<br />
S 1<br />
S 2<br />
…<br />
S 10000<br />
F<br />
F<br />
S 'intervallo'<br />
'impossibioe'<br />
L a, c<br />
a =a and b
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 78<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Diversamente procederà all’infinito (come potrebbe servire per il controllo <strong>di</strong> un processo industriale).<br />
Questo tipo <strong>di</strong> ciclo è detto ‘con ripetizione per falso’, o a ‘uscita per vero’ ed ancora ‘con controllo in coda’ (cioè<br />
alla fine del ciclo).<br />
Notiamo ancora: con questo tipo <strong>di</strong> ciclo le istruzioni vengono eseguite almeno una volta.<br />
Ve<strong>di</strong>amo il flow chart completo per la stampa dei numeri da 1 a 10000<br />
F<br />
I<br />
Num
Esercizi risolti sulla struttura iterativa<br />
ITE1: stampa dei primi N numeri naturali,<br />
con N letto da tastiera.<br />
L n<br />
i
ITE3. Stampa dei primi N numeri naturali,<br />
con N letto da tastiera; a fianco <strong>di</strong> ciascun<br />
numero in<strong>di</strong>care se e' pari o <strong>di</strong>spari<br />
F<br />
S '<strong>di</strong>spari'<br />
L n<br />
i
ITE5. Far inserire una sequenza <strong>di</strong> numeri da tastiera.<br />
La sequenza si intende terminata quando viene<br />
inserito il numero 0. Si deve calcolare e visualizzare<br />
la somma <strong>di</strong> tutti i numeri inseriti.<br />
F<br />
totale
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 82<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
1. Chiedere da tastiera l’inserimento <strong>di</strong> una sequenza <strong>di</strong> date. Ad ogni data, espressa nella forma giorno mese ed<br />
anno (‘g’, ‘m’ ed ‘a’ nel flow chart), aggiungere un giorno e stampare la data risultante. E’ l’utente del programma<br />
che <strong>di</strong>ce quando sono finite le date (rispondendo ad una opportuna domanda posta dal programma). Ipotesi<br />
semplificativa: considerare Febbraio sempre da 28 giorni. Traccia: è necessario prestare attenzione alle date che<br />
corrispondono a fine mese o anno: nella data risultante cambierà sicuramente il mese e forse anche l’anno …<br />
g>31<br />
V<br />
g
2. Stampa dei numeri da B ad A con B>A.<br />
Controllare che B sia maggiore <strong>di</strong> A<br />
prima <strong>di</strong> iniziare.<br />
L A<br />
L B<br />
B > A<br />
V<br />
s B<br />
B
I SOTTOPROGRAMMI - riutilizzare per sopravvivere !<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 84<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Se un programmatore professionista dovesse sempre ricominciare da zero nello scrivere i programmi, <strong>di</strong>venterebbe<br />
presto un ... accattone professionista !K. Ad esempio, se tutte le volte che fosse necessario lo stesso calcolo<br />
complesso dovessimo riscrivere le sue istruzioni, i programmi <strong>di</strong>venterebbero più lunghi del necessario, più <strong>di</strong>fficili da<br />
leggere, sprecheremmo più tempo e rischieremmo ogni volta <strong>di</strong> commettere errori <strong>di</strong>versi (e sempre per risolvere lo<br />
stesso problema).<br />
Una pericolosa illusione ...<br />
L’utilizzo del ‘copia/incolla’ per ricopiare le righe farebbe risparmiare solo fatica ma ci esporrebbe a gravi rischi ... Per<br />
convincercene, immaginiamo che le istruzioni siano una cinquantina e <strong>di</strong> averle copiate/incollate più volte in <strong>di</strong>versi<br />
programmi. Cosa accadrebbe se scoprissimo un errore nelle righe copiate così tante volte? Dovremmo mo<strong>di</strong>ficare<br />
tutte le copie delle cinquanta istruzioni incriminate … e guai a <strong>di</strong>menticarcene una !<br />
La soluzione: i sottoprogrammi<br />
Per fortuna una soluzione c’è e la sapete già usare. Lo avete fatto tutte le volte che avete usato coman<strong>di</strong> come<br />
sqrt(9) o clrscr. Questi coman<strong>di</strong> richiamano un programma secondario (si parla infatti <strong>di</strong> sottoprogrammi) che<br />
svolgono il compito previsto. Nel caso della sqrt il compito è il calcolo della ra<strong>di</strong>ce quadrata <strong>di</strong> un numero (quello che<br />
deve essere specificato tra parentesi dopo il nome del comando); nel caso <strong>di</strong> clrscr non deve essere calcolato nulla<br />
ma semplicemente cancellato il video. Dopo l’esecuzione del comando il programma principale continua la sua<br />
<strong>elaborazione</strong>.<br />
ESEMPIO (calcolo della ra<strong>di</strong>ce quadrata <strong>di</strong> un numero)<br />
Program prova;<br />
var<br />
x: real; (* il numero <strong>di</strong> cui si vuole la ra<strong>di</strong>ce quadrata *)<br />
ra<strong>di</strong>ce_quadrata: real; (* il risultato da calcolare *)<br />
begin<br />
writeln(‘Inserisci un numero ed io calcolero’’ la sua ra<strong>di</strong>ce quadrata: ’);<br />
readln(x);<br />
ra<strong>di</strong>ce_quadrata:=sqrt(x);<br />
writeln(‘risultato: ‘, ra<strong>di</strong>ce_quadrata );<br />
readln;<br />
end.<br />
per sod<strong>di</strong>sfare la richiesta della riga precedente il programma principale si ‘ferma’ e<br />
richiama il programma secondario sqrt; quando si riceve il risultato, il programma<br />
principale continua con la riga qui sotto stampando il valore calcolato *)<br />
Tipi <strong>di</strong> sottoprogrammi: procedure e funzioni<br />
Quando un sottoprogramma restituisce al programma principale un valore (come sqrt con la ra<strong>di</strong>ce quadrata) si<br />
chiama funzione (function). Quando non restituisce nulla si chiama procedura (procedure). Un esempio <strong>di</strong> procedure<br />
è rappresentata dal comando clrscr: non restituisce nulla programma principale ma si limita a cancellare il video.<br />
I parametri<br />
Un sottoprogramma può aver bisogno <strong>di</strong> uno o più dati per portare a termine il suo compito. Ad esempio sqrt ha<br />
bisogno del numero <strong>di</strong> cui si vuole calcolare la ra<strong>di</strong>ce. Clrscr non ha invece bisogno <strong>di</strong> alcun dato. Questi dati si<br />
chiamano parametri e vengono in<strong>di</strong>cati tra parentesi dopo il nome del sottoprogramma.<br />
Nel comando sqrt( 9 ) il parametro è costituito dalla costante numerica 9. Nel comando sqrt (12+3) il parametro è<br />
costituito dal risultato dell’espressione 12+3. Nel comando sqrt(X) il parametro è il valore contenuto nella variabile X<br />
al momento dell’uso del comando.<br />
Se i parametri sono più d’uno vanno separati con la virgola. Ad esempio il comando gotoxy(10, 5) sposta il punto <strong>di</strong><br />
scrittura sul video alla colonna 10, riga 5. Il primo parametro è il numero della colonna, il secondo il numero della<br />
riga. NOTA: per provare il comando gotoxy dovete in<strong>di</strong>care uses crt come spiegato alla pagina successiva.
Abbiamo due possibilità:<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 85<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Interessante… ma quali e quanti sottoprogrammi abbiamo a <strong>di</strong>sposizione ?<br />
Usare un sottoprogramma scritto da altri. I sottoprogrammi riutilizzabili sono spesso raccolti in librerie. Una libreria è<br />
un file (archivio) normalmente presente sul <strong>di</strong>sco e contiene blocchi <strong>di</strong> co<strong>di</strong>ce già tradotto in linguaggio macchina che<br />
possono essere a comando incorporati in un programma. Alcune librerie sono sempre fornite insieme all’ambiente <strong>di</strong><br />
sviluppo che si decide <strong>di</strong> usare (sia che si tratti <strong>di</strong> un prodotto commerciale sia che si tratti <strong>di</strong> un prodotto gratuito<br />
come accade per l’open source).<br />
Ad esempio con il Turbo Pascal sono <strong>di</strong>sponibili un buon numero <strong>di</strong> librerie insieme al prodotto acquistato. Il Turbo<br />
Pascal chiama le librerie unit (unità <strong>di</strong> programmazione). NOTA: per un elenco completo <strong>di</strong> queste librerie consultate<br />
l'help in linea del turbo Pascal (lo potete attivare con la combinazione SHIFT-F1, cercando poi sotto la lettera S il<br />
paragrafo Standard Unit).<br />
La funzione sqrt ed altri sottoprogrammi <strong>di</strong> uso assai comune sono memorizzate in una libreria (la System) per la<br />
quale non è neppure necessario in<strong>di</strong>carne l’uso: viene automaticamente inclusa con il resto del programma.<br />
Il comando clrscr e gotoxy() invece sono due dei sottoprogrammi <strong>di</strong>sponibili nella unit crt. Per questa ed altre unit<br />
(ed in particolare per tutte quelle create ed aggiunte dai programmatori) è invece necessario in<strong>di</strong>care il loro nome<br />
nella sezione uses del programma. Esistono unit con sottoprogrammi per fare grafica (graph) e TANTE altre cose che<br />
scopriremo insieme un poco alla voltaJ.<br />
program prova;<br />
begin<br />
writeln( sqrt(9) )<br />
end.<br />
Il co<strong>di</strong>ce della sqrt<br />
viene estratto dalla<br />
libreria ed<br />
incorporato nel<br />
programma finale<br />
Sqrt<br />
random<br />
+ =<br />
Programma eseguibile finale<br />
traduzione in linguaggio macchina<br />
delle istruzioni scritte dal<br />
programmatore<br />
In commercio sono poi <strong>di</strong>sponibili parecchie librerie per i compiti più svariati. Se una libreria è fatta bene vale mille<br />
volte i sol<strong>di</strong> che costa: sviluppare software costa MOLTO! Su Internet è facile trovare ed acquistare queste librerie.<br />
Su internet, soprattutto sui siti de<strong>di</strong>cati alla programmazione si trovano anche MOLTE librerie gratuite.<br />
….<br />
….<br />
….<br />
….<br />
….<br />
Ecc.<br />
Scriversi i sottoprogrammi da soli raggruppandoli poi in librerie (che possono essere scambiate, rese <strong>di</strong>sponibili<br />
gratuitamente su Internet o vendute). Un sottoprogramma viene scritto <strong>di</strong> solito prima all’interno <strong>di</strong> un programma<br />
normale e solo in un secondo tempo viene aggiunto ad una libreria.<br />
+<br />
co<strong>di</strong>ce della sqrt prelevato dalla<br />
libreria
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 86<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Come si fa in Turbo Pascal ad usare un sottoprogramma <strong>di</strong> una libreria?<br />
Prima della sezione VAR del programma si mette una sezione intitolata USES e si elencano, separati da virgole, i nomi<br />
delle unit che contengono i sottoprogrammi che si vogliono utilizzare. Come <strong>di</strong>cevo prima, i sottoprogrammi clrscr e<br />
gotoxy() sono contenuti nella unit crt e quin<strong>di</strong> per usarli:<br />
program prova;<br />
uses crt;<br />
begin<br />
clrscr;<br />
gotoxy(10,5);<br />
writeln(‘sto scrivendo alla colonna 10, quinta riga dello schermo …);<br />
readln;<br />
end.<br />
Prima <strong>di</strong> esaminare in dettaglio come si scrivono i sottoprogrammi con il turbo Pascal dovete essere convinti della<br />
loro utilità.<br />
Riassunto dei benefici dell’uso dei sottoprogrammi:<br />
Risparmio <strong>di</strong> tempo e <strong>di</strong> spazio: le istruzioni che corrispondono al sottoprogramma sono scritte una volta sola,<br />
tradotte ed inserite in una libreria; il programmatore non le deve riscrivere (o copiare/incollare) ma può richiamarle<br />
semplicemente scrivendo il nome del sottoprogramma ed in<strong>di</strong>cando tra parentesi le costanti o le variabili che<br />
contengono i valori necessari al funzionamento del sottoprogramma (i parametri).<br />
Non incorre nei rischi del copia/incolla:<br />
se viene scoperto un errore nel sottoprogramma: si mo<strong>di</strong>ficano solo le istruzioni <strong>di</strong> quest'ultimo e si aggiorna poi la<br />
libreria con la nuova versione; è poi sufficiente ricompilare/linkare i programmi che ne facevano uso (questi ultimi<br />
non sono stati mo<strong>di</strong>ficati!);<br />
se scritto in modo corretto il sottoprogramma non interferisce con il resto del programma: un sottoprogramma può<br />
avere le sue variabili per i cicli e per tutto il resto; ha, in definitiva, un suo spazio <strong>di</strong> lavoro che lo rende in<strong>di</strong>pendente;<br />
riceverete maggiori dettagli più avanti;<br />
Rende il programma più comprensibile: è palese come il co<strong>di</strong>ce sulla destra, a <strong>di</strong>fferenza <strong>di</strong> quello a sinistra,<br />
comunichi al lettore imme<strong>di</strong>atamente lo scopo per cui è stato scritto:<br />
const<br />
pi_greco=3.14;<br />
var<br />
x: real;<br />
begin<br />
writeln( 4/3 * pi_greco * 12 * 12 * 12)<br />
end.<br />
writeln( VolumeSfera(12) )<br />
Infatti pochi sanno riconoscere nella formula a sinistra il calcolo del volume <strong>di</strong> una sfera <strong>di</strong> raggio 12 ... Nel riquadro<br />
a destra è invece palese che si sta invocando una funzione dal nome molto chiaro ed è altrettanto evidente che il<br />
valore comunicato tra parentesi è il raggio della sfera. In ogni caso le modalità d'uso <strong>di</strong> ogni sottoprogramma sono<br />
documentate nel manuale tecnico che accompagna sempre una libreria.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 87<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Diminuisce la complessità della scrittura dei programmi: un programma può essere assemblato a partire da<br />
sottoprogrammi più semplici da trattare. E’ anche più facile trovare gli errori concentrandosi solo sulle righe <strong>di</strong> un<br />
sottoprogramma per volta. Inoltre rende possibile sviluppare uno stesso programma insieme ad altri programmatori<br />
senza che questi interferiscano tra loro: ad ogni programmatore viene assegnato lo sviluppo <strong>di</strong> un certo numero <strong>di</strong><br />
sottoprogrammi che vengono poi fusi assieme.<br />
Nessun team <strong>di</strong> sviluppo si sognerebbe oggi <strong>di</strong> sviluppare un progetto software senza adottare questo tipo <strong>di</strong><br />
programmazione detto modulare. Questi concetti sono stati poi ampliati nella programmazione ad oggetti (OOP)<br />
che sarà materia <strong>di</strong> stu<strong>di</strong>o in quarta/quinta.<br />
Mentre vengono sviluppati e provati i sottoprogrammi vengono <strong>di</strong> solito scritti <strong>di</strong>rettamente all'interno <strong>di</strong> un<br />
programma che li usa. È solo in un secondo momento, quando il sottoprogramma è stato perfettamente testato, <strong>di</strong><br />
quest'ultimo viene inserito in una libreria.<br />
Qui a lato ho evidenziato come in un programma i sottoprogrammi<br />
vengono scritti dopo la sezione VAR e devono terminare prima del begin<br />
<strong>di</strong> inizio programma. Non c'è limite al numero <strong>di</strong> sottoprogrammi che si<br />
possono scrivere (se non quelli imposti dall'ambiente <strong>di</strong> sviluppo per la<br />
scrittura del programma principale).<br />
Come <strong>di</strong>cevo, questo facilita la scrittura dei sottoprogrammi ma ne riduce<br />
la facilità <strong>di</strong> riutilizzo (dovremmo ricorrere ad un copia/incolla per usarli in<br />
altri programmi), pur mantenendo inalterati tutti gli altri benefici. Una<br />
volta che il sottoprogramma è stato ben testato lo si può togliere dal<br />
programma principale ed includerlo in una libreria, recuperando *tutti* i<br />
benefici <strong>di</strong>scussi prima.<br />
Anche noi procederemo così, visto che l’obiettivo è imparare a scrivere i<br />
sottoprogrammi.<br />
NOTA: nel programma principale possiamo sfruttare un qualsiasi<br />
sottoprogramma in più punti, tutte le volte che ciò si rende necessario!<br />
program prova;<br />
const ...<br />
var ...<br />
sottoprogramma 1<br />
sottoprogramma 2<br />
Begin<br />
... uso sottoprogramma1<br />
... uso sottoprogramma1<br />
... uso sottoprogramma2<br />
....<br />
End.
Come si scrive un sottoprogramma ?<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 88<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Iniziano con la parola procedure o function seguita dal nome che il programmatore vuole dare al sottoprogramma. Se<br />
non sono previsti parametri si mette il punto e virgola subito dopo il nome del sottoprogramma (tutti i nostri esempi<br />
iniziali non avranno parametri per semplicità …). Poi tra begin e end si mettono le istruzioni che si vogliono far<br />
eseguire quando si richiama il sottoprogramma. Ad esempio, ecco come si scrive una procedure per stampare sullo<br />
schermo un rettangolo fatto da asterischi.<br />
program esempio;<br />
uses crt;<br />
procedure<br />
<strong>di</strong>segnaQuadrato;<br />
begin<br />
writeln('****');<br />
writeln('****');<br />
writeln('****');<br />
writeln('****');<br />
end;<br />
begin<br />
<strong>di</strong>segnaQuadrato;<br />
writeln;<br />
writeln;<br />
<strong>di</strong>segnaQuadrato;<br />
writeln;<br />
writeln;<br />
readln<br />
end.<br />
Nel prossimo esempio scriveremo un sottoprogramma che restituisce un numero scelto a caso per il gioco del lotto<br />
(un numero da 1 a 90). Poiché restituisce un valore dobbiamo usare una function, che si <strong>di</strong>fferenzia leggermente da<br />
una procedure, ma non <strong>di</strong> molto.<br />
program esempio;<br />
function estraiLotto : integer;<br />
begin<br />
randomize;<br />
estraiLotto := random(90) + 1<br />
end;<br />
begin<br />
writeln (‘primo numero lotto estratto: ‘);<br />
writeln ( estraiLotto );<br />
writeln (‘secondo numero lotto estratto: ‘);<br />
writeln ( estraiLotto );<br />
end.<br />
Il sottoprogramma viene <strong>di</strong>chiarato una volta sola e<br />
richiamato due volte nel programma principale.<br />
Fosse necessario stampare anche mille rettangoli, le<br />
istruzioni del sottoprogramma sono state scritte una<br />
volta sola.<br />
Se si vogliono aumentare o <strong>di</strong>minuire il numero <strong>di</strong><br />
righe o colonne degli asterischi è sufficiente farlo<br />
solo nel sottoprogramma ed il nuovo funzionamento<br />
si ripercuoterà automaticamente in TUTTO il<br />
programma.<br />
Ho evidenziato le 2 cose che cambiano rispetto ad<br />
una procedure.<br />
1. Dopo il nome del sottoprogramma (e dopo la<br />
parentesi chiusa dell’eventuale lista dei parametri<br />
che nell’esempio manca) si mettono i due punti ed il<br />
tipo del valore che viene restituito.<br />
2. La function DEVE terminare con l’assegnazione<br />
al suo nome del valore da restituire a chi usa la<br />
function.
Sottoprogrammi con uso <strong>di</strong> parametri<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 89<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
La funzione estraiLotto non può essere utilizzata per altre simulazioni; ad esempio per un gioco <strong>di</strong> da<strong>di</strong> (dove,<br />
ovviamente, l'intervallo <strong>di</strong> valori e quello che va da uno a sei). Possiamo però riscrivere la funzione in modo da poter<br />
in<strong>di</strong>care al momento della chiamata il massimo numero da estrarre. Cambiamo anche il nome della funzione in<br />
quanto sarebbe fuorviante lasciare il riferimento al lotto.<br />
program esempio;<br />
function estrai(Massimo: integer) : integer;<br />
begin<br />
randomize;<br />
estraiLotto := random(massimo) + 1<br />
end;<br />
begin<br />
writeln (‘primo numero lotto estratto: ‘);<br />
writeln ( estrai(90) );<br />
writeln (‘tiro il dado … è uscito: ‘);<br />
writeln ( estrai(6) );<br />
end.<br />
program esempio;<br />
var numero: integer;<br />
procedure asterischi(quanteRighe: integer);<br />
var i: integer;<br />
begin<br />
for i:=1 to quanteRighe do<br />
writeln(‘***************’);<br />
end;<br />
begin<br />
writeln (‘quante righe <strong>di</strong> * devo stampare? ‘);<br />
readln(numero);<br />
asterischi( numero );<br />
readln;<br />
end.<br />
I parametri vengano specificati tra parentesi prima<br />
del nome del sottoprogramma. Per ciascuno deve<br />
essere specificato il tipo. Notate come sia ancora<br />
necessario, se si tratta <strong>di</strong> una funzione, in<strong>di</strong>care il<br />
tipo del valore restituito dal sottoprogramma.<br />
Il nome che scegliamo per il parametro serve al<br />
sottoprogramma per sapere come riferirsi al valore<br />
che riceve quando viene chiamato.<br />
Anche le procedure possono ricevere valori sotto<br />
forma <strong>di</strong> parametri. Nell'esempio qui a lato la<br />
procedura riceve il numero <strong>di</strong> righe <strong>di</strong> asterischi che<br />
deve stampare (il parametro chiamato quanteRighe).<br />
Essendo una procedura, dopo la parentesi determina<br />
l'elenco dei parametri non deve essere specificato un<br />
tipo come abbiamo visto per le funzioni.<br />
In questo esempio c'è un altro particolare molto<br />
interessante da notare: dopo l'intestazione con il<br />
nome della procedura/funzione è possibile iniziare<br />
una sezione VAR in cui <strong>di</strong>chiarare le variabili ad<br />
uso esclusivo del sottoprogramma. Nel caso in<br />
questione si tratta della variabile <strong>di</strong> controllo del<br />
ciclo for.<br />
Torneremo presto su questo argomento.
Parametri per valore (by value)<br />
program esempio;<br />
var numero: integer; simboli: string;<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 90<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
procedure asterischi(quanteRighe: integer; riga: string);<br />
Qui a lato trovate una versione potenziata della<br />
var i: integer;<br />
procedura esaminata in precedenza. Chi la usa non è<br />
begin<br />
più limitato ad una riga <strong>di</strong> asterischi ma può<br />
for i:=1 to quanteRighe do<br />
scegliere, specificandolo come secondo parametro,<br />
writeln(riga);<br />
end;<br />
la riga <strong>di</strong> caratteri da usare.<br />
begin<br />
writeln (‘quante righe <strong>di</strong> * devo stampare? ‘);<br />
readln( numero );<br />
writeln (‘che simboli uso per la stampa? ‘);<br />
readln( simboli);<br />
asterischi( numero, simboli );<br />
readln;<br />
end.<br />
program esempio;<br />
var num1, num2: integer;<br />
function max(a,b: integer) : integer;<br />
begin<br />
if a>=b then<br />
max:=a<br />
else<br />
max:=b<br />
end;<br />
begin<br />
writeln (‘inserisci un numero‘);<br />
readln ( num 1 );<br />
writeln (‘inserisci un altro numero‘);<br />
readln ( num 2 );<br />
writeln (‘il maggiore dei numeri inseriti è: ‘);<br />
writeln ( max(num1, num2) );<br />
readln;<br />
end.<br />
Quando ce n'è più d'uno, i parametri devono essere<br />
separati con un punto e virgola e per ciascuno deve<br />
essere specificato il tipo corrispondente.<br />
NOTATE: mentre quando scriviamo il testo del<br />
sottoprogramma gli eventuali parametri devono<br />
essere separati con un punto e virgola, quando<br />
invece il sottoprogramma viene usato i valori o le<br />
variabili che corrispondono ai parametri previsti<br />
vengono invece separati con una virgola.<br />
La funzione qui a lato restituisce il più grande dei<br />
due valori che riceve come parametri. Essendo<br />
questi ultimi dello stesso tipo, è possibile<br />
<strong>di</strong>chiararne il tipo insieme (separando però i loro<br />
nomi con una virgola)<br />
IMPORTANTE: i nomi delle variabili che<br />
eventualmente vengono utilizzate al momento del<br />
richiamo del sottoprogramma non devono chiamarsi<br />
per forza come i parametri. Detto in altre parole: per<br />
il fatto <strong>di</strong> aver chiamato a e b i parametri della<br />
funzione non siamo assolutamente obbligati a<br />
chiamare allo stesso modo le variabili del<br />
programma principale usate per chiamare il<br />
sottoprogramma; nell'esempio, le variabili <strong>di</strong>chiarate<br />
nel programma principale si chiamano infatti num1<br />
e num2: quello che accade è che il valore contenuto<br />
in num1 viene copiato nel parametro a e che il<br />
valore contenuto in num2 viene copiato nel<br />
parametro b. Si parla infatti <strong>di</strong> passaggio dei<br />
parametri per valore (by value).<br />
NOTA. Se vi state domandando come si faccia a <strong>di</strong>stinguere la modalità <strong>di</strong> passaggio dei parametri by value dall'altra<br />
che esamineremo tra poco, considerate questa semplice ‘regola’: quando nella specifica del parametro appare solo il<br />
suo nome ed il suo tipo il passaggio è by value. Dovremo infatti aggiungere qualche cosa nella <strong>di</strong>chiarazione per<br />
scegliere l'altra modalità.
Caratteristiche del passaggio dei parametri per valore<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 91<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Il passaggio per valore ha una caratteristica decisamente interessante: è ‘sicuro’ in quanto non consente al<br />
sottoprogramma <strong>di</strong> mo<strong>di</strong>ficare, tramite un parametro, una variabile del programma principale. Cerchiamo <strong>di</strong> capire il<br />
perché con un esempio.<br />
program esempio;<br />
var numero: integer; simboli: string;<br />
procedure asterischi(quanteRighe: integer; riga: string);<br />
begin<br />
repeat<br />
writeln(riga);<br />
quanteRighe := quanteRighe – 1<br />
until quanteRighe=0;<br />
end;<br />
begin<br />
numero:=5;<br />
simboli:=’******************’;<br />
asterischi( numero, simboli );<br />
writeln( numero ); (* che valore viene stampato ? *)<br />
readln;<br />
end.<br />
I parametri in<strong>di</strong>cati nella procedura sono chiamati parametri formali; quelle in<strong>di</strong>cate nel programma al momento<br />
dell'utilizzo del sottoprogramma sono invece chiamati parametri attuali.<br />
program esempio;<br />
var numero: integer; simboli: string;<br />
Parametri formali<br />
procedure asterischi(quanteRighe: integer; riga: string);<br />
var i: integer;<br />
begin<br />
for i:=1 to quanteRighe do<br />
writeln(riga);<br />
end;<br />
begin<br />
writeln (‘quante righe <strong>di</strong> * devo stampare? ‘);<br />
readln( numero );<br />
writeln (‘che simboli uso per la stampa? ‘);<br />
readln( simboli);<br />
Parametri attuali<br />
asterischi( numero, simboli );<br />
readln;<br />
end.<br />
Questa volta nel sottoprogramma il ciclo è<br />
realizzato con la struttura repeat until. Ma<br />
attenzione: quest’ultimo usa come contatore il<br />
parametro quanteRighe stesso, <strong>di</strong>minuendolo <strong>di</strong><br />
uno ad ogni ciclo.<br />
La domanda da un milione <strong>di</strong> euro è: se la<br />
variabile numero prima <strong>di</strong> chiamare la procedura<br />
vale 5, dopo aver chiamato la procedura (che<br />
apporta mo<strong>di</strong>fiche al suo primo parametro) quale<br />
sarà il suo valore?<br />
Risposta: lo stesso che aveva prima della<br />
chiamata; detto in altre parole, il passaggio dei<br />
parametri by value non consente ad un<br />
sottoprogramma <strong>di</strong> mo<strong>di</strong>ficare il valore <strong>di</strong> una<br />
variabile esterna utilizzata nel programma<br />
principale per richiamare la procedura stessa.<br />
Il passaggio è by value: il sottoprogramma<br />
non può mo<strong>di</strong>ficare il valore dei parametri<br />
attuali numero e simboli.<br />
Il valore che la variabile del programma<br />
numero ha al momento dell'uso del<br />
sottoprogramma viene assegnato alla<br />
variabile parametro formale<br />
corrispondente (quanteRighe). La stessa<br />
cosa avviene per il secondo parametro. Di<br />
fatto il sottoprogramma opera su una<br />
copia dei parametri attuali e della copia<br />
può <strong>di</strong>sporre come preferisce senza<br />
alterare realmente il valore delle variabili<br />
Se la procedura potesse invece mo<strong>di</strong>ficare (senza che il suo utilizzatore ne fosse consapevole) le variabili che<br />
corrispondono ai parametri attuali si correrebbe il rischio <strong>di</strong> incappare in errori molto <strong>di</strong>fficili da scoprire.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 92<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Immaginiamo infatti la situazione in cui la procedura asterischi, una volta sperimentata e considerata ‘perfetta’,<br />
venga tolta come co<strong>di</strong>ce dal programma visto prima ed inserita in una libreria chiamata stampe. Il programma<br />
apparirebbe allora così strutturato:<br />
program esempio;<br />
In<strong>di</strong>co l’uso della libreria<br />
uses stampa;<br />
var numero: integer; simboli: string;<br />
begin<br />
numero:=5;<br />
simboli:=’******************’;<br />
asterischi( numero, simboli );<br />
Come vedete, le istruzioni della procedura<br />
asterischi non sono più visibili.<br />
Il co<strong>di</strong>ce corrispondente, già compilato in<br />
linguaggio macchina, viene incorporato<br />
nell'eseguibile finale dal linker.<br />
Nel programma rimangono solo le righe<br />
end.<br />
che invocano il sottoprogramma.<br />
Il programmatore non ha modo <strong>di</strong> capire se la procedura asterischi mo<strong>di</strong>ficherà o meno il valore <strong>di</strong> uno dei suoi<br />
parametri formali! Ed anche se il co<strong>di</strong>ce dal sottoprogramma fosse ancora inserito <strong>di</strong>rettamente nel programma<br />
saremo sempre costretti ad un alto livello <strong>di</strong> attenzione perdendo molto tempo nel controllare le istruzioni <strong>di</strong> ogni<br />
sottoprogramma per capire se esiste questa possibilità.<br />
Insomma, sarebbe un bel guaio se il sottoprogramma potesse mo<strong>di</strong>ficare (senza che questo fosse <strong>di</strong>chiarato in<br />
qualche modo) a piacimento il valore <strong>di</strong> uno dei parametri attuali (numero, simboli)!<br />
Invece, grazie ai vincoli imposti dal passaggio dei parametri per valore (by value) il programmatore è<br />
sicuro che un sottoprogramma neanche per errore potrà mo<strong>di</strong>ficare il valore <strong>di</strong> un parametro attuale.<br />
Esistono comunque dei vincoli tra i parametri in<strong>di</strong>cati ne la scrittura del co<strong>di</strong>ce <strong>di</strong> un sottoprogramma (chiamati<br />
parametri formali) e quelli che il programmatore utilizza nel programma principale quando intende usare il<br />
sottoprogramma (chiamati parametri attuali):<br />
per ogni parametro formale deve essere in<strong>di</strong>cato al momento dell'utilizzo del sottoprogramma un corrispondente<br />
parametro attuale; riferendoci all'esempio della procedura asterischi, non è ammesso il suo utilizzo specificando solo<br />
il numero delle righe o solo i singoli da utilizzare; non ha ovviamente senso anche cercare <strong>di</strong> usare più parametri<br />
attuali <strong>di</strong> quelli previsti;<br />
il parametro attuale dev'essere dello stesso tipo del parametro formale o almeno compatibile; il parametro formale<br />
quanteRighe della procedura asterischi è <strong>di</strong> tipo integer, per cui, richiamandola, come primo parametro attuale<br />
potremo solo in<strong>di</strong>care o una costante integer o una variabile integer o una qualsivoglia espressione che restituisca un<br />
valore integer;<br />
se un parametro formale fosse <strong>di</strong> tipo real allora il corrispondente parametro attuale potrebbe essere al limite anche<br />
un integer visto che quest'ultimo tipo <strong>di</strong> dato è compatibile con il real (ad esempio il 5, integer, verrebbe convertito<br />
nel real 5.0); il contrario non sarebbe invece possibile, in quanto il numero <strong>di</strong> byte necessario a rappresentare un real<br />
è maggiore <strong>di</strong> quello necessario a rappresentare un integer; ad esempio, il numero real 12.67 non viene<br />
automaticamente troncato al valore integer 12 a causa <strong>di</strong> una per<strong>di</strong>ta <strong>di</strong> precisione che potrebbe risultare<br />
inaccettabile; il programmatore può però in<strong>di</strong>care come parametro attuale l'espressione trunc(12.67) che <strong>di</strong>etro<br />
esplicito comando ‘tronca’ un valore real in un integer (in realtà in un longint, compatibile a sua volta con un integer<br />
a patto che il valore cada nell'intervallo previsto per gli integer);<br />
i parametri attuali devono essere forniti nello stesso or<strong>di</strong>ne logico previsto per quelli formali; la procedura asterischi<br />
si aspetta il numero delle righe come primo parametro e la stringa dei singoli da usare come secondo; tentare <strong>di</strong><br />
invertire l'or<strong>di</strong>ne non ha gravi conseguenze, in questo particolare caso, perché i tipi dei due parametri sono <strong>di</strong>versi ed<br />
il compilatore si accorgerebbe subito del problema e bloccherebbe la compilazione con un messaggio d'errore; ben<br />
<strong>di</strong>verso è il caso <strong>di</strong> parametri dello stesso tipo: il compilatore non s'accorgerebbe <strong>di</strong> nulla dal momento che dal suo<br />
punto <strong>di</strong> vista si tratterebbe comunque <strong>di</strong> due interi o <strong>di</strong> due stringhe eccetera<br />
Viva l’in<strong>di</strong>pendenza!<br />
Quella che sto per enunciare non è una regola sintattica ma una dettata dall'esperienza. Idealmente un<br />
sottoprogramma dovrebbe essere strutturato in modo da essere il più in<strong>di</strong>pendente possibile dal programma che lo<br />
utilizzerà o da altri sottoprogrammi.<br />
Questo obiettivo viene raggiunto soprattutto con l'utilizzo dei parametri, evitando <strong>di</strong> usare <strong>di</strong>rettamente<br />
eventuali variabili del programma principale. Consideriamo infatti questo esempio (ho ripreso la forma più<br />
semplice della procedura asterischi):
program esempio;<br />
var i: integer;<br />
procedure asterischi(quanteRighe: integer);<br />
begin<br />
for i:=1 to quanteRighe do<br />
writeln(‘***************’);<br />
end;<br />
begin<br />
writeln (‘quante righe <strong>di</strong> * devo stampare? ‘);<br />
readln(numero);<br />
asterischi( numero );<br />
readln;<br />
end.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 93<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Se proviamo a mandare in esecuzione il programma tutto sembra funzionare e, in effetti, funziona veramente... Ma la<br />
procedura è in realtà meno leggibile, meno riutilizzabile e meno sicura.<br />
Meno leggibile: non è sufficiente passare in rassegna tutte le istruzioni della procedura per capire tutto della<br />
procedura; infatti per capire cosa sia la i usata dal ciclo for dobbiamo cercare nel programma principale la sua<br />
<strong>di</strong>chiarazione; e se qualcuno tra voi sta pensando che realtà è ovvio che la i sia una variabile <strong>di</strong> tipo integer, vi invito<br />
a provare il seguente programma!<br />
program prova;<br />
var i: char;<br />
begin<br />
for i:=’A’ to ‘Z’ do<br />
write( i );<br />
readln;<br />
end.<br />
La mo<strong>di</strong>fica che ho apportato è piccola ma<br />
sostanziale: ho spostato la <strong>di</strong>chiarazione della<br />
variabile i utilizzata per il ciclo for dall'interno della<br />
procedura alla sezione var del programma<br />
principale.<br />
Non è più così ovvio che la variabile usata per un ciclo for sia<br />
per forza <strong>di</strong> tipo integer, vero ???<br />
Meno riutilizzabile: se spostiamo la procedura in un altro programma o la inclu<strong>di</strong>amo in una libreria non è detto che<br />
in quel programma o in quella libreria sia presente, come nelle programma originale, una variabile <strong>di</strong> tipo integer<br />
chiamata i; il ciclo for della procedura non potrebbe quin<strong>di</strong> funzionare. E per quanto vi possa sembrare strano questo<br />
è il caso più fortunato tra quelli che possono capitare: infatti il compilatore ci avverte dell'assenza della variabile ed il<br />
programmatore può intervenire e soprattutto si accorge che c'è un problema.<br />
Meno sicura: provate a pensare se nel programma in cui viene copiata la procedura esiste già una variabile con lo<br />
stesso nome e dello stesso tipo: il compilatore non farebbe una grinza perché la procedura pretende una certa<br />
variabile nel programma principale e questa viene trovata. Il problema è che se in questo nuovo programma quella<br />
stessa variabile serve per altri scopi, non appena si invoca la procedura quest'ultima mo<strong>di</strong>fica in modo inaspettato il<br />
suo valore interferendo con il resto del programma.<br />
Ne <strong>di</strong>scende un'altra regola: tutte le variabili <strong>di</strong> lavoro <strong>di</strong> un sottoprogramma dovrebbero essere definite all'interno <strong>di</strong><br />
quest'ultimo. Eventuali valori/variabili esterne verranno comunicati tramite un parametro.
Passaggio <strong>di</strong> parametri per in<strong>di</strong>rizzo (by reference)<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 94<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Come abbiamo visto, il passaggio <strong>di</strong> parametri per valore non consente la mo<strong>di</strong>fica <strong>di</strong> variabili esterne. Qualche volta<br />
però questo è proprio ciò <strong>di</strong> cui abbiamo bisogno... Immaginiamo <strong>di</strong> volere scrivere un sottoprogramma per il calcolo<br />
delle due ra<strong>di</strong>ci reali (se esistono) <strong>di</strong> un’equazione <strong>di</strong> secondo grado:<br />
function equazione2grado(a, b, c: real) : real;<br />
begin<br />
…. Calcoli …<br />
end;<br />
Gli unici valori <strong>di</strong> cui questa funzione ha bisogno sono i coefficienti del termine <strong>di</strong> secondo grado, del termine <strong>di</strong><br />
primo grado e del termine noto (per applicare la notissima formula<br />
x<br />
12<br />
− b μ<br />
=<br />
2<br />
b − 4ac<br />
2a<br />
). Ecco quin<strong>di</strong><br />
giustificata la presenza dei tre parametri chiamati a, b e c. Purtroppo, però, una funzione può restituire un solo<br />
valore: impossibile, quin<strong>di</strong>, far restituire con i meccanismi noti i due valori corrispondenti alle due soluzioni richieste.<br />
La situazione parrebbe senza vie d'uscita: o si fa restituire la funzione la prima delle due ra<strong>di</strong>ci (X1) oppure si fa<br />
restituire la seconda (X2).<br />
Qualcuno potrebbe essere tentato dalla seguente strada (ma, come appena visto, assolutamente da evitare):<br />
program equazioni;<br />
var x1,x2: real;<br />
function equazione2grado(a, b, c: real) : real;<br />
begin<br />
…. Calcoli …<br />
x1:= …; x2:= …;<br />
end;<br />
Quello che occorre è un modo ‘sicuro’ per consentire ad un sottoprogramma la mo<strong>di</strong>fica <strong>di</strong> una variabile esterna;<br />
sicuro significa che un programmatore che sta per utilizzare un sottoprogramma è in grado, consultando la<br />
documentazione, <strong>di</strong> capire che la variabile che sta in<strong>di</strong>cando come parametro attuale può essere mo<strong>di</strong>ficata dal<br />
sottoprogramma ed in che modo.<br />
Tutto questo accade con l'utilizzo dei parametri passati per in<strong>di</strong>rizzo (by reference). A livello sintattico la<br />
mo<strong>di</strong>fica è minima: basta aggiungere prima del nome <strong>di</strong> un parametro formale la parola var:<br />
program equazioni;<br />
var soluzione1, soluzione2: real;<br />
procedure equazione2grado(a, b, c: real; var x1,x2: real);<br />
begin<br />
…. Calcoli …<br />
x1:= …; x2:= …;<br />
end;<br />
begin<br />
equazione2grado(3,4,5, soluzione1, soluzione2);<br />
writeln(‘Le soluzione sono ‘, soluzione1, soluzione2);<br />
readln;<br />
end.<br />
Questa soluzione utilizza <strong>di</strong>rettamente due<br />
variabili del programma principale. Si tratta <strong>di</strong> una<br />
pratica assolutamente da evitare per tutti i motivi<br />
visti in precedenza.<br />
Quando un parametro formale è<br />
specificato per in<strong>di</strong>rizzo una mo<strong>di</strong>fica ad<br />
esso si ripercuote in modo permanente<br />
sulla variabile utilizzata come parametro<br />
attuale al momento della chiamata.<br />
Detto in altre parole: nella sezione delle<br />
variabili del programma principale sono<br />
state <strong>di</strong>chiarate due variabili (soluzione1 e<br />
soluzione2) per memorizzare i risultati che<br />
verranno calcolati dalla procedura.<br />
La procedura viene poi richiamata<br />
in<strong>di</strong>cando proprio queste due variabili<br />
come ultimi due parametri attuali.<br />
Poiché soluzione1 corrisponde al<br />
parametro formale X1 e poiché X1 è stato<br />
<strong>di</strong>chiarato per in<strong>di</strong>rizzo, ogni mo<strong>di</strong>fica fatta<br />
ad X1 si ripercuoterà anche su soluzione1;
ESERCIZI SVOLTI – primo blocco<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 95<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SOT 1. <strong>di</strong>fficoltà: bassa Che percentuale rappresenta un numero A rispetto ad un altro numero B? Esempio: 100<br />
rispetto a 150 = 75 (100 è il 75% <strong>di</strong> 150).<br />
program percentuale;<br />
uses newdelay,crt;<br />
(* che percentuale è A <strong>di</strong> B? *)<br />
function perc(A, B: real): real;<br />
begin<br />
perc := (A/B) * 100<br />
end;<br />
begin<br />
clrscr;<br />
(* esempi d'uso ... *)<br />
writeln( perc(1, 1):3:2, '%' ); (* 100% *)<br />
writeln( perc(17, 34):3:2, '%' ); (* 50% *)<br />
writeln( perc(8, 24):3:2, '%' ); (* 33.33333333% ...*)<br />
writeln( perc(234.78, 4356.87):3:2, '%' ); (* e chi lo sa ?? *)<br />
(* viene calcolato un valore corretto anche quando A < B *)<br />
writeln( perc(150, 100):3:2, '%' ); (* 150% *)<br />
writeln('INVIO per continuare ...');<br />
readln;<br />
end.<br />
SOT 2. <strong>di</strong>fficoltà: bassa Calcolare una certa percentuale <strong>di</strong> un numero.<br />
program provaPercentuale;<br />
uses newdelay, crt;<br />
function percentuale(perc: real; numero: real): real;<br />
begin<br />
percentuale := (numero/100) * perc;<br />
end;<br />
begin<br />
clrscr;<br />
writeln( percentuale(20, 100):2:2); (* 20% <strong>di</strong> 100 = 20 *)<br />
writeln( percentuale(20, 35):2:2); (* 20% <strong>di</strong> 35 = 7 *)<br />
writeln( percentuale(0, 47):2:2); (* 0% <strong>di</strong> qualunque numero = 0 *)<br />
writeln( percentuale(20, 0):2:2); (* 20% <strong>di</strong> 0 = 0 *)<br />
readln;<br />
end.
SOT 3. <strong>di</strong>fficoltà: bassa Calcolare un prezzo comprensivo d’IVA.<br />
program provaPercentuale;<br />
uses newdelay, crt;<br />
var prezzo, iva: real;<br />
(* sfruttiamo la funzione scritta prima … *)<br />
function percentuale(perc: real; numero: real): real;<br />
begin<br />
percentuale := (numero/100) * perc;<br />
end;<br />
function prezzoConIVA(prezzoSenzaIVA: real; percentualeIVA: real): real;<br />
begin<br />
prezzoConIVA := prezzoSenzaIVA + percentuale(prezzoSenzaIVA, percentualeIVA);<br />
end;<br />
begin<br />
clrscr;<br />
writeln('Inserire prezzo senza IVA');<br />
readln(prezzo);<br />
writeln('Inserire pecentuale IVA da applicare');<br />
readln(iva);<br />
writeln('Ecco il totale IVA compresa: ', prezzoConIVA(prezzo, iva):6:2);<br />
readln;<br />
end.<br />
SOT 4. <strong>di</strong>fficoltà: bassa Conversione da metri a chilometri e viceversa.<br />
program conversioni;<br />
uses newdelay, crt;<br />
var metri, km: real;<br />
(* da m a km *)<br />
function m_km(quantiMetri: real): real;<br />
begin<br />
m_km := quantiMetri / 1000;<br />
end;<br />
(* da km a m *)<br />
function km_m(quantiKm: real): real;<br />
begin<br />
km_m := quantiKm * 1000;<br />
end;<br />
begin<br />
clrscr;<br />
writeln('Inserire metri');<br />
readln(metri);<br />
writeln(metri:5:2,' metri corrispondono a ',m_km(metri):5:2,' chilometri');<br />
writeln('Inserire ora i chilometri');<br />
readln(km);<br />
writeln(km:5:2,' chilometri corrispondono a ',km_m(km):5:2,' metri');<br />
readln;<br />
end.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 96<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 97<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SOT 5. <strong>di</strong>fficoltà: bassa Convertire un certo numero <strong>di</strong> secon<strong>di</strong> nei minuti corrispondenti. Si vogliono anche<br />
sapere i secon<strong>di</strong> che avanzano.<br />
program conversioni;<br />
uses newdelay, crt;<br />
var ss, avanzano: integer;<br />
(* da secon<strong>di</strong> a minuti; viene calcolato anche l'avanzo *)<br />
function ss_mm(quantiSecon<strong>di</strong>: integer; var avanzo: integer): integer;<br />
begin<br />
avanzo := quantiSecon<strong>di</strong> mod 60;<br />
ss_mm := quantiSecon<strong>di</strong> <strong>di</strong>v 60;<br />
end;<br />
begin<br />
clrscr;<br />
writeln('Inserire secon<strong>di</strong>');<br />
readln(ss);<br />
writeln(ss,' secon<strong>di</strong> corrispondono a ',ss_mm(ss,avanzano),' minuti e ', avanzano, ' secon<strong>di</strong>');<br />
readln;<br />
end.<br />
SOT 6. <strong>di</strong>fficoltà: bassa Convertire un certo numero <strong>di</strong> minuti nelle ore corrispondenti. Si vogliono anche sapere i<br />
minuti che avanzano.<br />
program conversioni;<br />
uses newdelay, crt;<br />
var mm, avanzano: integer;<br />
(* da secon<strong>di</strong> a minuti; viene calcolato anche l'avanzo *)<br />
function ss_mm(quantiSecon<strong>di</strong>: integer; var avanzo: integer): integer;<br />
begin<br />
avanzo := quantiSecon<strong>di</strong> mod 60;<br />
ss_mm := quantiSecon<strong>di</strong> <strong>di</strong>v 60;<br />
end;<br />
(* da minuti a ore; viene calcolato anche l'avanzo *)<br />
function mm_hh(quantiMinuti: integer; var avanzo: integer): integer;<br />
begin<br />
(* sfrutto la precedente: il calcolo da fare e' infatti lo stesso ! *)<br />
mm_hh := ss_mm(quantiMinuti, avanzo);<br />
end;<br />
begin<br />
clrscr;<br />
writeln('Inserire minuti');<br />
readln(mm);<br />
writeln(mm,' minuti corrispondono a ',mm_hh(mm,avanzano),' ore e ', avanzano, ' minuti');<br />
readln;<br />
end.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 98<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SOT 7. <strong>di</strong>fficoltà: bassa Convertire un certo numero <strong>di</strong> secon<strong>di</strong> nelle ore corrispondenti. Si vogliono anche sapere<br />
i minuti ed i secon<strong>di</strong> che che avanzano.<br />
program conversioni;<br />
uses newdelay, crt;<br />
var ss, avanzano, avanzano_mm: integer;<br />
(* da secon<strong>di</strong> a minuti; viene calcolato anche l'avanzo *)<br />
function ss_mm(quantiSecon<strong>di</strong>: integer; var avanzo: integer): integer;<br />
begin<br />
avanzo := quantiSecon<strong>di</strong> mod 60;<br />
ss_mm := quantiSecon<strong>di</strong> <strong>di</strong>v 60;<br />
end;<br />
(* da minuti a ore; viene calcolato anche l'avanzo *)<br />
function mm_hh(quantiMinuti: integer; var avanzo: integer): integer;<br />
begin<br />
(* sfrutto la precedente: il calcolo da fare e' infatti lo stesso ! *)<br />
mm_hh := ss_mm(quantiMinuti, avanzo);<br />
end;<br />
(* da secon<strong>di</strong> a ore; viene calcolato anche l'avanzo *)<br />
function ss_hh(quantiSecon<strong>di</strong>: integer; var avanzo_ss, avanzo_mm: integer): integer;<br />
var quantiMinuti: integer;<br />
begin<br />
quantiMinuti := ss_mm(quantiSecon<strong>di</strong>, avanzo_ss);<br />
ss_hh := mm_hh(quantiMinuti, avanzo_mm)<br />
end;<br />
begin<br />
clrscr;<br />
writeln('Inserire i secon<strong>di</strong> per convertirli in ore ...'); readln(ss);<br />
write(ss,' secon<strong>di</strong> corrispondono a ',ss_hh(ss,avanzano, avanzano_mm),' ore, ');<br />
writeln(avanzano_mm, ' minuti e ', avanzano,' secon<strong>di</strong>');<br />
readln;<br />
end.<br />
SOT 8. <strong>di</strong>fficoltà: bassa Estrarre la parte decimale <strong>di</strong> un numero reale X (quella 'dopo la virgola'); in pratica simula<br />
la funzione standard frac... Esempio: decimale(13,75) - > 0,75.<br />
program parteDecimale;<br />
uses newdelay,crt;<br />
(* NOTA. Con il Pascal la parte decimale e' separata da quella intera da un punto, non dalla virgola;<br />
NOTA. Viene sfruttata la funzione predefinita TRUNC (che elimina la parte decimale da un reale.<br />
TRUNC(10.7) -> 10 *)<br />
function decimale(x: real): real;<br />
begin<br />
decimale := x - trunc(x);<br />
end;<br />
begin<br />
clrscr;<br />
(* esempi d'uso ... *)<br />
writeln( decimale(0.8917):4:4 ); (* 0.8917 *)<br />
writeln( decimale(4.8917):4:4 ); (* 0.8917 *)<br />
writeln( decimale(368.8917):4:4 ); (* 0.8917 *)<br />
writeln('INVIO per continuare ...'); readln; end.
SOT 9. <strong>di</strong>fficoltà: bassa Dato un carattere <strong>di</strong>re se rappresenta una lettera maiuscola.<br />
program controllaMaiuscolo;<br />
uses newdelay,crt;<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 99<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
(* restituisce true solo se il carattere e’ una lettera maiuscola cioe' solo se il suo co<strong>di</strong>ce ascii e' compreso tra quello<br />
della A e quello della Z *)<br />
function isUpCase(c: char) : boolean;<br />
begin<br />
isUpCase := ( ord(c) >= ord('A') ) and ( ord(c)
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 100<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
(* trasformo in minuscolo: al co<strong>di</strong>ce ascii del carattere sommo la sfasatura con il set delle minuscole, e ritrasformo il<br />
risultato nel carattere corrispondente *)<br />
lowCase := chr( ord(c) + <strong>di</strong>stanza )<br />
end<br />
else<br />
lowCase:=c<br />
end;<br />
begin<br />
clrscr;<br />
(* esempi d'uso ... *)<br />
writeln( lowCase('A') );<br />
writeln( lowCase('a') );<br />
writeln( lowCase('Z') );<br />
writeln( lowCase('z') );<br />
writeln( lowCase('!') ); (* inalterato ... *)<br />
writeln('INVIO per continuare ...');<br />
readln;<br />
end.<br />
SOT 11. <strong>di</strong>fficoltà: alta Data una stringa convertirla in minuscolo o maiuscolo a seconda del valore <strong>di</strong> un<br />
parametro come (se come='m' converte in minuscolo, se come='M' converte in maiuscolo; se come non ha un<br />
valore valido la stringa viene restituita immo<strong>di</strong>ficata). Sfruttare alcune funzioni precedenti (SOT9 e SOT10)<br />
program MinuscoloMaiuscolo;<br />
uses newdelay, crt;<br />
(* per un commento ve<strong>di</strong> SOT9 *)<br />
function isUpCase(c: char) : boolean;<br />
begin isUpCase := ( ord(c) >= ord('A') ) and ( ord(c)
is:='';<br />
for i:=1 to length(s) do<br />
ris:=ris + upcase(s[i]); (* sfrutta la funzione predefinita upcase … *)<br />
maiuscolo:=ris;<br />
end;<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 101<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
(* converte un maiuscolo a seconda del parametro 'come'; se come='m' converte in minuscolo, se come='M'<br />
converte in maiuscolo; se come non ha un valore valido la stringa viene restituita identica *)<br />
function MiniMaiu(s: string; come: char) : string;<br />
begin<br />
case come of<br />
'm': MiniMaiu := minuscolo(s);<br />
'M': MiniMaiu := maiuscolo(s);<br />
else<br />
MiniMaiu := s;<br />
end;<br />
end;<br />
begin<br />
clrscr;<br />
(* esempi d'uso ... *)<br />
writeln( MiniMaiu('AaZz12!','m') );<br />
writeln( MiniMaiu('AaZz12!','M') );<br />
writeln( MiniMaiu('AaZz12!','t') );<br />
writeln('INVIO per continuare ...');<br />
readln;<br />
end.<br />
Sfruttando i sottoprogrammi già esistenti, la soluzione del<br />
problema originale <strong>di</strong>venta banale.
Metodologie Top Down e Bottom Up<br />
Problema originale<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 102<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
L’esercizio precedente introduce in modo naturale una tecnica <strong>di</strong> sviluppo utilizzata per problemi <strong>di</strong> me<strong>di</strong>a/alta<br />
complessità. Invece <strong>di</strong> tentare <strong>di</strong> risolvere il problema come un tutt’uno, compito <strong>di</strong> solito molto <strong>di</strong>fficile, si procede<br />
ad una sud<strong>di</strong>visione in sottoproblemi. E’ intuitivo che ogni sottoproblema sia più semplice <strong>di</strong> quello originale. Per ogni<br />
sottoproblema che si ritiene ancora troppo complesso si procede ad ulteriore sud<strong>di</strong>visione. Il proce<strong>di</strong>mento viene<br />
ripetuto fino ad ottenere un certo numero <strong>di</strong> sottoproblemi sufficientemente semplici da essere risolti e co<strong>di</strong>ficati con<br />
una certa facilità. A livello pratico questo coincide ad in<strong>di</strong>viduare tutta una serie <strong>di</strong> sottoprogrammi che a loro volta<br />
ne richiamano altri. Questa metodologia viene chiamata TOP DOWN (dall’alto al basso), perché graficamente<br />
possiamo rappresentarla come una struttura a piramide in cui in cima (top) si mette il problema originale e via via<br />
che si scende <strong>di</strong> livello sottoproblemi sempre più semplici, fino ad arrivare alla base (bottom) della piramide in cui<br />
troviamo i sottoproblemi più semplici.<br />
Ve<strong>di</strong>amo <strong>di</strong> riconoscere questo proce<strong>di</strong>mento nell’esercizio precedente. Si trattava <strong>di</strong> realizzare un comando per<br />
trasformare una stringa in minuscolo o in maiuscolo. Questo problema iniziale viene sud<strong>di</strong>viso in modo molto naturale<br />
nei due sottoproblemi 1) convertire una stringa in minuscolo e 2) convertire una stringa in maiuscolo:<br />
Convertire una<br />
Stringa in Minuscolo<br />
Convertire una Stringa<br />
in Minuscolo/Maiuscolo<br />
Convertire una<br />
Stringa in Maiuscolo<br />
sottoproblemi<br />
Il sottoproblema ‘convertire una stringa in minuscolo’ può essere ulteriormente scomposto in due altri sotto problemi:<br />
1) decidere se un carattere è minuscolo e 2) convertire un singolo carattere in minuscolo (più semplice rispetto alla<br />
conversione <strong>di</strong> un'intera stringa). Anche per l'altro sotto problema viene in<strong>di</strong>viduato il sotto problema corrispondente<br />
alla conversione in maiuscolo <strong>di</strong> un singolo carattere.<br />
Convertire una<br />
Stringa in Minuscolo<br />
Convertire una Stringa<br />
in Minuscolo/Maiuscolo<br />
Convertire una<br />
Stringa in Maiuscolo<br />
Il carattere è minuscolo ? Converti un singolo carattere in minuscolo Converti un singolo carattere in maiuscolo<br />
NOTA: questa tecnica viene in<strong>di</strong>cata anche come ‘<strong>di</strong>vide et impera’ (<strong>di</strong>vi<strong>di</strong> e mantieni sotto controllo) da un detto<br />
degli antichi romani: per mantenere sotto controllo un vasto territorio appena conquistato vaste fette della popolazione<br />
venivano deportate in aree geografiche lontane. In questo modo quel popolo perdeva la sua unità e la sua forza ed era<br />
più facile dominarlo.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 103<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Oltre alla riduzione della complessità la metodologia top-down offre un'altra caratteristica: consente <strong>di</strong> allestire la<br />
struttura portante dell'intero programma senza aver completato i singoli componenti, così da poterlo provare nei suoi<br />
aspetti macroscopici. Per fare un paragone è come se avessimo a <strong>di</strong>sposizione il prototipo <strong>di</strong> un'automobile con il<br />
telaio (ma non i vetri e le rifiniture), il volante e le ruote (ma un finto servosterzo e niente copertoni), il cruscotto con<br />
i controlli ma senza un vero impianto elettrico, pedali e cambio ma niente <strong>sistema</strong> <strong>di</strong> frenatura e solo un abbozzo del<br />
motore. Certamente tutto questo non ci permetterebbe <strong>di</strong> fare un giro <strong>di</strong> pista a Monza ma consentirebbe agli<br />
ingegneri ed ai meccanici <strong>di</strong> salire veramente in macchina e saggiare dal vivo almeno le caratteristiche e economiche<br />
(visibilità, como<strong>di</strong>tà del posto guida, spazi interni, corretta <strong>di</strong>sposizione dei controlli eccetera); insomma non è come<br />
avere singoli pezzi, pur funzionanti ma che non consentono <strong>di</strong> trovare il tutto almeno in alcuni dei suoi aspetti.<br />
Di nuovo, cerchiamo <strong>di</strong> ritrovare questi concetti nell'esempio precedente. Dopo aver in<strong>di</strong>viduato i sottoprogrammi da<br />
sviluppare il programmatore può rapidamente scrivere solo l'intelaiatura <strong>di</strong> ciascuno <strong>di</strong> essi ma già collegando il tutto<br />
a formare un programma funzionante:<br />
program MinuscoloMaiuscolo;<br />
uses newdelay, crt;<br />
function isUpCase(c: char) : boolean;<br />
begin<br />
isUpCase := true;<br />
end;<br />
function lowCase(c: char) : char;<br />
begin<br />
lowCase := ‘a’;<br />
end;<br />
function minuscolo(s: string): string;<br />
begin<br />
minuscolo:=’stringa <strong>di</strong> prova’;<br />
end;<br />
function maiuscolo(s: string): string;<br />
maiuscolo:=’STRINGA DI PROVA’;<br />
end;<br />
function MiniMaiu(s: string; come: char) :<br />
string;<br />
begin<br />
case come of<br />
'm': MiniMaiu := minuscolo(s);<br />
'M': MiniMaiu := maiuscolo(s);<br />
else<br />
MiniMaiu := s;<br />
end;<br />
end;<br />
begin<br />
clrscr;<br />
(* esempi d'uso ... *)<br />
writeln( MiniMaiu('AaZz12!','m') );<br />
writeln( MiniMaiu('AaZz12!','M') );<br />
writeln( MiniMaiu('AaZz12!','t') );<br />
writeln('INVIO per continuare ...');<br />
readln;<br />
end.<br />
Ecco il ‘trucco’: pur avendo stabilito l'interfaccia <strong>di</strong> ogni<br />
sottoprogramma (il nome ed i parametri che riceverà) il co<strong>di</strong>ce<br />
<strong>di</strong> ciascuno <strong>di</strong> essi è ridotto all'osso, il minimo in<strong>di</strong>spensabile<br />
per poterli richiamare i poter provare il programma principale.<br />
Ogni funzione non calcola veramente il suo risultato ma<br />
restituisce un valore fasullo (però del tipo giusto): ad esempio<br />
la funzione maiuscolo restituisce come risultato la stringa fissa<br />
‘ STRINGA DI PROVA’.<br />
Questo permette comunque a programmatore <strong>di</strong> scrivere nei<br />
minimi dettagli la funzione MiniMaiu che, per così <strong>di</strong>re, non si<br />
accorge <strong>di</strong> richiamare delle funzioni incomplete (anche se,<br />
naturalmente, i valori che si vede restituire sono sempre quelli e<br />
fasulli.<br />
Il programmatore può anche organizzare il corpo principale del<br />
programma verificando che tutti i sottoprogrammi si richiamino<br />
correttamente tra loro. Fatto questo può procedere a completare<br />
veramente ciascun sottoprogramma oppure, e questo è un altro<br />
vantaggio dell'aver costruito rapidamente lo ‘scheletro’ del<br />
programma, può fidare il completamento a più programmatori<br />
in contemporanea e da ciascuno consegnare una copia <strong>di</strong> questo<br />
scheletro in modo da poter testare il co<strong>di</strong>ce senza avere a<br />
<strong>di</strong>sposizione la parte che viene sviluppata dagli altri.<br />
Il completamento può avvenire anche per affinamenti<br />
successivi: si iniziano con l'introdurre versioni non perfette ma<br />
via via sempre più funzionanti dei vari sottoprogrammi. Questo<br />
aiuta a concentrarsi sugli aspetti fondamentali del problema: ad<br />
esempio non è importante all'inizio preoccuparsi <strong>di</strong> tutti i<br />
possibili errori <strong>di</strong> inserimento dati che potrebbe commettere<br />
l'utente; la gestione degli errori è senz'altro un aspetto che può<br />
essere introdotto in un secondo momento.<br />
Naturalmente dopo che ciascun programmatore avrà<br />
completato il test sulla sua parte sarà necessaria una fase <strong>di</strong> test<br />
conclusiva con tutto il co<strong>di</strong>ce al suo posto!
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 104<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
La tecnica del top-down presuppone, però, che il programmatore abbia un'idea chiara, almeno a gran<strong>di</strong> linee, del<br />
programma nella sua versione completa. Solo in questo modo potrà infatti prefigurarsi i sottoprogrammi che<br />
dovranno essere sviluppati e come questi dovranno interagire tra loro. Diversamente non sarebbe infatti in grado <strong>di</strong><br />
abbozzare lo scheletro dell'intero programma.<br />
Bottom-Up<br />
Quando questo non è possibile (per l'estrema complessità del progetto e/o per l'alto grado <strong>di</strong> innovazione dello<br />
stesso che non consente <strong>di</strong> avere le idee chiare su tutto fin dall'inizio) può essere d'aiuto procedere in un altro modo:<br />
cominciare a sviluppare piccole porzioni sulle quali si hanno comunque idee chiare senza sapere ancora come si<br />
incastreranno tra loro (almeno nei dettagli). Sarà solo in un secondo momento che ci si preoccuperà <strong>di</strong> assemblare il<br />
programma con questi componenti. Per fare un paragone, è un po' come se si volesse realizzare un veicolo<br />
innovativo per spostarsi sulla sabbia: il progettista potrebbe non aver ancora le idee chiare sulla forma e le<br />
caratteristiche definitive del veicolo. Potrebbe però decidere <strong>di</strong> cominciare a realizzare delle ruote speciali, per poi<br />
passare al motore, poi alle singole parti del telaio e così via. Infine deciderà come assemblare insieme queste parti:<br />
certamente potrebbe essere costretto a rifare dei pezzi o a scartarne alcuni o ad adattarne degli altri (è il prezzo per<br />
questa modalità <strong>di</strong> sviluppo in cui non è possibile progettare nei dettagli fin dall'inizio); inoltre, <strong>di</strong> nuovo non avendo<br />
progettato fin dall'inizio le interconnessioni tra le varie parti, sarà più <strong>di</strong>fficile affidarne lo sviluppo a <strong>di</strong>versi<br />
programmatori senza essere costretti a dell'ulteriore lavoro in fase <strong>di</strong> assemblaggio (un programmatore potrebbe<br />
aver progettato l'interfaccia <strong>di</strong> un suo sottoprogramma in modo non ottimale per un altro).<br />
Questa tecnica è chiamata BOTTOM-UP (dal basso verso l'alto): si parte dai dettagli e dai componenti più semplici<br />
per assemblare via via i componenti <strong>di</strong> più alto livello, quelli più complessi.<br />
Non si può <strong>di</strong>re che la tecnica sia superiore all'altra, tant'è che non è infrequente il caso in cui vengano in realtà<br />
utilizzate entrambe: il progetto viene fatto magari in modo top-down ma lo sviluppo parte in modo dettagliato dei<br />
livelli più bassi. La tecnica bottom-up si rivela particolarmente efficace quando un progetto viene sviluppato seguendo<br />
la filosofia della OOP (Object Oriented Programmino) che vede programmatore sviluppare componenti autonomi con<br />
l'obiettivo <strong>di</strong> poterli facilmente riutilizzare in progetti <strong>di</strong>versi. L’OOP verrà affrontata in quarta.
ESERCIZI SVOLTI – secondo blocco<br />
SOT 12. <strong>di</strong>fficoltà: bassa Dati due numeri determinare il maggiore.<br />
program massimo;<br />
var n1,n2,x: real;<br />
(* restituisce il massimo tra due valori comunicati *)<br />
function max(a,b: real): real;<br />
var risultato: real;<br />
begin<br />
if a>=b then<br />
risultato:=a<br />
else<br />
risultato:=b;<br />
max:=risultato<br />
end;<br />
(* prove <strong>di</strong> utilizzo della funzione massimo *)<br />
begin<br />
(* specificando come parametri delle costanti numeriche ... *)<br />
writeln ( max(3,46):2:0 );<br />
writeln ( max(46,3):2:0 );<br />
writeln ( max(3,3):2:0 );<br />
writeln ( max(-43,1) :2:0);<br />
(* specificando come parametri delle variabili esterne *)<br />
writeln('Inserisci due numeri e ti <strong>di</strong>ro'' qual''e'' il piu'' grande');<br />
write('Dimmi il primo -> '); readln(n1);<br />
write('Dimmi il secondo -> '); readln(n2);<br />
writeln ( max(n1,n2) :2:0);<br />
writeln ( max(n1,46) :2:0);<br />
writeln ( max(46,n2) :2:0);<br />
(* una funzione puo' essere usata <strong>di</strong>rettamente nel calcolo <strong>di</strong> un'espressione *)<br />
x:=13 * ( max(n1,6) - 18);<br />
writeln('X: ',x:2:0);<br />
(* max puo' essere usata nella con<strong>di</strong>zione <strong>di</strong> un if *)<br />
if max(n1,n2) > 100 then<br />
writeln('Il massimo tra i due valori supera 100');<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 105<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
(* o <strong>di</strong> un repeat ... *)<br />
(* ad una centralina <strong>di</strong> controllo arrivano i dati sull'inquinamento letti da due sonde poste in punti strategici della<br />
citta'; leggere questi dati fino a quando una delle due sonde comunica un valore maggiore <strong>di</strong> 50 (microgrammi/mc)<br />
*)<br />
repeat<br />
write('Inserire valore sonda n. 1 -> ');<br />
readln(n1);<br />
write('Inserire valore sonda n. 2 -> ');<br />
readln(n2)<br />
until max(n1,n2)>50; (* grazie alla funzione max il controllo può essere effettuato in contemporanea *)<br />
readln;<br />
end.
SOT 13. <strong>di</strong>fficoltà: bassa Determinare il numero <strong>di</strong> vocali presenti in una stringa.<br />
program prova;<br />
uses newdelay,crt;<br />
(* conta quante vocali ci sono in una stringa, prima tecnica *)<br />
function contaVocali(s: string) : integer;<br />
var nv,i: integer;<br />
begin<br />
nv:=0; (* numero vocali *)<br />
for i:=1 to length(s) do<br />
case s[i] of<br />
'a','A','e','E','i','I','o','O','u','U': nv:=nv+1;<br />
end;<br />
contaVocali:=nv<br />
end;<br />
(* conta quante vocali ci sono in una stringa, secondo tecnica *)<br />
(* usa la funzione POS invece del case *)<br />
function contaVocali2(s: string) : integer;<br />
var nv,i: integer;<br />
begin<br />
nv:=0; (* numero vocali *)<br />
for i:=1 to length(s) do<br />
if pos(s[i], 'aAeEiIoOuU')0 then<br />
nv:=nv+1;<br />
contaVocali2:=nv<br />
end;<br />
begin<br />
writeln(contaVocali('casa dolce casa'));<br />
readln<br />
end.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 106<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SOT 14. <strong>di</strong>fficoltà: bassa Scrivere un comando che invita l'operatore a premere un tasto per continuare.<br />
(* ha bisogno <strong>di</strong> commenti ? *)<br />
procedure atten<strong>di</strong>;<br />
begin<br />
writeln('.............. PREMI INVIO PER CONTINUARE ...............');<br />
readln<br />
end;
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 107<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SOT 15. <strong>di</strong>fficoltà: bassa Scrivere un comando per la visualizzazione <strong>di</strong> un messaggio qualsiasi tra due cornicette<br />
<strong>di</strong> asterischi; l'esecuzione del programma deve anche interrompersi invitando l'utente a per un tasto per continuare<br />
(sfruttare il comando precedente).<br />
Program prova;<br />
procedure atten<strong>di</strong>;<br />
begin<br />
writeln('.............. PREMI INVIO PER CONTINUARE ...............');<br />
readln<br />
end;<br />
(* visualizza la stringa ricevuta ed attende la pressione <strong>di</strong> INVIO *)<br />
procedure messaggio(mes: string);<br />
begin<br />
writeln('---------------------------------------------');<br />
writeln(mes);<br />
writeln('---------------------------------------------');<br />
atten<strong>di</strong>;<br />
end;<br />
begin<br />
clrscr;<br />
messaggio(‘Ciao, come va?’);<br />
end.<br />
SOT 16. <strong>di</strong>fficoltà: bassa Scrivere un sottoprogramma per il calcolo <strong>di</strong> x y con X e Y interi positivi.<br />
program potenze;<br />
uses newdelay, crt;<br />
var unaBase,unEsponente: integer;<br />
function eleva(base,esponente: integer):real;<br />
var risultato: real; i: integer;<br />
begin<br />
risultato:=1;<br />
for i:=1 to esponente do<br />
risultato:=risultato*base;<br />
eleva:=risultato<br />
end;<br />
begin<br />
clrscr;<br />
write('Dimmi la base '); readln(unaBase);<br />
write('Dimmi l''esponente '); readln(unEsponente);<br />
writeln('base: ',unaBase,' - esponente: ',unEsponente);<br />
writeln('il risultato e'': ', eleva(unaBase,unEsponente):6:0 );<br />
readln<br />
end.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 108<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
SOT 17. <strong>di</strong>fficoltà: me<strong>di</strong>a Togliere da una stringa eventuali spazi inutili all'inizio o alla fine della stringa stessa.<br />
program elimina_spazi;<br />
uses crt;<br />
var stringa: string;<br />
function togli_spazi(s: string): string;<br />
var i: integer;<br />
inizio,fine: integer;<br />
spazio: boolean;<br />
risultato: string;<br />
begin<br />
inizio:=1; fine:=length(s);<br />
(* cerco il primo carattere <strong>di</strong>verso da spazio a sinistra *)<br />
spazio:= (s[inizio]=' ');<br />
while ( inizio=inizio ) and spazio do<br />
begin<br />
dec(fine);<br />
spazio:=(s[fine]=' ')<br />
end;<br />
risultato:='';<br />
for i:=inizio to fine do<br />
risultato:=risultato+s[i];<br />
togli_spazi:=risultato<br />
end;<br />
begin<br />
clrscr;<br />
writeln( '#',togli_spazi(''),'#' );<br />
writeln( '#',togli_spazi('a'),'#' );<br />
writeln( '#',togli_spazi(' a'),'#' );<br />
writeln( '#',togli_spazi('a '),'#' );<br />
writeln( '#',togli_spazi('ab'),'#' );<br />
writeln( '#',togli_spazi(' a'),'#' );<br />
writeln( '#',togli_spazi('a '),'#' );<br />
writeln( '#',togli_spazi(' ab'),'#' );<br />
writeln( '#',togli_spazi('ab '),'#' );<br />
writeln( '#',togli_spazi(' ab '),'#');<br />
writeln( '#',togli_spazi(' questa e’’ una frase '),'#' );<br />
readln<br />
end.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 109<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
REGOLE DI VISIBILITA (SCOPING RULES) E DURATA DELLE VARIABILI<br />
Scrivendo programmi <strong>di</strong> una certa complessità è importante capire per ogni entità (variabili, costanti,<br />
sottoprogrammi, tipi ecc.) dove può essere usata e la sua durata (che non è detto che si estenda dal momento in cui il<br />
programma viene fatto partire a quello in cui viene terminato).<br />
Quando un'applicazione è costituita da un unico file sorgente e non si fa uso <strong>di</strong> sottoprogrammi il problema non<br />
sussiste: in un qualsiasi punto del co<strong>di</strong>ce tra il begin <strong>di</strong> inizio e l’end finale possiamo far riferimento ad una variabile<br />
qualsiasi, ad una costante qualsiasi ecc; queste stesse variabili esistono dal momento in cui il programma inizia la<br />
sua esecuzione fino al momento in cui il programma viene terminato.<br />
La situazione si complica quando in un programma si <strong>di</strong>chiarano dei sottoprogrammi (procedure o funzioni): infatti<br />
possiamo avere parametri con lo stesso nome <strong>di</strong> variabili o costanti del programma principale, oppure variabili o<br />
costanti <strong>di</strong>chiarate nelle rispettive sezioni del sottoprogramma e che hanno lo stesso nome <strong>di</strong> variabili e costanti del<br />
programma principale. Cerchiamo <strong>di</strong> mettere a fuoco la situazione con un esempio:<br />
program principale;<br />
var A: integer; S: string;<br />
X: real;<br />
procedure P(A: integer);<br />
var S: real;<br />
begin<br />
write(A);<br />
S := 3.14*X;<br />
end;<br />
begin<br />
A := 4;<br />
S := ciao;<br />
end.<br />
In quali punti del co<strong>di</strong>ce è visibile la variabile A <strong>di</strong>chiarata nella sezione var<br />
del programma principale?<br />
Quando ci troviamo all'interno del blocco begin … end. del programma<br />
principale e tentiamo <strong>di</strong> assegnare un valore alla variabile A, viene usata<br />
quella <strong>di</strong>chiarata nella sezione var del programma principale oppure quella<br />
<strong>di</strong>chiarata come parametro per la procedura P?<br />
E quando ci troviamo all'interno del blocco begin … end. del programma<br />
principale e tentiamo <strong>di</strong> assegnare un valore alla variabile S, viene usata<br />
quella <strong>di</strong>chiarata nella sezione var del programma principale oppure quella<br />
<strong>di</strong>chiarata come variabile locale nella procedura P?<br />
La variabile A usata con l'istruzione write nella procedura è il suo parametro<br />
o la variabile A <strong>di</strong>chiarata nella sezione var del programma principale?<br />
La variabile S usata con l’assegnamento nella procedura è la sua variabile<br />
locale o, <strong>di</strong> nuovo, quella della sezione var del programma principale?<br />
La variabile A usata nel corpo principale del programma è quella <strong>di</strong>chiarata<br />
nella sezione var del programma principale o il parametro della procedura?<br />
E infine: la variabile S usata nel corpo principale del programma è quella<br />
<strong>di</strong>chiarata nella sezione var del programma principale o la variabile locale<br />
con lo stesso nome <strong>di</strong>chiarata nella procedura?<br />
Come potete ben capire è essenziale stabilire delle regole, pena il caos totale!<br />
• Le entità <strong>di</strong>chiarate nella sezione var del programma principale sono visibili:<br />
o in un punto qualsiasi tra il begin e l’end. del programma principale<br />
o in un sottoprogramma qualsiasi a patto che nessun parametro o variabile locale abbiano lo stesso<br />
nome: in questo ultimo caso tra il begin e l'end hanno il sopravvento i parametri delle variabili<br />
locali (potremmo <strong>di</strong>re che si fa sempre riferimento alla <strong>di</strong>chiarazione ‘più vicina’ al punto in cui<br />
stiamo scrivendo);<br />
NOTA: abbiamo comunque più volte riba<strong>di</strong>to che un sottoprogramma non dovrebbe mai tentare <strong>di</strong><br />
utilizzare <strong>di</strong>rettamente una variabile esterna (sempre e solo attraverso un parametro!)<br />
• Le entità <strong>di</strong>chiarate nella sezione var del programma principale esistono dal momento in cui inizia il<br />
programma fino al momento in cui termina.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 110<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Le entità <strong>di</strong>chiarate a livello <strong>di</strong> sottoprogramma (parametri e variabili locali) sono visibili:<br />
o tra il begin e l'end <strong>di</strong> quel sottoprogramma; parametri e variabili locali non possono quin<strong>di</strong> nessun<br />
modo essere utilizzati all'esterno del sottoprogramma in cui sono <strong>di</strong>chiarati;<br />
o in altri sottoprogrammi ma solo se <strong>di</strong>chiarati all'interno del primo (e a patto che questi ultimi a<br />
loro volta non utilizzino parametri o loro variabili locali con lo stesso nome);<br />
• Le entità <strong>di</strong>chiarate a livello <strong>di</strong> sottoprogramma (parametri e variabili locali) vengono create nel momenti in<br />
cui inizia l'esecuzione del sottoprogramma e vengono <strong>di</strong>strutte nel momenti in cui l'esecuzione del<br />
sottoprogramma termina; ne deriva che quando un sottoprogramma viene chiamato più volte i valori assunti in<br />
precedenza dalle variabili locali non sono più <strong>di</strong>sponibili.<br />
Applicando queste regole all'esempio precedente: la variabile A del programma può essere usata nel corpo<br />
principale ma non nella procedura (il parametro A la oscura); stessa situazione per la stringa S del programma (non<br />
può essere utilizzata nella procedura perché oscurata dalla variabile locale S; la variabile X può essere utilizzata<br />
invece dappertutto, nel programma e nella procedura (pratica, quest'ultima, da sconsigliare); il parametro A e la<br />
variabile locale S della procedura possono essere utilizzati solo in quest'ultima.<br />
La letteratura informatica in<strong>di</strong>ca anche con la <strong>di</strong>citura ambiente locale l’insieme delle entità <strong>di</strong>chiarate all'interno <strong>di</strong><br />
una stessa entità sintattica (programma, sottoprogramma); ad esempio l'ambiente locale per una procedura è<br />
costituito dai suoi parametri e dalle sue variabili locali. Ciò che è invece <strong>di</strong>chiarato all'esterno <strong>di</strong> una certa unità<br />
sintattica viene invece in<strong>di</strong>cato come ambiente non locale. Ad esempio, rispetto ad un sottoprogramma le variabili<br />
globali fanno parte dell'ambiente non locale. Attenzione però a non far corrispondere l'ambiente non locale con le<br />
variabili globali: se consideriamo infatti il caso <strong>di</strong> un sottoprogramma <strong>di</strong>chiarato all'interno <strong>di</strong> un altro per<br />
quest'ultimo l'ambiente non locale è costituito certamente delle variabili globali ma anche dalle variabili locali alla<br />
sottoprogramma che lo contiene:<br />
program prova;<br />
var x: real;<br />
procedure P1;<br />
var y: real;<br />
procedure P2;<br />
var z: real;<br />
begin<br />
end;<br />
begin<br />
end;<br />
begin end.<br />
La procedura P2 è <strong>di</strong>chiarata all'interno <strong>di</strong> P1. Il senso <strong>di</strong> ciò sta nel fatto che P2<br />
svolge un compito molto particolare che a senso solo nell'ambito <strong>di</strong> P1. Dichiarata<br />
in questo modo, solo P1 può invocare P2 evitando usi maldestri.<br />
L'ambiente non locale <strong>di</strong> P2 è costituito non solo dalla variabile x del programma<br />
principale ma anche dalla variabile y della procedura P1.<br />
Quando l'applicazione è il risultato della compilazione e link <strong>di</strong> più sorgenti la situazione si complica: in generale<br />
bisogna decidere cosa concedere in utilizzo <strong>di</strong> un certo sorgente quando stiamo scrivendo il co<strong>di</strong>ce <strong>di</strong> un altro. I<br />
linguaggi si <strong>di</strong>fferenziano per possibilità e notazioni sintattiche anche molto <strong>di</strong>verse. Di solito è comunque possibile<br />
in<strong>di</strong>care un’entità <strong>di</strong>chiarata nella sezione var principale come globale rispetto agli altri file sorgenti; ad esempio,<br />
dare la possibilità dal file sorgente<br />
‘unit1’ <strong>di</strong> usare una variabile globale<br />
File principale<br />
della ‘Unit2’ o <strong>di</strong> richiamare un<br />
sottoprogramma <strong>di</strong> una qualsiasi altra<br />
Unit. In alcuni linguaggi, turbo Pascal<br />
Unit1 Unit2 Unit3 UnitN<br />
compreso, per accedere all'entità <strong>di</strong><br />
un altro file si deve rendere palese la<br />
volontà <strong>di</strong> usare quest'ultimo con una<br />
<strong>di</strong>rettiva al compilatore (uses crt non<br />
vi <strong>di</strong>ce nulla?).<br />
.EXE
TIPO DI DATO ARRAY<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 111<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Voglio presentarvi alcune situazioni ‘problematiche’ per evidenziare l’inadeguatezza degli strumenti da<br />
programmatore che sono attualmente in vostro possesso.<br />
Caso <strong>di</strong> stu<strong>di</strong>o 1: a proposito <strong>di</strong> me<strong>di</strong>e …<br />
Avete fatto carriera e vi ritrovate prof. Informatica! Avete anche appena terminato <strong>di</strong> correggere il primo pacco <strong>di</strong><br />
compiti (sigh) e vorreste contare quanti alunni hanno preso un voto superiore alla me<strong>di</strong>a della classe: che<br />
informatici sareste se non pensaste <strong>di</strong> utilizzare un programmino per calcolare una volta per tutte questo valore ?<br />
Program me<strong>di</strong>e;<br />
var<br />
i, voto: integer; (* serve per i cicli, voto per leggere da tastiera il voto *)<br />
somma_voti, num_alunni: integer; (* num_alunni: numero degli alunni *)<br />
quanti_sopra_me<strong>di</strong>a: integer;<br />
me<strong>di</strong>a: real;<br />
begin<br />
somma_voti:=0; me<strong>di</strong>a:=0; quanti_sopra_me<strong>di</strong>a:=0;<br />
writeln(Quanti alunni ci sono? );<br />
readln(num_alu);<br />
for i:=1 to num_alu do<br />
begin<br />
writeln(Inserisci voto prossimo alunno: );<br />
readln(voto);<br />
somma_voti:=somma_voti + voto<br />
end;<br />
me<strong>di</strong>a:=somma_voti /num_alu;<br />
Dopo aver impiegato solo pochi millesimi <strong>di</strong> secondo per scrivere questo co<strong>di</strong>ce, vi accorgete <strong>di</strong> … non sapere<br />
come andare avanti! Infatti ora che avete calcolato la me<strong>di</strong>a dovreste contare i voti che la superano, ma non avete<br />
più i voti a <strong>di</strong>sposizione per confrontarli ! Infatti la variabile voto conterrà solo l’ultimo voto letto. I precedenti sono<br />
stati sommati nella variabile somma_voti e poi soprascritti ciascuno dal successivo (questo ovviamente perché la<br />
readln utilizza nel ciclo sempre la stessa variabile per leggere i voti).
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 112<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
ŁŁ Una prima orripilante soluzione (sicuri <strong>di</strong> esservi laureati in Informatica??) potrebbe essere quella <strong>di</strong> chiedere<br />
una seconda volta all’operatore gli stessi voti per contare quelli sopra la me<strong>di</strong>a<br />
(si immagina <strong>di</strong> continuare il programma <strong>di</strong> prima):<br />
for i:=1 to num_alu do<br />
begin<br />
writeln(Inserisci voto prossimo alunno: );<br />
readln(voto);<br />
if voto>me<strong>di</strong>a then<br />
quanti_sopra_me<strong>di</strong>a:= quanti_sopra_me<strong>di</strong>a + 1<br />
end;<br />
Stiamo scherzando? A parte le furiose proteste dell’utente che si vede costretto a ripetere l’inserimento<br />
dei dati, provate a pensare al rischio <strong>di</strong> inserire dati <strong>di</strong>versi dai precedenti con risultati del tutto<br />
falsati…<br />
ATTENZIONE: la seconda ‘soluzione’ che viene proposta qui <strong>di</strong> seguito è, come il primo, solo un esempio. Come<br />
tale è NECESSARIO per apprezzare e capire l’uso del nuovo strumento <strong>di</strong> programmazione che presenterò, ma non<br />
deve essere ‘stu<strong>di</strong>ato’.<br />
ŁŁ Seconda soluzione (lascio decidere a voi se più o meno orripilante della precedente): ‘banale,<br />
come ho fatto a non pensarci prima: basta usare una variabile <strong>di</strong>versa per ogni voto!’<br />
Program me<strong>di</strong>e;<br />
var<br />
i, somma_voti, num_alunni, quanti_sopra_me<strong>di</strong>a: integer;<br />
me<strong>di</strong>a: real;<br />
voto1, voto2, voto3, voto4, , voto32 (aiuto!): integer;<br />
begin<br />
soma_voti:=0; me<strong>di</strong>a:=0; quanti_sopra_me<strong>di</strong>a:=0;<br />
writeln(Quanti alunni ci sono? );<br />
readln(num_alu);<br />
Il bello arriva adesso! Poiché non si sta usando una sola variabile (voto) per leggere i voti, non è possibile usare un<br />
ciclo per leggere i voti! E’ necessario leggere il primo. Poi se il numero <strong>di</strong> alunni è maggiore <strong>di</strong> 1, significa che devo<br />
sicuramente leggerne anche un secondo; poi se il numero <strong>di</strong> alunni è anche maggiore <strong>di</strong> due significa che devo<br />
leggerne almeno anche un terzo e così via …<br />
writeln(Inserisci voto primo alunno: );<br />
readln(voto1);<br />
somma_voti:= somma_voti + voto1<br />
if num_alu>1 then<br />
begin<br />
writeln(Inserisci voto secondo alunno: );<br />
readln(voto2);<br />
somma_voti:= somma_voti + voto2<br />
end;
e così via fino a …<br />
if num_alu>2 then<br />
begin<br />
writeln(Inserisci voto prossimo alunno: );<br />
readln(voto3);<br />
somma_voti:= somma_voti + voto3<br />
end;<br />
if num_alu>31 then<br />
begin<br />
writeln(Inserisci voto 32-mo alunno: );<br />
readln(voto32);<br />
somma_voti:= somma_voti + voto<br />
end;<br />
me<strong>di</strong>a:=somma_voti / num_alu; (* alleluia *)<br />
e ora ripetiamo tutto per contare quelli sopra la me<strong>di</strong>a<br />
if voto1>me<strong>di</strong>a then<br />
quanti_sopra_me<strong>di</strong>a:= quanti_sopra_me<strong>di</strong>a + 1;<br />
if num_alu>1 then<br />
if voto2>me<strong>di</strong>a then<br />
quanti_sopra_me<strong>di</strong>a:= quanti_sopra_me<strong>di</strong>a + 1;<br />
ecc. ecc. (abbiate pietà !)<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 113<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Non so voi, ma io ho già perso <strong>di</strong> vista l’obiettivo che ci eravamo posti… Immaginate se invece degli<br />
alunni <strong>di</strong> una sola classe considerassimo quelli dell’intero istituto per una qualche statistica!
ED ECCO LA SOLUZIONE …<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 114<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Quello che ci serve è un contenitore (si tratterà poi pur sempre <strong>di</strong> una variabile, anche se <strong>di</strong> tipo particolare) capace<br />
<strong>di</strong> contenere non un solo voto ma un insieme <strong>di</strong> voti. Il nome del contenitore in questo modo sarebbe uno solo,<br />
risolvendo l’inconveniente del proliferare selvaggio del numero delle variabili; inoltre, come vi spiegherò tra poco,<br />
sarà ancora possibile usare i cicli per elaborare con poche righe <strong>di</strong> co<strong>di</strong>ce tutti i dati.<br />
VOTI<br />
7 4 5 6 6 8 8 5<br />
voti[1] voti[2] voti[3] voti[4] voti[5] voti[6] ecc.<br />
• Il nome del contenitore è VOTI. Questo è il nome che il programmatore ha deciso <strong>di</strong> dare all’ARRAY.<br />
Quando l’array è come quello nel <strong>di</strong>segno soprastante (lineare, mono<strong>di</strong>mensionale) è anche chiamato<br />
vettore. Esistono array bi<strong>di</strong>mensionali (pensate ad una griglia tipo battaglia navale, o ad uno schema tipo<br />
parole crociate) chiamati matrici.<br />
• I dati non sono depositati nel vettore alla rinfusa ma tenuti in ‘scomparti’ separati.<br />
• Ogni ‘scomparto’ occupa una precisa posizione (1, 2, 3, 4 ecc.) specificata tramite un numero detto in<strong>di</strong>ce<br />
che va in<strong>di</strong>cato tra parentesi quadrate dopo il nome dell’array come in voti[1], voti[2] ecc.<br />
Ecco come si <strong>di</strong>chiara in Pascal una variabile array adatta a contenere 32 interi:<br />
var (* NOTA. sarebbe meglio mettere una costante invece del 32:<br />
voti: array[1 .. 32] of integer; array[1 .. NUM_ALU] of integer *)<br />
Per poter passare gli array ai sottoprogrammi è però meglio abituarsi fin da subito a <strong>di</strong>chiarare prima un nuovo tipo<br />
<strong>di</strong> dati e solo dopo le variabili <strong>di</strong> quel tipo:<br />
NOTA: la sezione type deve essere scritta prima della sezione var<br />
type<br />
vet_int = array[1 .. 32] of integer; (* vet_int sta per vettore <strong>di</strong> interi *)<br />
var<br />
voti: vet_int;<br />
OSSERVAZIONI<br />
• Quando si <strong>di</strong>chiara un tipo non si usano i due punti ma l’uguale (… = array … )<br />
• Nella sezione var è poi possibile <strong>di</strong>chiarare tutte le variabili <strong>di</strong> quel tipo che si vogliono: tutte<br />
corrisponderanno a contenitori per 32 interi<br />
Voti è <strong>di</strong> tipo vet_int, cioè un array[1 .. 32] of integer, esattamente come prima. Anche un parametro <strong>di</strong> un<br />
sottoprogramma potrà essere <strong>di</strong>chiarato <strong>di</strong> tipo vet_int (la <strong>di</strong>citura array[1 ..32] of integer non sarebbe infatti<br />
accettata come tipo <strong>di</strong> un parametro).
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 115<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Una volta specificata la posizione tra parentesi quadre la scrittura in<strong>di</strong>ca esattamente una variabile del tipo contenuto<br />
nel vettore. Detto con altre parole: vet[7] è, a tutti gli effetti, una variabile integer e può essere usata dove siete<br />
abituati ad usare una qualsiasi altra variabile integer:<br />
Writeln(Dimmi un valore e lo memorizzerò nella posizione 4 del vettore);<br />
readln( voti[4] );<br />
writeln(Ora visualizzo il contenuto del sesto elemento del vettore: );<br />
writeln( voti[6] );<br />
if vet[2]>x then <br />
repeat<br />
<br />
until x>vet[5];<br />
x:=6 * vet[3] 2*vet[7]<br />
writeln ('Dimmi una posizione e visualizzerò il contenuto <strong>di</strong> quellelemento: );<br />
readln (posizione);<br />
writeln ( voti[posizione] );<br />
L’ultimo esempio è particolarmente interessante: invece <strong>di</strong> specificare un numero preciso, come in<strong>di</strong>ce è possibile<br />
mettere una variabile integer. Questo dà al programmatore la possibilità <strong>di</strong> scan<strong>di</strong>re, passare in rassegna, più<br />
elementi del vettore cambiando in un ciclo il valore della variabile in<strong>di</strong>ce:<br />
for i:=1 to num_voti do<br />
writeln( voti[i] );<br />
Cercate <strong>di</strong> capire bene questo spezzone <strong>di</strong> co<strong>di</strong>ce: è alla base <strong>di</strong> tutte le elaborazioni con gli array che vi saranno<br />
spiegate in questo corso!<br />
La variabile i, grazie al ciclo, assume tutte le posizioni del vettore e <strong>di</strong> volta in volta in<strong>di</strong>vidua nel ciclo elementi<br />
successivi, uno dopo l’altro dal primo all’ultimo. La prima volta che viene eseguito il ciclo la i vale 1 e quin<strong>di</strong> la<br />
writeln corrisponde a writeln(voti[1]) e viene quin<strong>di</strong> visualizzato il contenuto del primo elemento del vettore. Poi la<br />
i <strong>di</strong>venta 2, e viene elaborato il secondo elemento e così via fino all’ultimo in<strong>di</strong>cato nel ciclo (num_voti).<br />
E’ arrivato il momento <strong>di</strong> mettere tutto insieme e risolvere elegantemente il problema <strong>di</strong> partenza …
Program me<strong>di</strong>e;<br />
const<br />
MAX_ALUNNI=32;<br />
type<br />
vettore_32_interi = array[1 .. MAX_ALUNNI] of integer;<br />
var<br />
voti: vettore_32_interi;<br />
i, somma_voti, num_alunni, quanti_sopra_me<strong>di</strong>a: integer;<br />
me<strong>di</strong>a: real;<br />
begin<br />
somma_voti:=0; me<strong>di</strong>a:=0; quanti_sopra_me<strong>di</strong>a:=0;<br />
writeln(Quanti alunni ci sono? );<br />
readln(num_alu);<br />
(* CARICAMENTO DEI DATI NEL VETTORE *)<br />
for i:=1 to num_alu do<br />
begin<br />
writeln(Inserisci voto prossimo alunno: );<br />
readln(voti[i]);<br />
somma_voti:=somma_voti + voti[i]<br />
end;<br />
me<strong>di</strong>a:=somma_voti / num_alu;<br />
(* I DATI SONO ANCORA NEL VETTORE: CONTROLLIAMO TUTTI *)<br />
for i:=1 to num_alu do<br />
if voti[i]>me<strong>di</strong>a then<br />
quanti_sopra_me<strong>di</strong>a:= quanti_sopra_me<strong>di</strong>a + 1;<br />
writeln(Risultato: ,quanti_sopra_me<strong>di</strong>a);<br />
end.<br />
OSSERVAZIONI<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 116<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
a) Se gli alunni <strong>di</strong>ventassero anche 10000 il co<strong>di</strong>ce rimarrebbe identico! Sarebbe sufficiente mo<strong>di</strong>ficare il<br />
valore della costante MAX_ALUNNI<br />
b) Non è obbligatorio usare tutte le posizioni del vettore: al massimo ce ne sono <strong>di</strong>sponibili MAX_ALUNNI<br />
ma il valore comunicato al computer (num_alu) potrebbe anche essere inferiore. A <strong>di</strong>re la verità, il<br />
programma dovrebbe controllare che questo valore sia compreso tra 1 e MAX_ALUNNI perché<br />
tentare <strong>di</strong> usare un elemento prima dellinizio del vettore o dopo la fine è un gravissimo errore che<br />
spesso porta al blocco del programma ed eventualmente del computer stesso
Caso <strong>di</strong> stu<strong>di</strong>o 2: MENU<br />
Attraverso questo esempio imparerete a passare i vettori ai sottoprogrammi.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 117<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Un menu presenta sullo schermo un elenco <strong>di</strong> opzioni delle quali una sola sarà quella scelta dall’utente. Un menu<br />
può presentarsi a tutto schermo, come nell’esempio qui sotto a sinistra, o sotto forma <strong>di</strong> barra dei menu, come<br />
nell’esempio qui sotto a destra.<br />
Scegli il gioco che preferisci<br />
1. Tennis<br />
2. Poker<br />
3. Scacchi<br />
4. ESCI<br />
SCEGLI => _<br />
File Mo<strong>di</strong>fica<br />
Apri<br />
Salva<br />
Chiunque abbia provato ad usare un programma per computer sa che sono veramente tante le situazioni in cui è<br />
necessario <strong>di</strong>alogare con l’utente presentando un menu <strong>di</strong> scelte.<br />
Limitandoci alla tipologia rappresentata qui sopra a sinistra, fondamentalmente lo scopo <strong>di</strong> un menu è sempre lo<br />
stesso: scrivere un titolo, sotto <strong>di</strong> questo scrivere un certo numero <strong>di</strong> scelte e leggere la risposta dell’utente.<br />
Situazione perfetta per la scrittura <strong>di</strong> un sottoprogramma: comunico titolo, opzioni, numero <strong>di</strong> queste ultime e come<br />
valore <strong>di</strong> ritorno ci si aspetterà il numero della scelta fatta. Naturalmente il sottoprogramma provvederà anche a<br />
rifiutare scelte impossibili (valori minori <strong>di</strong> 1 o più gran<strong>di</strong> del numero delle opzioni previste), a visualizzare<br />
messaggi <strong>di</strong> errore e a far ripetere la scelta fino a che l’utente non in<strong>di</strong>cherà forzatamente una delle opzioni previste:<br />
Titolo<br />
n. opzioni<br />
opzione 1<br />
opzione 2<br />
opzione 3<br />
opzione 4<br />
Ecc.<br />
MENU<br />
numero della scelta<br />
fatta
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 118<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
La chiamata alla funzione menu che corrisponde all’esempio cui ci stiamo riferendo sarebbe allora:<br />
scelta:=menu(Scegli il gioco che preferisci,4,TennisPokerScacchiESCI)<br />
Un’altra situazione tipica è quella in cui, segnalando un errore, si vuole chiedere all’utente come procedere.<br />
Immaginiamo che si sia verificato un errore <strong>di</strong> registrazione <strong>di</strong> un documento:<br />
scelta:=menu(Errore <strong>di</strong> scrittura su <strong>di</strong>sco,3,RiprovaIgnoraannulla)<br />
Scusi prof., mi sa che ha <strong>di</strong>menticato un parametro nel secondo esempio (lo studente me<strong>di</strong>o finge clamorosamente <strong>di</strong><br />
essere <strong>di</strong>sinteressato a quello che scrivi, ma prova a sbagliare una virgola …): devono essere sei (il titolo, numero<br />
opzioni e le quattro scelte).<br />
E già, ma se le scelte sono solo 3 come nel secondo esempio? O se ne servissero 12?? L’unica soluzione ora alla<br />
vostra portata sarebbe quella <strong>di</strong> prevedere il massimo numero possibile <strong>di</strong> scelte, ignorando quelle che <strong>di</strong> volta in<br />
volta non servono; la <strong>di</strong>chiarazione dell’intestazione <strong>di</strong> questa funzione sarebbe un vero ‘mostro’ che potrebbe<br />
assomigliare alla seguente:<br />
Function menu(titolo: string; num_opzioni: integer;<br />
scelta1,scelta2,scelta3,scelta4,,sceltaN: string): integer;<br />
dove N rappresenta il massimo numero <strong>di</strong> scelte utilizzabile; se le opzioni fossero <strong>di</strong> più la situazione non sarebbe<br />
gestibile a meno <strong>di</strong> mo<strong>di</strong>ficare il sottoprogramma.<br />
Chiamare questa funzione sarebbe veramente una ‘pena’. Immaginiamo infatti <strong>di</strong> voler chiedere una conferma<br />
all’utente (in questo caso le opzioni sarebbero solo due: sì o no):<br />
scelta:=menu(Sei sicuro,2,SiNo)<br />
anche se le opzioni sono solo due, TUTTI i parametri devono comunque essere sempre in<strong>di</strong>cati (qui si immagina che<br />
quelli inutili siano in<strong>di</strong>cati come stringa nulla (‘’)<br />
Anche il co<strong>di</strong>ce della funzione sarebbe piuttosto pesante ed intricato:<br />
Function menu(titolo: string; num_opzioni: integer;<br />
scelta1,scelta2,scelta3,scelta4,,sceltaN: string): integer;<br />
begin<br />
writeln(titolo); (* e fino a qui *)<br />
e qui immaginatevi, un po come nel caso della me<strong>di</strong>a, tutta una serie <strong>di</strong> if then che a seconda del<br />
numero <strong>di</strong> opzioni specificato vanno o non vanno a stampare la variabile stringa che corrisponde a<br />
quellopzione; per semplicità mi limito solo ad iniziare <br />
writeln(1 ,scelta1);<br />
if num_opzioni>1 then<br />
writeln(2 ,scelta2);<br />
if num_opzioni>2 then<br />
writeln(3 ,scelta3);<br />
ecc.<br />
ED ECCO LA SOLUZIONE CON GLI ARRAY …<br />
La soluzione che sfrutta gli array prevede invece <strong>di</strong> passare le opzioni sotto forma <strong>di</strong> vettore <strong>di</strong> stringhe:<br />
type vettore_stringhe = array[1 .. 20] of string;<br />
Function menu(titolo: string; num_opzioni: integer; scelte: vettore_stringhe): integer;<br />
var i,scelta: integer;<br />
begin<br />
writeln(titolo);<br />
for I:=1 to num_opzioni do<br />
writeln(I, , scelte[i]);<br />
Stampa tutte le opzioni precedute dalla I e da un trattino: 1<br />
– opzione1, 2 – opzione 2 ecc.
writeln(Scegli -> );<br />
readln(scelta);<br />
menu:=scelta<br />
end;<br />
OPERAZIONI TIPICHE CON GLI ARRAY<br />
In tutti gli esempi che seguono si immaginano valere le seguenti <strong>di</strong>chiarazioni:<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 119<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
const<br />
MAX_ELEMENTI=100;<br />
type<br />
vettore_interi = array[1 .. MAX_ELEMENTI] of integer;<br />
var<br />
vettore: vettore_interi; N: integer; (* N è il numero <strong>di</strong> elementi da usare *)<br />
1. Caricamento <strong>di</strong> un vettore <strong>di</strong> N elementi, con N < MAX_ELEMENTI<br />
for i:=1 to N do<br />
begin<br />
writeln(Inserire elemento n. , i)<br />
readln( vettore[i] )<br />
end;<br />
Ve<strong>di</strong>amo la stessa operazione fatta con un sottoprogramma. Poiché non inten<strong>di</strong>amo avvantaggiarci <strong>di</strong> un valore<br />
restituito da una funzione, useremo una procedura.<br />
Essa riceverà il vettore da caricare per in<strong>di</strong>rizzo: <strong>di</strong>versamente non potremmo mo<strong>di</strong>ficare in modo permanente i<br />
valori degli elementi del vettore passato alla procedura. Il secondo parametro corrisponde al numero <strong>di</strong> elementi da<br />
caricare (chi usa la procedura deve assicurarsi <strong>di</strong> non passare come numero <strong>di</strong> elementi un valore superiore alla<br />
capacità massima del vettore).<br />
procedure carica_vettore(VAR vet: vettore_interi; N: integer);<br />
var i: integer;<br />
begin<br />
for i:=1 to N do<br />
begin<br />
writeln(Inserire elemento n. , i)<br />
readln( vet[i] )<br />
end;<br />
end;<br />
Per semplicità non è stato controllato l’input dell’utente<br />
(che potrebbe essere num_opzioni)<br />
NOTA: caricare un vettore <strong>di</strong><br />
stringhe, piuttosto che <strong>di</strong> reali<br />
comporterebbe solo la mo<strong>di</strong>fica<br />
delle <strong>di</strong>chiarazioni nella sezione<br />
type.<br />
NOTA<br />
Qualcuno potrebbe obiettare che la procedura risulta costituita da un numero <strong>di</strong> righe <strong>di</strong> co<strong>di</strong>ce superiore rispetto<br />
alla soluzione che non usa i sottoprogrammi. Ricor<strong>di</strong>amoci però che la procedura potrà essere chiamata tutte le volte<br />
che si vuole per caricare vettori dello stesso tipo (ANCHE in programmi <strong>di</strong>versi: basta copiarla/incollarla od<br />
includerla in una libreria). Qualche volta si tratterà <strong>di</strong> voti, altre volte <strong>di</strong> temperature, pesi od altezze ecc. La<br />
procedura non conosce il significato degli interi che sta caricando: svolge in modo anonimo il suo compito: riempire<br />
<strong>di</strong> interi un vettore che riceve come parametro, punto.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 120<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Suggerimento: prova a rendere ancora più generale la procedura prevedendo un terzo parametro corrispondente ad<br />
un messaggio personalizzato che solleciti l’utente in modo appropriato. In questo modo invece del generico ‘Inserire<br />
elemento n.’, potranno essere usati messaggi tipo ‘Inserire la temperatura n.’, o ‘Inserire il peso dell’oggetto n.’ ecc.<br />
2. Visualizzazione degli elementi <strong>di</strong> un vettore <strong>di</strong> N elementi, con N < MAX_ELEMENTI<br />
for i:=1 to N do<br />
writeln( vettore[i] );<br />
Procedura per visualizzare gli elementi <strong>di</strong> un vettore. Riceve il vettore (per<br />
valore, visto che non dobbiamo mo<strong>di</strong>ficare in alcun modo il suo contenuto!)<br />
ed il numero <strong>di</strong> elementi da considerare.<br />
NOTA: chi usa la procedura deve assicurarsi <strong>di</strong> non passare come numero <strong>di</strong><br />
elementi un valore superiore alla capacità massima del vettore.<br />
procedure visualizza_vettore(vet: vettore_interi; N: integer);<br />
var i: integer;<br />
begin<br />
for i:=1 to N do<br />
writeln( vet[i] )<br />
end;
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 121<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
3. Somma dei valori degli elementi <strong>di</strong> un vettore <strong>di</strong> N elementi, con N < MAX_ELEMENTI<br />
somma:=0;<br />
for i:=1 to N do<br />
somma:=somma + vettore[i] ;<br />
4. Me<strong>di</strong>a degli elementi <strong>di</strong> un vettore <strong>di</strong> N elementi, con N < MAX_ELEMENTI<br />
NOTA: questo esempio è particolarmente interessante se utilizziamo i sottoprogrammi. Infatti la funzione che<br />
calcola la me<strong>di</strong>a può richiamare quella, già scritta, che calcola la somma!<br />
somma:=0;<br />
for i:=1 to N do<br />
somma:=somma + vettore[i] ;<br />
me<strong>di</strong>a:= somma / N;<br />
Funzione per la stessa operazione.<br />
function somma_vettore(vet: vettore_interi; N: integer):integer;<br />
var tot: integer;<br />
begin<br />
tot:=0;<br />
for i:=1 to N do<br />
tot:=tot + vettore[i] ;<br />
somma_vettore:=tot<br />
end;<br />
Funzione per la stessa operazione.<br />
function me<strong>di</strong>a_vettore(vet: vettore_interi; N: integer):integer;<br />
begin<br />
me<strong>di</strong>a_vettore:= somma_vettore(vet,N) <strong>di</strong>v N<br />
end;<br />
NOTA: un sottoprogramma può passare ad un altro i suoi stessi parametri,<br />
con le stesse modalità viste sino ad ora (per valore o per in<strong>di</strong>rizzo).
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 122<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
5. Posizione dell’elemento con valore minimo in un vettore <strong>di</strong> N elementi, con N < MAX_ELEMENTI<br />
L’algoritmo è basato su questo ragionamento: all’inizio ipotizzo che il minimo sia l’elemento che si trova in<br />
posizione 1. Quin<strong>di</strong> controllo tutti gli altri elementi, dal secondo in poi, e tutte le volte che trovo una posizione con<br />
un valore più piccolo <strong>di</strong>venta quest’ultima la nuova posizione del minimo.<br />
posizione_min:= 1;<br />
for i:=2 to N do<br />
if vettore[i] < vettore[posizione_min] then (* trovata posizione i con valore più piccolo *)<br />
posizione_min := i;<br />
Funzione per la stessa operazione.<br />
function posizione_min(vet: vettore_interi; N: integer):integer;<br />
var pos_min:integer;<br />
begin<br />
pos_min:= 1;<br />
for i:=2 to N do<br />
if vet[i] < vet[pos_min] then (* trovata posizione i con valore più piccolo *)<br />
pos_min := i;<br />
posizione_min := pos_min<br />
end;<br />
NOTA: se nel vettore ci sono più elementi minimi con lo stesso valore, viene in<strong>di</strong>viduato il primo (quello con in<strong>di</strong>ce<br />
minore). Infatti quando viene confrontato il primo minimo con gli altri la con<strong>di</strong>zione vet[i] < vet[pos_min] non è<br />
verificata e l’aggiornamento della posizione non viene effettuata. Aggiungendo l’uguaglianza al confronto (vet[i]<br />
e pos_min con pos_max per<br />
ottenere l’algoritmo desiderato.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 123<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
7. Ricerca della posizione <strong>di</strong> un valore in un vettore <strong>di</strong> N elementi, con N < MAX_ELEMENTI<br />
writeln(Quale elemento cerchi? );<br />
readln(cercato);<br />
posizione:=0;<br />
for i:=1 to N do<br />
if vettore[i] = cercato then<br />
posizione:=i;<br />
function cerca_vettore(vet: vettore_interi; N: integer; cercato: integer):integer;<br />
var posizione: integer;<br />
begin<br />
posizione:=0;<br />
for i:=1 to N do<br />
if vet[i] = cercato then<br />
posizione:=i;<br />
cerca_vettore:=posizione<br />
end;<br />
Ed ecco la soluzione con il repeat, nel caso si voglia il primo elemento uguale a quello che si sta cercando. Questa<br />
soluzione può essere anche molto più efficiente <strong>di</strong> quella con il for. Ad esempio se gli elementi del vettore fossero<br />
<strong>di</strong>ecimila e quello che si sta cercando fosse nella quarta posizione, il ciclo for andrebbe inutilmente a controllare gli<br />
altri 9996, mentre il ciclo repeat si fermerebbe al quarto!<br />
posizione:=0; i:=0;<br />
repeat<br />
i:=i+1;<br />
if vettore[i] = cercato then<br />
posizione:=i<br />
until (i=N) or (posizione0);<br />
OSSERVAZIONI<br />
• Se l’elemento non viene trovato la variabile posizione rimarrà<br />
0. E’ importante controllarla prima <strong>di</strong> usarla! Ad esempio: if<br />
posizione>0 then (* elemento trovato, possiamo procedere<br />
*)<br />
• Se nel vettore l’elemento cercato appare più volte, il ciclo for<br />
localizza l’ultimo (comunque la scansione arriva ad N); per<br />
localizzare il primo è necessario controllare ‘manualmente’ la<br />
ricerca usando un ciclo repeat (come mostrato più avanti)<br />
function cerca_vettore(vet: vettore_interi; N: integer; cercato: integer):integer;<br />
var posizione,i: integer;<br />
begin<br />
posizione:=0; i:=0;<br />
repeat<br />
i:=i+1;<br />
if vettore[i] = cercato then<br />
posizione:=i<br />
until (i=N) or (posizione0);<br />
cerca_vettore:=posizione<br />
end;
7. Or<strong>di</strong>namento dei valori in un vettore <strong>di</strong> N elementi, con N < MAX_ELEMENTI<br />
METODO PER SCAMBIO – BUBBLE SORT<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 124<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Quello che sto per presentare è un metodo non molto efficiente quando il numero degli elementi supera il centinaio,<br />
ma ha il pregio <strong>di</strong> essere breve, relativamente semplice ed abbastanza facile da ricordare. Appartiene alla famiglia<br />
degli algoritmi <strong>di</strong> or<strong>di</strong>namento (SORT) detti per scambio perché adotta una scansione <strong>sistema</strong>tica che confronta<br />
‘ciecamente’ tutti gli elementi e scambia quelli fuori posto tra loro. Lo scambio <strong>di</strong> elementi è usato in modo pesante:<br />
altri algoritmi adottano tecniche che ‘ragionano’ <strong>di</strong> più sulla situazione del vettore od adottano tecniche più<br />
intelligenti (ripagate solo se il numero degli elementi è sufficientemente elevato) <strong>di</strong>minuendo il numero <strong>di</strong> scambi<br />
necessari. Ovviamente questi algoritmi sono più <strong>di</strong>fficili …<br />
Prima <strong>di</strong> esaminare il co<strong>di</strong>ce Pascal conviene presentare l’algoritmo in modo ‘informale’ aiutandosi anche con una<br />
simulazione grafica. Come punto <strong>di</strong> partenza consideriamo il seguente vettore <strong>di</strong>sor<strong>di</strong>nato:<br />
9 8 2 6 3<br />
L’algoritmo procede in questo modo:<br />
or<strong>di</strong>namento<br />
1. confronta la prima posizione con la seconda: se il numero contenuto nella prima posizione è maggiore <strong>di</strong> quello<br />
nella seconda scambia i numeri:<br />
confronto scambio<br />
9 8 2 6 3 9 8 2 6 3<br />
NOTA: tenete d’occhio il numero 9; come una bolla d’aria nell’acqua risalirà verso la ‘superficie’ ( la parte finale<br />
del vettore), da cui il nome dell’algoritmo (bubble=bolla sort=or<strong>di</strong>namento)<br />
2. spostati <strong>di</strong> una posizione verso destra e ripeti il confronto/scambio<br />
2 3 6 8 9<br />
3. ripeti il punto 2 fino a raggiungere la penultima posizione (che verrà confrontata con l’ultima)<br />
risultato<br />
8 9 2 6 3<br />
8 9 2 6 3 8 9 2 6 3 8 2 9 6 3<br />
8 2 9 6 3 8 2 9 6 3 8 2 6 9 3<br />
8 2 6 9 3 8 2 6 9 3 8 2 6 3 9
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 125<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Dopo questo primo ‘giro’ (passata) l’elemento più grande del vettore (il 9) ha raggiunto la sua giusta posizione. Ora<br />
si ripete il tutto fermandoci però alla penultima posizione (l’ultima, come detto prima, è già or<strong>di</strong>nata e va lasciata<br />
stare!). Per ricordarcelo, sfumiamo in grigio l’ultima casella del vettore.<br />
8 2 6 3 9 8 2 6 3 9 2 8 6 3 9<br />
2 8 6 3 9 2 8 6 3 9 2 6 8 3 9<br />
2 6 8 3 9 2 6 8 3 9 2 6 3 8 9<br />
Ora ripetiamo ma fermandoci alla terzultima posizione (le ultime due sono in or<strong>di</strong>ne)<br />
2 6 3 8 9<br />
nessuno scambio<br />
2 6 3 8 9 2 6 3 8 9 2 3 6 8 9<br />
NOTA: il vettore sarebbe già in or<strong>di</strong>ne, ma l’algoritmo ‘non se ne accorge’ e, ciecamente, continua i confronti:<br />
2 3 6 8 9 Nessuno scambio<br />
2 3 6 8 9<br />
2 3 6 8 9<br />
Ed ecco la co<strong>di</strong>fica in pascal:<br />
(* per n 1 volte *)<br />
for i:=1 to n - 1 do<br />
(* parti dalla prima posizione e confronta<br />
tutte le coppie fino alla n i *)<br />
for j := 1 to n - i do<br />
(* se trovi due celle a<strong>di</strong>acenti non<br />
in or<strong>di</strong>ne, scambiale *)<br />
if vettore[j] > vettore[j+1] then<br />
begin<br />
tmp:=vettore[j];<br />
vettore[j]:=vettore[j+1];<br />
vettore[j+1]:=tmp<br />
end;<br />
l’algoritmo si ferma quando ha ripetuto il processo n-1 volte<br />
2 6 3 8 9<br />
procedure bubble_sort(VAR vet: vettore_interi; n: integer);<br />
var i,j: integer;<br />
begin<br />
for i:=1 to n - 1 do<br />
for j := 1 to n - i do<br />
if vet[j] > vet[j+1] then<br />
begin<br />
tmp:=vet[j];<br />
vet[j]:=vet[j+1];<br />
vet[j+1]:=tmp<br />
end<br />
end;
7. Or<strong>di</strong>namento dei valori in un vettore <strong>di</strong> N elementi, con N < MAX_ELEMENTI<br />
METODO PER SELEZIONE – PER RICERCA DEL MINIMO<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 126<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Questo algoritmo appartiene alla categoria chiamata ‘per selezione’ perché invece <strong>di</strong> scambiare in continuazione<br />
celle a<strong>di</strong>acenti tra loro, cerca il miglior numero da spostare, riducendo <strong>di</strong> molto il numero degli scambi. In dettaglio:<br />
1. Partendo dalla prima posizione, localizza la cella con valore minimo; pren<strong>di</strong> il minimo e mettilo in prima<br />
posizione; il numero in prima posizione mettilo nella cella in cui hai trovato il minimo:<br />
cerca il minimo scambio risultato<br />
9 8 2 6 3<br />
2. Spostati in avanti <strong>di</strong> una casella ed ignora la prima (già in or<strong>di</strong>ne): cerca il minimo nella parte del vettore<br />
‘avanzata’; mettilo in seconda posizione; il numero in seconda posizione mettilo nella cella in cui hai trovato il<br />
minimo. Ripeti il proce<strong>di</strong>mento fino alla penultima posizione (a quel punto il numero in ultima posizione è per<br />
forza in or<strong>di</strong>ne.<br />
Ed ora il co<strong>di</strong>ce pascal:<br />
for i:=1 to n-1 do<br />
begin<br />
pos_min:=i;<br />
(* cerco la posizione del minimo<br />
tra i e ultimo elemento *)<br />
for j:=i+1 to n do<br />
if vet[j] < vet[pos_min] then<br />
pos_min:=j;<br />
(* scambio con il minimo *)<br />
tmp := vet[i] ;<br />
vet[i] := vet[pos_min];<br />
vet[pos_min]:=tmp<br />
end;<br />
9 8 2 6 3 2 8 9 6 3<br />
2 8 9 6 3 2 8 9 6 3 2 3 9 6 8<br />
2 3 9 6 8 2 3 9 6 8 2 3 6 9 8<br />
2 3 6 9 8 2 3 6 9 8 2 3 6 8 9<br />
procedure selezione_sort(VAR vet: vettore_interi; n: integer);<br />
var i,j: integer;<br />
begin<br />
for i:=1 to n-1 do<br />
begin<br />
pos_min:=i;<br />
for j:=i+1 to n do<br />
if vet[j] < vet[pos_min] then<br />
pos_min:=j;<br />
tmp := vet[i] ;<br />
vet[i] := vet[pos_min];<br />
vet[pos_min]:=tmp<br />
end<br />
end;
INTRODUZIONE ALL’USO DEI RECORD<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 127<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
I dati <strong>di</strong> un vettore o <strong>di</strong> una matrice devono essere tutti uguali: o tutti integer, o tutti real o string ecc. Questo limite<br />
porta a notevoli complicazioni anche per situazione semplici. Immaginiamo infatti <strong>di</strong> voler rappresentare la seguente<br />
scheda:<br />
Persona<br />
Cognome<br />
Nome<br />
Eta<br />
Fine-Persona<br />
Usando gli array sono possibili due soluzioni:<br />
1. Tre vettori in parallelo: uno per i cognomi, uno per i nomi<br />
ed uno per le età;<br />
2. Una matrice (<strong>di</strong> stringhe) a due colonne (la prima per il<br />
cognome e la seconda per il nome) ed un vettore per le<br />
età.<br />
Gestire vettori o matrici e vettori in parallelo non è molto agevole … Per fortuna molti linguaggi <strong>di</strong> programmazione<br />
consentono al programmatore <strong>di</strong> definire nuovi tipi <strong>di</strong> dato, strutturati come meglio si crede, e <strong>di</strong> <strong>di</strong>chiarare poi<br />
variabili <strong>di</strong> quel tipo. Uno <strong>di</strong> questi tipi <strong>di</strong> dato è il record.<br />
Ve<strong>di</strong>amo l’esempio della scheda anagrafica visto poc’anzi:<br />
program ProvaRecord;<br />
Rossi Ver<strong>di</strong> Bianchi<br />
Mario Giovanni Sandra<br />
21 34 15<br />
Soluzione con tre vettori<br />
Rossi Ver<strong>di</strong> Bianchi<br />
Mario Giovanni Sandra<br />
Type Una_Persona<br />
TPersona = record<br />
Cognome: string;<br />
Cognome<br />
Nome: string;<br />
Eta: integer<br />
Nome<br />
End;<br />
…<br />
Eta<br />
Var<br />
Una_Persona: TPersona;<br />
21 34 15<br />
Soluzione con una matrice ed un vettore<br />
Il nuovo tipo si chiama TPersona (la T, per nulla obbligatoria, aiuta comunque a ricordare che si tratta <strong>di</strong> un ‘T’ipo). I<br />
nuovi tipi devono essere definiti in una sezione specifica, dopo le costanti: la sezione type. Potete naturalmente<br />
definire anche più <strong>di</strong> un tipo. Il tipo TPersona contiene tre valori (chiamati campi del record): cognome, nome ed eta<br />
(con i nomi delle variabili è sempre meglio evitare gli accenti…).<br />
Non è possibile lavorare <strong>di</strong>rettamente con i tipi (non potete ad esempio scrivere integer:=3 !) ma è necessario<br />
<strong>di</strong>chiarare variabili <strong>di</strong> quel tipo. Nella sezione var trovate quin<strong>di</strong> la variabile Una_Persona <strong>di</strong> tipo TPersona.<br />
Qui sopra potete vedere, a fianco della definizione del nuovo tipo, una rappresentazione grafica del record: l’intero<br />
rettangolo esterno è il record; all’interno i componenti. Ma come si ‘riempiono’ <strong>di</strong> dati le caselle tratteggiate?
Ecco il comando per memorizzare la stringa ‘Rossi’ nel campo cognome:<br />
Una_Persona . cognome := ’ROSSI’;<br />
QUINDI: si inizia con il nome della variabile record (Una_Persona),<br />
si mette un punto, e si in<strong>di</strong>ca il campo da usare (cognome).<br />
Similmente si potrebbe procedere con il nome e l’età.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 128<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
I campi <strong>di</strong> un record sono a tutti gli effetti variabili come quelle che già conoscete. E’ quin<strong>di</strong> possibile scrivere:<br />
readln(Una_Persona.nome); (* lettura da tastiera *)<br />
…<br />
writeln(‘La persona si chiama: ‘, Una_Persona.Cognome, ’ – ‘, Una_Persona.nome);<br />
…<br />
if Una_Persona.cognome=’Rossi’ then …<br />
NOTA: <strong>di</strong>menticarsi il nome della variabile record (a meno <strong>di</strong> utilizzo del with, spiegato più avanti) è un grave errore:<br />
cognome:=’rossi’ DI QUALE PERSONA ??? ci potrebbero essere tante variabili <strong>di</strong> tipo<br />
TPersona …<br />
Il costrutto with<br />
Immaginiamo <strong>di</strong> avere una variabile record con il nome piuttosto lungo e con molti campi all’interno. E’ scomodo<br />
riscrivere il nome della variabile tutte le volte che si vuole usare un suo campo; se poi questi ultimi sono molti è<br />
ancora più scomodo. Il Pascal mette a <strong>di</strong>sposizione un costrutto (with, cioè con …) che consente <strong>di</strong> scrivere una volta<br />
sola il nome della variabile record finchè non si ha finito <strong>di</strong> usare i suoi campi:<br />
with Una_Persona do<br />
begin<br />
readln(cognome);<br />
readln(nome);<br />
readln(eta)<br />
end;<br />
Vettori <strong>di</strong> record<br />
Una famiglia è fatta <strong>di</strong> più persone: Una_Famiglia: array[1..3] of TPersona;<br />
Ecco come dare un valore al primo record del vettore:<br />
Una_Famiglia[1].cognome:=’Giorgio’;<br />
Una_Famiglia[1].eta:=’Albenghi’;<br />
Una_Famiglia[1].eta:=43;<br />
Cognome<br />
Nome<br />
Tra il begin e end del costrutto with invece <strong>di</strong> scrivere:<br />
Readln(Una_Persona.cognome);<br />
è sufficiente scrivere:<br />
Readln(cognome);<br />
Attenzione! Il seguente è un errore piuttosto comune:<br />
Una_Famiglia.eta[3]:=23<br />
Sarebbe il field eta ad essere considerato un vettore!<br />
Quin<strong>di</strong> una_famiglia è prima <strong>di</strong> tutto un vettore: dopo il suo nome è necessario in<strong>di</strong>care quale elemento si intende<br />
usare (una_famiglia[1]). Poi, essendo ogni elemento del vettore un record si continua con la sintassi <strong>di</strong> questi ultimi<br />
(un punto ed il nome del campo da usare).<br />
Eta<br />
ROSSI
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 129<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Ecco un esempio interessante sul come usare il costrutto with con i vettori <strong>di</strong> record. Si tratta <strong>di</strong> un ciclo <strong>di</strong><br />
caricamento del vettore della famiglia:<br />
For i:=1 to num_componenti do<br />
With Una_Famiglia[i] do<br />
begin<br />
Readln(cognome);<br />
Readln(nome);<br />
Readln(eta)<br />
end<br />
Record <strong>di</strong> record<br />
(* con l’i-mo componente fai … *)<br />
Un field <strong>di</strong> un record può tranquillamente essere a sua volta un record: definiamo prima un record per le date e poi<br />
un record per i compleanni (che conterranno il record della data):<br />
Type<br />
TData=record (* un record normale *)<br />
Gg,mm,aa: integer;<br />
End;<br />
TCompleanno=record<br />
Data: TData; (* il record Data è contenuto nel record TCompleanno *)<br />
Nominativo: string;<br />
End;<br />
Var<br />
MioCompleanno: TCompleanno;<br />
accesso ai componenti senza with accesso ai componenti con with<br />
MioCompleanno.Data.gg := 30;<br />
MioCompleanno.Data.mm := 10;<br />
MioCompleanno.Data.aa := 1963;<br />
MioCompleanno.Nominativo:= ‘Fabrizio Camuso’;<br />
with MioCompleanno do<br />
begin<br />
with Data do<br />
begin<br />
gg:=30; mm:=10; aa:=1963<br />
end<br />
Nominativo:=’Fabrizio Camuso’<br />
end;<br />
Attenzione. Sarebbe un errore:<br />
with MioCompleanno do<br />
begin<br />
gg:=30; mm:=10; aa:=1963;<br />
end<br />
perchè gg, mm ed aa non sono <strong>di</strong>rettamente campi <strong>di</strong><br />
MioCompleanno ma del record Data in esso contenuto
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 130<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
ESEMPIO : scrivere un programma per la gestione <strong>di</strong> una biblioteca che preveda la ricerca dei dati <strong>di</strong> un libro per<br />
autore, o per titolo o per e<strong>di</strong>tore<br />
program libri;<br />
uses crt;<br />
const<br />
MAX_LIBRI=50;<br />
type<br />
TLibro=record<br />
co<strong>di</strong>ce: integer;<br />
autore,titolo,e<strong>di</strong>tore: string;<br />
end;<br />
var<br />
biblioteca: array [1..MAX_LIBRI] of TLibro;<br />
numero_libri,cont,scelta: integer; da_cercare: string;<br />
begin<br />
numero_libri:=0;cont:=0;scelta:=0;da_cercare:='';<br />
clrscr;<br />
repeat<br />
write('Quanti sono i libri (max. ',MAX_LIBRI,') ? ');<br />
readln(numero_libri);<br />
if (numero_libriMAX_LIBRI) then<br />
writeln('Valore non accettabile, riprova ...')<br />
until (numero_libri>0) and (numero_libri
epeat<br />
clrscr;<br />
writeln('RICERCA CODICI LIBRI PER ...');<br />
writeln('1 - Ricerca per autore');<br />
writeln('2 - Ricerca per titolo');<br />
writeln('3 - Ricerca per e<strong>di</strong>tore');<br />
writeln('4 - Fine operazioni');<br />
writeln;<br />
repeat<br />
write('Scegli un opzione: ');<br />
readln(scelta);<br />
if (scelta4) then<br />
writeln('Scelta non corretta');<br />
until (scelta>0) and (scelta
I FILE – GESTIONE TRADIZIONALE (non DBMS)<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 132<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Il file (archivio) è una collezione <strong>di</strong> dati registrati su un supporto <strong>di</strong> massa (floppy <strong>di</strong>sk, hard <strong>di</strong>sk, CD, DVD, nastro<br />
ecc.). Da un punto <strong>di</strong> vista fisico tutti i file sono memorizzati come una sequenza <strong>di</strong> byte: è il modo con cui questi<br />
ultimi sono interpretati che fa la <strong>di</strong>fferenza. Ad esempio, i 1000 byte che costituiscono un certo file potrebbero essere<br />
intesi come <strong>di</strong>eci schede (record) da 100 byte con le informazioni <strong>di</strong> un cliente della <strong>di</strong>tta. E <strong>di</strong> ciascun record i primi<br />
10 byte potrebbero rappresentare il co<strong>di</strong>ce del cliente, i successivi 30 il nominativo ecc. (i cosiddetti ‘fields’, campi).<br />
Ma, in un contesto completamente <strong>di</strong>verso 1000 byte potrebbero rappresentare la co<strong>di</strong>fica binaria <strong>di</strong> un’immagine o<br />
<strong>di</strong> un suono.<br />
Sui supporti, i byte sono <strong>di</strong> solito trattati a blocchi (cioè non viene mai letto/scritto un solo byte alla volta) secondo<br />
una tecnica <strong>di</strong> sud<strong>di</strong>visione dello spazio a <strong>di</strong>sposizione che <strong>di</strong>pende dal supporto (su un floppy <strong>di</strong>sk tracce<br />
concentriche sud<strong>di</strong>vise in ‘spicchi’ ad in<strong>di</strong>viduare settori, su un CD un’unica traccia a spirale, sul nastro bande<br />
magnetiche perpen<strong>di</strong>colari o elicoidali ecc.). Le letture/scritture avvengono in aree <strong>di</strong> transito in RAM chiamate<br />
buffer(s). Quando si leggono dati da un <strong>di</strong>spositivo, uno o più blocchi vengono memorizzati nel buffer <strong>di</strong> lettura e da<br />
lì prelevati man mano che l'applicazione ne fa richiesta (in modo da non effettuare inutili letture fisiche dei supporti,<br />
in quanto probabilmente a più richieste <strong>di</strong> lettura <strong>di</strong> dati da parte dell'applicazione corrisponderà una sola lettura <strong>di</strong><br />
dati dal <strong>di</strong>sco al buffer). Similmente i dati da registrare su file sono prima accumulati in un buffer <strong>di</strong> scrittura fino al<br />
riempimento <strong>di</strong> quest'ultimo e solo al raggiungimento <strong>di</strong> questa situazione vengono fisicamente scritti sul <strong>di</strong>sco<br />
(evitando in questo modo che ad ogni richiesta <strong>di</strong> scrittura da parte dell'applicazione avvenga una <strong>di</strong>spen<strong>di</strong>osa<br />
scrittura fisica sul <strong>di</strong>sco). Ogni <strong>di</strong>spositivo ha poi particolarità legate alla sua natura: un hard <strong>di</strong>sk può essere formato<br />
da più piatti ed il relativo drive avere molte testine <strong>di</strong> lettura/scrittura, un floppy ha invece un solo piatto (un film<br />
flessibile e sottile come un capello a forma <strong>di</strong> cerchio) ed una sola coppia <strong>di</strong> testine.<br />
Riassumendo, la logica <strong>di</strong> controllo <strong>di</strong> questi <strong>di</strong>spositivi è assai <strong>di</strong>versificata, sia perché l’accoppiata supporto e relativo<br />
drive è formata da <strong>di</strong>spositivi elettromeccanici <strong>di</strong>versi, sia perché <strong>di</strong>verse sono le tecniche con cui si può decidere <strong>di</strong><br />
usarli per <strong>di</strong>sporre dello spazio <strong>di</strong> memorizzazione (quante tracce gestire sul floppy? come organizzare la spirale <strong>di</strong> bit<br />
su <strong>di</strong> un CD?).<br />
Agli albori dell’informatica la gestione dei supporti era sotto la completa responsabilità dei programmatori, costretti a<br />
confrontarsi <strong>di</strong>rettamente con l’hardware (aspetto fisico) oltre che a decidere come usare lo spazio messo a<br />
<strong>di</strong>sposizione (aspetto logico). Detto in altre parole doveva occuparsi sia della gestione fisica delle informazioni che <strong>di</strong><br />
quella logica: cioè pilotare i <strong>di</strong>spositivi ed allo stesso tempo gestire i byte in modo da poterli interpretare come<br />
informazioni utili (quali archivi creare? Come strutturarli in campi informativi? Come mettere in relazione i record un<br />
archivio con quelli <strong>di</strong> un altro?). Questa doppia ‘responsabilità’ porta con se parecchi problemi tra cui, mettendo in<br />
evidenza i più eclatanti, è opportuno citare i seguenti:<br />
Necessità <strong>di</strong> conoscere in modo approfon<strong>di</strong>to l’hardware da controllare.<br />
Come conseguenza del punto precedente aumenta la complessità dell’attività <strong>di</strong> programmazione.<br />
I programmi sono molto legati all’hardware: quando quest’ultimo cambia essi vanno mo<strong>di</strong>ficati.<br />
L’efficienza nella gestione dei supporti <strong>di</strong>pende dalla competenza del programmatore.<br />
Poca uniformità: ogni programmatore adotta tecniche <strong>di</strong>verse <strong>di</strong> allocazione/gestione dello spazio<br />
Notevole spreco <strong>di</strong> tempo/uomo: ogni programmatore co<strong>di</strong>fica routine già co<strong>di</strong>ficate da altri (chi meglio, chi<br />
peggio).<br />
Basso livello <strong>di</strong> astrazione nella programmazione: lo sviluppatore invece <strong>di</strong> concentrarsi solo su aspetti logici<br />
legati al problema è costretto a gestire altri dettagli.<br />
Ci sono anche i soliti vantaggi <strong>di</strong> una programmazione a basso livello:<br />
controllo accurato dell’hardware<br />
possibilità, se si è BRAVI programmatori, <strong>di</strong> ottimizzare il co<strong>di</strong>ce per il massimo delle prestazioni
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 133<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Solo con l’avvento <strong>di</strong> sistemi operativi sufficientemente evoluti (DOS, UNIX, windows NT) il programmatore ha<br />
potuto svincolarsi dagli aspetti hardware ed in parte anche da quelli logici. Il modulo del <strong>sistema</strong> operativo chiamato<br />
‘file system’, infatti:<br />
si occupa della gestione hardware (file system fisico): pilota <strong>di</strong>spositivi anche molto <strong>di</strong>versi tra loro (floppy,<br />
hard <strong>di</strong>sk, CD ROM, nastri ecc.) per la registrazione e la lettura <strong>di</strong> blocchi <strong>di</strong> byte;<br />
mette a <strong>di</strong>sposizione delle entità logiche chiamate file (file system logico) gestite tramite un nome ed una<br />
posizione (path, cammino) in una struttura, <strong>di</strong> solito gerarchica (<strong>di</strong>rectory, sotto <strong>di</strong>rectory ecc.); mette in oltre a<br />
<strong>di</strong>sposizione coman<strong>di</strong> per creare, eliminare, spostare, rinominare i file; questi coman<strong>di</strong> possono non solo essere<br />
imparti grazie ad una riga <strong>di</strong> comando (MS DOS, Unix, Linux) o ad un ambiente a finestre (Windows, Unix,<br />
Linux) ma anche in un programma;<br />
L’esame dettagliato del modulo file system viene svolto nel corso <strong>di</strong> Sistemi. Nel corso <strong>di</strong> Informatica interessa invece<br />
cogliere gli aspetti più legati allo sviluppo del software. Che <strong>di</strong>re allora dei linguaggi <strong>di</strong> programmazione? Come si<br />
sono evoluti in risposta a queste problematiche?<br />
A livello <strong>di</strong> linguaggi ‘assembly’ il tutto si risolve attraverso chiamate a servizi del <strong>sistema</strong> operativo (nel caso <strong>di</strong> MS<br />
DOS attraverso il noto meccanismo degli interrupt software, come dovrebbe esservi chiarito nel corso <strong>di</strong> sistemi).<br />
I linguaggi <strong>di</strong> terza generazione (‘C’, Pascal, Basic, Cobol ecc.) offrono istruzioni più evolute (apri un file, chiu<strong>di</strong>lo,<br />
leggi dal file, scrivi su <strong>di</strong> esso, spostati ad una certa posizione ecc.) che sono poi tradotte dal compilatore negli stessi<br />
meccanismi a basso livello dell’assembly.<br />
Da alcuni anni la gestione delle informazioni è <strong>di</strong>ventata talmente importante per il successo dei sistemi informativi e<br />
le funzionalità richieste talmente sofisticate da rendere insufficienti le funzionalità offerte dai sistemi operativi e, <strong>di</strong><br />
conseguenza, dei programmi che vi fanno affidamento. Questo fatto giustifica l’esistenza sul mercato <strong>di</strong> complessi<br />
software de<strong>di</strong>cati esclusivamente alla gestione delle basi <strong>di</strong> dati aziendali: i DBMS (data base management system:<br />
saranno oggetto <strong>di</strong> un’ampia porzione del corso <strong>di</strong> Informatica <strong>di</strong> quinta).<br />
Vista l’esistenza dei DBMS perché parlare delle tecniche ‘tra<strong>di</strong>zionali’ in presenza <strong>di</strong> strumenti talmente avanzati? Ecco<br />
alcuni buoni motivi:<br />
i requisiti hardware/software (microprocessore, RAM, spazio su hard <strong>di</strong>sk) richiesti dai DBMS sono decisamente<br />
elevati: in più <strong>di</strong> una situazione non sono semplicemente <strong>di</strong>sponibili e l’unica alternativa (spesso perfettamente<br />
adeguata) è rappresentata dalle tecniche ‘tra<strong>di</strong>zionali’;<br />
certe elaborazioni sono molto <strong>di</strong>fficili da realizzare con i linguaggi specifici per i data base: questi linguaggi<br />
‘pagano’ la loro indubbia semplicità che li rende alla portata anche del non programmatore con una minore<br />
‘potenza’; il problema è così sentito che in molti linguaggi per data base sono stati reintrodotti costrutti <strong>di</strong><br />
programmazione tipici dei linguaggi tra<strong>di</strong>zionali<br />
per certe operazioni la velocità delle tecniche tra<strong>di</strong>zionali è impareggiabile: nessun DBMS supera in velocità<br />
linguaggi come il Pascal o il C per scrivere e leggere file <strong>di</strong> testo ( e non <strong>di</strong>menticate che le pagine WEB in HTML<br />
sono file <strong>di</strong> testo …)<br />
conoscere le tecniche passate aiuta a capire, apprezzare e sfruttare molto meglio ciò che le nuove mettono a<br />
<strong>di</strong>sposizione.
TIPI DI FILE<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 134<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
1. File <strong>di</strong> testo (text file): sono sequenze <strong>di</strong> bytes con valori numerici nell’intervallo valido per la co<strong>di</strong>fica dei<br />
caratteri per un certo <strong>sistema</strong> operativo. Nel caso <strong>di</strong> MS DOS / Windows (co<strong>di</strong>fica ASCII) questi file sono<br />
terminati dal carattere con co<strong>di</strong>ce 26 (CTRL Z). La fine <strong>di</strong> un file viene in<strong>di</strong>cata con la sigla EOF (End Of File,<br />
fine del file). L’inizio con BOF (Begin Of File, inizio del file)<br />
I file <strong>di</strong> testo sono sud<strong>di</strong>visi in righe. Ogni riga è terminata dai byte CR (Carriage Return, ritorno carrello) co<strong>di</strong>ce<br />
ASCII 13) e LF (Line Feed, avanzamento <strong>di</strong> riga, co<strong>di</strong>ce ASCII 10).<br />
Attenzione: le righe sono terminate o solo da CR o solo da LF o da tutti e due a seconda del <strong>sistema</strong> operativo in<br />
uso (con MS DOS/Windows le righe sono terminate da CR seguito da LF).<br />
Windows è in grado <strong>di</strong> trattare anche caratteri UNICODE, uno standard internazionale che consente, grazie<br />
all’uso <strong>di</strong> due byte per co<strong>di</strong>ficare ciascun carattere, <strong>di</strong> rappresentare set <strong>di</strong> caratteri molto ricchi. Per lo stesso<br />
motivo i linguaggi più recenti (Delphi con il suo Object Pascal ne è un esempio) sono in grado <strong>di</strong> definire variabili<br />
carattere aderenti a questo standard.<br />
Ecco come potremmo rappresentare graficamente un file <strong>di</strong> testo:<br />
BOF<br />
questa è la prima riga del file <strong>di</strong> testo. Può contener anche spazi come questi !! CR LF<br />
io sono la seconda RIGA bla bla bla bla bla! CR LF<br />
io sono la riga più corta !!! CR LF<br />
CR LF<br />
CR LF<br />
CR LF<br />
CR LF EOF<br />
EOF<br />
Le istruzioni per scrivere e leggere dati dai file <strong>di</strong> testo sono line-oriented nel senso che permettono <strong>di</strong> leggere o<br />
scrivere intere righe del file e non solamente una parte dei caratteri <strong>di</strong> una certa riga. Abbiamo così coman<strong>di</strong> per<br />
registrare una riga oppure per leggerla da un file. Una grossa limitazione dei file <strong>di</strong> testo in Pascal è che le righe<br />
già inserite non possono essere mo<strong>di</strong>ficate (riscritte).<br />
I file testuali sono il tipo <strong>di</strong> file meno sofisticato e meno efficiente per rappresentare situazioni reali <strong>di</strong> una certa<br />
complessità ma allo stesso tempo assicurano la massima portabilità tra elaboratori con <strong>di</strong>versi sistemi operativi e<br />
tra periferiche altrimenti incompatibili. Qualsiasi linguaggio su qualsiasi piattaforma hardware/software è infatti<br />
in grado <strong>di</strong> gestire file <strong>di</strong> testo. Nel peggiore dei casi si renderà necessaria una (semplice) transco<strong>di</strong>fica (ad<br />
esempio da ASCII a EBCDIC).<br />
Non sono inoltre particolarmente adatti a contenere informazioni non testuali: qualsiasi co<strong>di</strong>fica binaria (la<br />
rappresentazione interna <strong>di</strong> un numero in virgola mobile, il co<strong>di</strong>ce operativo od un operando <strong>di</strong> un’istruzione<br />
assembly, un immagine, un suono un’animazione) contenente un byte con valore 26 verrebbe considerato,<br />
inesorabilmente, EOF. Naturalmente ci sarebbe sempre il modo <strong>di</strong> co<strong>di</strong>ficare gli stessi dati usando solo byte<br />
<strong>di</strong>versi da EOF, CR e LF ma la soluzione risulterebbe meno efficiente e poco elegante. Meglio usare i file non<br />
testuali (ve<strong>di</strong> sotto).
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 135<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
2. File binari (‘binary file’ o non <strong>di</strong> testo): a livello fisico sono ancora formati da una sequenza <strong>di</strong> byte il cui<br />
valore non viene però interpretato come un carattere ASCII. Così, mentre un byte con valore (decimale) 65<br />
viene interpretato come la ‘A’ in un file <strong>di</strong> testo, lo stesso valore potrebbe specificare una tonalità <strong>di</strong> colore <strong>di</strong><br />
un’immagine in un file jpeg (uno dei formati grafici più <strong>di</strong>ffusi).<br />
La fine <strong>di</strong> questi file non è segnalata dal carattere CTRL Z ma attraverso altri meccanismi (ad esempio tenendo<br />
traccia della lunghezza in byte del file). Anche i byte con valore numerico 13 e 10 (i CR e LF dei file <strong>di</strong> testo) non<br />
hanno un significato particolare.<br />
Le istruzioni <strong>di</strong> lettura/scrittura per questi tipi <strong>di</strong> file permettono tipicamente <strong>di</strong> leggere blocchi <strong>di</strong> byte <strong>di</strong><br />
<strong>di</strong>mensioni specificate dal programmatore.<br />
Sono adatti per memorizzare qualsiasi tipo <strong>di</strong> file, testo compreso (ovviamente non potremo sfruttare le primitive<br />
<strong>di</strong> lettura e le particolarità dei file <strong>di</strong> testo). Sono file binari le traduzioni in linguaggio macchina dei programmi,<br />
immagini, suoni, archivi <strong>di</strong> basi <strong>di</strong> dati per i quali si è preferito questo formato a quello testuale ecc.<br />
3. File tipizzati: sono file per i quali le unità informative (chiamate record in Pascal) hanno una struttura (un tipo)<br />
ben precisa. Come i record Pascal gestiti in RAM, i record su file sono comodamente sud<strong>di</strong>visi in campi (fields).<br />
Ad esempio:<br />
type<br />
TCalciatori=Record<br />
matricola: integer;<br />
cognome: string;<br />
nome: string;<br />
ecc.<br />
end;<br />
var FileCalciatori: file of TCalciatori;<br />
Le istruzioni <strong>di</strong> lettura/scrittura sono in grado <strong>di</strong> leggere/scrivere un intero record per volta, <strong>di</strong> spostarsi per<br />
leggere/scrivere in una posizione qualsiasi del file, e, operazione impossibile con i file <strong>di</strong> testo, <strong>di</strong> mo<strong>di</strong>ficare<br />
(riscrivere) una scheda.<br />
Nota: riba<strong>di</strong>sco che a livello fisico i file sono comunque tutti formati da una sequenza <strong>di</strong> byte; la <strong>di</strong>fferenziazione che<br />
ne facciamo è a livello logico. Una parte della letteratura informatica si limita a <strong>di</strong>stinguere tra file <strong>di</strong> testo e non <strong>di</strong><br />
testo (i file tipizzati vengono considerati file binari).
TIPI DI ORGANIZZAZIONE (ACCESSO)<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 136<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
L’organizzazione fa riferimento alle modalità con cui è possibile accedere ai dati sui supporti:<br />
Sequenziale: i dati possono essere letti/scritti in modo strettamente lineare; per leggere/scrivere un dato è<br />
necessario leggere i precedenti per posizionare il <strong>di</strong>spositivo <strong>di</strong> lettura/scrittura nel punto giusto; il tempo per<br />
accedere ad un dato è quin<strong>di</strong> molto <strong>di</strong>pendente dalla sua posizione sul supporto; ad esempio, per un nastro<br />
magnetico l’unico tipo <strong>di</strong> organizzazione possibile è quella sequenziale: se il nastro è all’inizio, il tempo per<br />
leggere il primo blocco <strong>di</strong> dati è assai inferiore al tempo necessario per leggere un blocco che sta a metà o alla<br />
fine del nastro.<br />
Random: se è possibile raggiungere un blocco qualsiasi <strong>di</strong> dati impiegando praticamente lo stesso tempo<br />
in<strong>di</strong>pendentemente dalla posizione del blocco. Ad esempio il tempo necessario a pilotare una delle testine <strong>di</strong><br />
lettura/scrittura in un punto qualsiasi della superficie <strong>di</strong> un piatto <strong>di</strong> un hard <strong>di</strong>sk, partendo da una posizione<br />
qualsiasi, non è esattamente zero ma è comunque abbastanza piccolo da ritenere (quasi) identico il tempo<br />
necessario a leggere uno qualsiasi dei blocchi che costituiscono un file.<br />
NOTA: la <strong>di</strong>stinzione tra organizzazione <strong>di</strong> un file tra sequenziale e random è in<strong>di</strong>pendente dalle capacità fisiche del<br />
supporto ed è stabilita dalla modalità <strong>di</strong> accesso a quel file. Ad esempio, l’hard <strong>di</strong>sk a livello fisico è sicuramente<br />
random ma nulla vieta <strong>di</strong> organizzare la registrazione <strong>di</strong> un file in modo che il suo accesso sia comunque sequenziale<br />
(pensate ad un file <strong>di</strong> testo in Pascal).<br />
Nella letteratura informatica ci si riferisce alle seguenti come ad organizzazioni a se stanti ma in realtà sono tutte<br />
costruite su quella ‘random’:<br />
Con in<strong>di</strong>ci: quando l’accesso ai dati è velocizzato dalla gestione <strong>di</strong> strutture (su file separati o nello stesso file<br />
dei dati) che, grazie alla loro organizzazione, consentono <strong>di</strong> risalire rapidamente alla posizione in cui trovare i<br />
dati. Il modo <strong>di</strong> realizzare l’in<strong>di</strong>ce può variare grandemente. In<strong>di</strong>viduato un campo informativo in relazione al<br />
quale si vuole velocizzare l’accesso (ricerca per co<strong>di</strong>ce o per cognome, ecc.) l’in<strong>di</strong>ce conterrà una copia <strong>di</strong> tutti i<br />
valori contenuti nel file dati per quel campo (tutti i co<strong>di</strong>ci o tutti i cognomi ecc.) affiancati dalla posizione in cui<br />
sul file dati completo si trovano gli altri dati per quel record.<br />
L’in<strong>di</strong>ce è mantenuto in una forma che rende assai veloce trovare i valori che interessano (un certo co<strong>di</strong>ce o un<br />
certo cognome ecc.): ad esempio potrebbe essere or<strong>di</strong>nato (per sfruttare una ricerca <strong>di</strong>cotomica) o strutturato<br />
come un albero binario <strong>di</strong> ricerca; quest’ultima scelta è alla base della fortunatissima tecnica che sfrutta una<br />
forma mo<strong>di</strong>ficata degli alberi binari, chiamata ‘B+ tree’; essa è utilizzata per molte implementazioni del<br />
linguaggio Cobol, <strong>di</strong> molte librerie per la gestione degli archivi <strong>di</strong>sponibili per gli altri linguaggi, e <strong>di</strong> alcuni famosi<br />
DBMS.<br />
Qualunque sia forma dell’in<strong>di</strong>ce, l’idea <strong>di</strong> base è che, grazie alla sua natura or<strong>di</strong>nata, saranno necessarie poche<br />
letture su <strong>di</strong> esso per localizzare il valore che interessa ed accedere all’in<strong>di</strong>rizzo in cui si trova il resto dei dati nel<br />
file principale.<br />
Arisi 4<br />
Bini 2<br />
Bruni 3<br />
Calce 8<br />
<br />
L’in<strong>di</strong>ce è or<strong>di</strong>nato<br />
per cognome. I dati<br />
<strong>di</strong> Arisi si trovano<br />
nel quarto record del<br />
file dei dati<br />
Calcestruzzi Mario V. Milano Impiegato<br />
Bini Giorgio V. Corta Studente<br />
Bruni Massimo V. Lunga Studente<br />
Arisi Massimo V. Larga Avvocato<br />
Il file dati principale non è or<strong>di</strong>nato rispetto ad alcun campo. L’accesso<br />
è me<strong>di</strong>ato dall’uso dell’in<strong>di</strong>ce. Se l’accesso è random, ottenuta<br />
dall’in<strong>di</strong>ce la posizione dei dati <strong>di</strong> Arisi sarà sufficiente una sola<br />
lettura.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 137<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
A titolo <strong>di</strong> esempio consideriamo un file <strong>di</strong> dati anagrafici contenenti un milione <strong>di</strong> record. Una ricerca <strong>di</strong>cotomica<br />
sul file in<strong>di</strong>ce nel caso più sfortunato richiederà 20 letture (20 =(circa) log2(1000000) che è il numero <strong>di</strong> volte<br />
che può essere <strong>di</strong>mezzato un intervallo pari ad un milione) più una ventunesima nel file dati vero e proprio.<br />
Me<strong>di</strong>amente, in realtà, il numero <strong>di</strong> letture sarà minore <strong>di</strong> 20. Paragonate questo numero <strong>di</strong> letture con il<br />
numero me<strong>di</strong>o <strong>di</strong> letture nel file dati principale <strong>di</strong>sor<strong>di</strong>nato (500000 !) per rendervi conto del guadagno…<br />
Qualcuno, giustamente, starà ‘storcendo il naso per la puzza <strong>di</strong> bruciato’: non sarebbe meglio tenere or<strong>di</strong>nato il<br />
file principale e sfruttare la ricerca <strong>di</strong>cotomica o qualsiasi altro algoritmo pensato per la gestione degli in<strong>di</strong>ci<br />
(evitando l’uso <strong>di</strong> questi ultimi)? In linea <strong>di</strong> principio questo sarebbe possibile ma molto più lento perché il file<br />
principale è <strong>di</strong> solito molto più grande dell’in<strong>di</strong>ce. Tenere aggiornato l’in<strong>di</strong>ce è invece un compito assai più<br />
leggero e, grazie a particolari strutturazioni quali quella prevista dalla tecnica ‘B+ tree’, può essere fatto<br />
praticamente in tempo reale senza rallentamenti avvertibili dall’utente. Inoltre è possibile avere più in<strong>di</strong>ci per<br />
accedere ai dati secondo <strong>di</strong>verse chiavi <strong>di</strong> ricerca (per co<strong>di</strong>ce, per cognome, per in<strong>di</strong>rizzo ecc.): sarebbe<br />
veramente troppo oneroso rior<strong>di</strong>nare il file principale in base al campo che <strong>di</strong> volta in volta interessa! Oppure<br />
mantenere tante copie del file dati quanti sono i criteri <strong>di</strong> or<strong>di</strong>namento desiderati! Naturalmente tutto ha un<br />
limite: troppi in<strong>di</strong>ci tenderanno comunque a rallentare le operazioni a causa del sovraccarico necessario alla loro<br />
gestione (per ovviare in parte a questo problema nei moderni DBMS è possibile creare ‘al volo’ degli in<strong>di</strong>ci<br />
temporanei che verranno <strong>di</strong>strutti al termine dell’<strong>elaborazione</strong> al fine <strong>di</strong> non protrarre inutilmente l’aggravio<br />
della loro gestione).<br />
Relative: quando le unità informative (record) sono in<strong>di</strong>viduate da un numero che corrisponde alla loro<br />
posizione. E’ così possibile, ad esempio, chiedere <strong>di</strong> inserire un record alla posizione 20 (anche se non sono<br />
state occupate tutte le precedenti) piuttosto che alla 5. Similmente è possibile chiedere <strong>di</strong> leggere un record ad<br />
una posizione qualsiasi. Tra i linguaggi che supportano questa organizzazione ricor<strong>di</strong>amo molti <strong>di</strong>aletti del BASIC<br />
e del COBOL.<br />
Hashing. Non sempre è conveniente applicarla. L’idea è che il processore impiega a fare un calcolo molto<br />
meno tempo <strong>di</strong> quello necessario a leggere dati dai supporti <strong>di</strong> massa: si tenta allora trovare una formula (legge<br />
<strong>di</strong> trasformazione) che, partendo da una chiave <strong>di</strong> accesso (ad esempio il cognome) calcoli la posizione del<br />
resto dei dati. Ad esempio potremmo stabilire che, dato un cognome <strong>di</strong> una persona, il resto dei dati si trova nel<br />
record alla posizione corrispondente al numero dei caratteri del cognome. In questo modo, volendo trovare i<br />
dati <strong>di</strong> ‘Rossi’ sapremmo in modo super rapido che si trovano sul quinto record del file (Rossi è una stringa <strong>di</strong> 5<br />
caratteri). Ovviamente salta subito all’occhio il problema <strong>di</strong> questa formula troppo semplice: molti cognomi sono<br />
lunghi 5 caratteri e tutti i relativi record dovrebbero essere memorizzati nella stessa posizione … In effetti il<br />
problema dell’hashing sta proprio nella <strong>di</strong>fficoltà <strong>di</strong> trovare una legge <strong>di</strong> trasformazione appropriata. Un altro<br />
esempio <strong>di</strong> legge: sommare i valori ASCII delle lettere corrispondenti ai cognomi. Anche questa legge genera<br />
posizioni uguali (sapresti <strong>di</strong>re perché e valutare se è migliore rispetto alla precedente?) … Purtroppo è<br />
impossibile trovare una legge <strong>di</strong> trasformazione perfetta. I problemi maggiori sono:<br />
conflitti <strong>di</strong> posizione: quando i valori dei campi <strong>di</strong> due record <strong>di</strong>versi generano lo stesso in<strong>di</strong>rizzo; nell’esempio <strong>di</strong><br />
prima tutti gli anagrammi <strong>di</strong> un cognome generano lo stesso in<strong>di</strong>rizzo (vista la proprietà commutativa della<br />
somma); due tra le due tecniche più usate per aggirare questo problema sono o applicare in successione altre<br />
leggi <strong>di</strong> trasformazione per generare in<strong>di</strong>rizzi <strong>di</strong>versi o gestire delle vere e proprie aree <strong>di</strong> ‘overflow’, come<br />
in<strong>di</strong>cato nello schema<br />
ROSSI<br />
SORSI<br />
RISSO<br />
Somma dei<br />
co<strong>di</strong>ci ASCII<br />
Co<strong>di</strong>ce cognome<br />
322 ROSSI<br />
167 SORSI<br />
201 RISSO<br />
400<br />
1200<br />
seguente;<br />
Ipotizziamo <strong>di</strong> dover inserire<br />
per primo il record <strong>di</strong> ROSSI;<br />
la legge <strong>di</strong> trasformazione<br />
generebbe l’in<strong>di</strong>rizzo 400,<br />
usato per questo record;<br />
l’inserimento <strong>di</strong> SORSI<br />
richiederebbe <strong>di</strong> usare lo<br />
stesso in<strong>di</strong>rizzo: trovandolo già<br />
occupato si memorizza il
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 138<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
nuovo record in un’area speciale (<strong>di</strong> overflow, cioè <strong>di</strong> traboccamento) che inizia all’in<strong>di</strong>rizzo 1200 (valore scelto a<br />
caso); il 1200 viene memorizzato nel record <strong>di</strong> ROSSI per poter ritrovare in un secondo momento all’area <strong>di</strong><br />
overflow. Per memorizzare RISSO si trova, ovviamente, già occupata la solita posizione 400. Si rileva (grazie al<br />
1200 memorizzato prima) che esiste già un’area <strong>di</strong> owerflow. Quest’ultima viene fatta scorrere fino a supeare<br />
l’ultimo record che con<strong>di</strong>vide con tutti gli altri lo stesso valore generato dalla legge <strong>di</strong> trasformazione. Se c’è<br />
ancora spazio viene aggiunto in coda il record <strong>di</strong> RISSO. Dovendo reperire le informazioni <strong>di</strong> RISSO sarà<br />
necessario scorrere l’area <strong>di</strong> overflow vanificando in parte il vantaggio dell’hashing, ma se le aree <strong>di</strong> overflow<br />
non sono esasperate è possibile ancora un notevole guadagno nei tempi complessivi.<br />
<strong>di</strong>stribuzione ottimale dei valori generati: immaginando <strong>di</strong> non ammettere omonimie (come potrebbe accadere<br />
per la descrizione dei prodotti in un magazzino) non è <strong>di</strong>fficile inventarsi una legge <strong>di</strong> trasformazione hash che<br />
non causa conflitti; considerate questo algoritmo: facciamo corrispondere ogni lettera del cognome a numeri<br />
primi crescenti. Per ROSSI la R corrisponde al 2, la O al 3, la prima S al 5, la seconda S al 7 e la I all’11. Poi<br />
eleviamo ogni numero primo alla potenza corrispondente al co<strong>di</strong>ce ASCII delle lettere corrispondenti. Così<br />
eleveremo il 2 alla 82 (co<strong>di</strong>ce ASCII della R), il 5 alla 83 (co<strong>di</strong>ce ASCII della S), il 7 ancora alla 83 e così via. Poi<br />
moltiplichiamo tra loro i numeri ottenuti: il risultato è l’in<strong>di</strong>rizzo richiesto. La matematica ci assicura (teorema<br />
dell’unicità della scomposizione in potenze <strong>di</strong> numeri primi <strong>di</strong> un numero) che due cognomi <strong>di</strong>versi genereranno<br />
due in<strong>di</strong>rizzi <strong>di</strong>versi. Parrebbe <strong>di</strong> aver risolto il problema dei conflitti con un solo calcolo per qualsiasi campo da<br />
in<strong>di</strong>cizzare <strong>di</strong> natura alfanumerica! Immaginate però <strong>di</strong> dover inserire il prodotto ‘ZUCCA CON ZENZERO E<br />
ZAFFERANO’; il valore generato dall’algoritmo è MOLTO grande (MOLTO più grande del corrispondente decimale<br />
con tutti 9 !!). Il numero eccede anche le capacità <strong>di</strong> memorizzazione del più grande hd esistente, anche<br />
immaginando <strong>di</strong> associare un singolo byte su <strong>di</strong> esso ad un in<strong>di</strong>rizzo <strong>di</strong>verso …. Potremmo scoprire, quin<strong>di</strong>, che<br />
per memorizzare anche solo il primo record dovremmo farlo ad un in<strong>di</strong>rizzo impossibile da far corrispondere<br />
nella pratica: un miliardo <strong>di</strong> miliar<strong>di</strong>. Dovremmo creare tutti i record vuoti corrispondenti alle posizioni<br />
precedenti esaurendo (tempo permettendo) anche la batteria <strong>di</strong> hard <strong>di</strong>sk più capiente dell’universo …<br />
Morale: la tecnica hash è utile solo in casi particolari da analizzare attentamente quando la velocità <strong>di</strong><br />
reperimento dei dati è il fattore critico, <strong>di</strong>versamente l’organizzazione ad in<strong>di</strong>ci risulta più conveniente e<br />
sufficientemente performante.<br />
OPERAZIONI DI BASE PER LA GESTIONE DEI FILE IN PASCAL<br />
Qualunque sia l’organizzazione fisica possiamo in<strong>di</strong>viduare alcune operazioni <strong>di</strong> base tipiche. Per ciascuna <strong>di</strong> queste è<br />
importante, oltre al risultato, essere in grado <strong>di</strong> rilevare i motivi <strong>di</strong> eventuali errori (fine dello spazio su <strong>di</strong>sco, <strong>di</strong>sco in<br />
avaria, <strong>di</strong>sco non trovato, file non trovato ecc.). Il controllo degli errori inevitabilmente rende il co<strong>di</strong>ce più pesante,<br />
più <strong>di</strong>fficile da leggere: per questo motivo vedremo solo alcuni esempi seguendo i quali lo studente volenteroso (J)<br />
potrà perfezionare tutto il resto del co<strong>di</strong>ce.<br />
NOTA: le considerazioni che seguono sono specifiche del linguaggio Pascal ma mantengono gran parte della loro<br />
vali<strong>di</strong>tà anche per linguaggi <strong>di</strong>versi. Ogni volta <strong>di</strong>fferenzieremo tra file <strong>di</strong> testo e file tipizzati.<br />
Collegamento al file esterno – procedura ASSIGN<br />
Un file viene in<strong>di</strong>viduato dal suo nome e dal suo in<strong>di</strong>rizzo fisico (path). Ad esempio<br />
C:\dati\2002\lettere\clienti\milano\ecc.\lettera.doc. In un programma Pascal per comandare le varie operazioni con<br />
un certo file si invece un nome logico scelto dal programmatore (ad esempio lettera). Il nome logico è più pratico e<br />
rende anche possibile cambiare il nome e la posizione <strong>di</strong> un file su <strong>di</strong>sco senza dover necessariamente mo<strong>di</strong>ficare il<br />
programma (ad esempio memorizzando i nomi e le posizioni fisiche dei file su un file a parte, questo sì fisso, che<br />
viene letto all’inizio del programma e con il cui contenuto vengono inizializzati tutti i nomi <strong>di</strong> file logici che si<br />
intendono usare).
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 139<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Prima <strong>di</strong> poter usare un nome logico, che poi altro non è che una variabile <strong>di</strong> tipo speciale, è necessario in<strong>di</strong>care a<br />
quale file fisico lo si vuole far corrispondere.<br />
FILE DI TESTO<br />
Seguendo l’esempio <strong>di</strong> prima si vuole fa corrispondere il nome logico lettera al file fisico<br />
C:\dati\2002\lettere\clienti\milano\ecc.\lettera.doc. Si ricorre alla procedura standard Assign:<br />
Assign( , )<br />
Applichiamola al nostro esempio:<br />
var lettera: text; (* lettera è il nome logico; il tipo text in<strong>di</strong>ca che si tratta <strong>di</strong> un file <strong>di</strong> testo *)<br />
…<br />
Assign(lettera, ‘C:\ dati\2002\lettere\clienti\milano\ecc.\lettera.doc’)<br />
Ora, e non prima, è possibile comandare le altre operazioni …<br />
Possiamo rendere il nostro programma in grado <strong>di</strong> funzionare con <strong>di</strong>versi file fisici senza essere costretti a mo<strong>di</strong>ficare<br />
il suo co<strong>di</strong>ce: è sufficiente in<strong>di</strong>care nell’assign una variabile stringa come nome fisico del file:<br />
var lettera: text; nomeFisico: string;<br />
…<br />
writeln(‘Con quale file vuoi lavorare? ‘;<br />
readln(nomeFisico);<br />
Assign(lettera, nomeFisico)<br />
La procedura assign deve essere usata una volta sola prima <strong>di</strong> iniziare a lavorare con il file. Ovviamente dovremo<br />
comandare un assign per ciascun file che inten<strong>di</strong>amo usare.<br />
FILE TIPIZZATI<br />
Non c’è nessuna <strong>di</strong>fferenza nell’uso dell’assign. Dobbiamo però <strong>di</strong>chiarare il file in modo che sia riconosciuto come<br />
tipizzato:<br />
type<br />
TPersona=record<br />
cognome: string[30]; (* e' obbligatorio definire il numero dei caratteri della stringa ! *)<br />
co<strong>di</strong>ce: integer;<br />
end;<br />
fpersona=file of TPersona; (* senza il tipo non potremmo passare un file come parametro … *)<br />
var<br />
elenco: fPersona;<br />
…<br />
assign(elenco, ‘C:\dati.dat’)
Creazione – procedura REWRITE<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 140<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
L’assegnazione da sola non basta per usare un file. E’ necessario crearlo. Un file appena creato risulterà vuoto e non<br />
si è obbligati ad inserire subito nuovi dati (anche se <strong>di</strong> solito lo si fa). Un file creato e lasciato vuoto risulterà con 0<br />
byte nelle cartelle <strong>di</strong> windows. Non confondete quin<strong>di</strong> la creazione con l’inserimento dei dati.<br />
La creazione può fallire per vari motivi:<br />
- il percorso/nome del file fornito dal programmatore non è valido<br />
- il supporto <strong>di</strong> massa non è <strong>di</strong>sponibile (nastro non montato, floppy non inserito, CD non scrivibile)<br />
- il supporto è in avaria (floppy o hard <strong>di</strong>sk rotti …)<br />
- spazio sul supporto esaurito (anche se è <strong>di</strong>fficile che non ci sia spazio almeno per creare il file vuoto)<br />
Ma ve<strong>di</strong>amo la sintassi dell’istruzione <strong>di</strong> creazione (identica per file <strong>di</strong> testo e tipizzati):<br />
rewrite(nome_file_logico)<br />
Quin<strong>di</strong>, continuando gli esempi dell’istruzione precedente (assign) senza ripetere la <strong>di</strong>chiarazione dei tipi e delle<br />
variabili:<br />
TESTO TIPIZZATI<br />
assign(lettera, ‘…’); assign(elenco);<br />
rewrite(lettera) rewrite(elenco);<br />
ATTENZIONE!!!<br />
come già detto, la sintassi è identica<br />
Se comandate una rewrite su un file già presente in quella posizione e con lo stesso nome, quest’ultimo verrà<br />
eliminato, e si ricomincerà con un file vuoto. Più avanti vi verrà spiegata una tecnica per verificare la presenza <strong>di</strong> un<br />
vecchio file senza <strong>di</strong>struggerlo e chiedere conferma prima <strong>di</strong> procedere con la rewrite.<br />
Chiusura – procedura CLOSE<br />
Ora il file esiste (vuoto) ed il programmatore potrebbe continuare con le istruzioni che aggiungono righe/schede.<br />
Oppure terminare subito le operazioni. In ogni caso il termine delle operazioni con un certo file va segnalato con il<br />
comando CLOSE: close(nome_file_logico). Di nuovo, non ci sono <strong>di</strong>fferenze tra file <strong>di</strong> testo e tipizzati.<br />
TESTO TIPIZZATI<br />
assign(lettera, ‘…’); assign(elenco);<br />
rewrite(lettera) rewrite(elenco);<br />
close(lettera) close(elenco)<br />
Questa è ovviamente la sequenza dei coman<strong>di</strong> nel caso il programmatore decidesse <strong>di</strong> creare i file senza registrare<br />
alcun dato su <strong>di</strong> essi (ovviamente potrà farlo in seguito, ‘riaprendo’ i file). Diversamente immaginate tra la rewrite e<br />
la close le istruzioni che aggiungono nuove righe (file <strong>di</strong> testo) o schede (file tipizzati).<br />
N OTA: close restituisce anche al <strong>sistema</strong> operativo tutte le risorse usate per la gestione del file.
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 141<br />
Gestione Archivi Tra<strong>di</strong>zionali - versione 3.1 Settembre 2004<br />
Scrittura – le procedure APPEND (file <strong>di</strong> testo), WRITELN (file <strong>di</strong> testo) e WRITE (file tipizzati)<br />
Le procedure writeln e write permettono rispettivamente <strong>di</strong> aggiungere una riga ad un file <strong>di</strong> testo o un record ad un<br />
file tipizzato.<br />
Con il Pascal, per i file <strong>di</strong> testo ed i tipizzati gli inserimenti possono essere effettuati solo in fondo al file; non c’è<br />
modo <strong>di</strong> inserire nuove informazioni in mezzo al file senza crearne uno nuovo.<br />
L’operazione <strong>di</strong> inserimento potrebbe fallire:<br />
- lo spazio sull’unità è esaurito<br />
- l’unità non è <strong>di</strong>sponibile (ad esempio floppy non inserito)<br />
- l’unità è andata in avaria<br />
- il file è già stato aperto da un utente in modalità che impe<strong>di</strong>sce gli inserimenti agli altri utenti<br />
FILE DI TESTO<br />
Ad un file <strong>di</strong> testo possono essere esclusivamente aggiunte righe in fondo al file. Non è possibile, ad esempio,<br />
posizionarsi in una posizione interme<strong>di</strong>a ed inserire una riga. Il file deve essere stato prima pre<strong>di</strong>sposto o con rewrite<br />
(creando un file vuoto siamo ovviamente in fondo al file …) o con la procedura append(nome_file_logico).<br />
Quest’ultima deve essere usata quando si vogliono fare aggiunte ad un file <strong>di</strong> testo che contiene già alcune righe.<br />
Senza l’append dovremmo sempre creare da capo i file <strong>di</strong> testo riscrivendo anche le vecchie righe (vi ricordo che<br />
rewrite è <strong>di</strong>struttiva!).<br />
L’istruzione che aggiunge le righe al file è una variante dell’arcinota writeln. Semplicemente dovremo in<strong>di</strong>care il nome<br />
del file logico su cui scrivere e la stringa che rappresenta la riga da aggiungere: writeln(nome_file_logico, stringa).<br />
Dopo ogni writeln sul file, il punto <strong>di</strong> inserimento è automaticamente fatto avanzare.<br />
Come al solito, gli esempi che seguono fanno riferimento alle <strong>di</strong>chiarazioni dei paragrafi precedenti.<br />
File creato e scritto imme<strong>di</strong>atamente<br />
Var riga: string;<br />
begin<br />
Assign(lettera,’c:\...’);<br />
rewrite(lettera); (* FILE AZZERATO! *)<br />
repeat<br />
writeln(‘Inserire la riga da scrivere sul file’);<br />
readln(riga);<br />
if riga’FINE’ then<br />
writeln(lettera, riga);<br />
until riga=’FINE’;<br />
close(lettera);<br />
end.<br />
Aggiunga <strong>di</strong> righe ad un file esistente<br />
Var riga: string;begin<br />
begin<br />
Assign(lettera,’c:\...’);<br />
append(lettera); (* VECCHIE RIGHE CONSERVATE!*)<br />
repeat<br />
writeln(‘Inserire la riga da scrivere sul file’);<br />
readln(riga);<br />
if riga’FINE’ then<br />
writeln(lettera, riga);<br />
until riga=’FINE’;<br />
close(lettera);<br />
end
Ricorsione versione 2.02 - Febbraio 2005<br />
In entrambi gli esempi chi usa il programma comunica che le righe da aggiungere sul file <strong>di</strong> testo sono terminate<br />
<strong>di</strong>gitando la stringa ‘FINE’.<br />
FILE TIPIZZATI<br />
E’ molto importante notare che una scheda (record) viene letta/scritta tutta insieme, non un campo alla volta. Per le<br />
operazioni deve essere <strong>di</strong>chiarata una variabile record dello stesso tipo delle schede che si vogliono leggere/scrivere.<br />
Per aggiungere una scheda dovremo prima memorizzare i dati nella variabile record e poi comandare la registrazione<br />
<strong>di</strong> quest’ultima nel file tipizzato.<br />
L’istruzione che materialmente aggiunge la scheda è write(nome_file_logico, variabile_record_con_dati).<br />
Dopo ogni write sul file, il punto <strong>di</strong> inserimento è automaticamente fatto avanzare.<br />
File creato e scritto imme<strong>di</strong>atamente<br />
type<br />
TPersona=record<br />
cognome: string[30]; (* [30]: ricordatevi il numero <strong>di</strong> caratteri! *)<br />
co<strong>di</strong>ce: integer;<br />
end;<br />
var<br />
elenco: file of TPersona; (* la variabile file; si sarebbe anche potuto definire prima un tipo ‘file of TPersona’ *)<br />
unaPersona: TPersona;<br />
begin<br />
assign(elenco,'c:\prova.dat');<br />
(* crea il file azzerandolo, pronto per la prima scheda *)<br />
rewrite(elenco);<br />
repeat<br />
writeln('Inserimento da tastiera <strong>di</strong> un record da trasferire poi sul file');<br />
write('Inserire un co<strong>di</strong>ce: (zero per finire)' );<br />
readln(unaPersona.co<strong>di</strong>ce);<br />
write('Inserire un cognome: ');<br />
readln(unaPersona.cognome);<br />
if unaPersona.co<strong>di</strong>ce0 then<br />
write(elenco,unaPersona); AGGIUNGE LA SCHEDA IN FONDO AL FILE ED AVANZA<br />
until unaPersona.co<strong>di</strong>ce=0;<br />
close(elenco);<br />
end.<br />
Chi usa il programma comunica che le schede da aggiungere sono terminate <strong>di</strong>gitando come co<strong>di</strong>ce il valore zero.<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 1 <strong>di</strong> 177
Ricorsione versione 2.02 - Febbraio 2005<br />
Aggiunga <strong>di</strong> righe ad un file esistente – PROCEDURA SEEK E FUNZIONE FILESIZE<br />
L’append non esiste ma può essere simulata spostandosi in fondo al file con un comando speciale. Un grosso<br />
vantaggio, infatti, dei file tipizzati è la possibilità <strong>di</strong> spostarsi su una scheda qualsiasi senza dover leggere quelle che<br />
la precedono. Il comando è seek(nome_file_logico, posizione). ATTENZIONE: si inizia a contare da zero!! Quin<strong>di</strong> la<br />
posizione del primo record è la zero, quella del secondo record è due e così via. Per spostarsi sul primo record allora<br />
si userà seek(file , 0), per spostarsi invece sul decimo seek(file, 9).<br />
Per simulare l’append dovremmo allora spostarci oltre l’ultimo record. Se ci fossero N record l’istruzione sarebbe<br />
seek(file, N): infatti seek(file, N-1) in<strong>di</strong>viduerebbe proprio l’N-mo record ed in<strong>di</strong>cando N come posizione ci<br />
posizioneremmo sul record N+1 (cioè sul nuovo che abbiamo intenzione <strong>di</strong> inserire), sempre in virtù del fatto che si<br />
conta da 0. Già, ma come si fa a sapere quanti record ci sono nel file? Un metodo brutale potrebbe essere quello <strong>di</strong><br />
leggerli tutti fino a raggiungere la fine del file (EOF) ma sarebbe molto <strong>di</strong>spen<strong>di</strong>oso. Per fortuna esiste un comando<br />
specifico (solo per i file tipizzati; secondo voi perché ?): filesize(nome_file_logico).<br />
Inoltre, prima <strong>di</strong> poter usare seek il file deve essere pre<strong>di</strong>sposto all’uso (aperto) con la procedura<br />
reset(nome_file_logico), da usare ovviamente dopo l’assign.<br />
I coman<strong>di</strong> che portano oltre la fine del file sono allora:<br />
reset(nome_file_logico); QUESTO COMANDO PREDISPONE IL FILE ALL’USO, LO ‘APRE’<br />
seek( nome_file_logico, filesize(nome_file_logico) )<br />
Ed ecco un esempio completo:<br />
type<br />
TPersona=record<br />
cognome: string[30]; (* e' obbligatorio definire il numero dei caratteri della stringa ! *)<br />
co<strong>di</strong>ce: integer;<br />
end;<br />
fpersona=file of TPersona; (* senza il tipo non potremmo passare un file come parametro … *)<br />
var<br />
elenco: fPersona;<br />
…<br />
assign(elenco, ‘C:\dati.dat’);<br />
reset(elenco);<br />
seek(elenco, filesize(elenco ); SPOSTATI OLTRE L’ULTIMA SCHEDA PRESENTE NEL FILE<br />
repeat<br />
writeln('Inserimento da tastiera <strong>di</strong> un record da trasferire poi sul file');<br />
write('Inserire un co<strong>di</strong>ce: (zero per finire)' );<br />
readln(unaPersona.co<strong>di</strong>ce);<br />
write('Inserire un cognome: ');<br />
readln(unaPersona.cognome);<br />
if unaPersona.co<strong>di</strong>ce0 then<br />
write(elenco,unaPersona); LA SCHEDA IN FONDO AL FILE ED AVANZA<br />
until unaPersona.co<strong>di</strong>ce=0;<br />
close(elenco);<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 2 <strong>di</strong> 177
Ricorsione versione 2.02 - Febbraio 2005<br />
Lettura – la procedura RESET, READLN (file <strong>di</strong> testo), READ (file tipizzati)<br />
Per usare un file già creato in precedenza senza <strong>di</strong>struggerlo lo si deve prima pre<strong>di</strong>sporre all’uso con la procedura<br />
reset(nome_file_logico).<br />
Con reset in pratica si notifica l’uso del file al <strong>sistema</strong> operativo pre<strong>di</strong>spone le risorse necessarie (area <strong>di</strong> memoria<br />
RAM per i trasferimenti da e verso i <strong>di</strong>schi, tabelle per tenere traccia delle operazioni ecc.). Queste strutture<br />
occupano memoria centrale preziosa, per cui bisognerebbe tenere ‘aperti’ solo i file necessari.<br />
Quasi tutti i linguaggi <strong>di</strong>stinguono tra <strong>di</strong>versi tipi <strong>di</strong> apertura (Cobol, ‘C’) a seconda delle operazioni <strong>di</strong> I/O che si<br />
intendono fare: sola lettura, solo scrittura, lettura / scrittura, aggiunta a fine file (append). E’ impossibile in questa<br />
sede <strong>di</strong>scutere tutte le varianti: consultate attentamente il manuale del linguaggio in uso.<br />
Anche l’apertura può fallire:<br />
- il percorso/nome del file non è valido<br />
- il supporto <strong>di</strong> massa non è <strong>di</strong>sponibile (nastro non montato, floppy non inserito, CD non scrivibile)<br />
- il supporto è in avaria (floppy o hard <strong>di</strong>sk rotti …)<br />
- il file è già stato aperto da un utente in modalità esclusiva (blocca i tentativi <strong>di</strong> accesso <strong>di</strong> tutti gli altri)<br />
Con il Pascal, dopo l’apertura con reset <strong>di</strong> un file <strong>di</strong> testo si è pronti per leggere la prima riga. Dopo l’apertura <strong>di</strong> un<br />
file tipizzato si è pronti per leggere il primo record. Una funzione fondamentale per una lettura corretta sia dei file <strong>di</strong><br />
testo che <strong>di</strong> quelli tipizzati è la funzione EOF(nome_file_logico): che restituisce TRUE se siamo alla fine del file, FALSE<br />
altrimenti.<br />
Ve<strong>di</strong>amo alcuni esempi concreti <strong>di</strong> lettura.<br />
FILE DI TESTO<br />
… <strong>di</strong>chiarazioni …<br />
(* riapriamo il file per lettura *)<br />
reset(lettera);<br />
(* leggiamo tutte le righe fino alla fine *)<br />
while not eof(lettera) do<br />
begin<br />
readln(lettera,riga);<br />
writeln(riga)<br />
end;<br />
close(lettera);<br />
FILE TIPIZZATI<br />
… <strong>di</strong>chiarazioni …<br />
(* riapriamo il file per lettura *)<br />
reset(elenco);<br />
(* leggiamo tutte le schede fino alla fine *)<br />
while not eof(elenco) do<br />
begin<br />
readln(elenco,unaPersona);<br />
writeln(unaPersona.cognome,' - ',unaPersona.co<strong>di</strong>ce)<br />
end;<br />
close(lettera);<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 3 <strong>di</strong> 177
Ricorsione versione 2.02 - Febbraio 2005<br />
Non per<strong>di</strong>amo la bussola – puntatore all’area <strong>di</strong> lavoro, BOF ed EOF<br />
Nell’uso <strong>di</strong> un file è sempre molto importante rendersi conto in che punto dello stesso si sta lavorando. Possiamo<br />
immaginare un ‘puntatore’ che in<strong>di</strong>ca la posizione:<br />
file appena creato; è vuoto;<br />
siamo all’inizio del file (BOF,<br />
Begin Of File) e<br />
contemporaneamente alla fine<br />
(EOF, End Of File)<br />
NOTA BENE<br />
Tutte le operazioni <strong>di</strong> lettura/scrittura spostano sempre il puntatore in avanti <strong>di</strong> una ‘posizione’ (una<br />
riga per i file <strong>di</strong> testo, una scheda per i file tipizzati) verso la fine del file (EOF, End Of File).<br />
Dopo la creazione siamo all’inizio, ma anche alla fine (file è vuoto!) e quin<strong>di</strong> pronti per aggiungere<br />
nuovi blocchi <strong>di</strong> dati. Ad ogni inserimento <strong>di</strong> una riga/scheda la posizione viene spostata<br />
automaticamente oltre la fine del file e si è pronti per aggiungere un nuovo blocco <strong>di</strong> dati.<br />
Dopo aver letto una riga/scheda (se il file già ne contiene) la posizione si sposta automaticamente in<br />
avanti e si è pronti per leggere la successiva.<br />
Aggiornamento (QUESTA OPERAZIONE E’ POSSIBILE SOLO PER I FILE TIPIZZATI)<br />
Il file deve esistere e deve essere prima aperto con reset; si deve localizzare la scheda (record) da aggiornare cioè<br />
posizionarsi in corrispondenza del punto a partire dal quale è necessario sovrascrivere le vecchie informazioni con<br />
quelle nuove.<br />
Poi si legge la vecchia scheda, per due motivi:<br />
• recuperare quelle informazioni della scheda che non devono cambiare per poterle riscrivere uguali al<br />
momento della riscrittura della scheda; <strong>di</strong>versamente dovremmo costringere chi usa il programma a<br />
reinserirle (improponibile); ad esempio in una scheda anagrafica potrebbe cambiare solo l’in<strong>di</strong>rizzo: la<br />
lettura della vecchia scheda recupera tutte le altre informazioni (cognome, nome, co<strong>di</strong>ce, ecc.) che<br />
verranno riscritte uguali insieme al nuovo in<strong>di</strong>rizzo;<br />
• poter proporre le vecchie informazioni sul video facilitando chi usa il programma nell’inserimento dei<br />
campi del record che devono essere cambiati;<br />
La lettura della vecchia scheda fa però avanzare al record successivo e non siamo perciò più posizionati nel punto<br />
giusto per l’aggiornamento. Se scrivessimo in questa situazione andremmo ad aggiornare in realtà la scheda<br />
successiva (od aggiungeremmo una nuova scheda se fossimo avanzati oltre la fine del file). E’ quin<strong>di</strong> necessario<br />
ripetere il posizionamento con seek prima <strong>di</strong> riscrivere la scheda.<br />
BOF<br />
EOF<br />
il file contiene già dei dati; siamo posizionati in un<br />
punto interme<strong>di</strong>o per leggere un blocco <strong>di</strong> dati;<br />
solo con i file tipizzati potremmo anche riscrivere<br />
(aggiornare) il blocco dei dati<br />
L’aggiornamento può fallire:<br />
- i parametri in<strong>di</strong>cati sono errati<br />
- l’unità non è <strong>di</strong>sponibile (ad esempio il floppy è stato nel frattempo tolto)<br />
- l’unità è andata in avaria<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 4 <strong>di</strong> 177<br />
BOF<br />
EOF<br />
siamo alla fine del file; in questa<br />
posizione possiamo aggiungere<br />
nuovi blocchi <strong>di</strong> dati
Ricorsione versione 2.02 - Febbraio 2005<br />
Dopo l’aggiornamento ci si trova poi all’inizio dell’unità informativa successiva.<br />
Ecco un esempio che propone la sequenza delle operazioni per mo<strong>di</strong>ficare solo il cognome della persona<br />
corrispondente al secondo record (posizione 1, ricordatevi che si conta da zero …):<br />
type<br />
TPersona=record<br />
cognome: string[30]; (* e' obbligatorio definire il numero dei caratteri della stringa ! *)<br />
co<strong>di</strong>ce: integer;<br />
end;<br />
fpersona=file of TPersona; (* senza il tipo non potremmo passare un file come parametro … *)<br />
var<br />
elenco: fPersona;<br />
…<br />
assign(elenco, ‘C:\dati.dat’); reset(elenco);<br />
(* ora mo<strong>di</strong>fichiamo il secondo record la sequenza standard e':<br />
- seek alla posizione della scheda da mo<strong>di</strong>ficare<br />
- lettura della scheda con vecchi dati<br />
ATTENZIONE: a questo punto abbiamo superato la scheda da aggiornare<br />
- acquisizione nuovi dati che vengono sostituiti a quelli<br />
vecchi nella scheda<br />
- torniamo in<strong>di</strong>etro alla posizione giusta per scrittura ripetendo<br />
la seek iniziale (può anche essere fatta subito dopo la lettura, per non <strong>di</strong>menticarsene …)<br />
- scrittura su file (aggiornamento) della scheda con i dati mo<strong>di</strong>ficati *)<br />
seek(elenco,1); (* seek prima della scheda da mo<strong>di</strong>ficare *)<br />
read(elenco, unaPersona); (* lettura scheda con vecchi dati *)<br />
writeln(‘-------- VECCHI DATI -----‘);<br />
writeln(unaPersona.cognome,' - ',unaPersona.co<strong>di</strong>ce);<br />
write('Inserire il nuovo cognome: ');<br />
readln(unaPersona.cognome);<br />
(* riposizionamoci nel posto giusto per la scrittura *)<br />
seek(elenco,1);<br />
(* ora la variabile record contiene il vecchio co<strong>di</strong>ce ed il nuovo cognome: riscriviamola sul file *)<br />
write(elenco,unaPersona);<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 5 <strong>di</strong> 177
Ricorsione versione 2.02 - Febbraio 2005<br />
Eliminazione<br />
FILE DI TESTO<br />
Non è possibile eliminare una riga da un file <strong>di</strong> testo. L’unica possibilità è quella <strong>di</strong> ricopiare tutte le righe meno quelle<br />
da cancellare in un nuovo file (operazione assai <strong>di</strong>spen<strong>di</strong>osa).<br />
FILE TIPIZZATI<br />
Di nuovo, non è possibile eliminare fisicamente un record. Però, grazie alla possibilità <strong>di</strong> riscrittura si<br />
può‘contrassegnare’ una scheda in qualche modo convenzionale scelto dal programmatore. Ad esempio potrebbe<br />
esserci un campo ‘co<strong>di</strong>ce’ e decidere che se contiene zero la scheda è da considerarsi eliminata. Di tanto in tanto<br />
potremmo comandare in momenti <strong>di</strong> basso utilizzo dell’archivio (<strong>di</strong> notte, ad esempio) una riorganizzazione<br />
ricopiando in un nuovo file le schede non ‘eliminate’.<br />
Come controllare gli esiti delle operazioni richieste (GESTIONE DEGLI ERRORI)<br />
Il <strong>sistema</strong> operativo, sollecitato da coman<strong>di</strong> visti, può dare conferma in merito al loro sod<strong>di</strong>sfacimento oppure<br />
segnalare un errore. I casi che portano al fallimento possono essere vari: spazio sul supporto esaurito, il supporto<br />
in<strong>di</strong>cato non è scrivibile (CD ROM), il supporto o un altro <strong>di</strong>spositivo necessario per il suo uso (ad es. il controller<br />
dell’hd) è in avaria, le risorse <strong>di</strong> <strong>sistema</strong> non consentono al momento l’operazione (troppi programmi in uso, troppi<br />
utenti collegati, troppi file in uso, collegamento ad Internet al momento interrotto ecc.), il programma (e <strong>di</strong><br />
conseguenza l’utente che lo ha eseguito) non ha l’autorizzazione necessaria, il percorso in<strong>di</strong>cato nel nome del file<br />
non esiste o non è corretto, il nome del file in<strong>di</strong>cato non è corretto ecc.<br />
La gestione <strong>di</strong> queste situazioni può avvenire fondamentalmente in due mo<strong>di</strong>:<br />
Il <strong>sistema</strong> operativo rileva le anomalie ed agisce automaticamente: spesso questo significa un messaggio sullo<br />
schermo e la terminazione forzata del programma che ha generato l’errore. In qualche caso, ma con uno sforzo<br />
notevole, il programmatore può sostituire all’azione standard del <strong>sistema</strong> operativo le sue ‘routines’ <strong>di</strong> gestione<br />
degli ‘interrupt’ relativi (avete imparato a vostre spese come questo sia <strong>di</strong>fficile da realizzare, soprattutto in<br />
‘assembly)’.<br />
Il programma in esecuzione riceve una notifica <strong>di</strong> errore, dopo un’operazione tentata, ed ha la possibilità <strong>di</strong><br />
gestirlo e <strong>di</strong> ripristinare il funzionamento del programma in modo ‘accettabile’. La notifica può avvenire tramite:<br />
un valore restituito dalla funzione <strong>di</strong> I/O tentata: ad esempio la ‘fopen’ del ‘C’ dovrebbe restituire un puntatore<br />
ad un file (una struttura a record usata per le operazione <strong>di</strong> I/O con il corrispondente file fisico) ma potrebbe<br />
restituire ‘null’ in caso <strong>di</strong> insuccesso;<br />
un controllo esplicito del programmatore dopo aver tentato un’operazione: ad esempio in Pascal è possibile<br />
chiamare la funzione IOResult: se il valore restituito è 0 allora l’operazione è andata a buon fine, <strong>di</strong>versamente il<br />
valore in<strong>di</strong>ca il tipo <strong>di</strong> errore (ad esempio 1=file non trovato, 2=<strong>di</strong>sco pieno ecc.)<br />
l’uso <strong>di</strong> una struttura <strong>di</strong> controllo ad alto livello specifica: gli ambienti <strong>di</strong> programmazione più avanzati prevedono<br />
potenti strutture <strong>di</strong> programmazione per la gestione degli errori; ad esempio l’Object Pascal offre il costrutto ‘try<br />
… except …’ in cui dopo il termine ‘try’ (tenta) si mettono le istruzioni che potenzialmente potrebbero causare<br />
errori; nella sezione ‘except’, quasi come in un ‘case’ si elencano, usando dei nomi significativi, gli errori che si<br />
intendono ‘intercettare’ ed i relativi sottoprogrammi scritti dal programmatore per gestirli; molti <strong>di</strong>aletti del Basic<br />
(tra cui Visual Basic e Visual Basic for Application, VBA, il linguaggio degli applicativi Office) offrono un costrutto<br />
analogo: ‘on error ….’<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 6 <strong>di</strong> 177
Ricorsione versione 2.02 - Febbraio 2005<br />
ESEMPIO COMPLETO 1<br />
L’esempio seguente mostra come procedere alla creazione <strong>di</strong> un file controllando prima se ne esiste già uno<br />
vecchio.<br />
Se esiste l’operazione <strong>di</strong> reset va a buon fine e possiamo accorgercene dal valore 0 restituito dalla funzione<br />
IOResult: in questo caso si chiede conferma a chi sta usando il programma prima <strong>di</strong> procedere con una rewrite<br />
<strong>di</strong>struttiva.<br />
Se non esiste l’operazione <strong>di</strong> reset fallisce e possiamo accorgercene dal valore <strong>di</strong>verso da 0 restituito dalla funzione<br />
IOResult: in questo caso si può procedere con una rewrite <strong>di</strong>struttiva senza chiedere conferma.<br />
type<br />
TPersona=record<br />
cognome: string[30]; (* e' obbligatorio definire il numero dei caratteri della stringa ! *)<br />
co<strong>di</strong>ce: integer;<br />
end;<br />
fpersona=file of TPersona; (* senza il tipo non potremmo passare un file come parametro … *)<br />
var<br />
elenco: fPersona;<br />
begin<br />
(* la riga che segue abilita alla gestione degli errori e va inserita così com’è su una riga isolata;<br />
se la vostra tastiera non ha le graffe vi ricordo che potete tenere premuto ALT <strong>di</strong> sinistra e <strong>di</strong>gitare 123 o 125 sul<br />
tastierino numerico con tasto ‘Bloc Num’ del tastierino attivato *)<br />
{$I-}<br />
assign(elenco,'c:\prova.dat');<br />
reset(elenco);<br />
risposta:=SI;<br />
if IOResult=0 then<br />
begin<br />
writeln('Il file esiste gia'': procedo? (SI/NO)');<br />
readln(risposta);<br />
close(elenco)<br />
end;<br />
if risposta=’SI’ then<br />
begin<br />
rewrite(elenco);<br />
ecc. ecc.<br />
close(elenco)<br />
end;<br />
end.<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 7 <strong>di</strong> 177<br />
NOTA. Nel caso volessimo fare un aggiornamento o una lettura<br />
da un file la logica sarebbe semplicemente invertita: se non si<br />
riesce ad aprire il file ci si ferma; se ci si riesce si procede …<br />
IOResult fornisce co<strong>di</strong>ci numerici <strong>di</strong>versi a seconda delle<br />
situazioni e volendo essere più precisi potremmo usare un case:<br />
case IOResult of<br />
1: ….<br />
2: ….<br />
End.<br />
Ecco i co<strong>di</strong>ci più comuni (sequenza completa nell’help in linea<br />
del turbo pascal cercando ‘run time error codes’)<br />
2 File not found<br />
3 Path not found<br />
15 Invalid drive number<br />
100 Disk read error<br />
101 Disk write error<br />
102 File not assigned<br />
103 File not open<br />
104 File not open for input<br />
105 File not open for output<br />
150 Disk is write-protected<br />
152 Drive not ready<br />
156 Disk seek error<br />
157 Unknown me<strong>di</strong>a type<br />
158 Sector Not Found<br />
162 Hardware failure
Ricorsione versione 2.02 - Febbraio 2005<br />
ESEMPIO COMPLETO 2<br />
In questo secondo esempio gestiamo una semplice scheda <strong>di</strong> dati anagrafici; per sfruttare al meglio l’istruzione seek<br />
immaginiamo che ogni scheda sia in<strong>di</strong>viduata da un co<strong>di</strong>ce numerico che facciamo corrispondere alla sua posizione<br />
come record. Quin<strong>di</strong> se si cerca la scheda con co<strong>di</strong>ce 7 si utilizza seek(file, 6) e così via. Il co<strong>di</strong>ce viene mantenuto<br />
strettamente progressivo ed è assegnato in automatico dal programma<br />
Questo esempio fa uso <strong>di</strong> procedure e funzioni con parametri. In alcuni casi fa anche il controllo degli errori con i file.<br />
Il programma tiene sempre sotto controllo il numero dei record presenti nell’archivio (per poter generare in<br />
automatico il co<strong>di</strong>ce da assegnare alle schede). In partenza, infatti, se il file è vuoto mette la variabile inseriti a zero<br />
altrimenti gli assegna il numero <strong>di</strong> record presenti nel file. Il programma tiene poi aggiornato questo contatore ad<br />
ogni inserimento o cancellazione. La cancellazione non avviene in modo fisico ma mettendo a FALSE un boolean del<br />
record (campo attivo).<br />
Le scelte <strong>di</strong> chi usa il programma sono gestite da una funzione menu che riceve l’elenco delle voci da presentare (un<br />
vettore <strong>di</strong> stringhe) e restituisce il valore della scelta fatta.<br />
program ProvaFileTipizzati;<br />
(* e' necessario <strong>di</strong>sabilitare l'intercettazione degli errori <strong>di</strong> I/O da<br />
parte del Turbo Pascal al fine <strong>di</strong> gestirli in modo personalizzato *)<br />
{$I-} (* non cancellare ! *)<br />
const MAX_VOCI_MENU=9;<br />
type<br />
voci_menu=array[1..MAX_VOCI_MENU] of string;<br />
TPersona=record<br />
co<strong>di</strong>ce: integer;<br />
cognome: string[30];<br />
attivo: boolean; (* false: record da considerare cancellato *)<br />
end;<br />
fDati=file of TPersona;<br />
var<br />
dati: fDati; i,scelta:integer; ultimo_co<strong>di</strong>ce,unCo<strong>di</strong>ce: longint;<br />
nomeFile,riga: string; unaPersona: TPersona;<br />
proce<strong>di</strong>: boolean; conferma: char;<br />
menu_database: voci_<br />
menu;<br />
(* restituisce true se il file il cui nome logico è passato come parametro esiste, false altrimenti *)<br />
function esiste(var f: fdati): boolean;<br />
var esito: boolean;<br />
begin<br />
reset(f);<br />
if IOResult=0 then<br />
begin<br />
close(f);<br />
esito:=true<br />
end<br />
else<br />
esito:=false;<br />
esiste:=esito<br />
end;<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 8 <strong>di</strong> 177
Ricorsione versione 2.02 - Febbraio 2005<br />
(* riceve un vettore <strong>di</strong> stringhe ed il numero <strong>di</strong> queste da considerare e presentare come menu sullo schermo;<br />
intitola anche il menu con un altro parametro stringa; restituisce il numero della voce del menu scelta *)<br />
function menu(voci: voci_menu; n_voci: integer;titolo: string): integer;<br />
var i,voce_scelta: integer;<br />
begin<br />
repeat<br />
writeln(titolo);writeln;<br />
for i:=1 to n_voci do<br />
writeln(voci[i]);<br />
write('Scegli -> ');<br />
readln(voce_scelta);<br />
if (voce_sceltan_voci) then<br />
begin<br />
writeln('Scelta errata!! (UN TASTO PER RIPROVARE)');<br />
readln<br />
end<br />
until (voce_scelta>0) and (voce_scelta
Ricorsione versione 2.02 - Febbraio 2005<br />
repeat<br />
scelta:=menu(menu_database,7,'Gestione Nominativi');<br />
case scelta of<br />
1:begin (* azzeramento *)<br />
if ultimo_co<strong>di</strong>ce>0 then<br />
begin<br />
writeln('Attualmente sono inseriti ',ultimo_co<strong>di</strong>ce, ' nominativi; confermi <strong>di</strong>struzione? (s/n)');<br />
readln(conferma);<br />
if (conferma='s') or (conferma='S') then<br />
begin<br />
rewrite(dati);<br />
ultimo_co<strong>di</strong>ce:=0<br />
end<br />
end;<br />
writeln('Fatto!! INVIO per continuare');<br />
readln<br />
end;<br />
2:begin (* inserimento *)<br />
inc(ultimo_co<strong>di</strong>ce);<br />
with unaPersona do<br />
begin<br />
co<strong>di</strong>ce:=ultimo_co<strong>di</strong>ce;<br />
write('Inserire cognome: ');<br />
readln(cognome);<br />
attivo:=true<br />
end;<br />
reset(dati);<br />
seek(dati, FileSize(dati)); (* append ... *)<br />
write(dati,unaPersona);<br />
close(dati)<br />
end;<br />
3:begin (* ricerca *)<br />
if ultimo_co<strong>di</strong>ce>0 then<br />
begin<br />
write('Inserire il co<strong>di</strong>ce che interessa: ');readln(unCo<strong>di</strong>ce);<br />
if unCo<strong>di</strong>ce0 then<br />
begin<br />
write('Inserire il co<strong>di</strong>ce che interessa: ');readln(unCo<strong>di</strong>ce);<br />
if unCo<strong>di</strong>ce
Ricorsione versione 2.02 - Febbraio 2005<br />
begin<br />
writeln('Vecchi dati: ',unaPersona.co<strong>di</strong>ce, ' - ',unaPersona.cognome);<br />
write('Inserire nuovo cognome: ');<br />
readln(unaPersona.cognome);<br />
(* per riscrivere devo rimettermi PRIMA del record *)<br />
seek(dati,unCo<strong>di</strong>ce-1);<br />
write(dati,unaPersona);<br />
writeln('Fatto!')<br />
end<br />
else<br />
writeln('Nominativo cancellato')<br />
end<br />
else<br />
writeln('Ci sono solo ',ultimo_co<strong>di</strong>ce,' nominativi!')<br />
end<br />
else<br />
writeln('File Vuoto');<br />
writeln('INVIO per continuare');<br />
readln<br />
end;<br />
5:begin (* cancellazione *)<br />
if ultimo_co<strong>di</strong>ce>0 then<br />
begin<br />
write('Inserire il co<strong>di</strong>ce che interessa: ');readln(unCo<strong>di</strong>ce);<br />
if unCo<strong>di</strong>ce0 then<br />
visualizza_file(dati)<br />
else<br />
writeln('File Vuoto');<br />
writeln('INVIO per continuare');<br />
readln<br />
end<br />
end<br />
until scelta=7;<br />
end.<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 11 <strong>di</strong> 177
Ricorsione versione 2.02 - Febbraio 2005<br />
RICORSIONE<br />
Esistono problemi tutto sommato semplici da descrivere che portano ad algoritmi incre<strong>di</strong>bilmente complicati se<br />
trattati con gli strumenti <strong>di</strong> programmazione visti sino ad ora… Ad esempio: molte situazioni reali sono adatte per<br />
essere rappresentate con una struttura chiamata albero. Un albero è formato da elementi chiamati no<strong>di</strong> collegati da<br />
archi:<br />
figlio<br />
foglia<br />
nodo<br />
arco<br />
fratelli<br />
Ra<strong>di</strong>ce (root)<br />
Autore: Fabrizio Camuso (camuso@bigfoot.com) Pagina 12 <strong>di</strong> 177<br />
Nel <strong>di</strong>segno i no<strong>di</strong> sono rappresentati dai cerchietti, gli archi dai segmenti<br />
che li congiungono. In realtà si tratta <strong>di</strong> un caso particolare <strong>di</strong> albero<br />
chiamato albero binario, in quanto ogni nodo può avere al massimo due<br />
figli (ma anche uno o nessuno). Gli alberi più complessi (generici, senza<br />
limiti sul numero <strong>di</strong> figli) possono comunque essere rappresentati con un<br />
albero binario costruito in modo ‘furbo’ (scoprirete più avanti nel corso<br />
come …).<br />
Il nodo iniziale, quello in cima per intenderci, è chiamato root (ra<strong>di</strong>ce). I<br />
no<strong>di</strong> terminali (quelli senza figli) sono chiamati foglie. I figli <strong>di</strong>retti <strong>di</strong> uno<br />
stesso nodo sono fratelli tra loro.<br />
Qualche esempio d’uso: in un gioco un albero può rappresentare per ogni possibile ‘mossa’ le risposte<br />
dell’avversario e per ciascuna <strong>di</strong> queste le possibili contromosse e così via; i sistemi operativi utilizzano strutture ad<br />
albero per memorizzare la struttura delle cartelle e relative sotto cartelle; un albero genealogico; l’organigramma<br />
delle figure <strong>di</strong> un’azienda (boss, capi reparto, <strong>di</strong>pendenti ecc.); alcune delle tecniche più sofisticate <strong>di</strong> gestione degli<br />
archivi usano strutture ad albero (B+ tree).<br />
E’ ovvio che ciò che conta è il ‘contenuto’ <strong>di</strong> ogni nodo. Per un gioco (scacchi ad esempio) ogni nodo potrebbe<br />
memorizzare la situazione appena prima o appena dopo una certa mossa (la matrice della scacchiera). Il <strong>sistema</strong><br />
operativo potrebbe memorizzare l’elenco dei file in quella <strong>di</strong>rectory (non è proprio così …); per l’albero genealogico<br />
i dati anagrafici della persona associata a ciascun nodo ecc.<br />
Ma come si rappresentano gli archi? La soluzione classica prevede la gestione tramite memoria <strong>di</strong>namica: ogni nodo<br />
memorizza i puntatori ai figli. Ad esempio:<br />
type<br />
Pun=^Nodo;<br />
Nodo=record<br />
ParteInformativa: string;<br />
sx: Pun; (* memorizza il puntatore al figlio <strong>di</strong> sinistra *)<br />
dx: Pun (* memorizza il puntatore al figlio <strong>di</strong> destra *)<br />
end<br />
OK, immaginiamo <strong>di</strong> avere l’albero già perfettamente costruito e che la variabile puntatore inizio punti al primo<br />
nodo (root). Ed ecco la sfida: scrivere un programma che stampi l’elenco <strong>di</strong> tutte le parti informative memorizzate<br />
nell’albero. E’ una richiesta banale: se non riuscissimo a fare almeno questo l’albero sarebbe inutilizzabile! Da un<br />
punto <strong>di</strong> vista logico si tratta <strong>di</strong> visitare tutti i no<strong>di</strong> e per ciascuno stampare la parte informativa. Sfido chiunque a<br />
trovare una soluzione che faccia uso <strong>di</strong> cicli: essa esiste ma è <strong>di</strong> elevata complessità. E’ anche quasi incomprensibile<br />
… Ed è pure lunga alcune decine <strong>di</strong> righe. Guardate invece l’eleganza, la semplicità e l’estrema leggibilità <strong>di</strong> questa<br />
soluzione (ricorsiva):
procedure StampaAlbero(nodo: pun);<br />
begin<br />
if nodoNIL then<br />
begin<br />
1. writeln(nodo^.ParteInformativa);<br />
2. StampaAlbero(nodo^.sx);<br />
3. StampaAlbero(nodo^.dx)<br />
end<br />
end;<br />
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
NOTA: ho numerato le righe per<br />
facilitare il successivo commento<br />
del co<strong>di</strong>ce.<br />
1 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
Considerando l’if’ sono solo sei righe !!! Sembra quasi impossibile… La stampa completa è comandata con<br />
StampaAlbero(inizio) dove inizio è il puntatore al primo nodo dell’albero (la ra<strong>di</strong>ce). La logica <strong>di</strong> esecuzione è assai<br />
semplice: stampare la parte informativa del nodo <strong>di</strong> cui si è ricevuto il puntatore e chiamare quin<strong>di</strong> prima la stampa<br />
pe il figlio <strong>di</strong> sinistra ( StampaAlbero(nodo^.sx) ) e poi per quello <strong>di</strong> destra ( StampaAlbero(nodo^.dx) ). Il<br />
meccanismo si ripete sui rispettivi figli sinistra/destra fino a raggiungere le foglie.<br />
La procedura chiama quin<strong>di</strong> sé stessa (ricorre ai servizi <strong>di</strong> sé stessa, da cui il termine ricorsione) ma ogni volta<br />
passando un puntatore che la fa avvicinare alle foglie. Con queste ultime verranno chiamate delle ‘StampaAlbero’<br />
con parametro uguale a NIL (le foglie non hanno no<strong>di</strong> sotto <strong>di</strong> esse e quin<strong>di</strong> hanno il valore NIL nei loro puntatori sx<br />
e dx) terminando la catena delle chiamate.<br />
StampaAlbero(inizio^.sx)<br />
StampaAlbero(inizio^.sx^.sx)<br />
<br />
StampaAlbero(nil)<br />
StampaAlbero(inizio)<br />
StampaAlbero(nil)<br />
StampaAlbero(inizio^.dx)<br />
StampaAlbero(inizio^.dx^.dx)<br />
<br />
StampaAlbero(nil)<br />
E’ molto importante capire che in un certo istante avremo più copie della procedura StampaAlbero attivate (ma una<br />
sola ‘funzionante’). Infatti la prima chiamata ( StampaAlbero(inizio) ) chiama sé stessa con il puntatore al suo figlio<br />
<strong>di</strong> sinistra e deve poi letteralmente rimanere in attesa alla riga n. 2 aspettando la fine dell’esecuzione <strong>di</strong><br />
StampaAlbero(inizio^.sx). E quest’ultima dopo aver stampato la parte informativa del suo nodo aspetterà alla stessa<br />
riga per lo stesso motivo e così via. Riferendosi al <strong>di</strong>segno precedente, verranno chiamate in sequenza ben 4<br />
StampaAlbero che esplorano tutto la parte a sinistra dell’abero, fino a raggiungere la prima foglia (l’ultimo nodo in<br />
basso a sinistra). Solo dopo che il meccanismo avrà fatto esplorare tutti i no<strong>di</strong> a sinistra della ra<strong>di</strong>ce, la procedura<br />
che ha ricevuto inizio come puntatore e che è stata fino ad ora in attesa potrà continuare la sua esecuzione e<br />
chiamare sé stessa sul figlio <strong>di</strong> destra: <strong>di</strong> nuovo si mette in attesa per la fine dell’esecuzione <strong>di</strong> questa chiamata che<br />
prima farà esplorare tutti i no<strong>di</strong> alla destra della ra<strong>di</strong>ce. Solo allora la prima procedura terminerà ‘raggiungendo il<br />
suo end’.<br />
Quando la procedura è chiamata con NIL non fa niente ma si limita a terminare, senza richiamare più sé stessa ed<br />
interrompendo la catena delle chiamate. E’ molto importante che si raggiunga una situazione <strong>di</strong> terminazione:<br />
<strong>di</strong>versamente si andrebbe avanti fino all’esaurimento della memoria messa a <strong>di</strong>sposizione per la ricorsione (stack)<br />
mandando in crash il processo <strong>di</strong> esecuzione e, forse, causando anche il blocco dell’elaboratore.<br />
Un algoritmo ricorsivo corretto prima o poi invece si ferma perché raggiunge la con<strong>di</strong>zione <strong>di</strong> terminazione che è<br />
chiamata la base della ricorsione (in realtà potrebbe ancora esaurire la memoria se ha bisogno <strong>di</strong> troppi passi per<br />
essere concluso, ma concettualmente l’algoritmo è corretto). Pensate alla base della ricorsione come al caso che<br />
termina la ‘catena’ delle chiamate. Nel nostro esempio la base è rappresentata da nodo=NIL: la con<strong>di</strong>zione dell’if<br />
non è verificata e la procedura non fa nulla (soprattutto termina e non fa altre chiamate a se stessa).<br />
E’ anche importante notare che ogni chiamata ricorsiva impegna la procedura con un problema più semplice<br />
dell’originale: la prima procedura attivata deve stampare l’intero albero; la seconda il sottoalbero <strong>di</strong> sinistra, la terza<br />
il sottoalbero <strong>di</strong> sinistra ancora e così via fino a che, in profon<strong>di</strong>tà, una procedura riceverà un puntatore ad una<br />
foglia; a questo punto avviene l’ultima chiamata alla procedura che riceverà un puntatore NIL: la procedura non fa<br />
nulla e la ‘catena’ si ferma.<br />
NOTA: LA PARTE CHE SEGUE (1 PAGINA) E’ UN APPROFONDIMENTO PIUTTOSTO DIFFICILE DA<br />
SEGUIRE. E’ FACOLTATIVA.<br />
ATTENZIONE: DOPO LA PARTE FACOLTATIVA LA DISPENSA PROSEGUE!!<br />
2 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
Esaminiamo in dettaglio cosa accade: la procedura viene invocata una prima volta e riceve il puntatore al primo<br />
nodo. Se questo è NIL (cioè se l’albero è vuoto) si ferma subito, come deve essere. Altrimenti stampa la parte<br />
informativa del primo nodo. Poi invoca se stessa sul figlio <strong>di</strong> sinistra con l’istruzione StampaAlbero(nodo^.sx). A<br />
questo punto le procedure attivate sono due! La prima ad essere cronologicamente chiamata, StampaAlbero(inizio),<br />
che rimane in attesa alla riga 2 aspettando il termine <strong>di</strong> StampaAlbero(nodo^.sx) che rappresenta la seconda<br />
procedura attivata in or<strong>di</strong>ne <strong>di</strong> tempo. E’ molto importante capire che l’istruzione della riga 3,<br />
StampaAlbero(nodo^.dx), della prima procedura attivata non verrà eseguita fino a che non terminerà quella della riga<br />
2.<br />
La seconda procedura attivata inizia la sua esecuzione e stampa la parte informativa del nodo puntato dal parametro<br />
che ha ricevuto (se <strong>di</strong>verso da NIL). Poi effettua a sua volta una chiamata ricorsiva, attivando una terza copia della<br />
procedura StampaAlbero inviando come parametro il puntatore al suo figlio <strong>di</strong> sinistra. E così via. Ecco<br />
schematizzata la sequenza sul <strong>di</strong>segno dell’albero:<br />
5<br />
NIL<br />
4<br />
6<br />
7<br />
NIL<br />
8<br />
3<br />
9<br />
11<br />
NIL<br />
Gestione dello stack per la ricorsione<br />
2<br />
10<br />
12<br />
1<br />
I numeri in<strong>di</strong>cano ovviamente la sequenza cronologica. Solo quelli vicino alle<br />
frecce continue in<strong>di</strong>cano però l’attivazione <strong>di</strong> una nuova ‘incarnazione’ della<br />
procedura. Le frecce tratteggiate in<strong>di</strong>cano il raggiungimento della base della<br />
ricorsione che fa terminare, relativamente a quella parte dell’albero, la catena<br />
delle chiamate. Ad esempio, la procedura attiva al punto 4, dopo aver<br />
stampato la sua parte informativa fa la chiamata ricorsiva passando come<br />
parametro NIL (infatti non ha figli). La procedura chiamata (punto 5) non fa<br />
allora nulla e termina. Il controllo ritorna allora alla procedura del punto 4<br />
(seguite la freccia tratteggiata numerata 6) che è in attesa alla riga 2 del<br />
co<strong>di</strong>ce e che può finalmente fare la seconda chiamata ricorsiva<br />
(corrispondente alla riga 3 del co<strong>di</strong>ce) passando ancora NIL (anche il figlio <strong>di</strong><br />
destra non esiste). La procedura invocata (numerata con 7) termina e<br />
restituisce il controllo ancora una volta a quella numerata con 4. Quest’ultima<br />
ha infine esaurito le sue istruzioni e termina, restituendo il controllo a quella<br />
numerata con 3, che chiama 10, che chiama 11 (con nil) e così via …<br />
Tutte queste ‘incarnazioni’ della stessa procedura con<strong>di</strong>vidono lo stesso co<strong>di</strong>ce. Non si pensi cioè che se il<br />
meccanismo ricorsivo ha bisogno <strong>di</strong> 20 chiamate, in memoria sia presente 20 volte il co<strong>di</strong>ce della procedura. Quello<br />
che accade è che quando un’incarnazione <strong>di</strong> una procedura ad un qualche passo della ricorsione ha bisogno <strong>di</strong><br />
chiamare sé stessa, il suo stato (una sua copia delle variabili locali con i valori fino a quel momento calcolati e un<br />
‘segnalibro’ all’istruzione cui era arrivata) è memorizzato in un area <strong>di</strong> memoria speciale, chiamata stack. Quando<br />
il controllo è restituito, anche molti passi dopo, ad una procedura, il suo stato è ripreso dallo stack ed essa può<br />
continuare dal punto in cui era stata interrotta. Lo stack è una struttura <strong>di</strong>namica LIFO (Last In First Out) in cui<br />
l’ultimo dato inserito è il primo che può essere ripreso. Per fare un paragone pensate ad una pila <strong>di</strong> piatti: l’ultimo<br />
piatto lavato sarà il primo ad essere ripreso per il risciacquo. Questo modo <strong>di</strong> funzionare è essenziale per poter<br />
accedere nell’or<strong>di</strong>ne giusto agli stati delle <strong>di</strong>verse procedure: infatti è quella chiamata per ultima che riavrà il<br />
controllo per prima e continuerò dal punto in cui era stata interrotta. Osservando il grafico precedente: la 3 chiama la<br />
4 (e lo stato della 3 viene salvato); la 4 chiama la 5 (e lo stato della 4 viene salvato); la 5 finisce: deve riprendere la<br />
4, è il suo stato che serve, non quello della 3! La 4, chiamata per ultima, ha bisogno prima della 3 del suo stato: gli<br />
stati devono essere ripristinati nell’or<strong>di</strong>ne inverso in cui sono stati salvati. Uno stack (realizzato come un vettore <strong>di</strong><br />
byte in una zona della RAM) si comporta esattamente in questo modo. Nella simulazione sottostante, proc sta per<br />
Proc 4 chiama proc 5<br />
procedura.<br />
Proc 1 chiama proc 2<br />
Lo stato <strong>di</strong> proc 1<br />
viene salvato sullo<br />
stack<br />
Proc 1 è messa in<br />
attesa<br />
Proc 2 chiama proc 3<br />
Lo stato <strong>di</strong> proc 2<br />
viene salvato sullo<br />
stack<br />
Proc 2 è messa in<br />
attesa<br />
Proc 1 è sempre in<br />
attesa<br />
Stato proc 2<br />
Stato proc 1 Stato proc 1<br />
Proc 3 chiama proc 4<br />
Lo stato <strong>di</strong> proc 3<br />
viene salvato sullo<br />
stack<br />
Proc 3 è messa in<br />
attesa<br />
Proc 1 e 2 sono<br />
sempre in attesa<br />
Stato proc 3<br />
Stato proc 2<br />
Stato proc 1<br />
Lo stato <strong>di</strong> proc 4<br />
viene salvato sullo<br />
stack<br />
Proc 4 è messa in<br />
attesa<br />
Proc 1, 2 e 3 sono<br />
sempre in attesa<br />
Stato proc 4<br />
Stato proc 3<br />
Stato proc 2<br />
Stato proc 1<br />
Proc 5 termina<br />
Lo stato <strong>di</strong> proc 4<br />
viene tolto dallo stack<br />
Proc 4 riprende dalla<br />
riga alla quale si era<br />
interrotta<br />
Proc 1, 2 e 3 sono<br />
sempre in attesa<br />
Stato proc 3<br />
Stato proc 2<br />
Stato proc 1<br />
3 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
RIPRENDE LA PARTE NON FACOLTATIVA DELLA DISPENSA!!<br />
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
In alcuni problemi è proponibile sia una soluzione iterativa (con i cicli) che ricorsiva. Quest’ultima risulterà più<br />
lenta nell’esecuzione e consumerà più memoria (salvare lo stato occupa tempo e spazio) ma più naturale se il<br />
problema ha una natura essa stessa ricorsiva. In questi ultimi casi spesso la soluzione ricorsiva è MOLTO più<br />
semplice da programmare, al punto da risultare l’unica strada percorribile… La ricorsione si è rivelata uno<br />
strumento assai utile nel campo dell’Intelligenza Artificiale (IA) dove strutture complesse come gli alberi sono<br />
all’or<strong>di</strong>ne del giorno per rappresentare la conoscenza ed il percorso decisionale per la soluzione <strong>di</strong> un problema.<br />
QUALCHE ESEMPIO DI SOTTOPROGRAMMA RICORSIVO<br />
Per definire un algoritmo ricorsivo dobbiamo sempre in<strong>di</strong>viduare:<br />
Esempio 1: calcolo del fattoriale.<br />
In<strong>di</strong>chiamo con Fatt(N) il fattoriale <strong>di</strong> un numero intero >=0. E’ il prodotto <strong>di</strong> tutti gli interi da 1 a N. Ad esempio<br />
Fatt(6) = 6 * 5 * 4 * 3 * 2 * 1. Per definizione si stabilisce che Fatt(0) è 1. E’ una funzione assai usata in<br />
matematica: ad esempio nel calcolo delle probabilità (combinazioni, <strong>di</strong>sposizioni semplici o con ripetizione,<br />
coefficienti binomiali ecc.).<br />
L’obiettivo è darne una definizione ricorsiva (esprimere un fattoriale usando ancora un fattoriale ma calcolato su un<br />
valore più semplice). Si ragiona così: qual è il caso più semplice <strong>di</strong> calcolo del fattoriale? Quando N è 0: infatti<br />
possiamo subito affermare senza bisogno <strong>di</strong> fare calcoli che il risultato è 1. E se dobbiamo calcolare Fatt(4)? Il<br />
risultato imme<strong>di</strong>ato non lo possiamo calcolare ma possiamo <strong>di</strong>re che è 4 * Fatt(3). Infatti Fatt(3)=3*2*1 e quin<strong>di</strong><br />
4*Fatt(3) equivale a 4 * 3*2*1, il calcolo <strong>di</strong> partenza.<br />
Abbiamo allora ricondotto un caso più complesso, Fatt(4), al calcolo <strong>di</strong> un caso più semplice, Fatt(3). Come <strong>di</strong>re:<br />
non so calcolare <strong>di</strong>rettamente Fatt(4) ma se riesco a calcolare Fatt(3) risalgo attraverso una moltiplicazione a Fatt(4).<br />
Fatt(3) è ancora troppo ‘<strong>di</strong>fficile’ da calcolare ma lo si riconduce a Fatt(2): Fatt(3) = 3 * Fatt(2). Fatt(2) = 2 *<br />
Fatt(1). Fatt(1) = 1 * Fatt(0). Ma Fatt(0) so che vale uno senza bisogno <strong>di</strong> ricalcolare nessun altro fattoriale: ho<br />
raggiunto il caso base, la base della ricorsione che termina la catena delle chiamate ricorsive!<br />
Schematicamente<br />
1 se N =0<br />
Fatt(N) =<br />
La base della ricorsione: è il caso più semplice, quello per il quale sappiamo subito ‘calcolare’ il<br />
risultato. Quello a cui il meccanismo ricorsivo tenta <strong>di</strong> ricondursi un poco alla volta, passo dopo<br />
passo.<br />
Il passo ricorsivo: quando non siamo <strong>di</strong> fronte al caso più semplice dobbiamo tentare <strong>di</strong><br />
esprimerlo attraverso una ‘formula’ che richiama lo stesso sottoprogramma che stiamo scrivendo<br />
ma con argomenti semplificati che ci avvicinano al caso che rappresenta la base della ricorsione<br />
N * Fatt(N-1) se N>0<br />
function Fatt(N: integer): real;<br />
begin<br />
if N=0 then<br />
Fatt:=1<br />
else<br />
Fatt:= N * Fatt(N-1)<br />
end;<br />
NOTA: come valore restituito ho scelto un real in quanto il<br />
calcolo del fattoriale molto rapidamente porta al<br />
superamento della capacità <strong>di</strong> una variabile integer.<br />
4 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
Esempio 2: calcolo <strong>di</strong> x y con x ed y interi positivi.<br />
In<strong>di</strong>chiamo con XallaY(x,y) il sottoprogramma.<br />
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
L’obiettivo è darne una definizione ricorsiva (esprimere una potenza usando ancora una potenza ma calcolata su un<br />
valore più semplice). Si ragiona così: qual è il caso più semplice ? Quando si chiede <strong>di</strong> elevare un numero alla zero.<br />
In questo caso infatti per definizione il risultato è 1. Un numero elevato alla prima può essere calcolato come il<br />
numero stesso moltiplicato la sua potenza zero. Infatti x 1 = x * x 0 = x * 1 = x. Abbiamo allora ricondotto un caso<br />
più complesso al calcolo <strong>di</strong> uno <strong>di</strong> cui sappiamo subito il risultato (x 0 ). Ora che sappiamo calcolare un numero<br />
elevato alla prima siamo in grado <strong>di</strong> calcolare i quadrati, Infatti un numero al quadrato può essere calcolato come il<br />
numero stesso moltiplicato per la sua potenza prima: x 2 = x * x 1 . E così via: x 3 = x * x 2 = x * x * x 1 = x * x * x * x 0 ;<br />
x 4 = x * x 3 ecc.<br />
Abbiamo allora ricondotto in generale un caso più complesso, x y , al calcolo <strong>di</strong> un caso più semplice, x * x y-1 .<br />
Schematicamente<br />
XallaY(x,y) =<br />
Esempio 3: calcolo <strong>di</strong> x*y con x ed y interi, y0.<br />
In<strong>di</strong>chiamo con XperY(x,y) il sottoprogramma.<br />
L’obiettivo è darne una definizione ricorsiva (esprimere un prodotto usando ancora un prodotto ma calcolato su un<br />
valore più semplice). Si ragiona così: qual è il caso più semplice ? Quando si chiede <strong>di</strong> moltiplicare un numero per<br />
1: il risultato è il numero stesso. Se invece dobbiamo calcolare un numero per 2 possiamo esprimere il calcolo come<br />
il numero sommato al prodotto del numero per 1: x * 2 = x + (x*1). Di nuovo abbiamo ricondotto un prodotto<br />
complesso al calcolo <strong>di</strong> un prodotto più semplice <strong>di</strong> cui sappiamo subito il risultato (x*1). E così via: x * 3 = x + x*2<br />
= x + x + x*1; x * 4 = x + x * 3 ecc.<br />
Schematicamente<br />
XperY(x,y) =<br />
1 se y =0<br />
x * XallaY(x,y-1) se y>0<br />
x se y =1<br />
x + XperY(x,y-1) se y>0<br />
function XallaY(x,y: integer): real;<br />
begin<br />
if y=0 then<br />
XallaY:=1<br />
else<br />
XallaY:= x * XallaY(x,y-1)<br />
end;<br />
NOTA: come valore restituito ho scelto un real in quanto il<br />
calcolo <strong>di</strong> una potenza molto rapidamente può portare al<br />
superamento della capacità <strong>di</strong> una variabile integer.<br />
function XperY(x,y: integer): real;<br />
begin<br />
if y=1 then<br />
XperY:=x<br />
else<br />
XperY:= x + XperY(x,y-1)<br />
end;<br />
NOTA: come valore restituito ho scelto un real in quanto il<br />
calcolo <strong>di</strong> prodotto può portare abbastanza facilmente al<br />
superamento della capacità <strong>di</strong> una variabile integer.<br />
Nota: matematicamente un prodotto è in effetti una somma ripetuta … x * 4 = x + x + x + x che è esattamente<br />
quanto fa la ricorsione!<br />
5 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
Esempio 4: calcolo <strong>di</strong> x+y con x ed y interi.<br />
In<strong>di</strong>chiamo con XpiuY(x,y) il sottoprogramma.<br />
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
L’obiettivo è darne una definizione ricorsiva (esprimere una somma usando ancora una somma ma calcolata su un<br />
valore più semplice). Si ragiona così: qual è il caso più semplice ? Quando si chiede <strong>di</strong> sommare zero ad un numero:<br />
il risultato è il numero stesso. Se invece dobbiamo sommare 1 possiamo esprimere il calcolo come 1 più il risultato<br />
della somma tra il numero e 0; infatti: x + 1 = 1 + (x+0). Di nuovo abbiamo ricondotto un prodotto complesso al<br />
calcolo <strong>di</strong> un prodotto più semplice <strong>di</strong> cui sappiamo subito il risultato (x+0). E così via: x + 4 = 1 + (x+3) = 1 + 1 +<br />
(x+2) = 1 + 1 + 1 + (x+1) = 1 + 1 + 1 + 1 + (x+0) = 1 + 1 + 1 + 1 + x.<br />
Schematicamente<br />
XpiuY(x,y) =<br />
Esempio 5: calcolo della serie <strong>di</strong> Fibonacci (esempio <strong>di</strong> doppia chiamata ricorsiva)<br />
La serie <strong>di</strong> Fibonacci è la seguente: 0 1 1 2 3 5 8 13 21 34 55 … dove ogni elemento è la somma dei due precedenti<br />
(ad eccezione del primo e del secondo che sono per definizione 0 ed 1).<br />
In<strong>di</strong>chiamo con Fib(N) l’ennesimo numero della sequenza (si parte dalla posizione 0!). Ad esempio Fib(0)=0;<br />
Fib(1)=1; 2; Fib(7)=13.<br />
L’obiettivo è darne una definizione ricorsiva: un numero verrà appunto espresso come somma dei 2 numeri <strong>di</strong><br />
Fibonacci che lo precedono, fino ai primi due che sono fissati per definizione. Fib(4) = Fib(3) + Fib(2) =<br />
Fib(2)+Fib(1) + Fib(1)+Fib(0) = Fib(1) + Fib(0) + Fib(1) + Fib(1) + Fib(0) = 3.<br />
Schematicamente<br />
Fib(N) =<br />
x se y =0<br />
1 + XpiuY(x,y-1) se y>0<br />
0 se N =0, 1 se N=1<br />
Esercizi suggeriti:<br />
• calcolo <strong>di</strong> A-B<br />
• calcolo <strong>di</strong> A / B<br />
• inversione <strong>di</strong> una stringa<br />
Fib(N-2)+Fib(N-1) se N>1<br />
procedure XperY(x,y: integer): integer;<br />
begin<br />
if y=0 then<br />
XpiuY:=x<br />
else<br />
XpiuY:= 1 + XpiuY(x,y-1)<br />
end;<br />
procedure Fib(N: integer): integer;<br />
begin<br />
if N=0 then<br />
Fib:=0<br />
else<br />
if N=1 then<br />
Fib:=1<br />
else<br />
Fib:=Fib(N-2) + Fib(N-1)<br />
end;<br />
6 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
LIMITI DELLA MEMORIA ALLOCATA STATICAMENTE<br />
LIMITE 1<br />
Consideriamo la seguente <strong>di</strong>chiarazione:<br />
var<br />
voti: array[1..10] of integer;<br />
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
Il programmatore sta in<strong>di</strong>cando al compilatore che intende usare un vettore <strong>di</strong> 10 interi. Il compilatore <strong>di</strong><br />
conseguenza, nella traduzione in linguaggio macchina, pre<strong>di</strong>spone (alloca) la quantità <strong>di</strong> RAM (byte) necessaria.<br />
Con la maggior parte dei linguaggi <strong>di</strong> programmazione, quando il programma è mandato in esecuzione non c’è modo<br />
<strong>di</strong> mutare questa situazione (aumentare ad esempio il numero <strong>di</strong> elementi nel vettore). L’unico modo per aggiungere<br />
elementi al vettore è quello <strong>di</strong> mo<strong>di</strong>ficare il sorgente e ricompilare il programma.<br />
Immaginate la scena: un programmatore ‘tirchio’ <strong>di</strong>mensiona un vettore con troppo pochi elementi; un cliente, a<br />
mille chilometri <strong>di</strong> <strong>di</strong>stanza, usa il programma in modo da esaurire il vettore: è costretto ad attendere l’intervento del<br />
programmatore, con grave <strong>di</strong>sagio (ed arrabbiatura).<br />
Esagerare con il numero <strong>di</strong> elementi del vettore potrebbe comportare uno spreco <strong>di</strong> RAM inaccettabile (se<br />
me<strong>di</strong>amente il numero <strong>di</strong> elementi usati del vettore è sensibilmente inferiore al massimo). Decidere invece <strong>di</strong><br />
memorizzare i dati su <strong>di</strong>sco farebbe crollare le prestazioni.<br />
Naturalmente non saremo sempre così ‘sfortunati’: i mesi <strong>di</strong> un anno sono sempre do<strong>di</strong>ci, ed i giorni al massimo<br />
366; gli alunni <strong>di</strong> una classe <strong>di</strong>fficilmente supereranno i quaranta, ecc. Diciamo che il problema è particolarmente<br />
evidente in quelle situazioni in cui il programmatore non può fare una stima esatta o quasi delle necessità.<br />
Pensate, ad esempio, al gioco degli scacchi: è impossibile prevedere quante ‘mosse’ durerà la partita. Quanti<br />
elementi dovrà allora avere il vettore che memorizza le mosse? Se poi volessimo impostare un livello <strong>di</strong> gioco<br />
<strong>di</strong>fficile il computer dovrà allora valutare molte posizioni per ogni mossa. Ma qualcuno preferirà un livello <strong>di</strong> gioco<br />
per principianti. Alcuni software permettono ad<strong>di</strong>rittura <strong>di</strong> giocare più partite in contemporanea. Quanta memoria<br />
RAM serve, quind,i per far funzionare il programma? Che cosa dovrebbe <strong>di</strong>chiarare il programmatore nella sezione<br />
VAR ?<br />
LIMITE 2<br />
Lo spazio allocato staticamente in RAM non può essere<br />
mo<strong>di</strong>ficato (aumentato o <strong>di</strong>minuito) durante l’esecuzione del<br />
programma.<br />
Immaginate ora <strong>di</strong> avere un elenco <strong>di</strong> nomi in un vettore e <strong>di</strong> averlo or<strong>di</strong>nato alfabeticamente (operazione ‘costosa’).<br />
Immaginiamo anche che il vettore abbia dei ‘posti liberi’ in fondo. Ogni ulteriore inserimento rappresenta<br />
un’operazione piuttosto onerosa se vogliamo mantenere or<strong>di</strong>nato l’elenco. Infatti non basta aggiungere in fondo il<br />
nuovo nome: non è detto, infatti, che l’elenco rimanga or<strong>di</strong>nato. E’ assai più probabile che il giusto posto per il<br />
nuovo nome sia in una posizione interme<strong>di</strong>a. Sarà allora necessario spostare verso destra <strong>di</strong> una posizione una parte<br />
degli elementi per fare spazio al nuovo. Come si <strong>di</strong>ceva, l’inserimento in queste con<strong>di</strong>zioni è un’operazione piuttosto<br />
onerosa ed accettabile solo se gli inserimenti a vettore già or<strong>di</strong>nato sono rari: purtroppo sono molteplici le<br />
applicazioni in cui è vero io il contrario … Non va meglio nel caso si debba eliminare un elemento interme<strong>di</strong>o<br />
(perché?).<br />
Una struttura allocata staticamente è inefficiente per<br />
frequenti operazioni <strong>di</strong> inserimento ed eliminazione.<br />
7 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
LIMITE 3<br />
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
Date un’occhiata alla struttura dati <strong>di</strong>segnata qui a lato (si chiama albero). Essa è adatta per<br />
rappresentare situazioni in cui esiste una gerarchia tra gli elementi da memorizzare (struttura<br />
<strong>di</strong>rectory, organigramma aziendale, mosse e contromosse in un gioco ecc.). Beh, penso sarete<br />
d’accordo nell’affermare che non è facile rappresentarla usando gli array. E che <strong>di</strong>re in merito<br />
alla rappresentazione <strong>di</strong> una rete stradale: in questo caso non c’è ad<strong>di</strong>rittura nessun or<strong>di</strong>ne tra<br />
i no<strong>di</strong> da collegare!<br />
ALLOCAZIONE DINAMICA DELLA MEMORIA<br />
Una struttura allocata staticamente è<br />
inadeguata per rappresentare strutture non<br />
lineari.<br />
Il programmatore può invece scegliere, nei casi in cui risulta conveniente, <strong>di</strong> gestire la memoria <strong>di</strong>namicamente.<br />
Significa che avrà a <strong>di</strong>sposizione delle istruzioni con cui comandare durante l’esecuzione del programma la<br />
allocazione <strong>di</strong> altro spazio per le variabili ma anche per restituire lo spazio che non serve più.<br />
UN’ APPLICAZIONE CONCRETA<br />
Immaginiamo che ci sia stato commissionato un programma per monitorare in tempo reale gli ingressi allo SMAU.<br />
In particolare si vogliono inserire le località <strong>di</strong> provenienza dei visitatori in un elenco che deve essere consultabile<br />
con la massima velocità per poter effettuare statistiche in tempo reale. Da subito viene scartata l’idea <strong>di</strong> utilizzare i<br />
file: troppo lenti. Si utilizzerà la RAM cercando comunque <strong>di</strong> non ‘sprecarla’ per non compromettere le prestazioni<br />
del <strong>sistema</strong>. Si decide <strong>di</strong> organizzare le informazioni in questo modo: blocchi (vettori) da 20 stringhe ciascuno fino<br />
ad un massimo <strong>di</strong> 1000 blocchi. Fissiamo anche la lunghezza <strong>di</strong> ogni stringa: 50 caratteri. All’inizio si comincia con<br />
un solo blocco (vettore): esaurito il primo si chiederà memoria per il secondo e così via ...<br />
Rifletti ...<br />
Capita la <strong>di</strong>fferenza? Non verranno pre<strong>di</strong>sposti da subito 1000 vettori da 20 stringhe! Verrà dato spazio ai vettori in<br />
RAM progressivamente, man mano che se ne avvertirà l’esigenza ...<br />
L’uso della memoria <strong>di</strong>namica consentirà <strong>di</strong> occupare all’inizio solo la memoria necessaria per le 20 stringhe del primo<br />
blocco e <strong>di</strong> occuparne dell’altra solo quando veramente necessario. E’ facile convincersi che in questo modo la<br />
memoria eventualmente sprecata è quella corrispondente a 19 stringhe, nell’ipotesi <strong>di</strong> usare solo la prima stringa<br />
dell’ultimo blocco allocato.<br />
Rifletti ...<br />
Pur non essendo ottimale (rimane il limite delle 20*1000 blocchi=20000 stringhe) in questo modo riusciamo a gestire<br />
una GROSSA quantità <strong>di</strong> stringhe ottimizzando l’uso della memoria. In seguito estenderemo la tecnica per gestire<br />
elenchi ‘infinitamente’ lunghi (ad esaurimento RAM ... o spazio su <strong>di</strong>sco visto che tutti i moderni sistemi operativi<br />
supportano la gestione virtuale della RAM usando il <strong>di</strong>sco come estensione ‘lenta’ <strong>di</strong> quest’ultima)<br />
8 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
Proce<strong>di</strong>amo con gradualità. Prima preoccupiamoci della gestione <strong>di</strong>namica <strong>di</strong> un singolo vettore <strong>di</strong> 20 stringhe, cioè <strong>di</strong><br />
un solo blocco. Poi estenderemo la gestione a 1000 blocchi. Come si fa a chiedere al Pascal <strong>di</strong> allocare spazio per un<br />
vettore <strong>di</strong> 20 stringhe? Ed il vettore si userà come tutti gli altri vettori? Un passo alla volta ...<br />
Iniziamo con il definire un tipo che rappresenta un singolo blocco:<br />
type<br />
vettore = array[1..20] of string[50]; (* il punto <strong>di</strong> partenza è un normale vettore <strong>di</strong> stringhe *)<br />
pun = ^vettore; (* il tipo blocco ‘punta’ ad oggetti <strong>di</strong> tipo vettore; si parla <strong>di</strong> tipo puntatore *)<br />
(* il carattere ^ è fondamentale per in<strong>di</strong>care che si tratta <strong>di</strong> un puntatore! *)<br />
var<br />
blocco: pun (* variabile puntatore che memorizzerà l’in<strong>di</strong>rizzo <strong>di</strong> un blocco quando sarà creato *)<br />
La variabile blocco è <strong>di</strong> tipo pun, cioè (risalendo la catena delle <strong>di</strong>chiarazioni) è un puntatore ad oggetti <strong>di</strong> tipo<br />
vettore (si scrive ^vettore). Al momento non sta puntando a nulla perché il programmatore non ha ancora chiesto<br />
l’allocazione <strong>di</strong> memoria per il blocco <strong>di</strong> stringhe.<br />
NOTA BENE: blocco non può essere usata come una variabile qualsiasi. Ad esempio producono un errore le seguenti<br />
istruzioni:<br />
NO !!! writeln(blocco) o readln(blocco) o blocco:=13; NO !!!<br />
Un puntatore è una variabile speciale. Serve a contenere in<strong>di</strong>rizzi <strong>di</strong> oggetti creati nella RAM. Quin<strong>di</strong> prima deve<br />
essere creato un oggetto e poi si memorizza nel puntatore dove inizia in memoria quell’oggetto. Usando poi il<br />
puntatore potremo scrivere dati in quel blocco <strong>di</strong> memoria, recuperarli, <strong>di</strong>struggere il blocco <strong>di</strong> memoria quando non<br />
servirà più.<br />
APPROFONDIMENTO<br />
La <strong>di</strong>mensione effettiva <strong>di</strong> un puntatore <strong>di</strong>pende da come il processore gestisce gli in<strong>di</strong>rizzi <strong>di</strong> memoria e dalla<br />
capacità del compilatore <strong>di</strong> sfruttare appieno le possibilità offerte da un processore. Il Turbo Pascal è in grado <strong>di</strong><br />
gestire sia in<strong>di</strong>rizzi <strong>di</strong> 2 byte, cioè 16 bit (memoria <strong>di</strong> al massimo 2^16 – 1 = 64Kbyte, sia in<strong>di</strong>rizzi <strong>di</strong> 4 byte (32bit,<br />
memoria segmentata DOS) in<strong>di</strong>candoli con una coppia numero <strong>di</strong> segmento (64K possibili segmenti) + posizione nel<br />
segmento (da 0 a 64K).<br />
Per chiarire in modo inequivocabile che un puntatore non sta in<strong>di</strong>viduando nessun oggetto in memoria lo si inizializza<br />
con un valore speciale: NIL (nulla).<br />
blocco := NIL;<br />
In questo modo il programmatore può verificare se il puntatore sta puntando ad un oggetto valido (uno degli errori<br />
più pericolosi che si possano commettere è infatti l’uso <strong>di</strong> un puntatore che non sta puntando a nulla):<br />
if bloccoNIL then … uso ‘sicuro’ del puntatore …<br />
9 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
L’istruzione new: allocazione della memoria<br />
Torniamo all’esempio precedente e continuiamo dal punto in cui ci eravamo interrotti<br />
type<br />
vettore = array[1..20] of string[50];<br />
pun = ^vettore;<br />
var<br />
blocco: pun<br />
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
E’ arrivato il momento <strong>di</strong> creare un blocco (vettore) e <strong>di</strong> memorizzare il suo in<strong>di</strong>rizzo nella variabile blocco.<br />
new(blocco);<br />
Letteralmente: crea un nuovo oggetto del tipo in<strong>di</strong>viduato dalla variabile puntatore. Poiché blocco è un puntatore ad<br />
oggetti <strong>di</strong> tipo vettore e questi ultimi sono array <strong>di</strong>20 stringhe, è appunto un vettore <strong>di</strong> 20 stringhe che viene creato<br />
in memoria. Poiché ogni stringa occupa 50 carratteri, il blocco complessivo <strong>di</strong> byte occupati è 20*50=1000 byte.<br />
Ecco quello che accade più in dettaglio: il <strong>sistema</strong> operativo, se la memoria non è esaurita, riserva un blocco <strong>di</strong> tanti<br />
byte consecutivi quanti sono quelle necessari a memorizzare 20 stringhe da 50 caratteri (circa 1000 byte). Non è<br />
dato sapere a priori dove si troverà nella RAM questo blocco ma ciò che conta è sapere l’in<strong>di</strong>rizzo del suo primo byte:<br />
è il suo in<strong>di</strong>rizzo ad essere memorizzato nella variabile puntatore blocco. Considerando la situazione rappresentata<br />
nel <strong>di</strong>segno soprastante, ho immaginato che il <strong>sistema</strong> operativo abbia trovato posto a partire dal byte all’in<strong>di</strong>rizzo<br />
1436: è questo il valore che viene memorizzato nella variabile blocco. Se non c’è spazio il <strong>sistema</strong> operativo<br />
restituisce NIL.<br />
E’ molto importante capire che al momento della scrittura del programma il vettore non esiste ancora. E neppure alla<br />
partenza del programma: è necessario il comando esplicito new. NOTA: la new deve essere comandata una sola volta<br />
per tutto il vettore. Nel caso il vettore non servisse più sarà possibile restituire la memoria al <strong>sistema</strong> operativo e<br />
riutilizzare il puntatore blocco per un altro vettore (subito o in un secondo momento).<br />
Utilizzo della memoria allocata<br />
blocco<br />
1436<br />
Il valore <strong>di</strong> bocco è lin<strong>di</strong>rizzo del primo<br />
byte allocato; i byte non contengono<br />
ancora niente.<br />
Immaginando <strong>di</strong> aver già creato l’oggetto vettore, proviamo a memorizzare una stringa nel suo primo elemento:<br />
blocco^[1]:='Sono la prima stringa del blocco';<br />
New(un_PBlocco)<br />
RAM<br />
...<br />
...<br />
1436<br />
10 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )<br />
1434<br />
1435
Attenzione a non usare blocco come se fosse esso stesso il vettore <strong>di</strong> stringhe:<br />
blocco[1]:=’una stringa’ NO!!<br />
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
Il puntatore infatti non è l’oggetto ma l’in<strong>di</strong>rizzo dell’oggetto. Per passare dall’in<strong>di</strong>rizzo all’oggetto è necessario<br />
in<strong>di</strong>care il simbolo ^ dopo il nome del puntatore. In progressione: blocco è l’in<strong>di</strong>rizzo, blocco^ è l’oggetto puntato,<br />
cioè il vettore e blocco^[1] è il suo primo elemento<br />
Ed ora un ciclo for per valorizzare a stringa vuota i restanti elementi<br />
for i:=2 to 20 do<br />
blocco^[i] := ‘’;<br />
Ed un altro ciclo for che visualizza tutte le stringhe:<br />
for i:=1 to 20 do<br />
writeln( blocco^[i] );<br />
L’istruzione <strong>di</strong>spose: restituzione della memoria<br />
Quando il vettore ha esaurito la sua utilità possiamo restituire con il comando <strong>di</strong>spose la memoria al <strong>sistema</strong><br />
operativo, cioè deallocarla:<br />
<strong>di</strong>spose(blocco); blocco:=nil;<br />
end.<br />
NOTA BENE: dopo la <strong>di</strong>spose il puntatore perde <strong>di</strong> significato. E’ un GRAVE errore tentare <strong>di</strong> usare un puntatore<br />
deallocato. Purtroppo, il Turbo Pascal non mette automaticamente a nil un puntatore deallocato (con altri linguaggi<br />
accade) ed è allora buona pratica farlo personalmente.<br />
E’ altrettanto GRAVE deallocare un puntatore che non punta a niente (NIL): <strong>di</strong> solito va in crash il programma.<br />
Rifletti ...<br />
Il fatto che la memoria possa essere restituita quando non serve più rappresenta già un grosso vantaggio rispetto<br />
all’uso <strong>di</strong> un normale vettore <strong>di</strong> stringhe. Il prezzo da pagare è una certa complicazione delle operazioni e, cosa ben<br />
più tangibile, un maggiore rischio <strong>di</strong> commettere errori gravi.<br />
In effetti se la quantità <strong>di</strong> memoria non rappresenta un problema conviene sovra<strong>di</strong>mensionare gli array standard ed<br />
evitare complicazioni. D’altra parte in molte situazioni la quantità <strong>di</strong> memoria è il problema principale (provate a<br />
pensare se un programma <strong>di</strong> grafica tipo PhotoShop dovesse allocare decine <strong>di</strong> megabyte <strong>di</strong> RAM per niente per ogni<br />
immagine ad alta risoluzione che poi non sarà veramente caricata in memoria...).<br />
ATTENZIONE: SE AVETE CAPITO GLI ARGOMENTI FIN QUI ESPOSTI AVETE RAGGIUNTO COMPLETAMENTE GLI<br />
OBIETTIVI!!<br />
IL SEGUITO PUO’ ESSERE CONSIDERATO UN APPROFONDIMENTO, NON ALLA PORTATA DI TUTTI.<br />
11 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
Dopo le prime 20 stringhe avremmo esaurito lo spazio, nè più nè meno che con un vettore classico. La soluzione<br />
consiste nell’usare un vettore <strong>di</strong> puntatori, <strong>di</strong> 1000 puntatori per l’esattezza.<br />
Intanto è necessario <strong>di</strong>chiarare il vettore <strong>di</strong> puntatori:<br />
type<br />
P1000Blocchi=array[1..1000] of blocco;<br />
var<br />
Localita: P1000Blocchi;<br />
Ora abbiamo a <strong>di</strong>sposizione il tipo vettore <strong>di</strong> 1000 puntatori a blocchi <strong>di</strong> 20 stringhe ciascuno e relativa variabile.<br />
(* inizializzo il vettore <strong>di</strong> puntatori *)<br />
for i:=1 to 1000 do<br />
Localita[i]:=nil;<br />
E’ utile rappresentare graficamente la situazione (solo con i primi elementi del vettore <strong>di</strong> puntatori):<br />
(* chie<strong>di</strong>amo spazio per due vettori: il primo ed il quinto, senza un motivo particolare, giusto per prova *)<br />
new( Localita[1] );<br />
new( Localita[5] );<br />
localita[1]<br />
localita[5]<br />
NIL<br />
NIL<br />
NIL<br />
NIL<br />
localita<br />
Localita[1]^<br />
Localita[5]^<br />
Solo due puntatori hanno a questo punto collegati due blocchi. Tutte le caselle dei vettori contengono valori casuali.<br />
(* inizializzo le stringhe dei vettori, ma solo per i puntatori a blocco <strong>di</strong>versi da nil, ovviamente *)<br />
for i:=1 to 1000 do<br />
if Localita[i]nil then<br />
for j:=1 to 20 do<br />
Localita[i]^[j]:='';<br />
NIL<br />
NIL<br />
NIL<br />
NIL<br />
NIL<br />
NIL<br />
Ora tutte le caselle dei vettori contengono la stringa nulla. Notate il controllo if localita[i]NIL che seleziona solo gli<br />
elementi del vettore <strong>di</strong> puntatori che hanno collegato veramente un vettore <strong>di</strong> stringhe (il primo ed il quinto<br />
elemento, nel nostro caso).<br />
NIL<br />
12 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
Localita è il nome <strong>di</strong> un vettore. Localita[i] accede all’i-mo elemento <strong>di</strong> quel vettore (un puntatore). Localita[i]^<br />
accede all’elemento puntato che è a sua volta un vettore <strong>di</strong> stringhe. Localita[i]^ [j] è il j-mo elemento <strong>di</strong> questo<br />
vettore, cioè la j-ma delle 20 stringhe <strong>di</strong> quel vettore. Vi gira la testa?<br />
(* memorizzo una stringa nel quarto elemento del primo vettore *)<br />
Localita[1]^[4]:='le cose si complicano ...';<br />
(* ed una nel terzo elemento del quinto vettore *)<br />
Localita[5]^[3]:='ma non piu'' <strong>di</strong> tanto!';<br />
localita[1]<br />
localita[5]<br />
writeln('---------------------------'); writeln('Stringhe nel vettore n. 1: '); writeln('---------------------------');<br />
for i:=1 to 20 do<br />
writeln( (Localita[1]^)[i] );<br />
readln;<br />
writeln('---------------------------'); writeln('Stringhe nel vettore n. 5: '); writeln('---------------------------');<br />
for i:=1 to 20 do<br />
writeln( Localita[5]^[i] );<br />
readln;<br />
(* deallochiamo i vettori <strong>di</strong> stringhe *)<br />
for i:=1 to 1000 do<br />
if localita[i]nil then<br />
begin<br />
<strong>di</strong>spose(localita[i]);<br />
localita[i]:=nil<br />
end<br />
end.<br />
Notate , <strong>di</strong> nuovo, il controllo sul puntatore a NIL prima <strong>di</strong> deallocare. Deallocare con <strong>di</strong>spose un puntatore a NIL è<br />
un GRAVE errore e <strong>di</strong> solito porta al crash del programma.<br />
Rifletti ...<br />
NIL<br />
NIL<br />
NIL<br />
NIL<br />
Localita[1]^<br />
Localita[5]^<br />
Quanta memoria può far risparmiare la gestione <strong>di</strong>namica? A volte moltissima ! Se ad esempio si scoprisse che<br />
me<strong>di</strong>amente solo metà delle stringhe sono necessarie, la gestione statica staticamente userebbe sempre e comunque<br />
1.000.000 <strong>di</strong> byte (20.000 stringhe x 50 caratteri) mentre quella <strong>di</strong>namica circa la metà: 520.000 (2 byte a puntatore<br />
x 1000 puntatori + 10.000 stringhe x 50 caratteri) !!<br />
Risparmi a parte la gestione <strong>di</strong>namica restituisce memoria nei momenti in cui non serve e migliora le prestazioni del<br />
<strong>sistema</strong> operativo e, quin<strong>di</strong>, dell’elaboratore.<br />
QUI TERMINA L’APPROFONDIMENTO.<br />
Localita[1]^[4]<br />
Le cose si complicano ..<br />
Localita[5]^[3]<br />
ma non più <strong>di</strong> tanto!<br />
13 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
Gli errori più comuni<br />
Allocazione <strong>di</strong>namica della memoria versione 2.5 Luglio 2003<br />
• Usare un puntatore prima <strong>di</strong> averlo ‘agganciato’ con l’istruzione new ad un blocco <strong>di</strong> memoria valido<br />
• Usare un puntatore <strong>di</strong>menticandosi il simbolo <strong>di</strong> puntatore (^):<br />
se p è un puntatore ad un intero:<br />
new(p);<br />
SI’: p:=3; NO: p^:=3<br />
• Usare un puntatore dopo averlo ‘sganciato’ con l’istruzione <strong>di</strong>spose dal blocco <strong>di</strong> memoria cui puntava:<br />
se p è un puntatore ad un intero:<br />
new(p);<br />
p^:=3;<br />
<strong>di</strong>spose(p); ora p non punta più a nulla e non è più utilizzabile<br />
writeln( p^);<br />
NO !! p non ha più significato: l’area <strong>di</strong> memoria cui puntava è stata restituita con <strong>di</strong>spose al <strong>sistema</strong><br />
operativo che potrebbe utilizzarla per altri scopi; usare l’in<strong>di</strong>rizzo non più valido contenuto in p potrebbe<br />
portare a conseguenze anche molto gravi per la stabilità del <strong>sistema</strong>.<br />
• Sbagliare la posizione del simbolo <strong>di</strong> puntatore (^) con vettori o record. Ecco una rassegna <strong>di</strong> casi:<br />
TYPE<br />
p= ^string;<br />
vett : array[1..10] of p;<br />
rec=record<br />
co<strong>di</strong>ce: integer;<br />
pun: p<br />
end;<br />
vetRec=array[1..10] of rec;<br />
recVet=record<br />
co<strong>di</strong>ce: integer;<br />
v: array[1..10] of p<br />
end;<br />
VAR<br />
vpun: vett;<br />
unRec: rec;<br />
vrec: vetRec;<br />
unRecVet: recVet;<br />
14 Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it )
I record con il Turbo Pascal versione 1.3 Luglio 2003<br />
SI’: new( vpun[3] ); SI’: vpun[3]^:=’ciao’; NO: new(vpun)<br />
NO: vpun^[3]:=’ciao’; (giusto se vpun fosse un puntatore a vettore <strong>di</strong> stringhe e non un vettore <strong>di</strong> puntatori)<br />
SI’: new(unRec.pun); SI’: unRec.pun^:=’ciao’;<br />
NO: unRec^.pun:=’ciao’; (giusto se unRec fosse un puntatore ad un record contenente una stringa pun)<br />
SI’: new( vrec[2].pun ); SI’: vrec[2].pun^:=’ciao’;<br />
SI’: new (unRecVet.v[2]); SI’: unRecVet.v[2]^:=’ciao’;<br />
In generale è sufficiente ricordarsi <strong>di</strong> in<strong>di</strong>care il simbolo <strong>di</strong> puntatore (^) dopo il nome del puntatore (con la<br />
particolarità che v[i] e non ‘v’ è il nome del puntatore quando ‘v’ è un vettore.<br />
LISTE SEMPLICI<br />
Pur con i vantaggi visti, un limite alle stringhe gestibili nell’esempio precedente esiste comunque: ventimila. Ve<strong>di</strong>amo<br />
<strong>di</strong> superarlo. Realizzeremo una concatenazione <strong>di</strong> elementi (lista) che potrà essere espansa senza limiti (RAM<br />
permettendo).<br />
Nel <strong>di</strong>segno P rappresenta il puntatore che in<strong>di</strong>vidua l’ingresso alla lista; ogni elemento (nodo) della lista ha una<br />
parte informatica (INFO) che può essere qualsiasi cosa: un semplice intero, una stringa, un vettore ecc. ogni nodo è<br />
collegato al successivo con un puntatore. Il punto <strong>di</strong> ingresso in<strong>di</strong>vidua la testa della lista e all’altro capo troviamo<br />
invece la cosiddetta coda della lista.<br />
Una lista ha anche altri vantaggi, oltre a quello <strong>di</strong> poter ‘crescere’ indefinitamente: se gli elementi sono in or<strong>di</strong>ne<br />
(alfabetico, per esempio) l’aggiunta <strong>di</strong> un nuovo elemento, mantenendo l’or<strong>di</strong>namento, è questione <strong>di</strong> pochissime<br />
operazioni. Anche l’eliminazione <strong>di</strong> un elemento ha un costo estremamente inferiore rispetto ai vettori.<br />
BASTA ARRAY? Il fatto che la lista sia ‘forte’ proprio dove gli array sono ‘deboli’ non significa che debba essere d’ora<br />
in poi preferita ad essi (anzi!). Le liste hanno purtroppo delle controin<strong>di</strong>cazioni, che sono comuni a tutte le strutture<br />
realizzate con la memoria <strong>di</strong>namica: , ma è ancora troppo presto per parlarne…<br />
Graficamente rappresenteremo le liste come segue:<br />
P<br />
P<br />
P<br />
testa della lista<br />
NIL<br />
Quando la lista è vuota. P, il puntatore che rappresenta il suo punto <strong>di</strong> ingresso, vale NIL. Si<br />
arriva in questa situazione o perché ancora nessun elemento è stato inserito alla lista o perché<br />
sono stati tutti eliminati.<br />
NIL<br />
La lista come esempio <strong>di</strong> ADT<br />
P<br />
Quando la lista è formata da un solo elemento, puntato da P. L’elemento è costituito<br />
logicamente da due parti: quella informativa ed un puntatore all’elemento che<br />
eventualmente segue nella lista (vale NIL se si è alla fine)<br />
NIL Una lista in uno stato interme<strong>di</strong>o.<br />
La lista è un esempio <strong>di</strong> ADT (Abstract Data Type, Tipo <strong>di</strong> Dato Astratto): nuovi tipi definiti dal programmatore per i<br />
quali è definito un ben preciso insieme <strong>di</strong> operazioni.<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 1<br />
NIL<br />
coda della lista
I record con il Turbo Pascal versione 1.3 Luglio 2003<br />
Ai programmatori non interessa come sia realizzato internamente l’ADT (potrebbe essere costruito usando vettori<br />
piuttosto che record per memorizzare i dati, usare un metodo <strong>di</strong> or<strong>di</strong>namento piuttosto che un altro ecc.). Al<br />
programmatore interessa solo sapere come ottenere i servizi <strong>di</strong> un ADT cioè quali coman<strong>di</strong> può inviare ad un<br />
esemplare <strong>di</strong> un ADT e quali risultati ottiene.<br />
Per fare un esempio, è come se il Pascal offrisse solo il tipo integer, con le operazioni che tutti conoscete. Il<br />
programmatore potrebbe aggiungere l’ADT real o string con tutte le operazioni note. Chi userà gli ADT non si<br />
preoccuperà <strong>di</strong> come i real e le string sono memorizzate e gestite: gli basta sapere come creare variabili <strong>di</strong> quei tipi,<br />
come assegnare loro valori, fare operazioni con esse, come visualizzarle sullo schermo ecc. E non sarà possibile<br />
chiedere operazioni non previste per quell’ADT (moltiplicare due stringhe, per esempio).<br />
Per l’ADT lista semplice potremmo definire almeno le seguenti operazioni: aggiunta <strong>di</strong> un elemento all’inizio o alla fine<br />
della lista (o in punto interme<strong>di</strong>o), aggiunta <strong>di</strong> un elemento mantenendo la lista in or<strong>di</strong>ne, eliminazione <strong>di</strong> un<br />
elemento, estrazione (l’elemento tolto non viene <strong>di</strong>strutto ma resta a <strong>di</strong>sposizione del programmatore), ricerca <strong>di</strong> un<br />
elemento, visita <strong>di</strong> tutti gli elementi <strong>di</strong> una lista, controllo per sapere se la lista è vuota, <strong>di</strong>struzione della lista<br />
Prima <strong>di</strong> scrivere le operazioni ve<strong>di</strong>amo come rappresentare gli elementi della lista: un elemento deve contenere due<br />
cose, la parte informativa ed il puntatore all’elemento che lo segue nella lista. Sono due informazioni completamente<br />
<strong>di</strong>verse, per cui la struttura dati più naturale da usare è il record.<br />
Verrebbe spontaneo scrivere la <strong>di</strong>chiarazione del record nel modo seguente (immaginando che la parte informativa<br />
sia un semplice intero, realizzando così una lista <strong>di</strong> integer …):<br />
type<br />
elemento=record<br />
inf: integer; (* informazione *)<br />
pun: ^elemento (* puntatore *)<br />
end;<br />
Da un punto <strong>di</strong> vista logico è corretto: pun è il puntatore all’elemento successivo che è in effetti <strong>di</strong> tipo elemento, per<br />
cui un puntatore a questo tipo si scrive proprio ^elemento. Purtroppo il compilatore non conosce ancora esattamente<br />
cosa sia elemento quando <strong>di</strong>chiariamo pun perché stiamo proprio definendo elemento e non abbiamo ancora finito!<br />
Capite? Ci stiamo mordendo la coda: per definire elemento dovremmo già sapere cosa sia elemento!<br />
Il Pascal offre una scappatoia: prima della definizione <strong>di</strong> elemento possiamo definire un tipo puntatore ad esso. Si<br />
chiama <strong>di</strong>chiarazione forward (in avanti). E’ un’eccezione: il compilatore sa che dovrebbe trovare la <strong>di</strong>chiarazione <strong>di</strong><br />
cosa sia elemento dopo:<br />
type<br />
puntatore= ^elemento; (* OK: il compilatore si aspetta <strong>di</strong> trovare dopo cosa sia elemento *)<br />
elemento=record<br />
inf: integer; (* informazione *)<br />
pun: puntatore (* il compilatore sa già cos’è il tipo puntatore *)<br />
end;<br />
Una volta definiti i tipi è necessario <strong>di</strong>chiarare una variabile che rappresenta l’inizio della lista:<br />
var<br />
InizioLista: puntatore;<br />
NO !!<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 2
InizioLista<br />
Proviamo a creare un nodo isolato <strong>di</strong> questa lista:<br />
begin<br />
(*creiamo il primo nodo*)<br />
new(InizioLista);<br />
InizioLista^.inf:=12;<br />
InizioLista^.pun:=nil;<br />
Creiamo un secondo elemento (per il momento non collegato al primo):<br />
var<br />
InizioLista: puntatore;<br />
NuovoNodo: puntatore:<br />
…<br />
new(NuovoNodo);<br />
NuovoNodo^.inf:=24;<br />
NuovoNodo^.pun:=nil;<br />
I record con il Turbo Pascal versione 1.3 Luglio 2003<br />
NOTA BENE Non è possibile usare ancora InizioLista per creare il secondo nodo: perderemmo il primo:<br />
new(InizioLista);<br />
InizioLista ^.inf:=24;<br />
InizioLista ^.pun:=nil<br />
NIL<br />
Da qualche parte in memoria i dati del primo elemento ci sono ancora: avendo però cambiato l’in<strong>di</strong>rizzo memorizzato<br />
nel puntatore con la seconda new, sono <strong>di</strong>ventati irraggiungibili!! InizioLista porta ora al secondo nodo, perdendo la<br />
possibilità <strong>di</strong> usare il primo.<br />
Ora concateniamo i due no<strong>di</strong>, agganciando il secondo al primo:<br />
InizioLista^.pun:=NuovoNodo;<br />
?????<br />
InizioLista è un puntatore. InizioLista^ è l’oggetto puntato, un record:<br />
per accedere ai suoi campi informativi si usa la notazione ‘punto’.<br />
InizioLista^.inf è quin<strong>di</strong> la sua parte informativa, InizioLista^.pun quella<br />
puntatore. Dopo queste istruzioni possiamo rappresentare graficamente<br />
la lista così:<br />
InizioLista<br />
InizioLista<br />
NIL<br />
Nella casella puntatore del primo nodo (la casella dopo il 12) viene memorizzato l’in<strong>di</strong>rizzo del secondo (NuovoNodo)<br />
NIL<br />
NuovoNodo<br />
InizioLista<br />
NuovoNodo<br />
NIL<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 3<br />
NIL<br />
NIL
I record con il Turbo Pascal versione 1.3 Luglio 2003<br />
In effetti il secondo nodo è raggiunto in questo momento da due puntatori: quello usato per crearlo e quello del nodo<br />
che lo precede (realizzando così la catena).<br />
Dopo aver visto i meccanismi fondamentali, siete pronti per l’analisi <strong>di</strong> un programma abbastanza completo <strong>di</strong><br />
gestione delle liste semplici. Naturalmente tutte le operazioni verranno implementate come sottoprogrammi! Infatti<br />
sarebbe molto sconveniente scrivere un co<strong>di</strong>ce apposito per aggiungere il terzo elemento e poi il quarto ecc.<br />
Per brevità non viene mai effettuato il controllo sul buon esito delle new.<br />
Program liste_semplici;<br />
uses crt;<br />
type<br />
puntatore= ^elemento;<br />
elemento=record<br />
inf: integer; (* informazione *)<br />
pun: puntatore (* puntatore *)<br />
end;<br />
STAMPA DELLA LISTA (IN EFFETTI E’ UNA VISITA COMPLETA DI TUTTI GLI ELEMENTI DI UNA LISTA)<br />
procedure stampa_lista(p: puntatore);<br />
begin<br />
writeln('----');<br />
while pnil do<br />
begin<br />
write(p^.inf,' ');<br />
p:=p^.pun<br />
end;<br />
writeln('----');<br />
end;<br />
p è il puntatore all’inizio della lista;<br />
p:=p^.pun è l’istruzione classica con cui si passa da un nodo al<br />
successivo. Se infatti p punta ad un certo nodo, la scrittura p^.pun<br />
corrisponde al contenuto della sua casella puntatore, cioè l’in<strong>di</strong>rizzo<br />
del nodo successivo:<br />
NB: p è passato per valore; possiamo quin<strong>di</strong> mo<strong>di</strong>ficarlo senza timore<br />
<strong>di</strong> perdere l’inizio della lista…<br />
INSERIMENTO IN TESTA DI UN NUOVO ELEMENTO: riceve il puntatore 'p' ad una lista ed un valore da inserire<br />
procedure inserisci_in_testa(var p:puntatore; valore:integer);<br />
var nuovo_nodo: puntatore;<br />
begin<br />
new(nuovo_nodo); nuovo_nodo^.inf:=valore;<br />
nuovo_nodo^.pun:=p;<br />
p:=nuovo_nodo;<br />
NIL<br />
end;<br />
P<br />
E’ importante prima salvare il valore <strong>di</strong> p<br />
nella casella puntatore del nuovo nodo:<br />
<strong>di</strong>versamente (invertendo cioè l’or<strong>di</strong>ne dei<br />
due assegnamenti) si perderebbe il valore<br />
originale <strong>di</strong> p cioè del primo nodo, rendendo<br />
irraggiungibile l’intera lista!!<br />
2<br />
p:=nuovo_nodo<br />
1<br />
nuovo_nodo^.pun:=p<br />
P<br />
nuovo_nodo<br />
P^.pun<br />
P^.pun^.pun<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 4
I record con il Turbo Pascal versione 1.3 Luglio 2003<br />
(*INSERIMENTO IN CODA DI UN NUOVO ELEMENTO: riceve il puntatore 'p' ad una lista ed un valore da inserire *)<br />
procedure inserisci_in_coda(var p:puntatore; valore:integer);<br />
var nuovo_nodo,fine_lista: puntatore;<br />
begin<br />
new(nuovo_nodo); nuovo_nodo^.inf:=valore;<br />
nuovo_nodo^.pun:=nil;<br />
(* devo <strong>di</strong>stinguere il caso lista vuota *)<br />
if pnil then (* lista non vuota *)<br />
begin<br />
fine_lista:=p; (* parto dall'inizio ... *)<br />
while fine_lista^.punnil do (* ... ed avanzo fino alla fine ... *)<br />
fine_lista:=fine_lista^.pun;<br />
fine_lista^.pun:=nuovo_nodo<br />
end<br />
else<br />
p:=nuovo_nodo<br />
end;<br />
Anche se sarà poi necessario <strong>di</strong>fferenziare il caso lista vuota,<br />
queste istruzioni vanno bene in entrambi i casi.<br />
INSERIMENTO DI UN NUOVO ELEMENTO IN UNA POSIZIONE INTERMEDIA: riceve il puntatore 'p' al nodo che<br />
precederà quello da inserire<br />
procedure inserisci_in_mezzo(var p:puntatore; valore:integer);<br />
var nuovo_nodo: puntatore;<br />
begin<br />
new(nuovo_nodo); nuovo_nodo^.inf:=valore;<br />
if p=nil then (* la lista e' vuota *)<br />
begin<br />
nuovo_nodo^.pun:=nil; p:=nuovo_nodo<br />
end<br />
else<br />
begin<br />
nuovo_nodo^.pun:=p^.pun; (* aggancio successivo *)<br />
p^.pun:=nuovo_nodo (* mi faccio puntare da precedente *)<br />
end<br />
end;<br />
P<br />
Il <strong>di</strong>segno qui a lato fa evidentemente<br />
riferimento al caso lista non vuota…<br />
Il <strong>di</strong>segno qui a lato si riferisce al caso lista<br />
non vuota…<br />
P<br />
2 p^.pun:=nuovo_nodo<br />
fine_lista:=fine_lista^.pun …<br />
nuovo_nodo<br />
fine_lista<br />
nuovo_nodo 1 nuovo_nodo^.pun:=p^.pun<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 5<br />
NIL<br />
NIL
RICERCA IN UNA LISTA NON ORDINATA<br />
I record con il Turbo Pascal versione 1.3 Luglio 2003<br />
La procedura non si limita a restituire un puntatore all’elemento eventualmente trovato ma anche il puntatore a<br />
quello che eventualmente lo precede. Ho scelto questo comportamento perché per alcune operazioni, una volta<br />
trovato un elemento, è necessario <strong>di</strong>sporre anche del puntatore all’elemento precedente (ad esempio nel caso<br />
volessimo cancellare l’elemento trovato)<br />
procedure cerca_non_or<strong>di</strong>nata(p: puntatore; valore: integer; var corrente, precedente: puntatore);<br />
begin<br />
precedente:=nil; corrente:=nil;<br />
if pnil then<br />
begin<br />
corrente:=p;<br />
while (corrente^.infvalore) and (corrente^.punnil) do<br />
begin<br />
precedente:=corrente;<br />
precedente corrente<br />
corrente:=corrente^.pun<br />
end;<br />
if corrente^.infvalore then<br />
begin<br />
precedente:=nil;<br />
corrente:=nil<br />
end<br />
end<br />
end;<br />
RICERCA IN UNA LISTA ORDINATA<br />
La ricerca è effettuata su una lista non or<strong>di</strong>nata. Se valore viene trovato, in<br />
corrente viene restituito il puntatore al nodo cercato, in precedente quello al<br />
nodo che lo precedete (nil se il nodo cercato è il primo). Se il valore non<br />
viene trovato sia corrente che precedente sono messi a nil).<br />
Questa versione della ricerca lavora su una lista or<strong>di</strong>nata (<strong>di</strong>ciamo in modo crescente). Trovato un elemento il cui<br />
valore supera quello che si sta cercando non ha più senso continuare la ricerca … quelli seguenti saranno per forza<br />
tutti maggiori!<br />
procedure cerca_or<strong>di</strong>nata(p: puntatore; valore: integer; var corrente, precedente: puntatore);<br />
begin precedente:=nil; corrente:=nil;<br />
if pnil then<br />
begin<br />
corrente:=p;<br />
while (corrente^.inf
ELIMINAZIONE DI UN ELEMENTO<br />
I record con il Turbo Pascal versione 1.3 Luglio 2003<br />
Questa procedura invoca quella <strong>di</strong> ricerca per ottenere il puntatore al nodo da cancellare ed a quello che lo precede<br />
(se esiste). Sono infatti queste le informazioni necessarie per una corretta eliminazione.<br />
procedure elimina(var p: puntatore; valore: integer);<br />
var corrente,precedente: puntatore;<br />
Cancellazione in mezzo/coda alla lista.<br />
begin<br />
cerca_non_or<strong>di</strong>nata(p,valore,corrente,precedente);<br />
if correntenil then<br />
precedente corrente<br />
begin<br />
if precedentenil then<br />
(* eliminazione in mezzo o in coda *)<br />
precedente^.pun:=corrente^.pun<br />
P<br />
NIL<br />
else (* eliminazione in testa *)<br />
p:=corrente^.pun;<br />
<strong>di</strong>spose(corrente)<br />
precedente<br />
corrente<br />
end<br />
end;<br />
NIL<br />
P<br />
Cancellazione in testa alla lista.<br />
INSERIMENTO IN UNA LISTA ORDINATA<br />
Il nuovo elemento deve essere inserito mantenendo la lista or<strong>di</strong>nata.<br />
procedure inserisci_in_or<strong>di</strong>ne(var p:puntatore; valore:integer);<br />
var nuovo_nodo,precedente,corrente: puntatore;<br />
begin<br />
new(nuovo_nodo); nuovo_nodo^.inf:=valore;<br />
if p=nil then<br />
begin<br />
Inserisce nuovi valori mantenendo un<br />
nuovo_nodo^.pun:=nil; p:=nuovo_nodo<br />
or<strong>di</strong>namento crescente. La tecnica usata è la<br />
stessa del sort per inserzione (ricerca del giusto<br />
end<br />
punto <strong>di</strong> inserimento) usata per i vettori ma<br />
else<br />
grazie ai puntatori è evitato tutto il<br />
if valore
ESTRAZIONE DI UN ELEMENTO<br />
I record con il Turbo Pascal versione 1.3 Luglio 2003<br />
Estrai si <strong>di</strong>fferenzia dalla elimina perché ‘sgancia’ il nodo che contiene l’informazione cercata ma non lo <strong>di</strong>strugge:<br />
restituisce il puntatore al nodo ed è poi responsabilità del chiamante liberare la memoria.<br />
function estrai(var p: puntatore; valore: integer): puntatore;<br />
var corrente,precedente: puntatore;<br />
begin<br />
cerca_non_or<strong>di</strong>nata(p,valore,corrente,precedente);<br />
if correntenil then<br />
begin<br />
if precedentenil then (* eliminazione in mezzo o in coda *)<br />
precedente^.pun:=corrente^.pun<br />
else (* eliminazione in testa *)<br />
p:=corrente^.pun<br />
end;<br />
estrai:=corrente; (* eventualmente, nil *) end;<br />
Ora che padroneggiate (J ) i meccanismi <strong>di</strong> gestione delle liste siete in grado <strong>di</strong> capire gli svantaggi della gestione<br />
<strong>di</strong>namica della RAM (dei vantaggi abbiamo già <strong>di</strong>scusso…):<br />
• Gli algoritmi sono più <strong>di</strong>fficili ed è più facile commettere errori<br />
• Non è possibile un accesso casuale agli elementi: per usare quello in posizione ‘N’ è necessario scorrere gli<br />
‘N-1’ che precedono con un ciclo. Confrontate questa tecnica con l’imme<strong>di</strong>atezza degli array: vett[N]<br />
• A parità <strong>di</strong> tipo e <strong>di</strong> numero <strong>di</strong> elementi usa più memoria, quella usata per memorizzare i puntatori!! (ma in<br />
alcune situazioni globalmente abbiamo comunque un grosso risparmio: ricordate l’esercizio sullo SMAU e le<br />
20.000 stringhe??)<br />
Autore: Fabrizio Camuso (email: camuso@camuso.it sito web: www.camuso.it ) Pag. 8