Sei sulla pagina 1di 293

Claudio Marsan

Programmazione in Ada 95

Lavoro di maturit 2002 Liceo cantonale di Mendrisio

Dispense per la parte introduttiva del Lavoro di Maturit 2002, Liceo cantonale di Mendrisio.

ultima revisione: 11 marzo 2003

A Questo testo stato scritto dallautore con L TEX2.

Claudio Marsan Liceo cantonale di Mendrisio Via Agostino Maspoli CH6850 Mendrisio

e-mail: claudio.marsan@liceomendrisio.ch

INDICE

1 Introduzione al linguaggio Ada 95 1.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.1 Genesi . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.2 Dove viene usato Ada? . . . . . . . . . . . . . . . . . . 1.1.3 Specicazioni di Ada . . . . . . . . . . . . . . . . . . . . 1.2 Alcune caratteristiche di Ada . . . . . . . . . . . . . . . . . . . 1.3 Primi passi con Ada 95 . . . . . . . . . . . . . . . . . . . . . . 1.3.1 Il pi semplice programma in Ada 95 . . . . . . . . . . 1.3.2 Hello, World! . . . . . . . . . . . . . . . . . . . . . . . . 1.3.3 Come creare un programma scritto in Ada 95? . . . . . 1.3.4 Input e output . . . . . . . . . . . . . . . . . . . . . . . 1.3.5 Un programma un po pi complicato . . . . . . . . . . 1.3.6 Identicatori . . . . . . . . . . . . . . . . . . . . . . . . 1.3.7 Variabili e costanti . . . . . . . . . . . . . . . . . . . . . 1.3.8 Tipi di dati . . . . . . . . . . . . . . . . . . . . . . . . . 1.3.9 Il tipo INTEGER . . . . . . . . . . . . . . . . . . . . . . 1.3.10 Il tipo FLOAT . . . . . . . . . . . . . . . . . . . . . . . 1.3.11 I tipi enumerativi . . . . . . . . . . . . . . . . . . . . . . 1.3.12 Alcuni attributi utili . . . . . . . . . . . . . . . . . . . . 1.3.13 Flusso di controllo . . . . . . . . . . . . . . . . . . . . . 1.4 Istruzioni condizionali . . . . . . . . . . . . . . . . . . . . . . . 1.4.1 Listruzione if ... then ... end if; . . . . . . . . 1.4.2 Listruzione if ... then ... else ... end if; . 1.4.3 Operatori relazionali . . . . . . . . . . . . . . . . . . . . 1.4.4 Operatori logici . . . . . . . . . . . . . . . . . . . . . . . 1.4.5 Listruzione if ... then ... elsif ... end if; 1.4.6 Selezione multipla . . . . . . . . . . . . . . . . . . . . . 1.5 Istruzioni di ripetizione . . . . . . . . . . . . . . . . . . . . . . 1.5.1 Cicli semplici . . . . . . . . . . . . . . . . . . . . . . . . 1.5.2 Il ciclo for . . . . . . . . . . . . . . . . . . . . . . . . . . 1.5.3 Il ciclo while . . . . . . . . . . . . . . . . . . . . . . . . 1.5.4 Listruzione exit . . . . . . . . . . . . . . . . . . . . . . 1.5.5 Cicli nidicati . . . . . . . . . . . . . . . . . . . . . . . . 1.6 Listruzione null . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7 Listruzione goto . . . . . . . . . . . . . . . . . . . . . . . . . . 1.8 Blocchi di istruzioni . . . . . . . . . . . . . . . . . . . . . . . . iii

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1 1 1 2 2 3 4 4 5 6 7 9 10 13 14 14 23 29 32 35 35 35 36 37 38 38 40 42 43 44 45 47 49 51 52 53

iv 1.9

INDICE Qualche cenno sugli array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 63 63 64 66 70 75 76 77 78 79 82 85 88 92 95 98 101 101 103 106 107 110 110 113 116 117 119 120 121 124 126 127 128 131 132 135 135 136 137 138 146 147 148 149 151 152 153 156 157 Liceo cantonale di Mendrisio, 2002

2 Procedure e funzioni in Ada 95 2.1 Introduzione . . . . . . . . . . . . . . . . . . . . . 2.2 Funzioni matematiche predenite . . . . . . . . . 2.3 Funzioni . . . . . . . . . . . . . . . . . . . . . . . 2.4 Procedure . . . . . . . . . . . . . . . . . . . . . . 2.5 Sovraccaricare funzioni e procedure . . . . . . . . 2.6 Parametri di default . . . . . . . . . . . . . . . . 2.7 Sottoprogrammi ricorsivi . . . . . . . . . . . . . . 2.8 Esempi di algoritmi . . . . . . . . . . . . . . . . . 2.8.1 Calcolo del massimo comune divisore . . . 2.8.2 Divisione per tentativi . . . . . . . . . . . 2.8.3 Il crivello di Eratostene . . . . . . . . . . 2.8.4 Lalgoritmo di Sundaram . . . . . . . . . 2.8.5 Il metodo della fattorizzazione di Fermat 2.8.6 Il metodo di bisezione . . . . . . . . . . . 2.8.7 Il metodo di Newton . . . . . . . . . . . . 3 Tipi di dati in Ada 95 3.1 Astrazione dei dati . . . . . . . . . . 3.2 Ancora sui tipi interi . . . . . . . . . 3.2.1 Input e output di interi . . . 3.2.2 Tipi interi non segnati . . . . 3.3 Ancora sui tipi reali . . . . . . . . . 3.3.1 Numeri reali a virgola mobile 3.3.2 Attributi per i numeri reali in 3.3.3 Numeri reali a virgola ssa . 3.4 Tipi discreti . . . . . . . . . . . . . . 3.5 Sottotipi . . . . . . . . . . . . . . . . 3.6 Array . . . . . . . . . . . . . . . . . 3.6.1 Array vincolati . . . . . . . . 3.6.2 Array non vincolati . . . . . 3.6.3 Array multidimensionali . . . 3.6.4 Array di array . . . . . . . . 3.7 Record . . . . . . . . . . . . . . . . . 3.7.1 Array di record . . . . . . . . 3.7.2 Record con parte variante . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . virgola mobile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . .

4 Files in Ada 95 4.1 Il concetto di le . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Files di testo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Creazione, apertura e chiusura di un le di testo . . . . . 4.2.2 Accesso agli elementi di un le di testo . . . . . . . . . . . 4.2.3 Altre manipolazioni possibili con i les di testo . . . . . . 4.3 Files binari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Creazione, apertura e chiusura di un le binario . . . . . 4.3.2 Accesso agli elementi di un le binario sequenziale . . . . 4.3.3 Manipolazione di les binari sequenziali . . . . . . . . . . 4.3.4 Accesso agli elementi di un le binario ad accesso diretto 4.3.5 Manipolazione di les binari ad accesso diretto . . . . . . 4.4 Altre osservazioni sulluso dei les . . . . . . . . . . . . . . . . . 5 Gestione delle eccezioni in Ada 95 Claudio Marsan

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

INDICE 5.1 5.2 5.3 5.4 5.5 5.6 Introduzione . . . . . . . . . . . . . . . Eccezioni predenite . . . . . . . . . . Trattamento di uneccezione . . . . . . Dichiarazione di uneccezione . . . . . Sollevare e propagare uneccezione . . Ancora sul trattamento delle eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

v 157 157 161 165 166 166 171 171 172 173 173 176 182 183 185 192 196 197 208 210 213 221 221 228 230 233 241 241 242 243 245 246 248 253 259 259 259 261 264 265 265 267 269 270 271 287

6 Packages in Ada 95 6.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Specicazione di un package . . . . . . . . . . . . . . . . . 6.3 Lambiente di programmazione di Ada 95 . . . . . . . . . 6.4 Uso dei packages . . . . . . . . . . . . . . . . . . . . . . . 6.5 Il corpo di un package . . . . . . . . . . . . . . . . . . . . 6.6 Un package per trattare i numeri razionali . . . . . . . . . 6.6.1 Costruzione del package Long_Integer_Math_Lib 6.6.2 Il package Rational_Numbers . . . . . . . . . . . . 6.6.3 Il package Rational_Numbers_IO . . . . . . . . . . 6.6.4 Il programma di test . . . . . . . . . . . . . . . . . 6.7 I packages disponibili in Ada 95 . . . . . . . . . . . . . . 6.8 Sottoprogrammi separati e in biblioteca . . . . . . . . . . 6.9 Paradigma modulare . . . . . . . . . . . . . . . . . . . . . 6.10 Astrazione dei tipi di dati . . . . . . . . . . . . . . . . . . 7 Genericit in Ada 95 7.1 Dichiarazioni e attualizzazioni . . . . 7.2 Tipi parametri . . . . . . . . . . . . 7.3 Parametri funzionali . . . . . . . . . 7.4 Un package per i vettori dello spazio

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

8 Strutture di dati dinamiche 8.1 Introduzione . . . . . . . . . . . . . . . . . . 8.2 Dichiarazione di tipi puntatore . . . . . . . 8.3 Lallocatore new . . . . . . . . . . . . . . . . 8.4 Rappresentazione schematica dei puntatori 8.5 Accesso alle variabili puntate . . . . . . . . 8.6 Assegnazioni . . . . . . . . . . . . . . . . . 8.7 Liste concatenate . . . . . . . . . . . . . . . 8.8 Alberi . . . . . . . . . . . . . . . . . . . . . 8.8.1 Denizione . . . . . . . . . . . . . . 8.8.2 Alberi binari . . . . . . . . . . . . . 8.8.3 Ordinamento con albero binario . . 8.9 Pile di interi dinamiche . . . . . . . . . . . 9 Alcuni metodi di ordinamento 9.1 Ordinamento per selezione . . 9.2 Ordinamento a bolle . . . . . 9.3 Ordinamento per inserzione . 9.4 Quick sort . . . . . . . . . . . 9.5 Ecienza . . . . . . . . . . . Bibliograa

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

vi

INDICE

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 1 Introduzione al linguaggio Ada 95


1.1 Introduzione

Ada un linguaggio di programmazione evoluto, originariamente sponsorizzato dal DoD (Department of Defense, ossia Ministero della Difesa degli Stati Uniti ) per essere utilizzato nellarea applicativa dei sistemi embedded (un sistema detto embedded quando il computer inserito ed parte integrante in un processo operativo pi complesso come, per esempio, una fabbrica di prodotti chimici, un missile oppure un impianto di lavaggio industriale).

1.1.1

Genesi

Nel 1974 il DoD si accorse che i costi per lo sviluppo di software erano troppo elevati e la parte maggiore dei costi era dovuta ai sistemi embedded. Analizzando in profondit i linguaggi di programmazione emerse che il Cobol era il linguaggio standard per le elaborazioni gestionali e che il Fortran era lequivalente per il calcolo scientico e tecnico. Nel campo dei sistemi embedded il numero dei linguaggi utilizzati (e dei loro dialetti!) era enorme (qualche centinaio!), provocando cos elevate spese per compilatori inutili e per le attivit di addestramento e di manutenzione necessarie a causa della mancanza di uno standard. Si decise cos, in attesa di avere un unico linguaggio di programmazione, di approvare e introdurre alcuni linguaggi: CMS2Y, CMS2M, SPL/1, TACPOL, JOVIAL J3, JOVIAL J73 e, ovviamente, Cobol e Fortran. Nel 1975 il DoD decise di uniformare i linguaggi di programmazione e richiese la progettazione di un linguaggio unico che potesse essere utilizzato per applicazioni di vario tipo (scientiche, commerciali, per la gestione di sistemi in tempo reale e di sistemi di comando). Nessuno fra i linguaggi esistenti era abbastanza completo per soddisfare le richieste del DoD. Nel 1977 fu cos pubblicata la lista delle caratteristiche che il nuovo linguaggio doveva avere. I linguaggi esistenti furono divisi in tre categorie cos classicabili: inadatto: vi rientravano i linguaggi superati o destinati ad altre aree applicative, e quindi da non prendere in ulteriore considerazione (per esempio: Fortran e Coral 66); non inadatto: vi rientravano quei linguaggi che, pur non essendo considerati soddisfacenti allo stato attuale, presentavano alcune caratteristiche interessanti come possibili elementi di studio per lo sviluppo del nuovo linguaggio (vedi RTL/2 e Lis); raccomandato come punto di partenza: vi rientravano tre linguaggi: Pascal, PL1 e Algol 68, che erano considerati possibili basi di partenza per la progettazione del nuovo linguaggio. 1

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Delle 17 proposte pervenute dagli Stati Uniti e dallEuropa ne vennero scelte quattro, alle quali vennero assegnate dei colori per garantire lanonimato: CII Honeywell Bull (verde); Intermetrics (rosso); Softech (blu); SRI International (giallo). I quattro linguaggi di programmazione scelti vennero sottoposti a vari esami in tutto il mondo e nel 1978 restarono in lizza solo due candidati (il rosso e il verde). Nel 1979 il verde fu decretato vincitore del concorso. Il linguaggio prescelto era stato progettato da un gruppo di ricercatori francesi della CII Honeywell Bull, guidati da Jean Ichbiah. Il DoD annunci poi che il linguaggio prescelto si sarebbe chiamato Ada, in onore di quella che considerata il primo programmatore della storia: Augusta Ada Byron, contessa di Lovelace (18151851), glia di Lord Byron e assistente di Charles Babbage. Nel 1980, dopo vari miglioramenti in alcune parti, venne rilasciata la prima versione denitiva del linguaggio: essa fu proposta allANSI (American National Standards Institute) come standard. Per il lavoro di standardizzazione ci vollero due anni e varie modiche di piccola entit. Nel gennaio 1983 fu pubblicato il manuale di riferimento (norma ANSI) che den cos uno standard, accettato nel 1987 anche dallISO (International Standards Organization). Nel 1991 pi di 400 compilatori Ada erano stati validati, ossia avevano passato un test formato da una serie di migliaia di piccoli programmini (ACVC, Ada Compiler Validation Capability), progettati per valutare la conformit con lo standard. Ci garantisce lestrema portabilit dei programmi Ada, ossia la possibilit di compilare lo stesso programma con un altro compilatore o su unaltra macchina senza dover riscrivere o modicare del codice. Nel 1988 inizi un processo di revisione che doveva permettere di estendere il linguaggio: il progetto venne denominato Ada 9X, dove con 9X si intendeva che il processo di revisione doveva essere terminato negli anni Novanta. Nel 1995 venne cos denito un nuovo standard: Ada95 (norme ANSI e ISO).

1.1.2

Dove viene usato Ada?

Ada non rimasto connato nel DoD, ma usato anche: nei computer di bordo di quasi tutti gli aerei commerciali; in quasi tutti i sistemi di controllo del traco aereo; per il controllo di treni ad alta velocit e metropolitane; in applicazioni bancarie per il trasferimento di fondi; per il controllo di satelliti per la comunicazione e per la navigazione; in robotica industriale, elettronica medica, telecomunicazioni, . . . Si pu quindi vedere dagli esempi citati che Ada un linguaggio utilizzato soprattutto per lo sviluppo di applicazioni molto importanti, complesse e nelle quali richiesto un alto grado di sicurezza.

1.1.3

Specicazioni di Ada

Le specicazioni di Ada furono ssate per: rendere i programmi leggibili e adabili; Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.2. ALCUNE CARATTERISTICHE DI ADA facilitare il loro sviluppo e la loro manutenzione; fare della programmazione unattivit umana; rendere i programmi ecaci; consentire una grande portabilit dei programmi.

Queste specicazioni furono ssate nello standard del 1983 (si parla cos di Ada 83). Questo standard denisce: ci che permesso in Ada; ci che non permesso in Ada; ci che lasciato libero al compilatore, ssando comunque dei limiti a tale libert. Ogni compilatore deve essere sottoposto ad un processo di validazione, processo che ha lintenzione di controllare che: i programmi Ada siano tradotti ed eseguiti correttamente; i programmi non conformi allo standard Ada siano riutati; gli scarti di un programma rispetto allo standard facciano parte degli scarti autorizzati e siano realizzati nella maniera descritta nello standard; le unit predenite (input/output, . . . ) siano fornite e siano conformi allo standard. Lo standard assicura cos che leetto di un programma scritto in Ada sia noto e identico (entro gli scarti permessi) per ogni implementazione del linguaggio! Un compilatore pu chiamarsi compilatore Ada solo dopo aver passato con successo il processo di validazione. La qualica di compilatore Ada vale solo per un anno; dopo un anno richiesta una nuova validazione!

1.2

Alcune caratteristiche di Ada

Ada un linguaggio algoritmico moderno, utilizzabile per applicazioni in campi diversi. Esso propone le facilitazioni presenti in linguaggi classici come il Pascal, ma anche altre pi speciche a certi campi particolari: le istruzioni di controllo: if, case, while, for, loop, exit, return, goto; richiamo di procedure; richiesta di rendezvous per la sincronizzazione dei task. le strutture che consentono la modularit: blocchi; procedure e funzioni; package; procedure, funzioni e package generici. le strutture concorrenti : processi chiamati task ; rendezvous tra due task. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

4 la gestione delle eccezioni;

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

lorganizzazione dei dati per tipo: tipi scalari: interi, reali, booleani, caratteri, enumerativi; tipi strutturati: array, stringhe, record ; puntatori; tipi privati. il concetto di sottotipo; il calcolo numerico. Ada propone il concetto di unit di compilazione. Ogni unit pu essere compilata separatamente dalle altre a condizione che tutte quelle che usa siano gi state compilate; conseguentemente la compilazione di un progetto deve farsi secondo un certo ordine. Sono unit di compilazione: le dichiarazioni di procedure e funzioni; il corpo di procedure e funzioni; la dichiarazione di package; il corpo dei package; le dichiarazioni di unit generiche; le istanziazioni di unit generiche; il corpo dei task. La biblioteca o libreria Ada contiene tutte le unit gi compilate ad un dato momento e, in ogni caso, le unit predenite. La biblioteca Ada pu avere anche delle sottolibrerie (struttura gerarchica ad albero). Ogni implementazione seria di Ada permette la manipolazione della biblioteca (inserimento, sostituzione, eliminazione di unit) tale da garantire una ricompilazione automatica delle unit non aggiornate e di quelle da loro dipendenti. Da notare che le dipendenze non si dichiarano in modo esplicito sulla riga di comando del compilatore o del linker, ma sono ricavate dalle istruzioni with delle singole unit.

1.3
1.3.1

Primi passi con Ada 95


Il pi semplice programma in Ada 95

Quello che segue il pi semplice programma che si pu scrivere in Ada 95: cos semplice che non fa nulla! procedure Nulla is begin null; end Nulla; Nel prossimo paragrafo vedremo un programma in Ada 95 che fa qualcosa pi di nulla! Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95

1.3.2

Hello, World!

Tradizionalmente, quando si inizia a studiare un linguaggio di programmazione, si inizia a scrivere un programma che visualizza sullo schermo la scritta Hello, World!. Ecco la versione in Ada 95 di tale programma (nota: i numeri di linea servono solo come riferimento, non fanno cio parte del programma e dunque non vanno scritti): 01 02 03 04 05 06 07 08 09 10 11 12 -----Nome del file: HELLO.ADB Autore: Claudio Marsan Data dellultima modifica: 9 gennaio 2002 Scopo: scrive sul video la frase "Hello, world!" Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Hello is begin Ada.Text_IO.Put(Item => "Hello, World!"); end Hello;

Analizziamo brevemente il programma. Le linee 0105 iniziano con due trattini (): esse sono trattate come linee di commento. possibile usare i commenti anche dopo unistruzione. Un commento si estende no alla ne di una riga. Le linee 06 e 08 sono bianche: il compilatore Ada permettere luso di linee bianche per aumentare la leggibilit di un programma. La linea 07 la linea dimportazione: essa, in questo caso, serve per richiamare il package Ada.Text_IO che contiene le procedure necessarie per linput e loutput di caratteri e stringhe (tale package fornito con il compilatore Ada). La linea 09 la linea di intestazione del programma: il nome che appare dopo la parola riservata procedure importante perch quello che deve essere specicato in fase di link (consiglio: fare in modo che tale nome coincida con il nome del le, per non avere inutili complicazioni). La linea 10 indica linizio del corpo della procedura che contiene le istruzioni eseguibili del programma. La linea 11 listruzione necessaria per visualizzare sullo schermo Hello, World!. Il nome Ada.Text_IO.Put da intendere nel modo seguente: il comando Put che serve per visualizzare una stringa di caratteri da prendere dal package Ada.Text_IO. possibile fare la stessa cosa senza dover continuamente premettere Ada.Text_IO. alle procedure di input/output, ma rinunceremo a questa opportunit. La linea 12 termina il programma. Possiamo notare che ogni istruzione termina con un punto e virgola e che dopo la linea di intestazione e dopo begin non bisogna mettere il punto e virgola (esse non sono infatti considerate istruzioni!). Osservazione 1.3.1 una buona abitudine quella di usare i commenti; in particolare consigliabile inserire il nome del le che contiene il programma, il nome dellautore, cosa fa il programma, la data dellultima modica e con quale sistema operativo il programma stato testato. Osservazione 1.3.2 Tutti i package standard forniti con il compilatore Ada 95 iniziano il loro nome con Ada seguito da un punto. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

1.3.3

Come creare un programma scritto in Ada 95?

Dopo aver pensato e progettato un programma, bisogna passare alla sua realizzazione pratica, ossia bisogna iniziare il lavoro al computer. Generalmente (e ci vale per ogni linguaggio di programmazione) si distinguono le fasi seguenti: 1. La fase di edizione: si usa un programma chiamato editor (potrebbe essere, in ambiente MSWindows, Notepad o PFE oppure un qualsiasi programma di elaborazione testi che permetta di salvare in formato ASCII). 2. La fase di compilazione: si usa un programma, chiamato compiler (compilatore) che, letto un le ASCII, genera codice oggetto (ossia del codice comprensibile alla CPU) oppure, se vi sono errori di sintassi, genera dei messaggi derrore. Se il compilatore segnala errori bisogna tornare alla fase di edizione (nel peggiore dei casi: alla progettazione!), correggere gli errori, ricompilare, . . . 3. La fase di collegamento: si usa un programma, detto linker, che collega il le contenente il codice oggetto generato dal compilatore con eventuali librerie o altri programmi (potrebbero esserci errori anche in questa fase). Se tutto funziona per il verso giusto il linker genera un le eseguibile (in ambiente MSWindows un le con lestensione .exe). 4. La fase di esecuzione: per eseguire il programma basta, solitamente, scrivere il nome del le eseguibile creato dal linker nella linea di comando di una nestra di shell. In progetti complicati c anche la fase di debugging: per essa si usa un particolare programma, detto debugger, che permette di scoprire dove ci sono errori logici nel programma o nei suoi algoritmi, dove il programma non eciente, dove il programma perde molto tempo in esecuzione, ... Tutte le operazioni appena descritte vanno eseguite da una linea di comando. Negli ultimi anni tuttavia si sono diusi gli IDE (Integrated Development Environment, ossia: ambiente di sviluppo integrato) che permettono di aumentare la produttivit del programmatore ma anche di fargli prendere delle brutte abitudini (per esempio: prova, se non va bene riprova!). In un ambiente di sviluppo integrato abbiamo, nello stesso programma, un editor (molto spesso perno sensibile al linguaggio di programmazione, ossia: riconosce le parole riservate, le strutture del linguaggio, . . . ), il compilatore, il linker e il debugger, nonch altri strumenti (per esempio una guida in linea sul linguaggio): cos possibile eseguire tutte le fasi descritte prima senza lasciare mai lambiente di sviluppo integrato, magari cliccando su delle icone (solitamente gli IDE hanno uninterfaccia graca e consentono luso del mouse). Noi programmeremo in Ada 95 in ambiente MSWindows, usando il compilatore della GNAT (versione 3.13p). Ad esso abbinato un ambiente di sviluppo integrato, AdaGIDE, molto facile e intuitivo da usare. Esempio 1.3.1 Ammettiamo di voler realizzare il programma Hello, descritto nel paragrafo precedente, in ambiente MSWindows. Ecco le operazioni da eseguire: 1. lanciare lambiente di sviluppo integrato AdaGIDE; 2. digitare il codice del programma (in caso di errori di battitura si pu correggere usando le stesse combinazioni di tasti dei pi comuni programmi dellambiente MSWindows); 3. salvare il le con il nome HELLO.ADB; 4. compilare il le cliccando sullapposita icona (verranno creati il le di testo HELLO.ALI e il le oggetto HELLO.O); 5. correggere eventuali errori nch il compilatore non segnala pi errori; 6. lanciare il linker cliccando sullapposita icona (verr creato il le HELLO.EXE); Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95 7. eseguire il programma cliccando sullapposita icona.

Visto che AdaGIDE crea diversi le conveniente creare un direttorio per ogni programma: sar pi facile mantenere pulito il proprio spazio disco. Osservazione 1.3.3 Come accennato in precedenza, il nome del le che contiene il programma deve avere lo stesso nome del programma; il nome del le sar poi completato dallestensione .ADB (Ada body) (altri compilatori richiedono lestensione .ADA). Con il compilatore GNAT ammessa anche lestensione .ADS (Ada specication), ma tratteremo questo pi avanti. Esercizio 1.3.1 Scrivere, compilare, correggere ed eseguire il programma Hello.

1.3.4

Input e output

Il package Ada.Text_IO contiene le procedure necessarie per linput e loutput di singoli caratteri (CHARACTER) e stringhe (STRING, una stringa una sequenza di caratteri). Alcune procedure del package sono (nota: la sintassi sar pi chiara in seguito, quando avremo studiato le procedure): procedure Put(Item : in Character); (visualizza un carattere sullo schermo, a partire dalla posizione corrente del cursore) procedure Put(Item : in String); (visualizza una stringa sullo schermo, a partire dalla posizione corrente del cursore) procedure Put_Line(Item : in String); (visualizza una stringa sullo schermo, a partire dalla posizione corrente del cursore, e sposta il cursore allinizio di una nuova linea) procedure New_Line(Spacing : in Positive_Count := 1); (scrive una riga vuota oppure il numero di righe vuote indicato) procedure Set_Line(To : procedure Set_Col(To : cata) in Positive_Count); (posiziona il cursore alla riga indicata) in Positive_Count); (posiziona il cursore alla colonna indi-

procedure Get(Item : out Character); (legge un carattere dato da tastiera e lo memorizza nella variabile Item) procedure Get(Item : out String); (legge una stringa di caratteri data da tastiera e la memorizza nella variabile Item) procedure Get_Line(Item : out String; Last : out Natural); (legge la sequenza di caratteri digitati prima di aver digitato il tasto <Enter>, memorizza tale sequenza nella variabile Item e memorizza inoltre il numero di caratteri letti nella variabile Last) procedure Skip_Line(Spacing : in Positive_Count := 1); (salta la lettura di una intera riga oppure il numero di intere righe indicato) Per usare una delle procedure elencate sopra in un programma bisogna scrivere il nome della procedura, preceduto da Ada.Text_IO., e seguito dagli eventuali argomenti, scritti tra parentesi. Esempio 1.3.2 Mediante listruzione Ada.Text_IO.Get(Item => ch); il prossimo carattere digitato da tastiera sar letto nella variabile ch di tipo CHARACTER. Uno spazio bianco (blank ) conta come carattere, <Enter> invece no! Esempio 1.3.3 Nel frammento di programma seguente si pu notare luso di alcune procedure del package Ada.Text_IO. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 ... Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Ciao"); Ada.Text_IO.New_Line(Spacing => 3); ...

Un carattere va racchiuso tra apici quando argomento di una procedura o appare nella parte destra di unistruzione di assegnazione; le stringhe sono sequenze di caratteri (lettere, numeri, caratteri speciali) e vanno racchiuse tra virgolette quando sono argomento di una procedura o appaiono nella parte destra di unistruzione di assegnazione. Caratteri e stringhe saranno trattati pi avanti. Per evitare di scrivere in continuazione Ada.Text_IO. si potrebbe fare uso della clausola use, scrivendo, dopo with Ada.Text_IO;, la linea use Ada.Text_IO; Come detto in precedenza rinunciamo a tale opportunit per una maggiore chiarezza e qualicheremo ogni volta le procedure e le funzioni premettendo il nome del package da cui esse provengono! Molti programmatori Ada esperti ritengono pi vantaggioso qualicare tutti i riferimenti piuttosto che usare la clausola use. Volendo essere molto spicci il programma visto nel paragrafo precedente potrebbe addirittura essere riscritto nel modo seguente: with Ada.Text_IO; use Ada.Text_IO; procedure Hello is begin Put("Hello, World!"); end Hello; oppure, ancora in forma pi compatta: with Ada.Text_IO; use Ada.Text_IO; procedure Hello is begin Put("Hello, World!"); end Hello; Naturalmente siamo scandalizzati da un simile modo di procedere, poich non fa parte del bagaglio culturale del programmatore Ada il motto Mai scrivere due righe di codice quando se ne pu scrivere una sola! Esercizio 1.3.2 Scrivere un programma (due versioni: la prima non fa uso della clausola use, la seconda s) che visualizzi sullo schermo il vostro nome, cognome e indirizzo email, uno per riga, iniziando dalla decima colonna della quinta riga e mantenendo lallineamento a sinistra. Alla ne lasciare 5 righe vuote. Finora abbiamo visto esempi di output. Se vogliamo dare un input dobbiamo avere una variabile, di tipo adatto, nella quale memorizzare i dati che inseriamo. Vediamo un esempio nel quale si chiede di inserire un carattere. -----Nome del file: CARATTERE.ADB Autore: Claudio Marsan Data dellultima modifica: 15 gennaio 2002 Scopo: input e output di un carattere Testato con: Gnat 3.13p su Windows 2000

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95 with Ada.Text_IO; procedure Carattere is ch : CHARACTER; -- ch una variabile di tipo carattere begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Dare un carattere: "); Ada.Text_IO.Get(Item => ch); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il carattere digitato e: "); Ada.Text_IO.Put(Item => ch); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put_Line(Item => "Adesso scrivo una ""x"""); Ada.Text_IO.Put(Item => x); Ada.Text_IO.New_Line(Spacing => 2); end Carattere; Ecco loutput del programma:

Dare un carattere: A Il carattere digitato e: A Adesso scrivo una "x" x Osservazione 1.3.4 Per visualizzare il carattere in una stringa necessario scriverlo due volte consecutivamente.

1.3.5

Un programma un po pi complicato

Il seguente programma legge una misura di lunghezza in pollici e la trasforma in centimetri secondo lequivalenza 1 = 2.54 cm (anche in questo programma i numeri di riga servono solo per il successivo commento al programma e non devono essere digitate!): 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 -----Nome del file: POLLICI_CM.ADB Autore: Claudio Marsan Data dellultima modifica: 15 gennaio 2002 Scopo: converte pollici in centimetri Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; with Ada.Float_Text_IO; procedure Pollici_cm is -------------------------------------------- Dichiarazione di variabili e costanti -------------------------------------------cm_per_pollice : CONSTANT FLOAT := 2.54; pollici : FLOAT; Claudio Marsan

Liceo cantonale di Mendrisio, 2002

10

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

17 centimetri : FLOAT; 18 19 begin 20 ----------------------------------------21 -- Lettura della misura da trasformare -22 ----------------------------------------23 Ada.Text_IO.New_Line; 24 Ada.Text_IO.Put(Item => "Dare una misura in pollici: "); 25 Ada.Float_Text_IO.Get(Item => pollici); 26 27 -------------------------------------------28 -- Trasformazione da pollici a centimetri -29 -------------------------------------------30 centimetri := cm_per_pollice * pollici; 31 32 ----------------------------------33 -- Visualizzazione del risultato -34 ----------------------------------35 Ada.Text_IO.New_Line; 36 Ada.Text_IO.Put(Item => "Tale misura corrisponde a "); 37 Ada.Float_Text_IO.Put(Item => centimetri); 38 Ada.Text_IO.Put(Item => " centimetri."); 39 Ada.Text_IO.New_Line; 40 end Pollici_cm; Grazie alla riga 07 possiamo usare le procedure di input/output per caratteri e stringhe; grazie alla riga 08 possiamo usare le procedure di input/output per i numeri reali (in Ada i numeri reali si chiamano FLOAT). La riga 15 contiene la denizione della costante cm_per_pollice di tipo FLOAT: per il momento possiamo ritenere cm_per_pollice come un oggetto che vale 2.54 e che non pu modicare il suo valore nel programma. Le righe 16 e 17 deniscono due variabili di tipo FLOAT: esse servono per contenere dei valori reali e possono modicare il loro contenuto nel programma. Con la riga 25 si memorizza nella variabile pollici il numero reale digitato dallutente. La riga 30 contiene unistruzione di assegnazione: alla variabile centimetri viene assegnato il valore del prodotto della costante cm_per_pollice con la variabile pollici. Il simbolo := loperatore di assegnazione. Con la riga 37 si pu scrivere il contenuto della variabile centimetri sullo schermo. Da notare che la procedura si chiama Put, come quella usata per stampare un carattere o una stringa; tuttavia essa proviene dal package Ada.Float_Text_IO e non dal package Ada.Text_IO. Ecco un esempio duso del programma: Dare una misura in pollici: 10.0 Tale misura corrisponde a 2.54000E+01 centimetri.

Probabilmente ci saremmo aspettati la visualizzazione del risultato come 25.4; vedremo pi avanti perch non cos!

1.3.6

Identicatori

Un identicatore un nome usato per riferirsi ad ogni oggetto in Ada e deve soddisfare alcune rigide regole: Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95 1. un identicatore deve iniziare con una lettera;

11

2. dopo la lettera iniziale lidenticatore pu essere composto da lettere, cifre e caratteri di sottolineatura, nel numero desiderato, a patto che non ci siano due caratteri di sottolineatura consecutivi e che lultimo carattere non sia un carattere di sottolineatura; 3. Ada case insensitive, ossia non distingue tra lettere maiuscole e lettere minuscole; 4. non c limite alla lunghezza di un identicatore, ma ogni identicatore deve poter essere scritto su ununica riga (nota: la lunghezza minima di una riga in Ada di 200 caratteri); 5. spazi bianchi e caratteri speciali non sono ammessi come parte di un identicatore. Esercizio 1.3.3 Dire quali fra i seguenti identicatori valido in un programma Ada: x y__1 metri-sec X1 metri sec mETRiSec 1X metri_sec _metri_sec X_1 metri/sec x_Y_1_ MetriSec

Gli identicatori vanno scelti in modo sensato: essi dovrebbero avere un nome, possibilmente non troppo lungo (gli identicatori si scrivono pi volte allinterno di un programma!), che ricordi lo scopo o lazione dellidenticatore. Attenzione alle 69 parole riservate in Ada 95 poich esse non possono essere usate come nomi di identicatori: abort abs abstract accept access aliased all and array at begin body case constant declare delay delta digits do else elsif end entry exception exit for function generic goto if in is new not null return reverse select separate subtype tagged task terminate then type

of or others out package pragma private procedure protected raise range record rem renames requeue

until use when while with xor

limited loop mod

Solitamente si seguono le seguenti convenzioni: le parole riservate sono scritte in minuscolo; le variabili sono scritte con liniziale di ogni parola di cui sono composte in maiuscolo e tutte le altre lettere in minuscolo; i tipi di dati sono scritti completamente in maiuscolo; le costanti sono scritte completamente in maiuscolo; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

12

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 i valori di enumerazione sono scritti completamente in maiuscolo; gli attributi sono scritti completamente in maiuscolo; i nomi di procedure sono scritti con liniziale di ogni parola di cui sono composte in maiuscolo e tutte le altre lettere in minuscolo; i nomi di funzioni sono scritti con liniziale di ogni parola di cui sono composte in maiuscolo e tutte le altre lettere in minuscolo; i nomi di package sono scritti con liniziale di ogni parola di cui sono composte in maiuscolo e tutte le altre lettere in minuscolo; i nomi di librerie sono scritti con liniziale di ogni parola di cui sono composte in maiuscolo e tutte le altre lettere in minuscolo

Esercizio 1.3.4 Cosa verr scritto sullo schermo eseguendo il programma seguente? -----Nome del file: COSA_FA.ADB Autore: Claudio Marsan Data dellultima modifica: 21 gennaio 2002 Scopo: esercizio sulloutput di un programma Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Cosa_fa is c, ch : CHARACTER; begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put_Line(Item => "Dare un carattere: "); Ada.Text_IO.Get(Item => c); Ada.Text_IO.New_Line; Ada.Text_IO.Set_Col(To => 5); Ada.Text_IO.Put(Item => "Dare un altro carattere: "); Ada.Text_IO.Get(Item => ch); Ada.Text_IO.Set_Line(To => 10); Ada.Text_IO.Set_Col(To => 12); Ada.Text_IO.Put(Item => c); Ada.Text_IO.Set_Col(To => 15); Ada.Text_IO.Put(Item => c); Ada.Text_IO.Set_Col(To => 18); Ada.Text_IO.Put(Item => C); Ada.Text_IO.Set_Col(To => 21); Ada.Text_IO.Put(Item => "C"); Ada.Text_IO.Set_Col(To => 24); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => ch); Ada.Text_IO.Put_Line(Item => "ch"); Ada.Text_IO.New_Line; end Cosa_fa; Vericare poi con il calcolatore la vostra previsione. Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95

13

1.3.7

Variabili e costanti

In Ada variabili e costanti sono detti oggetti. Ogni oggetto ha: un nome valido (vedi regole sugli identicatori); un valore. Possiamo immaginare una variabile come un contenitore: sul contenitore c unetichetta (nome della variabile) che permette di distinguere i vari contenitori e al suo interno c il contenuto (il valore della variabile). Prima di poter usare una variabile o una costante in un programma bisogna dichiararle, dopo lintestazione della procedura (nella cosiddetta parte dichiarativa). La dichiarazione di una variabile ha la forma seguente: nome_variabile : TIPO_VARIABILE; oppure, se abbiamo pi variabili dello stesso tipo: nome_var_1, nome_var_2, ... : TIPO_VARIABILE; Per esempio: ... 01 ch : CHARACTER; 02 lato, area, perimetro : FLOAT; 03 numero_avv_postale : INTEGER; ... Nella riga 01 abbiamo denito la variabile ch di tipo CHARACTER; nella riga 02 abbiamo denito le variabili lato, area e perimetro, tutte di tipo FLOAT (numeri reali); nella riga 03 abbiamo denito la variabile numero_avv_postale di tipo INTEGER (numero intero). Allinizio, quando si dichiara una variabile, essa ha un valore indenito (alcuni compilatori tuttavia pongono le variabili numeriche uguali a 0); si resta in questo stato no a che non le si d un valore allinterno del programma. In Ada tuttavia possibile assegnare, in fase di dichiarazione, un valore iniziale alle variabili. La sintassi la seguente: nome_variabile : TIPO_VARIABILE := espressione; oppure, se abbiamo pi variabili dello stesso tipo alle quali bisogna assegnare lo stesso valore iniziale: nome_var_1, nome_var_2, ... : TIPO_VARIABILE := espressione; Per esempio: ... 01 lato : FLOAT 02 perimetro : FLOAT 03 n, m : INTEGER ... := 12.3; := 4*lato; := 0;

Nella riga 01 abbiamo denito la variabile lato di tipo FLOAT e ad essa abbiamo assegnato il valore iniziale 12.3; nella riga 02 abbiamo denito la variabile perimetro e ad essa abbiamo assegnato il valore iniziale 4*lato; nella riga 03 abbiamo denito le variabili n e m di tipo INTEGER e le abbiamo inizializzate con il valore 0. Da notare che lordine della dichiarazione delle variabili importante: nellesempio sopra necessario denire prima lato di perimetro! Una costante pu pure essere vista come un contenitore con unetichetta e un contenuto: rispetto ad una variabile per il contenuto deve essere specicato nella dichiarazione e poi non pu pi essere modicato (possiamo pensare che il contenitore viene sigillato ermeticamente). Una costante si dichiara nel modo seguente: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

14

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 nome_costante : CONSTANT TIPO_COSTANTE := espressione;

oppure nome_costante : CONSTANT := espressione_numerica; nel caso in cui si voglia dichiarare una costante numerica che sia utilizzabile poi in associazione con vari tipi numerici (in tal caso, a dipendenza dellinizializzazione, si dice che la costante di tipo universal_integer oppure universal_real). Per esempio: ... 01 NAP_Mendrisio : CONSTANT INTEGER := 6850; 02 PI_greco : CONSTANT := 3.1415926; 03 MB : CONSTANT := 1048576; ... Nella riga 01 abbiamo denito la costante NAP_Mendrisio di tipo INTEGER con il valore 6850; nella riga 02 abbiamo denito la costante PI_greco di tipo universal_real con il valore 3.1415926; nella riga 03 abbiamo denito la variabile MB di tipo universal_integer con il valore 1048576.

1.3.8

Tipi di dati

Il compito di un programma di manipolare oggetti dierenti. Spesso un oggetto rappresenta, in un programma, qualcosa che avviene nel mondo reale. Oggetti dierenti hanno propriet dierenti: in Ada si dice che tali oggetti sono di tipo diverso. Un tipo in Ada caratterizzato da: i valori che possono assumere gli oggetti appartenenti al tipo; le operazioni che si possono compiere con gli oggetti appartenenti al tipo. Per esempio per il tipo FLOAT i possibili valori sono, in principio, tutti i numeri reali e le operazioni sono le comuni operazioni matematiche come laddizione e la moltiplicazione. Osservazione 1.3.5 Bisogna prestare molta attenzione al fatto seguente: Ada un linguaggio che controlla molto attentamente il tipo dei dierenti oggetti. Oggetti di un certo tipo possono assumere solo valori accettabili per quel tipo (per esempio se lato una variabile di tipo FLOAT ad essa non pu essere assegnato un valore intero). Se da una parte questa caratteristica di Ada pu sembrare molto pedante, dallaltra parte aiuta a costruire programmi migliori e pi adabili. Ada un linguaggio fortemente tipizzato: oltre ai tipi standard predeniti (essi sono deniti nel package Standard presente in tutte le implementazioni di Ada e al quale si accede direttamente, senza dover far uso della clausola with, il programmatore pu denire dei propri tipi, anche molto complessi, per descrivere gli oggetti del suo programma.

1.3.9

Il tipo INTEGER

Il tipo INTEGER rappresenta il concetto matematico di numero intero. Dalla matematica noto che linsieme Z := {. . . , 3, 2, 1, 0, 1, 2, 3, . . .} dei numeri interi innito ma, essendo la memoria di un computer nita, potremo rappresentare solo un sottoinsieme nito di Z. Tale sottoinsieme dipende dal numero di bit che una macchina usa per memorizzare un intero: macchine diverse possono usare numeri di bit diversi (16, 32, 64). Per sapere i valori minimo e massimo degli interi si pu ricorrere agli attributi FIRST e, rispettivamente, LAST, che si usano come nellesempio seguente. Esempio 1.3.4 Questo programma visualizza sullo schermo il pi piccolo e il pi grande INTEGER che sa manipolare il vostro compilatore Ada 95: Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95 ------Nome del file: LIMITI_INTEGER.ADB Autore: Claudio Marsan Data dellultima modifica: 21 gennaio 2002 Scopo: scrive sul video il pi piccolo e il pi grande fra gli interi rappresentabili Testato con: Gnat 3.13p su Windows 2000

15

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Limiti_Integer is begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu piccolo INTEGER e: "); Ada.Integer_Text_IO.Put(Item => INTEGERFIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu grande INTEGER e: "); Ada.Integer_Text_IO.Put(Item => INTEGERLAST); Ada.Text_IO.New_Line; end Limiti_Integer; Louput che si ottiene usando il compilatore GNAT 3.13p su Windows 2000 il seguente: Il piu piccolo INTEGER e: -2147483648 Il piu grande INTEGER e: 2147483647 Da notare che 2147483647 = 231 1, ossia un intero viene rappresentato con 32 bit (1 bit serve per il segno). Oltre al tipo INTEGER sono dichiarati, nel package Standard, anche i seguenti tipi di dati interi (loccupazione in bit dipende dal compilatore e non prestabilita da nessuna delle norme richieste per la validazione di un compilatore Ada 95; i dati sono riferiti al compilatore GNAT 3.13p per Windows 95/NT/2000): SHORT_SHORT_INTEGER, 8 bit con segno; SHORT_INTEGER, 16 bit con segno; LONG_INTEGER, 32 bit con segno; LONG_LONG_INTEGER, 64 bit con segno. Gli attributi FIRST e LAST possono essere utilizzati anche per stabilire i limiti di questi tipi. Esempio 1.3.5 Questo programma visualizza sullo schermo il pi piccolo e il pi grande valore per i tipi interi predeniti nel package Standard (riferiti al vostro compilatore Ada 95): ------Nome del file: LIMITI_INTERI_PREDEFINITI.ADB Autore: Claudio Marsan Data dellultima modifica: 21 gennaio 2002 Scopo: scrive sul video il pi piccolo e il pi grande fra gli interi rappresentabili, per ognuno dei tipi interi predefiniti Testato con: Gnat 3.13p su Windows 2000

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

16

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

with Ada.Text_IO, Ada.Short_Short_Integer_Text_IO, Ada.Short_Integer_Text_IO, Ada.Integer_Text_IO, Ada.Long_Integer_Text_IO, Ada.Long_Long_Integer_Text_IO; procedure Limiti_Interi_Predefiniti is begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu piccolo SHORT_SHORT_INTEGER e: "); Ada.Short_Short_Integer_Text_IO.Put(Item => SHORT_SHORT_INTEGERFIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu grande SHORT_SHORT_INTEGER e: "); Ada.Short_Short_Integer_Text_IO.Put(Item => SHORT_SHORT_INTEGERLAST); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il piu piccolo SHORT_INTEGER e: "); Ada.Short_Integer_Text_IO.Put(Item => SHORT_INTEGERFIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu grande SHORT_INTEGER e: "); Ada.Short_Integer_Text_IO.Put(Item => SHORT_INTEGERLAST); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il piu piccolo INTEGER e: "); Ada.Integer_Text_IO.Put(Item => INTEGERFIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu grande INTEGER e: "); Ada.Integer_Text_IO.Put(Item => INTEGERLAST); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il piu piccolo LONG_INTEGER e: "); Ada.Long_Integer_Text_IO.Put(Item => LONG_INTEGERFIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu grande LONG_INTEGER e: "); Ada.Long_Integer_Text_IO.Put(Item => LONG_INTEGERLAST); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il piu piccolo LONG_LONG_INTEGER e: "); Ada.Long_Long_Integer_Text_IO.Put(Item => LONG_LONG_INTEGERFIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu grande LONG_LONG_INTEGER e: "); Ada.Long_Long_Integer_Text_IO.Put(Item => LONG_LONG_INTEGERLAST); Ada.Text_IO.New_Line; end Limiti_Interi_Predefiniti; Ecco loutput del programma: Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95

17

Il piu piccolo SHORT_SHORT_INTEGER e: -128 Il piu grande SHORT_SHORT_INTEGER e: 127 Il piu piccolo SHORT_INTEGER e: -32768 Il piu grande SHORT_INTEGER e: 32767 Il piu piccolo INTEGER e: -2147483648 Il piu grande INTEGER e: 2147483647 Il piu piccolo LONG_INTEGER e: -2147483648 Il piu grande LONG_INTEGER e: 2147483647 Il piu piccolo LONG_LONG_INTEGER e: -9223372036854775808 Il piu grande LONG_LONG_INTEGER e: 9223372036854775807

Sono permesse le seguenti operazioni per variabili dello stesso tipo intero (il risultato sempre un intero dello stesso tipo degli operandi): addizione: + sottrazione: moltiplicazione: * elevazione a potenza (attenzione: lesponente deve essere un numero naturale!): ** divisione: / resto: rem modulo: mod valore assoluto: abs Come in diversi linguaggi di programmazione la divisione presenta qualche problema: con a/b si ottiene la parte intera della divisione (per esempio: 15/7 = 2); con a rem b si ottiene il resto della divisione di a con b e tale resto ha sempre il segno di a; con a mod b si ottiene il resto della divisione di a con b e tale resto ha sempre il segno di b; vale: a = (a/b) * b + (a rem b); vale: a = b * n + (a mod b), dove n un numero intero. Esempio 1.3.6 Siano n e m variabili di tipo INTEGER. Allora dopo le istruzioni n := -15 rem 7; m := 15 rem (-7); il valore di n sar -1 (infatti: 15 = 27+(1)) e il valore di m sar 1 (infatti: 15 = 2(7)+1). Esempio 1.3.7 Siano n e m variabili di tipo INTEGER. Allora dopo le istruzioni n := 15 mod (-7); m := -15 mod 7; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

18

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

il valore di n sar -6 (infatti: 15 = 3(7)+(6)) e il valore di m sar 6 (infatti: 15 = 37+6). Esempio 1.3.8 Con il seguente programma possiamo controllare i risultati delle operazioni di divisione: -----Nome del file: DIVISIONI.ADB Autore: Claudio Marsan Data dellultima modifica: 21 gennaio 2002 Scopo: operazioni di divisione Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Divisioni is a, b quoto modulo resto : : : : INTEGER; INTEGER; INTEGER; INTEGER;

begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dai un intero a: "); Ada.Integer_Text_IO.Get(Item => a); Ada.Text_IO.Put(Item => "Dai un altro intero b: "); Ada.Integer_Text_IO.Get(Item => b); quoto := a / b; resto := a rem b; modulo := a mod b; Ada.Text_IO.Put(Item => "a/b = "); Ada.Integer_Text_IO.Put(Item => quoto); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "a mod b = "); Ada.Integer_Text_IO.Put(Item => modulo); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "a rem b = "); Ada.Integer_Text_IO.Put(Item => resto); Ada.Text_IO.New_Line; end Divisioni; Ecco un esempio di output del programma:

Dai un intero a: 5 Dai un altro intero b: 2 a/b = 2 a mod b = 1 a rem b = 1 Un altro esempio di output dello stesso programma:

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95 Dai un intero a: 7 Dai un altro intero b: -2 a/b = -3 a mod b = -1 a rem b = 1

19

Osservazione 1.3.6 Loutput del programma precedente mal formattato, ossia ci sono troppi spazi tra il carattere = e gli interi che vengono visualizzati. Infatti, per default, gli interi vengono visualizzati occupando sempre lo spazio necessario per lintero che occupa pi spazio (nel caso degli INTEGER: 11 spazi) e premettendo degli spazi bianchi se necessario. Il programmatore pu scegliere di utilizzare, nella procedura Put, anche largomento opzionale Width che permette di formattare loutput di un intero indicando il numero di spazi che deve occupare. Per esempio con la riga seguente Ada.Integer_Text_IO.Put(Item => n, Width => 0); il valore della variabile n, di tipo INTEGER, verr visualizzato senza premettere alcuno spazio bianco. Esempio 1.3.9 Il programma seguente visualizza loutput di una variabile di tipo INTEGER secondo varie formattazioni: -----Nome del file: OUTPUT_INTEGER_FORMATTATO.ADB Autore: Claudio Marsan Data dellultima modifica: 21 gennaio 2002 Scopo: mostra la formattazione delloutput di INTEGER Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Output_Integer_Formattato is n : INTEGER; begin Ada.Text_IO.New_Line(Spacing Ada.Text_IO.Put(Item => "Dai Ada.Integer_Text_IO.Get(Item Ada.Text_IO.New_Line(Spacing

=> un => =>

2); numero di tipo INTEGER: "); n); 2);

Ada.Text_IO.Put(Item => "Hai digitato: "); Ada.Integer_Text_IO.Put(Item => n, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Hai digitato: "); Ada.Integer_Text_IO.Put(Item => n, Width => 1); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Hai digitato: "); Ada.Integer_Text_IO.Put(Item => n, Width => 2); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Hai digitato: "); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

20

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 Ada.Integer_Text_IO.Put(Item => n, Width => 3); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Hai digitato: "); Ada.Integer_Text_IO.Put(Item => n, Width => 20); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Hai digitato: "); Ada.Integer_Text_IO.Put(Item => n, Width => 11); Ada.Text_IO.New_Line;

Ada.Text_IO.Put(Item => "Hai digitato: "); Ada.Integer_Text_IO.Put(Item => n); Ada.Text_IO.New_Line; end Output_Integer_Formattato; Un esempio di output:

Dai un numero di tipo INTEGER: 23

Hai Hai Hai Hai Hai Hai Hai

digitato: 23 digitato: 23 digitato: 23 digitato: 23 digitato: digitato: digitato:

23 23 23

Un altro esempio di output:

Dai un numero di tipo INTEGER: -1999

Hai Hai Hai Hai Hai Hai Hai

digitato: digitato: digitato: digitato: digitato: digitato: digitato:

-1999 -1999 -1999 -1999 -1999 -1999 -1999

Loutput seguente mostra chiaramente che, qualora lo spazio previsto dal programmatore per visualizzare il valore di una variabile di tipo INTEGER insuciente, il valore verr visualizzato ugualmente in modo corretto:

Dai un numero di tipo INTEGER: 1234567890 Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95

21

Hai Hai Hai Hai Hai Hai Hai

digitato: digitato: digitato: digitato: digitato: digitato: digitato:

1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890

Per garantire la portabilit di un programma sconsigliato luso del tipo INTEGER (come detto in precedenza i limiti per il tipo INTEGER dipendono dal tipo di macchina e/o dal compilatore Ada che si usa); invece meglio denire un proprio tipo di dato, con le limitazioni chiare. Ada permette di denire dei nuovi tipi di dati interi vincolati il cui dominio un intervallo di numeri interi. La sintassi la seguente: type nome_del_tipo is range minimo..massimo; Esempio 1.3.10 Con type Cifre is range 0..9; deniamo un tipo per manipolare le cifre: tale tipo potr assumere solo valori interi compresi tra 0 e 9 inclusi. Nel package Standard sono deniti i sottotipi interi seguenti: subtype NATURAL is INTEGER range 0..INTEGERLAST; subtype POSITIVE is INTEGER range 1..INTEGERLAST; In generale un sottotipo si denisce mediante la sintassi seguente: subtype S is T range minimo..massimo; dove S il nome del sottotipo, T il nome del tipo e minimo..massimo lintervallo dei valori ammessi. Con una simile dichiarazione S diventa un sottotipo di T; nessun nuovo tipo viene creato. Oggetti del sottotipo S sono anche del tipo T. conveniente usare i sottotipi quando con essi si ha una buona rappresentazione della realt e quando essi facilitano la ricerca di errori logici. Osservazione 1.3.7 Per il sottotipo S del tipo T si possono usare le procedure di input e output del tipo T. Consideriamo il seguente frammento di programma: ... N : NATURAL; P : POSITIVE; I : INTEGER; ... P := N + P; I := P - N; ...

01 02

Le istruzioni 01 e 02 sono ammesse poich tutte le variabili che intervengono sono di tipo intero; POSITIVE un sottoinsieme di NATURAL che, a sua volta, un sottoinsieme di INTEGER. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

22

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Esercizio 1.3.5 Scrivere un programma che chiede allutente un numero intero n di tre cifre, calcoli la somma dei cubi delle cifre di n e visualizzi poi tale somma sullo schermo. Presentiamo una possibile soluzione dellesercizio proposto: -----Nome del file: SOMMA_CUBI_DELLE_CIFRE.ADB Autore: Claudio Marsan Data dellultima modifica: 21 gennaio 2002 Scopo: somma i cubi delle cifre di un numero di tre cifre Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Somma_Cubi_delle_Cifre is subtype Tre_Cifre is INTEGER range 100..999; n : Tre_Cifre; n1, n2, n3 : INTEGER; somma : INTEGER; begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Dare un numero intero di 3 cifre: "); Ada.Integer_Text_IO.Get(Item => n); n1 := n2 := n3 := somma n / 100; -- centinaia (n / 10) mod 10; -- decine n mod 10; -- unita := n1**3 + n2**3 + n3**3;

Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "La somma dei cubi delle cifre di "); Ada.Integer_Text_IO.Put(Item => n, Width => 0); Ada.Text_IO.Put(Item => " vale: "); Ada.Integer_Text_IO.Put(Item => somma, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); end Somma_Cubi_delle_Cifre; Ecco un esempio di output del programma:

Dare un numero intero di 3 cifre: 329

La somma dei cubi delle cifre di 329 vale: 764

Se alla richiesta di input non diamo un numero di tre cifre otterremo il seguente messaggio derrore:

Dare un numero intero di 3 cifre: 25 Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95

23

raised CONSTRAINT_ERROR : a-tiinio.ads:61

Osservazione 1.3.8 Da notare che grazie alla denizione subtype Tre_Cifre possiamo usare, senza problemi, le procedure Get e Put presenti nel package Integer_Text_IO. Se invece avessimo usato la denizione type Tre_Cifre avremmo dovuto fornire delle procedure per linput e loutput di variabili del nuovo tipo. Nella pratica quotidiana, per facilitare la lettura di numeri grandi, separiamo ogni blocco di tre cifre, partendo da destra e spostandoci verso sinistra, con un apice. In Ada non possibile usare lapice ma, per lo stesso scopo, si user il trattino di sottolineatura _. Esempio 1.3.11 In un programma Ada possibile scrivere il numero 1000000 (ossia: 1 000 000) come 1_000_000 (ma anche come 10_00_0_00).

1.3.10

Il tipo FLOAT

Dalla matematica noto che linsieme dei numeri reali R lunione dellinsieme dei numeri razionali Q (linsieme dei numeri che si possono rappresentare sotto forma di frazione con numeratore e denominatore interi) con linsieme dei numeri irrazionali (un numero irrazionale quando non possibile esprimerlo sotto forma di frazione con numeratore e denominatore interi; per esempio 2 e sono irrazionali). Linsieme R innito (pi che numerabile) e denso (ossia: tra due qualsiasi numeri reali ne esistono inniti altri); dunque impensabile poter rappresentare esattamente R con un tipo di dati. In Ada il tipo FLOAT (per oating point numbers, numeri in virgola mobile) rappresenta unapprossimazione del concetto matematico di numero reale (per la precisione: di numero razionale). Osservazione 1.3.9 Il separatore decimale il punto e non la virgola (in Ada si scriver cos 3.14159 e non 3,14159). Il package Ada.Float_Text_IO fornisce le procedure Get e Put per linput e loutput di variabili di tipo FLOAT. Come gi visto nel paragrafo 1.3.5 loutput di un FLOAT in forma esponenziale, forma che pu essere piuttosto scomoda in certune circostanze. Fortunatamente esistono tre argomenti opzionali (entrambi sono degli interi non negativi) per la procedura Put: 1. Fore: indica il numero di spazi riservati per la componente intera del numero, ossia per la parte che precede il punto decimale; il valore di default 2. 2. Aft: indica il numero di decimali per la componente decimale del numero, ossia indica il numero di cifre dopo il punto decimale; il valore di default dipende dalla macchina e/o dal compilatore Ada in uso, con il compilatore GNAT 3.13p su Windows 2000 vale 5. 3. Exp: indica il numero di spazi riservati per lesponente; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

24

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 se il valore diverso da zero il valore della variabile verr visualizzato in notazione scientica (si dice che un numero reale positivo in notazione scientica quando il numero scritto nella forma k 10n , con k [1, 10[); uno spazio riservato per il segno dellesponente (anche il segno + viene rappresentato).

Riassumendo: un numero reale visualizzabile nel formato Fore . Aft E Exp se Exp diverso da zero, oppure nel formato Fore . Aft altrimenti (gli spazi bianchi fra le varie parti sono stati inseriti unicamente per facilitare la lettura del formato, in un programma non vanno mai inseriti!). Esempio 1.3.12 Se volessimo far stampare il valore approssimato di come 3.14159 dovremmo procedere come nel seguente frammento di programma: ... PI_GRECO : constant FLOAT := 3.14159; ... Ada.Text_IO.Put("Pi greco vale: "); Ada.Float_Text_IO.Put(Item => PI_GRECO, Fore => 1, Aft ... Le istruzioni ... x : FLOAT; ... Ada.Float_Text_IO.Put(x, 5, 4, 3); Ada.Float_Text_IO.Put(Item => x, Fore => 5, Aft => 4, Exp Ada.Float_Text_IO.Put(Item => x, Exp => 3, Fore => 5, Aft ...

=> 5, Exp

=> 0);

01 02 03

=> 3); => 4);

visualizzano tutte la variabile x con 5 spazi riservati prima del punto decimale, 4 spazi riservati dopo il punto decimale e tre spazi riservati per lesponente: nella 01 i parametri non sono qualicati e vengono riconosciuti per la loro posizione (il primo la variabile della quale bisogna visualizzare il valore, il secondo Fore, il terzo per Aft e il quarto per Exp); nelle righe 02 e 03 non ci sono problemi poich la qualicazione chiaricante e permette di scrivere i parametri nellordine voluto. Esempio 1.3.13 Il seguente programma illustra luso della formattazione delloutput con una variabile di tipo FLOAT: -----Nome del file: FLOAT_OUTPUT.ADB Autore: Claudio Marsan Data dellultima modifica: 21 gennaio 2002 Scopo: output formattato di FLOAT Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Float_Text_IO; procedure Float_Output is

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95 x : FLOAT := 3.1415926; begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Put(Item => x, Fore => 5, Ada.Float_Text_IO.Put(Item => x, Fore => 5, Aft => Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Put(Item => x, Fore => 1, Ada.Float_Text_IO.Put(Item => x, Fore => 1, Aft => Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Put(Item => x, Fore => 0, Ada.Float_Text_IO.Put(Item => x, Fore => 0, Aft => Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Put(Item => x): "); Ada.Float_Text_IO.Put(Item => x); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Put(x, Aft => 2): "); Ada.Float_Text_IO.Put(Item => x, Aft => 2); Ada.Text_IO.New_Line; Ada.Text_IO.Put("Put(x, Fore => 1, Exp => 2): "); Ada.Float_Text_IO.Put(x, Fore => 1, Exp => 2); Ada.Text_IO.New_Line; end Float_Output; Loutput del programma il seguente:

25

Aft => 4, Exp => 2): "); 4, Exp => 2); Aft => 9, Exp => 4): "); 9, Exp => 4); Aft => 8, Exp => 0): "); 8, Exp => 0);

Put(Item => x, Fore => 5, Aft => 4, Exp => 2): 3.1416E+0 Put(Item => x, Fore => 1, Aft => 9, Exp => 4): 3.141592503E+000 Put(Item => x, Fore => 0, Aft => 8, Exp => 0): 3.14159250 Put(Item => x): 3.14159E+00 Put(x, Aft => 2): 3.14E+00 Put(x, Fore => 1, Exp => 2): 3.14159E+0 Sono deniti anche altri tipi di reali: SHORT_FLOAT: dovrebbe essere meno preciso di FLOAT; LONG_FLOAT: pi preciso e grande di FLOAT; LONG_LONG_FLOAT: ancora pi preciso e pi grande. Con i tipi SHORT_FLOAT, FLOAT, LONG_FLOAT e LONG_LONG_FLOAT sono utilizzabili, tra gli altri, i seguenti attributi: FIRST, per identicare il pi piccolo numero rappresentabile; LAST, per identicare il pi grande numero rappresentabile; DIGITS, per indicare il numero di cifre signicative del numero; SIZE, per stabilire loccupazione di memoria in bit. Luso di questi attributi analogo a quello per i numeri interi. Esempio 1.3.14 Il seguente programma illustra luso degli attributi spiegati sopra: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

26 ------

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 Nome del file: LIMITI_FLOAT.ADB Autore: Claudio Marsan Data dellultima modifica: 23 gennaio 2002 Scopo: limiti per FLOAT e simili Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Short_Float_Text_IO, Ada.Float_Text_IO, Ada.Long_Float_Text_IO, Ada.Long_Long_Float_Text_IO, Ada.Integer_Text_IO; procedure Limiti_Float is begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il piu piccolo SHORT_FLOAT e: "); Ada.Short_Float_Text_IO.Put(Item => SHORT_FLOATFIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu grande SHORT_FLOAT e: "); Ada.Short_Float_Text_IO.Put(Item => SHORT_FLOATLAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Cifre significative di SHORT_FLOAT: "); Ada.Integer_Text_IO.Put(Item => SHORT_FLOATDIGITS, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Bit occupati da uno SHORT_FLOAT: "); Ada.Integer_Text_IO.Put(Item => SHORT_FLOATSIZE, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il piu piccolo FLOAT e: "); Ada.Float_Text_IO.Put(Item => FLOATFIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu grande FLOAT e: "); Ada.Float_Text_IO.Put(Item => FLOATLAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Cifre significative di FLOAT: "); Ada.Integer_Text_IO.Put(Item => FLOATDIGITS, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Bit occupati da un FLOAT: "); Ada.Integer_Text_IO.Put(Item => FLOATSIZE, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il piu piccolo LONG_FLOAT e: "); Ada.Long_Float_Text_IO.Put(Item => LONG_FLOATFIRST); Ada.Text_IO.New_Line;

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95 Ada.Text_IO.Put(Item => "Il piu grande LONG_FLOAT e: "); Ada.Long_Float_Text_IO.Put(Item => LONG_FLOATLAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Cifre significative di LONG_FLOAT: "); Ada.Integer_Text_IO.Put(Item => LONG_FLOATDIGITS, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Bit occupati da un LONG_FLOAT: "); Ada.Integer_Text_IO.Put(Item => LONG_FLOATSIZE, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il piu piccolo LONG_LONG_FLOAT e: "); Ada.Long_Long_Float_Text_IO.Put(Item => LONG_LONG_FLOATFIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu grande LONG_LONG_FLOAT e: "); Ada.Long_Long_Float_Text_IO.Put(Item => LONG_LONG_FLOATLAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Cifre significative di LONG_LONG_FLOAT: "); Ada.Integer_Text_IO.Put(Item => LONG_LONG_FLOATDIGITS, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Bit occupati da un LONG_LONG_FLOAT: "); Ada.Integer_Text_IO.Put(Item => LONG_LONG_FLOATSIZE, Width => 0); Ada.Text_IO.New_Line; end Limiti_Float; Ecco loutput che si ottiene eseguendo il programma con GNAT 3.13p su Windows 2000: Il piu piccolo SHORT_FLOAT e: -3.40282E+38 Il piu grande SHORT_FLOAT e: 3.40282E+38 Cifre significative di SHORT_FLOAT: 6 Bit occupati da uno SHORT_FLOAT: 32 Il piu piccolo FLOAT e: -3.40282E+38 Il piu grande FLOAT e: 3.40282E+38 Cifre significative di FLOAT: 6 Bit occupati da un FLOAT: 32 Il piu piccolo LONG_FLOAT e: -1.79769313486232E+308 Il piu grande LONG_FLOAT e: 1.79769313486232E+308 Cifre significative di LONG_FLOAT: 15 Bit occupati da un LONG_FLOAT: 64 Il piu piccolo LONG_LONG_FLOAT e: -1.18973149535723177E+4932 Il piu grande LONG_LONG_FLOAT e: 1.18973149535723177E+4932 Cifre significative di LONG_LONG_FLOAT: 18 Bit occupati da un LONG_LONG_FLOAT: 96

27

Siccome il numero di cifre signicative dei tipi reali dipende dalla macchina e/o dal compilatore, preferibile denire dei propri tipi reali. La sintassi per fare ci la seguente: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

28

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 type Nome_Tipo is digits numero;

dove Nome_Tipo il nome del tipo e numero il numero di cifre decimali che seguono il punto decimale. Esempio 1.3.15 Mediante ... type TEMPO is digits 4; type DISTANZA_ATOMICA is digits 15; ... si deniscono: il tipo TEMPO con 4 cifre decimali dopo il il punto decimale e destinato ad oggetti che rappresentano misurazioni del tempo; il tipo DISTANZA_ATOMICA con 15 cifre decimali dopo il punto decimale e destinato ad oggetti che rappresentano misurazioni di distanze atomiche. anche possibile limitare lintervallo dei numeri in virgola mobile che vogliamo denire. La sintassi la seguente: type Nome_Tipo is digits numero range minimo..massimo; Esempio 1.3.16 Le seguenti sono denizioni di tipi reali deniti su un intervallo limitato: ... type PERCENTUALE is digits 4 range 0.0 .. 100.0; type PROB is digits 6 range 0.0 .. 1.0; ... conveniente limitare i domini dei tipi reali poich, in caso di superamento dei limiti ssati, il compilatore reclama: questo aiuta a scrivere programmi pi adabili e permette di trovare pi facilmente alcuni tipi di errore. Con i numeri in virgola mobile si possono eseguire le comuni operazioni aritmetiche di somma, sottrazione, moltiplicazione, divisione (gli operandi devono essere sempre dello stesso tipo). Alcune osservazioni a proposito: la divisione tra numeri in virgola mobile restituisce un numero in virgola mobile; per il calcolo delle potenze si usa loperatore ** e lesponente deve necessariamente essere un intero; la priorit dellesecuzione delle operazioni quella dellalgebra; la priorit modicabile usando le parentesi (sono ammesse solo parentesi tonde!); nel package Ada.Numerics sono denite le costanti Pi per ed e per il numero di Eulero e; nel package Ada.Numerics.Elementary_Functions sono denite le pi comuni funzioni matematiche (funzioni trigonometriche, ciclometriche, logaritmiche, esponenziali, . . . ). Osservazione 1.3.10 Tipi diversi di numeri in virgola mobile non sono compatibili tra loro ne tantomeno lo sono con i tipi interi! Bisogna, per esempio, distinguere 100.0 da 100. Sono tuttavia permesse delle conversioni esplicite, come nellesempio seguente: ... x, y : FLOAT; ... x := y + 100; x := y + FLOAT(100); ...

-- sbagliato! -- corretto

Pi avanti vedremo altri tipi di numeri reali. Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95

29

1.3.11

I tipi enumerativi

Molti fenomeni del mondo reale sono deniti da parole piuttosto che da numeri, per esempio i giorni della settimana, i mesi dellanno, i colori, i semi delle carte da gioco, . . . . Per rappresentare fenomeni come quelli elencati in un programma i numeri non sono sucienti: in Ada c la possibilit di utilizzare i tipi enumerativi che consentono di elencare tutti i possibili valori che il tipo pu assumere. Esempio 1.3.17 Il tipo GIORNO adatto alla rappresentazione dei giorni della settimana: type GIORNO is (LUNEDI, MARTEDI, MERCOLEDI, GIOVEDI, VENERDI, SABATO, DOMENICA); In seguito potremo dichiarare delle variabili di tipo GIORNO: oggi, domani : GIORNO; e usarle in un programma: ... oggi := VENERDI; domani := SABATO; ... Esempio 1.3.18 Il tipo COLORE adatto alla rappresentazione dei colori principali nel sistema RGB (red, green, blue): type COLORE is (ROSSO, VERDE, BLU); In un programma potremmo poi avere: ... colore_auto : COLORE; ... begin ... colore_auto := BLU; ... Esempio 1.3.19 Il tipo SEME adatto alla rappresentazione dei semi delle carte da gioco francesi: type SEME is (QUADRI, CUORI, FIORI, PICCHE); In un programma potremmo poi avere: ... seme_giocato : SEME; ... begin ... seme_giocato := PICCHE; ... Osservazione 1.3.11 Si possono anche inizializzare, in fase di dichiarazione, variabili di tipo enumerativo, per esempio: ieri : GIORNO := GIOVEDI; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

30

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

La sintassi generale per la denizione di un tipo enumerativo la seguente: type Nome_Tipo is (val1, val2, ..., valN); Allinterno del tipo i valori, che sono costanti, sono ordinati nel modo seguente: val1 < val2 < ... < valN

Esempio 1.3.20 Riferendosi agli esempi precedenti avremo: MARTEDI < MERCOLEDI QUADRI < PICCHE ROSSO < BLU Siccome i tipi enumerativi sono ordinati si possono usare gli attributi SUCC(val) (restituisce il valore che, nella denizione del tipo, segue val) e PRED(val) (restituisce il valore che, nella denizione del tipo, precede val). Esempio 1.3.21 Il seguente frammento di programma riferito agli esempi precedenti: ... domani := GIORNOPRED(SABATO); -- VENERDI seme_giocato := SEMESUCC(QUADRI); -- CUORI colore_auto := COLORESUCC(BLU); -- errore! ... Da notare che gli attributi SUCC e PRED sono utilizzabili anche per i tipi interi (in Ada95 anche per i tipi in virgola mobile). Anch si possano utilizzare le procedure di input e output con i tipi enumerativi bisogna inserire allinterno della procedura una riga simile alla seguente (il signicato sar spiegato molto pi avanti, per il momento usiamola e basta!) package nome_del_package is new Ada.Text_IO.Enumeration_IO(nome_del_tipo); Sopra nome_del_package scelto dal programmatore e nome_del_tipo il nome del tipo enumerativo per il quale si desidera avere a disposizione le procedure di input e output. Esempio 1.3.22 Consideriamo il programma seguente: -----Nome del file: CARTE.ADB Autore: Claudio Marsan Data dellultima modifica: 30 gennaio 2002 Scopo: input/output di una variabile di tipo enumerativo Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Carte is type SEME is (QUADRI, CUORI, FIORI, PICCHE); package Seme_IO is new Ada.Text_IO.Enumeration_IO(SEME); seme_giocato : SEME; Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95

31

begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dare un seme: "); Seme_IO.Get(Item => seme_giocato); seme_giocato := SEMEPRED(seme_giocato); Seme_IO.Put(Item => seme_giocato); end Carte; Segue un esempio di output del programma: Dare un seme: FIORI CUORI Esempio 1.3.23 Consideriamo il programma seguente: -----Nome del file: GIORNI.ADB Autore: Claudio Marsan Data dellultima modifica: 30 gennaio 2002 Scopo: input/output di variabili di tipo enumerativo Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Giorni is type GIORNO is (LUNEDI, MARTEDI, MERCOLEDI, GIOVEDI, VENERDI, SABATO, DOMENICA); package Giorno_IO is new Ada.Text_IO.Enumeration_IO(GIORNO); ieri, oggi, domani : GIORNO; begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Che giorno e oggi? "); Giorno_IO.Get(Item => oggi); ieri := GIORNOPRED(oggi); Ada.Text_IO.Put(Item => "Ieri era "); Giorno_IO.Put(Item => ieri); Ada.Text_IO.New_Line; domani := GIORNOSUCC(oggi); Ada.Text_IO.Put(Item => "Domani sara "); Giorno_IO.Put(Item => domani); Ada.Text_IO.New_Line; end Giorni; -- Il programma genera un errore se la variabile "oggi" assume il valore -- "LUNEDI" oppure "DOMENICA" (si rimedia facilmente con le istruzioni -- condizionali che vedremo in seguito). Segue un esempio di output del programma: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

32

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Che giorno e oggi? giovedi Ieri era MERCOLEDI Domani sara VENERDI Per i giorni di inizio e ne settimana ci sono dei problemi che potranno essere risolti appena avremo studiato listruzione if ... then .... Vediamo comunque il messaggio derrore fornito dal compilatore GNAT 3.12p:

Che giorno e oggi? domenica Ieri era SABATO raised CONSTRAINT_ERROR : giorni.adb:29

Il tipo BOOLEAN un tipo enumerativo predenito che permette di utilizzare variabili che possono assumere solo i valori FALSE (falso) o TRUE (vero). Esso verr trattato pi avanti, quando studieremo le istruzioni condizionali. La sua denizione : type BOOLEAN is (FALSE, TRUE); Anche il tipo CHARACTER un tipo enumerativo predenito. Per memorizzare un carattere sono necessari 8 bit e dunque i possibili valori di una variabile di tipo CHARACTER sono 256 (in parte sono caratteri di controllo, in parte caratteri stampabili quali lettere accentate o con la dieresi). I primi 128 sono quelli del codice ASCII (i caratteri sono numerati da 0 a 127): type CHARACTER is (nul, soh, ..., a, ..., ~, del); Linsieme dei caratteri stampabili con Ada 95 denito nella norma ISO 8859 e si chiama LATIN_1. Il package Ada.Characters.Latin_1 contiene i nomi simbolici per i caratteri non stampabili. Per poter trattare anche linguaggi che non sono basati sullalfabeto latino, Ada 95 ha un altro tipo standard enumerativo, chiamato WIDE_CHARACTER. Questo tipo fa uso di 16 bit, cosa che permette di rappresentare ben 65536 caratteri diversi (essi sono specicati nello standard ISO 10646 BMP). Come gi visto linput e loutput di variabili di tipo CHARACTER possibile grazie alle procedure presenti nel package Ada.Text_IO; per variabili di tipo WIDE_CHARACTER occorrer invece il package Ada.Wide_Text_IO.

1.3.12

Alcuni attributi utili

Per il tipo CHARACTER (e WIDE_CHARACTER) ci sono due attributi interessanti: se ch una variabile di tipo CHARACTER allora con CHARACTERPOS(ch) si ottiene il codice ASCII del carattere ch; se N un intero allora con CHARACTERVAL(N) si ottiene il carattere avente il codice ASCII N. Esempio 1.3.24 CHARACTERPOS(a) restituir 97; CHARACTERVAL(97) restituir a. Gli attributi VALUE e IMAGE sono utilizzabili con i tipi discreti (interi ed enumerativi): TVALUE(s): trasforma la stringa s in un valore del tipo discreto T (se un simile valore non fa parte di T il compilatore genera un errore); TIMAGE(v): trasforma il valore v del tipo discreto T in una stringa. Esempio 1.3.25 Grazie agli attributi VALUE e IMAGE possiamo riscrivere il programma dellesempio 1.3.22 nel modo seguente, evitando di denire il package Semi_IO (nota: i numeri di riga non devono essere digitati): Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ------Nome del file: CARTE2.ADB Autore: Claudio Marsan Data dellultima modifica: Scopo: input/output di una usando gli attributi VALUE Testato con: Gnat 3.13p su

33

30 gennaio 2002 variabile di tipo enumerativo e IMAGE Windows 2000

with Ada.Text_IO; procedure Carte2 is type SEME is (QUADRI, CUORI, FIORI, PICCHE); seme_giocato : SEME; s : STRING(1..6); lung : NATURAL; begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dare un seme: "); Ada.Text_IO.Get_Line(Item => s, Last => lung); seme_giocato := SEMEVALUE(s(1..lung)); seme_giocato := SEMEPRED(seme_giocato); Ada.Text_IO.Put(Item => SEMEIMAGE(seme_giocato)); end Carte2;

Il programma necessita di qualche spiegazione: nella riga 15 si denisce la variabile s come una stringa di (al massimo) sei caratteri, sucienti per memorizzare i valori del tipo enumerativo SEME; nella riga 16 si denisce la variabile lung: in essa verr memorizzato il numero di caratteri digitato dallutente; nella riga 21 si legge la stringa s e si memorizza la sua lunghezza in lung; nella riga 22 il compito di SEMEVALUE quello di trasformare la stringa s (o meglio: i suoi primi lung caratteri) nel corrispondente valore del tipo SEME; nella riga 24 il compito di SEMEIMAGE quello di convertire il valore di seme_giocato in una stringa, cos da poterla visualizzare. Possiamo usare gli attributi VALUE e IMAGE anche con gli altri tipi di dati discreti, come per esempio gli interi da noi deniti. In Ada 95 luso di tali attributi permesso anche con i numeri in virgoli mobile. Esempio 1.3.26 Il programma seguente mostra luso di IMAGE con un tipo di dati in virgola mobile denito dal programmatore: -----Nome del file: MY_FLOAT.ADB Autore: Claudio Marsan Data dellultima modifica: 30 gennaio 2002 Scopo: uso di IMAGE Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

34

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

procedure My_Float is type REAL is digits 10; PI : REAL := 3.141592654; begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Pi greco vale circa: "); Ada.Text_IO.Put(Item => REALIMAGE(PI)); Ada.Text_IO.New_Line; end My_Float;

Esempio 1.3.27 Il seguente programma permette di leggere un numero di qualsiasi tipo e di memorizzarlo in una variabile di tipo FLOAT: ------Nome del file: LEGGI_NUMERO.ADB Autore: Claudio Marsan Data dellultima modifica: 30 gennaio 2002 Scopo: legge un numero reale come stringa e lo memorizza poi in una variabile reale Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Float_Text_IO; procedure Leggi_Numero is s : STRING(1..30); lung : NATURAL; x : FLOAT; begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Dai un numero reale x: "); Ada.Text_IO.Get_Line(Item => s, Last => lung); x := FLOATVALUE(s(1..lung)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "x vale: "); Ada.Float_Text_IO.Put(Item => x); end Leggi_Numero; Nel caso volessimo denire linsieme dei numeri in virgola mobile positivi dovremmo conoscere qual il pi piccolo FLOAT maggiore di 0. Questo valore si ottiene usando lattributo SMALL: per esempio FLOATSMALL ritorna il pi piccolo FLOAT positivo. Potremo cos denire i FLOAT positivi nel modo seguente: type POSITIVE_FLOAT is digits 6 range FLOATSMALL..FLOATLAST; Vedremo in seguito altri attributi utili. Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.4. ISTRUZIONI CONDIZIONALI

35

1.3.13

Flusso di controllo

I programmi che abbiamo visto nora hanno tutti la caratteristica di essere eseguiti in modo sequenziale, ossia il computer inizia ad eseguire la prima istruzione del programma, poi la seguente, poi la seguente, . . . no a che lultima istruzione che appare nel programma stata eseguita. Ma i computer possono fare di pi: possono eseguire unazione ripetutamente e decidere che alternativa prendere tra quelle proposte ad un certo punto del programma. Ci possibile tramite le strutture di controllo che governano il usso di controllo nellesecuzione di un programma. Si distinguono: esecuzione sequenziale: quella che abbiamo gi visto; esecuzione selettiva: ad essa sono associate le istruzioni condizionali ; esecuzione ripetitiva: ad essa sono associati i cicli. Nei prossimi paragra ci occuperemo di apprendere le istruzioni necessarie per modicare il usso di un programma.

1.4

Istruzioni condizionali

I linguaggi di programmazione solitamente permettono al programmatore di far decidere al computer se unistruzione o una sequenza di istruzioni deve essere eseguita oppure decidere quale fra due possibili sequenze di istruzioni deve essere eseguita. In Ada ci si pu fare essenzialmente con le istruzioni condizionali introdotte dalla parola riservata if.

1.4.1

Listruzione if ...

then ...

end if;

La forma pi semplice la seguente: if condizione then istruzione_1; istruzione_2; ... istruzione_N; end if; dove condizione unespressione booleana, ossia unespressione che pu assumere solo il valore vero (TRUE) o falso (FALSE). Esempio 1.4.1 Consideriamo il seguente frammento di programma: ... n : INTEGER; ... begin ... if n > 1_000_000 then Ada.Text_IO.Put(Item => "Piu di un milione"); end if; ... Se la variabile n assumer un valore maggiore di 1 000 000 (ossia se la condizione n > 1_000_000 TRUE) sullo schermo verr scritta la stringa Piu di un milione; in caso contrario il programma continua con la prima istruzione dopo end if;. Esempio 1.4.2 Consideriamo il seguente frammento di programma: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

36

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 ... n, m : INTEGER; ... begin ... m := n mod 2; if m = 0 then Ada.Integer.Text_IO.Put(Item => n); Ada.Text_IO.Put(Item => " e pari"); end if; ...

Se m sar uguale a 0 verranno eseguite le due istruzioni dopo la parola riservata then, in caso contrario il programma continua con la prima istruzione dopo end if;. Esempio 1.4.3 Nel prossimo frammento di programma si memorizza nella variabile massimo il pi grande fra i numeri in virgola mobile x e y: ... x, y, massimo : FLOAT; ... begin ... if x > y then massimo := x; end if; if x <= y then massimo := y; end if; ... Lultimo frammento di programma non molto eciente poich se x maggiore di y verr ugualmente eseguito il blocco delle istruzioni che seguono, anche se tale azione perfettamente inutile!

1.4.2

Listruzione if ...

then ...

else ...

end if;

Mediante listruzione condizionale if ... then ... else ... end if;

(se ... allora ... altrimenti ...) si pu rimediare a situazioni analoghe a quella dellesempio 1.4.3. La struttura generale seguente di tale istruzione : if condizione then istruzione_A_1; istruzione_A_2; ... istruzione_A_N; else istruzione_B_1; istruzione_B_2; ... istruzione_B_M; end if; Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.4. ISTRUZIONI CONDIZIONALI

37

Se condizione TRUE saranno eseguite istruzione_A_1, istruzione_A_2, . . . , istruzione_A_N; in caso contrario saranno eseguite istruzione_B_1, istruzione_B_2, . . . , istruzione_B_M. Esempio 1.4.4 Riprendiamo lesempio 1.4.3 migliorandone il codice: ... x, y, massimo : FLOAT; ... begin ... if x > y then massimo := x; else massimo := y; end if; ... Esempio 1.4.5 Miglioriamo il codice dellesempio 1.4.2: ... n, m : INTEGER; ... begin ... m := n mod 2; if m = 0 then Ada.Integer.Text_IO.Put(Item Ada.Text_IO.Put(Item => " e else Ada.Integer.Text_IO.Put(Item Ada.Text_IO.Put(Item => " e end if; ...

=> n); pari"); => n); dispari");

Osservazione 1.4.1 Come si pu notare dagli esempi visti conveniente indentare le istruzioni condizionali: questo comporta una maggiore leggibilit del programma, cosa che pu aiutare se bisogna correggerlo o modicarlo.

1.4.3

Operatori relazionali
Gli

Le espressioni condizionali contengono spesso un operatore relazionale (o di confronto). operatori relazionali ammessi in Ada sono i seguenti: = : uguale a ; /= : diverso da; < : minore di; <= : minore o uguale a; > : maggiore di; >= : maggiore o uguale a;

Per = e /= gli operandi possono essere di qualsiasi tipo, mentre per i restanti operandi il tipo deve essere semplice. Il risultato di unespressione condizionale un valore di tipo BOOLEAN. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

38

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

1.4.4

Operatori logici

Mediante gli operatori logici (gli operandi e il risultato sono di tipo BOOLEAN) possibile costruire condizioni complesse. Gli operatori logici ammessi in Ada sono not : negazione; and : intersezione; or : unione; xor : unione esclusiva; and then : intersezione cortocircuitata (fornisce lo stesso risultato di and con lunica dierenza che viene valutato prima loperando di sinistra; se questo FALSE il secondo operando non viene valutato); or else : unione cortocircuitata (fornisce lo stesso risultato di or con lunica dierenza che viene valutato prima loperando di sinistra; se questo TRUE il secondo operando non viene valutato). Vale la seguente tabella di verit: A TRUE TRUE FALSE FALSE B TRUE FALSE TRUE FALSE A and B TRUE FALSE FALSE FALSE A or B TRUE TRUE TRUE FALSE A xor B FALSE TRUE TRUE FALSE

Esempio 1.4.6 Consideriamo il seguente frammento di programma: ... nota : FLOAT; ... begin ... if nota >= 4.0 and then nota <= 6.0 then Ada.Text_IO.Put(Item => "Sufficiente"); else Ada.Text_IO.Put(Item => "Insufficiente"); end if; ... Se risulta che nota maggiore o uguale a 4.0 allora viene controllato se nota minore o uguale di 6.0; se lo viene scritta la stringa Sufficiente sullo schermo, altrimenti viene scritta la stringa Insufficiente. Se risulta che nota minore di 4.0 verr subito scritta la stringa Insufficiente sullo schermo.

1.4.5

Listruzione if ...

then ...

elsif ...

end if;

Vogliamo migliorare lesempio 1.4.6, riferito alla valutazione di un lavoro scritto, perch le categorie suciente e insuciente sono troppo poche (tralasciamo le cortocircuitazioni perch non rilevanti in questo contesto): ... nota : FLOAT; ... begin Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.4. ISTRUZIONI CONDIZIONALI ... if nota >= 4.0 and nota <= 5.0 then Ada.Text_IO.Put(Item => "Sufficiente"); end if; if nota > 5.0 and nota <= 6.0 then Ada.Text_IO.Put(Item => "Buono"); end if; if nota >= 3.0 and nota < 4.0 then Ada.Text_IO.Put(Item => "Insufficiente"); end if; if nota >= 1.0 and nota < 3.0 then Ada.Text_IO.Put(Item => "Pessimo"); end if; if nota < 1.0 or nota > 6.0 then Ada.Text_IO.Put(Item => "Immissione dati errata!"); end if; ...

39

Anche in questo caso se la prima condizione soddisfatta perfettamente inutile controllare le altre quattro. Con Ada possibile risolvere questo tipo di problema con la seguente costruzione: if prima_condizione then istruzione_1_1; istruzione_1_2; ... istruzione_1_k; elsif seconda_condizione then istruzione_2_1; istruzione_2_2; ... istruzione_2_m; ... elsif ultima_condizione then istruzione_u_1; istruzione_u_2; ... istruzione_u_n; else istruzione_1; istruzione_2; ... istruzione_z; end if; Tale costruzione va interpretata nel modo seguente: se prima_condizione vera vengono eseguite le istruzioni che la seguono no al primo elsif, altrimenti se seconda_condizione vera vengono eseguite le istruzioni che la seguono no al secondo elsif, altrimenti se . . . , . . . Il frammento di programma visto poco sopra pu cos essere riscritto nella forma seguente: ... nota : FLOAT; ... begin ... if nota >= 4.0 and

nota <= 5.0 then Claudio Marsan

Liceo cantonale di Mendrisio, 2002

40

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 Ada.Text_IO.Put(Item => "Sufficiente"); elsif nota > 5.0 and nota <= 6.0 then Ada.Text_IO.Put(Item => "Buono"); elsif nota >= 3.0 and nota < 4.0 then Ada.Text_IO.Put(Item => "Insufficiente"); elsif nota >= 1.0 and nota < 3.0 then Ada.Text_IO.Put(Item => "Pessimo"); else Ada.Text_IO.Put(Item => "Immissione dati errata!"); end if; ...

Le istruzioni condizionali possono, al loro interno, contenere altre istruzioni condizionali che a loro volta possono contenere altre istruzioni condizionali che a loro volta . . . (si parla di istruzioni condizionali nidicate). Esercizio 1.4.1 Sia data lequazione quadratica a coecienti reali Ax2 + Bx + C = 0. noto che, se := B 2 4AC 0, le sue soluzioni sono date dalla formula risolutiva B x1,2 = . 2A Scrivere un programma che chieda allutente i coecienti A, B e C e risolva lequazione data considerando tutti i possibili casi particolari (equazione impossibile, equazione indeterminata, equazione con due soluzioni reali distinte, equazione con due soluzioni reali coincidenti, equazione con due soluzioni complesse). Osservazione 1.4.2 In una costruzione if ... nale con else non obbligatoria. then ... elsif ... end if; la parte -

Osservazione 1.4.3 Listruzione che precede else oppure elsif deve terminare con un punto e virgola (in altri linguaggi non cos); dopo then non ci vuole il punto e virgola.

1.4.6

Selezione multipla

Mediante listruzione if possibile fare una selezione. Se in un programma dobbiamo fare una selezione tra pi percorsi da seguire potremmo usare una sequenza di istruzioni del tipo if ... then ... elsif ... if; oppure usare listruzione case (questultima soluzione da preferire). La sintassi dellistruzione case la seguente: case selettore is when lista_di_alternative_1 => sequenza_di_istruzioni_1; when lista_di_alternative_2 => sequenza_di_istruzioni_2; ... when lista_di_alternative_n => sequenza_di_istruzioni_n; end case; dove selettore deve essere di tipo discreto (ossia di tipo intero o enumerativo, dunque non di tipo a virgola mobile). Quando listruzione case verr eseguita selettore sar valutato: se il valore trovato appare tra i valori elencati in una delle liste di alternative allora la sequenza di istruzioni che segue la lista sar eseguita. Da notare che: Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.4. ISTRUZIONI CONDIZIONALI

41

tutti i possibili valori di selettore devono essere utilizzati; se necessario usare when others per fare riferimento a tutti i valori non elencati nelle liste; se presente lalternativa others essa deve essere lultima della lista. Esempio 1.4.7 Consideriamo il seguente frammento di programma: ... numero_del_mese : INTEGER; ... begin ... case numero_del_mese is when 1 => Ada.Text_IO.Put(Item when 2 => Ada.Text_IO.Put(Item when 3 => Ada.Text_IO.Put(Item ... when 12 => Ada.Text_IO.Put(Item when others => Ada.Text_IO.Put(Item end case; ...

=> "Gennaio"); => "Febbraio"); => "Marzo");

=> "Dicembre"); => "Numero del mese sbagliato!");

Se numero_del_mese assume un valore intero tra 1 e 12 verr scritto il corrispondente nome del mese, in caso contrario (when others) verr scritto un messaggio derrore. Esempio 1.4.8 Il frammento di programma seguente permette di stampare la stagione, a dipendenza del numero del mese:

... numero_del_mese : INTEGER; ... begin ... case numero_del_mese is when 1 | 2 | 12 => Ada.Text_IO.Put(Item when 3 | 4 | 5 => Ada.Text_IO.Put(Item when 6 | 7 | 8 => Ada.Text_IO.Put(Item when 9 | 10 | 11 => Ada.Text_IO.Put(Item when others => Ada.Text_IO.Put(Item end case; ...

=> "Inverno"); => "Primavera"); => "Estate"); => "Autunno"); => "Numero del mese sbagliato!");

Con la scrittura 1 | 2 | 12 (si legge: 1 oppure 2 oppure 12) si intende uno dei valori indicati. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

42

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Esempio 1.4.9 Il seguente frammento di programma legge un carattere e incrementa un contatore a dipendenza se il carattere una lettera, una cifra o altro: ... C : CHARACTER; conta_lettere : INTEGER := 0; conta_cifre : INTEGER := 0; conta_simboli : INTEGER := 0; ... begin ... Ada.Text_IO.Get(Item => C); case C is when a..z | A..Z => conta_lettere := conta_lettere + 1; Ada.Text_IO.Put_Line(Item => "Lettera"); when 0..9 => conta_cifre := conta_cifre + 1; Ada.Text_IO.Put_Line(Item => "Cifra"); when others => conta_simboli := conta_simboli + 1; Ada.Text_IO.Put_Line(Item => "Simbolo"); end case; ... Con a..z | A..Z si intende un carattere da a a z oppure da A a Z. Esercizio 1.4.2 Scrivere un programmino che simuli una calcolatrice che esegue le operazioni elementari fra numeri interi (lutente deve dare, per esempio, unespressione del tipo 5*4 e il programma deve restituire il risultato). Osservazione 1.4.4 Listruzione case comoda da usare quando bisogna preparare linterfaccia di un programma che prevede la scelta attraverso un menu di opzioni.

1.5

Istruzioni di ripetizione

Consideriamo una ditta con sette impiegati. Essa deve ripetere sette volte il calcolo del salario lordo e del salario netto nel programma per il calcolo degli stipendi, una volta per ogni impiegato. Si vede cos che molto importante che esista, nel linguaggio di programmazione in uso, la possibilit di specicare che un gruppo di operazioni possa essere ripetuto. Lesecuzione ripetuta di uno o pi passi in un programma detta ciclo (o anche iterazione); il corpo del ciclo conterr le istruzioni da ripetere. In Ada ci sono tre varianti per implementare un ciclo: una semplice istruzione loop per scrivere una parte di programma che deve essere eseguita un numero innito di volte; una istruzione loop con for per scrivere una parte di programma che deve essere eseguita un numero pressato di volte; una istruzione loop con while per scrivere una parte di programma che deve essere eseguita nch una certa condizione soddisfatta. Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.5. ISTRUZIONI DI RIPETIZIONE

43

1.5.1

Cicli semplici

Un ciclo semplice permette di ripetere innite volte la sequenza di istruzioni racchiusa tra loop e end loop: ... loop istruzione_1; istruzione_2; ... istruzione_n; end loop; ... istruzione_1, istruzione_2, ..., istruzione_n sono ripetute innite volte: il ciclo pu essere bloccato solo brutalmente (pressione di qualche tasto particolare, dipendente dal sistema operativo). Esempio 1.5.1 Il seguente frammento di programma deve controllare che la temperatura di un certo ambiente (per esempio la sala di un museo nella quale ci sono nestre, impianti di riscaldamento e di aria condizionata) sia tenuta entro certi limiti: ... loop Leggi_Temperatura(t); if t < t_min then Aumenta_Temperatura; elsif t > t_max then Diminuisci_Temperatura; end if; end loop; ... Leggi_Temperatura, Aumenta_Temperatura e Diminuisci_Temperatura sono delle procedure denite dal programmatore, t_min e t_max sono costanti denite dal programmatore oppure variabili a cui stato assegnato precedentemente un valore. chiaro, dal tipo di problema per il quale il programma viene utilizzato, che il programma non deve mai arrestarsi. Esempio 1.5.2 Il seguente programma stampa gli INTEGER, uno per riga: -----Nome del file: CICLO_LOOP.ADB Autore: Claudio Marsan Data dellultima modifica: 20 febbraio 2002 Scopo: esempio di ciclo LOOP (stampa gli INTEGER) Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Ciclo_Loop is i : INTEGER := 0; begin loop Liceo cantonale di Mendrisio, 2002 Claudio Marsan

44

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Ada.Integer_Text_IO.Put(Item => i); Ada.Text_IO.New_Line; i := i + 1; end loop; end Ciclo_Loop; Per bloccare questo programma in ambiente Windows 2000 bisogna premere contemporaneamente i tasti Ctrl-C.

1.5.2

Il ciclo for

La sintassi del ciclo for la seguente: for PARAMETRO in TIPO range MINIMO..MASSIMO loop istruzione_1; istruzione_2; ... istruzione_n; end loop; dove PARAMETRO un identicatore dichiarato automaticamente (trattato come una costante nella sequenza delle istruzioni) e MINIMO e MASSIMO sono espressioni del tipo discreto TIPO (interi o enumerativi, dunque nessun tipo a virgola mobile). MINIMO e MASSIMO devono inoltre essere dello stesso tipo (a meno che uno dei due sia di tipo universal_integer). possibile tralasciare TIPO range. Esempio 1.5.3 Il seguente frammento di programma stampa i primi 10 multipli positivi di 7: ... for i in INTEGER range 1..10 loop Ada.Integer_Text_IO.Put(Item => i * 7); Ada.Text_IO.New_Line; end loop; ... Lo stesso frammento pu anche essere riscritto nella forma seguente equivalente: ... for i in 1..10 loop Ada.Integer_Text_IO.Put(Item => i * 7); Ada.Text_IO.New_Line; end loop; ... Il parametro i non deve essere dichiarato e non pu essere modicato allinterno del ciclo; inoltre alla ne del ciclo esso non esiste pi! Se si vuole eseguire un ciclo al contrario (ossia dal pi grande valore che il contatore pu assumere al pi piccolo) bisogna far seguire la parola riservata in dalla parola riservata reverse. Esempio 1.5.4 Il seguente frammento di programma stampa le lettere (minuscole) dellalfabeto dalla z alla a: ... for C in reverse a..z loop Ada.Text_IO.Put(Item => C); Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.5. ISTRUZIONI DI RIPETIZIONE end loop; -- Si legger sullo schermo: -zyxwvutsrqponmlkjihgfedcba ...

45

Esercizio 1.5.1 Scrivere un programma che, richiesto un intero N minore di 1000, calcoli la somma S = 1 + 2 + 3 + + (N 1) + N e la visualizzi sullo schermo. Esercizio 1.5.2 Scrivere un programma che, richiesti due numeri interi M e N minori di 1000, calcoli la somma di tutti gli interi compresi tra M e N che sono multipli di 9 ma non di 2. Osservazione 1.5.1 Consideriamo il seguente frammento di programma: ... N : INTEGER; ... N := 10; for i in INTEGER range 1..N loop Ada.Integer_Text_IO.Put(Item => i, Width => 0); Ada.Text_IO.New_Line; N := 5; end loop; ... Il ciclo for ... loop verr ripetuto comunque 10 volte (ossia il valore di N quando inizia il ciclo), anche se N viene modicato allinterno del ciclo!

1.5.3

Il ciclo while

Quando non noto a priori quante volte bisogna ripetere una certa sequenza di passi di un programma si usa la costruzione while che ha la sintassi seguente: while condizione loop istruzione_1; istruzione_2; ... istruzione_N; end loop; dove condizione unespressione booleana. Il funzionamento di un ciclo while il seguente: dapprima viene valutata lespressione booleana condizione: se essa risulta falsa non viene eseguita alcuna delle istruzioni del ciclo e questo da considerare terminato; se essa risulta vera saranno eseguite istruzione_1, istruzione_2, ..., istruzione_N e poi verr nuovamente controllato il valore booleano di condizione. Si procede cos nch il valore di condizione resta vero. Esempio 1.5.5 Consideriamo il seguente frammento di programma: ... i : INTEGER; ... i := 0; while i < 6 loop Ada.Integer_Text_IO.Put(Item => i); i := i + 2; end loop; ... Liceo cantonale di Mendrisio, 2002 Claudio Marsan

46

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Loutput relativo a tale frammento di programma sar: 0 2 4

Esempio 1.5.6 Consideriamo il seguente frammento di programma: ... x : FLOAT; ... x := 10.0; while x > 1.0 loop Ada.Float_Text_IO.Put(Item => x, Fore => 6, Aft => 2, Exp => 0); x := x / 2.0; end loop; ... Loutput relativo al frammento di programma sar: 10.00 5.00 2.50 1.25

Da notare che, se in un ciclo while, lespressione booleana controllata allinizio non diventa mai falsa avremo un ciclo innito che potr essere interrotto solo brutalmente con una combinazione di tasti tipica del sistema operativo in uso. dunque importante assicurarsi che le istruzioni allinterno del ciclo while modichino il valore dellespressione booleana iniziale. Esempio 1.5.7 Il seguente frammento di programma genera un ciclo innito: ... while 1 /= 0 loop Ada.Text_IO.Put_Line(Item => "senza fine ..."); end loop; ... Sovente il ciclo while viene utilizzato quando richiesto come input un valore scelto tra alcuni possibili: se il valore introdotto dallutente non tra quelli possibili si presenta nuovamente la richiesta di input. Esempio 1.5.8 Volendo che la risposta ad una domanda sia s (s) oppure no (n) si proceder come nel seguente frammento di programma: ... risposta : CHARACTER; ... risposta := k; -- valore qualsiasi non accettabile! while (risposta /= S) and (risposta /= s) and (risposta /= N) and (risposta /= n) loop Ada.Text_IO.Put(Item => "Desideri un gelato? (s/n) "); Ada.Text_IO.Get(Item => risposta); if (risposta /= S) and (risposta /= s) and (risposta /= N) and (risposta /= n) then Ada.Text_IO.Put_Line(Item => "Risposta non accettabile! Riprova!"); end if; end loop; ... Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.5. ISTRUZIONI DI RIPETIZIONE

47

1.5.4

Listruzione exit

Il ciclo semplice loop ... end loop serve per ripetere un certo numero di istruzioni innite volte. Mediante listruzione exit tuttavia possibile uscire da un simile ciclo in modo non brutale e continuare con le istruzioni che seguono la riga end loop. Esistono due varianti dellistruzione exit: 1. exit; 2. exit when condizione; (condizione unespressione booleana) Esempio 1.5.9 Consideriamo il seguente frammento di programma: ... loop ... x := y - a**2; if x < 1.0 then exit; end if; ... end loop; ... Se x < 1.0 sar eseguita listruzione exit che comporta luscita dal ciclo e lesecuzione del programma passer alla prima istruzione dopo end loop. Esempio 1.5.10 Lesempio precedente usando per la costruzione exit when: ... loop ... x := y - a**2; exit when x < 1.0; ... end loop; ... Osservazione 1.5.2 Attenzione alluso delle istruzioni exit e di exit when poich con esse si possono creare facilmente programmi poco chiari e dicili da capire; se possibile usare invece un ciclo while. Lesercizio seguente permette di esercitare luso delle costruzioni for ... loop e delle istruzioni condizionali. Esercizio 1.5.3 Scrivere un programma che: 1. chieda allutente il primo termine e la ragione r di una serie geometrica (tali valori devono essere nuovamente richiesti se uguali a 0.0); 2. valuti il valore della serie se questa convergente (ossia se |r| < 1; tale valore sar determinato continuando a calcolare le somme parziali ntanto che la dierenza fra due somme parziali successive minore, in valore assoluto, di 1012 ); 3. chieda allutente, se la serie non convergente, quanti termini vuole sommare e calcoli tale somma. Come tipo reale usare i LONG_FLOAT. Liceo cantonale di Mendrisio, 2002 Claudio Marsan loop e while ...

48

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

I vari tipi di cicli possono essere usati per risolvere lo stesso tipo di problema, sebbene ognuno di essi sia pi adatto alla soluzione di un determinato tipo di problema. Esempio 1.5.11 Il seguente programma mostra lequivalenza dei vari cicli: -----Nome del file: CICLI.ADB Autore: Claudio Marsan Data dellultima modifica: 20 febbraio 2002 Scopo: mostrare lequivalenza delle varie forme di cicli Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Cicli is k, i : INTEGER; begin k := 12; for i in 1..100 loop if k < 50 then k := k + i*2; else k := k - i*3; end if; end loop; Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Valore di k (for): "); Ada.Integer_Text_IO.Put(Item => k, Width => 0); Ada.Text_IO.New_Line; k := 12; i := 1; while i <= 100 loop if k < 50 then k := k + i*2; else k := k - i*3; end if; i := i + 1; end loop; Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Valore di k (while): "); Ada.Integer_Text_IO.Put(Item => k, Width => 0); Ada.Text_IO.New_Line; k := 12; i := 1; loop if k < 50 then k := k + i*2; else Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.5. ISTRUZIONI DI RIPETIZIONE k := k - i*3; end if; i := i + 1; exit when i > 100; end loop; Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Valore di k (loop): "); Ada.Integer_Text_IO.Put(Item => k, Width => 0); Ada.Text_IO.New_Line; end Cicli; Ecco loutput del programma:

49

Valore di k (for): -53

Valore di k (while): -53

Valore di k (loop): -53

1.5.5

Cicli nidicati

La sequenza delle istruzioni allinterno di un ciclo (di qualsiasi tipo) pu essere formata da istruzioni di tipo arbitrario e quindi anche da altre istruzioni di ripetizione; in tal caso si parler di cicli nidicati. Esempio 1.5.12 Il seguente frammento di programma richiede allutente un intero n e stamper sul video un * nella prima riga, ** nella seconda riga, eccetera. ... n : INTEGER; ... Ada.Text_IO.Put(Item => Numero di linee da stampare: "); Ada.Integer_Text_IO.Get(Item => n); for i in 1..n loop for j in 1..i loop Ada.Text_IO.Put(Item => "*"); end loop; Ada.Text_IO.New_Line; end loop; ... Se n = 5 loutput sar il seguente: * ** *** **** *****

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

50

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Esempio 1.5.13 Vogliamo scrivere un programma che legga dieci righe di testo, lunghe ognuna al massimo 100 caratteri, e che conti il numero di lettere minuscole che esse contengono. Possiamo usare lalgoritmo seguente: 1. Poniamo numero_lettere_minuscole uguale a 0; 2. Ripetiamo quanto segue per 10 volte: (a) Leggi la riga corrente; (b) Ripeti quanto segue per ogni carattere della riga: se il carattere corrente compreso tra a e z incrementa di 1 il valore della variabile numero_lettere_minuscole; 3. Stampa il valore di numero_lettere_minuscole. Ecco il relativo codice: ------Nome del file: CONTA_LETTERE_MINUSCOLE.ADB Autore: Claudio Marsan Data dellultima modifica: 20 febbraio 2002 Scopo: conta le lettere minuscole presenti in 10 righe di testo, ognuna non pi lunga di 100 caratteri. Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Conta_Lettere_Minuscole is riga_corrente : STRING(1..100); lunghezza : INTEGER; numero_lettere_minuscole : INTEGER := 0; begin Ada.Text_IO.Put_Line(Item => "Scrivi 10 righe:"); Ada.Text_IO.New_Line; for numero_righe in 1..10 loop Ada.Text_IO.Get_Line(Item => riga_corrente, Last => lunghezza); for numero_carattere in 1..lunghezza loop if riga_corrente(numero_carattere) in a..z then numero_lettere_minuscole := numero_lettere_minuscole + 1; end if; end loop; end loop; Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Ci sono "); Ada.Integer_Text_IO.Put(Item => numero_lettere_minuscole, Width => 0); Ada.Text_IO.Put(Item => " lettere minuscole"); end Conta_Lettere_Minuscole; Per aumentare la leggibilit dei cicli nidicati si pu assegnare un nome a uno o a pi cicli. La sintassi sar allora la seguente: Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.6. LISTRUZIONE NULL nome_del_ciclo: for ... loop ... end loop nome_del_ciclo;

51

Esempio 1.5.14 Il seguente programma stampa le tabelline di moltiplicazione per i numeri da 1 a 10: -----Nome del file: TABELLINE.ADB Autore: Claudio Marsan Data dellultima modifica: 20 febbraio 2002 Scopo: stampa le tabelline; denomina i due cicli utilizzati Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Tabelline is begin ciclo_esterno: for i in 1..10 loop Ada.Integer_Text_IO.Put(Item => i, Width => 2); Ada.Text_IO.Put(Item => " =>"); ciclo_interno: for j in 1..10 loop Ada.Integer_Text_IO.Put(Item => i*j, Width => 6); end loop ciclo_interno; Ada.Text_IO.New_Line; end loop ciclo_esterno; end Tabelline; Ecco loutput del programma: 1 2 3 4 5 6 7 8 9 10 => => => => => => => => => => 1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100

1.6

Listruzione null

Listruzione null listruzione nulla: in sua presenza il compilatore Ada non esegue nulla. Questa istruzione necessaria qualora si debba indicare al compilatore di non eseguire nulla; in altri linguaggi di programmazione basta, per esempio, scrivere un punto e virgola per listruzione nulla. In fase di progettazione pu essere utile ricorrere allistruzione null per testare una parte di programma del quale gi pronta la struttura principale ma non la codica completa (mancano cio i dettagli dellimplementazione). Questo modo di procedere tipico della programmazione topdown. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

52

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Esempio 1.6.1 Consideriamo il seguente frammento di programma: ... x : INTEGER; ... case x in when 1 => x := 34; when 2 => x := 45; when 3 => null; when 4 => null; when others => x := 0; end case; ... if x > 0 then -- ancora da implementare null; else -- ancora da implementare null; end if; ... Il frammento di programma sopra perfettamente eseguibile, anche se incompleto: al posto dei vari null bisogner poi inserire il codice adeguato al caso. Anche se il programma incompleto in alcune parti tuttavia possibile testare la correttezza delle parti gi implementate. Osservazione 1.6.1 Listruzione null pu essere utile anche nella clausola when others in una costruzione case.

1.7

Listruzione goto

Una delle istruzioni pi utilizzate nel linguaggio di programmazione Basic listruzione di salto, ossia listruzione goto. Luso (o meglio: labuso) di questa istruzione rende praticamente illeggibili i programmi (gli americani parlano di spaghetti code). Nei linguaggi di programmazione strutturata (Pascal, Modula2, Ada) listruzione goto esiste ma non viene quasi mai citata nei manuali e, soprattutto, non viene mai utilizzata dai puristi di questi linguaggi. In Ada si fatto addirittura di pi: si cercato di limitarne il pi possibile luso (sembra che in Ada si sia introdotta listruzione goto per poter tradurre automaticamente gli innumerevoli programmi scritti in altri linguaggi, soprattutto in Fortran, gi esistenti). Per poter utilizzare listruzione goto bisogna dichiarare anche unetichetta (label ), ossia un identicatore racchiuso tra i simboli e che viene posto nel punto in cui deve riprendere il programma quando incontra la relativa istruzione di salto goto. La sintassi generale : ... <<etichetta>> ... goto etichetta; ... oppure ... goto etichetta; ... <<etichetta>> ... Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.8. BLOCCHI DI ISTRUZIONI

53

In entrambi i casi, appena letta listruzione goto, si passer allistruzione che segue etichetta. Esempio 1.7.1 Consideriamo il seguente frammento di programma: ... x : FLOAT; ... <<SOPRA>> ... if x < 12.0 then ... goto SOPRA; elsif x > 13.0 then ... goto SOTTO; else ... end if; ... <<SOTTO>> ... Osservazione 1.7.1 Ricordiamo le seguenti limitazioni dellistruzione goto: essa non pu trasferire il controllo del usso del programma al di fuori di una funzione, di una procedura, di un package, di un task; essa non pu trasferire il controllo del usso del programma allinterno di cicli, di istruzioni condizionali o di blocchi.

1.8

Blocchi di istruzioni

Un blocco di istruzioni una sequenza di istruzioni che pu essere posizionata in una qualunque parte del programma. Un blocco di istruzioni pu contenere una parte dichiarativa introdotta dalla parola riservata declare; le istruzioni del blocco sono racchiuse tra begin e end; possibile assegnare anche un identicatore al blocco. Esempio 1.8.1 Consideriamo il seguente frammento di programma: ... N : INTEGER; -- N una variabile globale ... begin Ada.Text_IO.Put(Item => "Dai un intero: "); Ada.Integer_Text_IO.Get(Item => N); if (N mod 2) = 0 then Ada.Text_IO.Put(Item => "Pari!"); else Ada.Text_IO.Put(Item =>"Dispari!"); end if; end; ... Lo stesso pu essere riscritto usando una parte dichiarativa: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

54

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 ... declare N : INTEGER; -- N una variabile locale begin Ada.Text_IO.Put(Item => "Dai un intero: "); Ada.Integer_Text_IO.Get(Item => N); if (N mod 2) = 0 then Ada.Text_IO.Put(Item => "Pari!"); else Ada.Text_IO.Put(Item =>"Dispari!"); end if; end; ...

Volendo utilizzare un identicatore per il blocco: ... BLOCCO: declare N : INTEGER; -- N una variabile locale begin Ada.Text_IO.Put(Item => "Dai un intero: "); Ada.Integer_Text_IO.Get(Item => N); if (N mod 2) = 0 then Ada.Text_IO.Put(Item => "Pari!"); else Ada.Text_IO.Put(Item =>"Dispari!"); end if; end BLOCCO; ... importante distinguere tra variabile globale e variabile locale: nel primo caso la variabile ha valore in tutto il programma, nel secondo caso solo allinterno del blocco in cui essa stata dichiarata. Esempio 1.8.2 Il seguente programma illustra la dierenza fra variabili globali e variabili locali: -----Nome del file: VARIABILI.ADB Autore: Claudio Marsan Data dellultima modifica: 20 febbraio 2002 Scopo: uso di variabili globali e locali Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Variabili is N : INTEGER := 100; -- variabile globale begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Programma principale ==> N vale: "); Ada.Integer_Text_IO.Put(Item => N, Width => 0); Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.8. BLOCCHI DI ISTRUZIONI Ada.Text_IO.New_Line(Spacing => 2); BLOCCO1: declare N : INTEGER := 10; M : INTEGER := 55; begin Ada.Text_IO.Put(Item => "Blocco Ada.Integer_Text_IO.Put(Item => Ada.Text_IO.New_Line(Spacing => Ada.Text_IO.Put(Item => "Blocco Ada.Integer_Text_IO.Put(Item => Ada.Text_IO.New_Line(Spacing =>

55

1 ==> N vale: "); N, Width => 0); 2); 1 ==> M vale: "); M, Width => 0); 2);

BLOCCO2: declare N : INTEGER := 1; begin Ada.Text_IO.Put(Item => "Blocco 2 ==> N vale: "); Ada.Integer_Text_IO.Put(Item => N, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Blocco 2 ==> BLOCCO1.N vale: "); Ada.Integer_Text_IO.Put(Item => BLOCCO1.N, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "N globale vale: "); Ada.Integer_Text_IO.Put(Item => Variabili.N, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Blocco 2 ==> M vale: "); Ada.Integer_Text_IO.Put(Item => M, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); end BLOCCO2; Ada.Text_IO.Put(Item => "Blocco 1 (dopo il 2) ==> N vale: "); Ada.Integer_Text_IO.Put(Item => N, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); end BLOCCO1; Ada.Text_IO.Put(Item => "Programma principale (dopo 1, 2) ==> N vale: "); Ada.Integer_Text_IO.Put(Item => N, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); end Variabili; Ecco loutput del programma:

Programma principale ==> N vale: 100 Blocco 1 ==> N vale: 10 Blocco 1 ==> M vale: 55 Blocco 2 ==> N vale: 1 Blocco 2 ==> BLOCCO1.N vale: 10 Liceo cantonale di Mendrisio, 2002 Claudio Marsan

56

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

N globale vale: 100 Blocco 2 ==> M vale: 55 Blocco 1 (dopo il 2) ==> N vale: 10 Programma principale (dopo 1, 2) ==> N vale: 100

Da notare che BLOCCO2 interno a BLOCCO1; tuttavia con BLOCCO1.N possibile accedere alla variabile N dichiarata in BLOCCO1. Inoltre con Variabili.N possibile accedere in ogni momento, anche allinterno di ogni blocco, alla variabile globale N. Esercizio 1.8.1 Costruire le tavole numeriche per le funzioni trigonometriche sin, cos e tan, da 0 a 90 , di grado in grado. Nota: dopo 15 , 30 , 45 , 60 e 75 il usso dei dati di output sullo schermo deve arrestarsi e riprendere con la pressione del tasto <Enter>.

1.9

Qualche cenno sugli array

Finora abbiamo studiato tipi di dati scalari (un oggetto di tipo scalare pu assumere solo un singolo valore). Ora passiamo ad esaminare gli array, lesempio pi elementare di tipo di dati strutturato. Essenzialmente un oggetto di tipo array consiste di una collezione numerata di componenti simili, ossia una specie di tabella nella quale ad ogni elemento della tabella associato un numero particolare (indice). Il concetto matematico di vettore realizza bene il concetto di array. Si distinguono due classi di array: gli array vincolati (constrained array), le cui dimensioni sono denite in fase di denizione del tipo di dato; gli array non vincolati (unconstrained array), dei quali non si predenisce la dimensione (ci consente di trattare oggetti di dimensione diversa). Per ora ci limitiamo a trattare brevemente le caratteristiche principali degli array vincolati. La dichiarazione di un tipo array avviene secondo la sintassi seguente (ci sono alcune varianti che per il momento non ci interessano): type NOME_TIPO is array(a..b) of TIPO_COMPONENTE; dove: NOME_TIPO il nome che si assegna al nuovo tipo di dato; TIPO_COMPONENTE il tipo delle singole componenti dellarray; a..b rappresenta il rango su cui possibile scegliere gli indici dellarray. Esempio 1.9.1 Con type VETTORE3D is array(1..3) of FLOAT; si denisce un array di tre componenti di tipo FLOAT. Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.9. QUALCHE CENNO SUGLI ARRAY Esempio 1.9.2 Con type NOTE_ESPE_2 is array(1..21) of NATURAL; si denisce un array di 21 componenti di tipo NATURAL. Esempio 1.9.3 anche possibile avere una dichiarazione come la seguente: type MISURE is array(INTEGER range 1..10) of FLOAT;

57

Esempio 1.9.4 Consideriamo un esperimento di laboratorio che consiste nel ripetere per 10 volte la misurazione di una certa grandezza sica. Volendo memorizzare ogni singola misurazione in una variabile dovremmo prevedere 10 variabili, con 10 nomi diversi. E se le variabili fossero 100? Oppure 1000? Conviene cos denire il seguente array vincolato: type SERIE_DI_MISURE is array(1..10) of FLOAT; Nella dichiarazione si sono date: 1. la numerazione delle componenti (da 1 a 10); 2. il tipo delle singole componenti (FLOAT). Possiamo dichiarare una variabile del nuovo tipo nel modo seguente: serie : SERIE_DI_MISURE; Cos facendo predisponiamo la variabile serie a contenere una sequenza di 10 misurazioni, ossia come avere denito 10 variabili di tipo FLOAT. Per accedere ad una singola componente di un array bisogna scrivere, tra parentesi tonde e dopo il nome della variabile, lindice della componente. Per esempio con serie(1) := 9.8101; serie(5) := 9.8098; serie(9) := 9.8093; si assegna alla prima componente della variabile serie il valore 9.8101, alla quinta componente il valore 9.8098 e alla nona componente il valore 9.8093. In un programma possiamo considerare serie(1), serie(2),. . . , serie(10) come variabili di tipo FLOAT a tutti gli eetti. I numeri 1, 2, . . . , 10 che indicano la numerazione delle componenti di un array sono detti indici. Si pu accedere anche ad una componente di un array tramite un indice che non una costante ma una variabile, in ogni caso di tipo discreto e tale da assumere un valore compreso nel dominio specicato per gli indici. Esempio 1.9.5 Nel seguente frammento di programma si mostra lassegnazione di un valore (dipendente da una componente di un array) ad una componente di un array: ... i : INTEGER; ... serie(i) := 2.0 * serie(i-1); ... Consideriamo sempre lesempio 1.9.4 dellesperimento di laboratorio. Ammettiamo di non volere fare pi solo 10 misurazioni ma 20. Se avessimo scritto un programma per lesempio 1.9.4 dovremmo ricercare tutte le occorrenze di 10 (quando questi ha il signicato di indice nale di una variabile di tipo SERIE_DI_MISURE) e sostituirle con 20: poco pratico! Conviene invece modicare la denizione del tipo nel modo seguente: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

58

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95 ... NUMERO_MISURE : CONSTANT INTEGER := 10; type SERIE_DI_MISURE is array(1..NUMERO_MISURE) of FLOAT; ...

Nel programma invece di usare 10 useremo la costante NUMERO_MISURE: se un giorno si decidesse di eseguire 20 invece di 10 misurazioni baster cambiare la denizione della costante nella riga apposita! Facendo come appena descritto sar pi facile mantenere e aggiornare i programmi. Quando si denisce un tipo di array vincolato non si obbligati a scegliere 1 come indice di partenza; si potrebbe anche, per esempio partire da -10. Esempio 1.9.6 Con la riga seguente deniamo un array di 18 componenti di tipo NATURAL; la prima componente ha indice -10, lultima 7: type LISTA is array(-10..7) of NATURAL; Gli indici possono essere di qualsiasi tipo discreto (intero o di enumerazione). Esempio 1.9.7 Nel seguente frammento di programma abbiamo alcune denizioni di array con componenti di tipo enumerativo: ... type GIORNO is (LUNEDI, MARTEDI, MERCOLEDI, GIOVEDI, VENERDI, SABATO, DOMENICA); type ORE_DI_LAVORO is array(LUNEDI..VENERDI) of FLOAT; mio_orario_LICEO : ORE_DI_LAVORO; ... mio_orario_LICEO(MARTEDI) := 4.0; mio_orario_LICEO(MERCOLEDI) := 9.0; ... La denizione del tipo ORE_DI_LAVORO non mette bene in risalto di che tipo sono gli indici. Per specicare il tipo degli indici si pu ricorrere alla seguente denizione alternativa: type ORE_DI_LAVORO is array(GIORNO range LUNEDI..VENERDI) of FLOAT; Esempio 1.9.8 Riferendoci allesempio 1.9.4 potremmo dare la seguente denizione, pi meticolosa: type SERIE_DI_MISURE is array(INTEGER range 1..10) of FLOAT; Esempio 1.9.9 Dovendo costruire un programma che conta loccorrenza di ogni singola lettera dellalfabeto in un testo sar conveniente denire il seguente array vincolato: type CONTA_LETTERE is array(CHARACTER range a..z) of INTEGER; Se si tralascia la numerazione degli indici tra parentesi si intende che tutti i possibili valori del tipo dato possono essere usati come valori indice. Esempio 1.9.10 La riga type ORE_STUDIO is array(GIORNO) of FLOAT; equivalente a: type ORE_STUDIO is array(LUNEDI..DOMENICA) of FLOAT; Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.9. QUALCHE CENNO SUGLI ARRAY

59

Talvolta conveniente introdurre un nuovo tipo oppure un sottotipo per gli indici utilizzando una dichiarazione speciale. Esempio 1.9.11 Consideriamo le righe seguenti: type NUMERO_DI_RIGA is range 1..72; type RIGA_DELLA_TABELLA is array(NUMERO_DI_RIGA) of INTEGER; subtype MINUSCOLE is CHARACTER range a..z; type CONTA_MINUSCOLE is array(MINUSCOLE) of INTEGER; La denizione del tipo NUMERO_DI_RIGA e del sottotipo MINUSCOLE aumenta la leggibilit del programma. Negli indici non necessariamente devono apparire solo costanti ma possono apparire anche delle variabili: ci signica che la grandezza dellarray non deve necessariamente essere nota quando il programma viene compilato! Esempio 1.9.12 Se N di tipo INTEGER allora sono valide le seguenti dichiarazioni di array: type TABELLA is array(N..2*N) of FLOAT; type VETTORE is array(INTEGER range 1..N) of FLOAT; subtype INDICE_LISTA is INTEGER range 100..100+N; type LISTA is array(INDICE_LISTA) of CHARACTER; Se dovesse capitare che lindice iniziale assuma un valore pi grande delindice nale avremo unarray vuoto, senza componenti. Esercizio 1.9.1 Costruire un programma che denisca il tipo VETTORE, realizzazione matematica dei vettori del piano, che permetta di calcolare la somma e il prodotto scalare dei vettori a e b, le cui componenti sono date dallutente. possibile assegnare valori a tutte le componenti di un array con unistruzione sola invece che assegnare i valori componente per componente: basta usare gli aggregati. Esempio 1.9.13 Deniamo il tipo seguente: type SERIE_DI_MISURE is array(1..4) of FLOAT; serie : SERIE_DI_MISURE; e consideriamo il seguente frammento di programma: ... serie(1) serie(2) serie(3) serie(4) ...

:= := := :=

3.1417; 3.1298; 3.1287; 3.1423;

Esso equivale a: ... serie := (3.1417, 3.1298, 3.1287, 3.1423); -- aggregato ... Esempio 1.9.14 Altro esempio di uso di aggregati: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

60 ... type a, b ... a := b := ...

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

LISTA is array(1..100) of INTEGER; : LISTA; (others => 0); -- (*) (1..10 => 7, 11..77 => 3, 85 => 5, others => 1); -- (**)

Nella riga (*) si assegna il valore 0 a tutte le componenti della variabile a; nella riga (**) si assegnano: il valore 7 alle prime 10 componenti di b; il valore 3 alle componenti da 11 a 77 di b; il valore 5 alla componente 85 di b; il valore 1 a tutte le altre componenti. Gli aggregati sono utili anche per inizializzare una variabile nella parte dichiarativa del programma. Esempio 1.9.15 Inizializzazione di array: ... type e1 : e2 : v : ...

VETTORE VETTORE VETTORE VETTORE

is := := :=

array(1..2) of FLOAT; (1.0, 0.0); (0.0, 1.0); (others => 0.0);

Da notare che: in ogni aggregato deve esistere esattamente un valore per ogni componente; se c others nellaggregato, esso dovr apparire per ultimo. Si pu accedere anche soltanto ad un certo numero di componenti consecutive di un array (si parla di slice). Esempio 1.9.16 Consideriamo il seguente frammento di programma: ... type LISTA is array(1..100) of INTEGER; c : LISTA; ... c(25..50) := -99; -- (*) ... Con la riga (*) si assegna alle componenti da 25 a 50 della variabile c il valore -99. Si possono anche denire array multidimensionali dichiarando pi indici contemporaneamente. Esempio 1.9.17 Con la riga seguente abbiamo la realizzazione del concetto matematico di matrice quadrata di ordine 3: type MATRICE is array(1..3, 1..3) of FLOAT; Esempio di assegnamento di variabili: Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.9. QUALCHE CENNO SUGLI ARRAY ... A : MATRICE; ... A(1,1) := 1.0; A(2,1) := -2.3; ...

61

Con il seguente frammento di programma si riempie la matrice quadrata di ordine 3 A con elementi uguali al rapporto fra lindice di riga e lindice di colonna: ... ciclo_esterno: for i in 1..3 loop ciclo_interno: for j in 1..3 loop A(i,j) := FLOAT(i)/FLOAT(j); end loop ciclo_interno; end loop ciclo_esterno; ...

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

62

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 2 Procedure e funzioni in Ada 95


2.1 Introduzione

Finora abbiamo visto che un programma scritto in Ada 95 composto da due parti: 1. una parte dichiarativa nella quale vengono descritti gli oggetti (variabili, costanti, denizione di tipi di dati) che saranno usati nel programma; 2. una parte contenente le istruzioni riguardandi le azioni che verranno eseguite dal programma. Gli algoritmi sono descritti mediante le istruzioni disponibili in Ada 95 (per esempio: istruzioni di assegnazione, istruzioni condizionali, cicli, . . . ). Talvolta per nella costruzione di un algoritmo pi utile esprimere alcuni passi in un livello superiore a quanto possibile in Ada 95, per esempio: calcola il logaritmo di x, ordina la tabella T , calcola la media delle misurazioni, stampa una pagina intestata, . . . Una simile tecnica di progettazione di un programma (topdown) permette, in una prima fase, di ignorare dettagli ininuenti del programma e di concentrarsi sullalgoritmo vero e proprio. In Ada 95 possibile denire dei sottoprogrammi composti da pi istruzioni di base; questi sottoprogrammi possono essere utilizzati come passi algoritmici di livello superiore quando lalgoritmo in fase di costruzione. Un programma (complesso) dovrebbe essere costruito da molti sottoprogrammi, ognuno dei quali descrive un particolare calcolo o passaggio del programma (nota: in programmi veramente complessi potrebbero esserci addirittura sottoprogrammi scritti in altri linguaggi di programmazione!). I sottoprogrammi possono essere visti come i blocchi fondamentali che sono necessari per costruire il programma principale. In Ada 95 ci sono due tipi di sottoprogrammi: funzioni (in inglese: function), usate per descrivere il calcolo di un particolare valore (per esempio la determinazione della media di un certo numero di misurazioni); procedure (in inglese: procedure), usate per descrivere unazione che il programma deve compiere ma che non fornisce un valore diretto (per esempio la stampa di una pagina intestata). Finora abbiamo visto funzioni e procedure predenite nel linguaggio Ada 95; nel seguito vedremo come possibile costruire le proprie funzioni e procedure. 63

64

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

2.2

Funzioni matematiche predenite

Nel package Ada.Numerics.Elementary_Functions sono denite le principali funzioni matematiche: radice quadrata: function Sqrt(X : FLOAT) return FLOAT; logaritmo naturale: function Log(X : FLOAT) return FLOAT; logaritmo nella base Base: function Log(X, Base : FLOAT) return FLOAT; funzione esponenziale: function Exp(X : FLOAT) return FLOAT; elevazione a potenza: function "**" (Left, Right : FLOAT) return FLOAT; funzioni trigonometriche: function function function function function function function function Sin(X : FLOAT) Sin(X, Cycle : Cos(X : FLOAT) Cos(X, Cycle : Tan(X : FLOAT) Tan(X, Cycle : Cot(X : FLOAT) Cot(X, Cycle : return FLOAT) return FLOAT) return FLOAT) return FLOAT) FLOAT; return FLOAT; return FLOAT; return FLOAT; return

FLOAT; FLOAT; FLOAT; FLOAT;

funzioni ciclometriche: function function function function function function function function Arcsin(X : FLOAT) Arcsin(X, Cycle : Arccos(X : FLOAT) Arccos(X, Cycle : Arctan(Y : FLOAT; Arctan(Y : FLOAT; Arccot(X : FLOAT; Arccot(X : FLOAT; return FLOAT; FLOAT) return FLOAT; return FLOAT; FLOAT) return FLOAT; X : FLOAT := 1.0) return FLOAT; X : FLOAT := 1.0; Cycle : FLOAT) return FLOAT; Y : FLOAT := 1.0) return FLOAT; Y : FLOAT := 1.0; Cycle : FLOAT) return FLOAT;

funzioni iperboliche: function function function function Sinh(X Cosh(X Tanh(X Coth(X : : : : FLOAT) FLOAT) FLOAT) FLOAT) return return return return FLOAT; FLOAT; FLOAT; FLOAT;

funzioni iperboliche inverse: Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.2. FUNZIONI MATEMATICHE PREDEFINITE function function function function Arcsinh(X Arccosh(X Arctanh(X Arccoth(X : : : : FLOAT) FLOAT) FLOAT) FLOAT) return return return return FLOAT; FLOAT; FLOAT; FLOAT;

65

Osservazione 2.2.1 I seguenti frammenti di programma sono equivalenti: 1. with Ada.Numerics.Elementary_Functions; procedure ... ... y := Ada.Numerics.Elementary_Functions.Sin(X => 1.57); ... 2. with Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions; procedure ... ... y := Sin(X => 1.57); ... Il secondo modo di scrivere tuttavia da preferire perch pi immediato. Inoltre evidente, a meno che non si sovraccarichi (overloading) la funzione Sin, qual il signicato di Sin. Osservazione 2.2.2 Largomento delle funzioni trigonometriche , per difetto, in radianti. Se si vuole, per esempio lavorare con i gradi sessadecimali bisogna specicare il parametro Cycle => 360.0, come nellesempio seguente. Esempio 2.2.1 Uso della funzione coseno con lo stesso angolo dato in radianti, in gradi sessadecimale e in gradi centesimali: ------Nome del file: COSENO.ADB Autore: Claudio Marsan Data dellultima modifica: 27 febbraio 2002 Scopo: mostrare luso di "Cycle" come parametro nelle funzioni trigonometriche Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Float_Text_IO, Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions;

procedure Coseno is y : FLOAT; begin Ada.Text_IO.Put_Line(Item => "Valore del coseno dellangolo piatto."); Ada.Text_IO.Put(Item => "In radianti: "); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

66

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95 y := Cos(X => 3.1415926); Ada.Float_Text_IO.Put(Item => y, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "In gradi sessadecimali (Cycle = 360.0): "); y := Cos(X => 180.0, Cycle => 360.0); Ada.Float_Text_IO.Put(Item => y, Exp => 0); Ada.Text_IO.New_Line;

Ada.Text_IO.Put(Item => "In gradi centesimali (Cycle = 400.0): "); y := Cos(X => 200.0, Cycle => 400.0); Ada.Float_Text_IO.Put(Item => y, Exp => 0); Ada.Text_IO.New_Line; end Coseno;

2.3

Funzioni

Una funzione pu essere vista come una scatola nera nella quale possono essere messi uno o pi valori. Fuori dalla scatola avremo, come risultato di elaborazioni avvenute allinterno della scatola, un valore dipendente dai valori immessi nella scatola. Esempio 2.3.1 Sqrt (square root, ossia radice quadrata) pu essere vista come una scatola nera che, preso in entrata un valore x (di tipo adeguato!), restituisce in uscita il valore x. Una funzione ha un nome, una lista di parametri formali (o argomenti ) e restituisce un risultato di un certo tipo di dato. Si chiama specicazione della funzione (function specication) (o dichiarazione della funzione, function declaration) una riga come la seguente: function Nome_Funzione(lista_parametri) return TIPO_RISULTATO; Dopo il nome della funzione, che deve soddisfare le regole gi viste per gli identicatori, bisogna indicare i dati che devono essere passati alla funzione tramite la lista dei parametri formali della funzione. Ogni parametro formale seguito da un due punti e dal rispettivo tipo di dato. Per quel che riguarda i parametri formali di una funzione notiamo che: 1. essi contengono i valori che devono essere inseriti nella funzione; 2. essi esistono solo nella funzione; 3. essi sono trattati come costanti nella funzione. Tra le parole riservate return e is bisogna specicare il tipo di dato del valore restituito dalla funzione. Esempio 2.3.2 Nel paragrafo 2.2 sono elencate le specicazioni delle funzioni presenti nel package Ada.Numerics.Elementary_Functions. Esempio 2.3.3 La seguente la specicazione della funzione Massimo, non predenita in Ada 95, che restitisce il pi grande fra due numeri interi: function Massimo(n, m : INTEGER) return INTEGER; n e m sono parametri formali di tipo INTEGER, il risultato della funzione sar di tipo INTEGER. La parte che contiene i calcoli che vengono svolti nella scatola nera, ossia lalgoritmo che produce il risultato della funzione, detta corpo della funzione (function body); esso ha la forma seguente: Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.3. FUNZIONI function Nome_Funzione(lista_parametri) return TIPO_RISULTATO is ... -- dichiarazione di variabili, costanti, ... locali risultato : TIPO_RISULTATO; begin ... -- lalgoritmo return risultato; -- il valore in uscita della funzione end Nome_Funzione;

67

Solitamente lutente non deve sapere nulla di ci che accade nel corpo della funzione (vi siete mai chiesti come fa, per esempio, la vostra TI-89 a calcolare sin, ln, , . . . ? No! Eppure usate queste funzioni tranquillamente!). Il valore che deve essere restituito dalla funzione deve essere preceduto dalla parola riservata return; tale valore deve essere dello stesso tipo di quello indicato nella specicazione della funzione. Quando listruzione return viene eseguita termina anche lesecuzione della funzione. In una funzione possono anche esserci pi istruzioni di tipo return; tuttavia buona prassi fare in modo che ce ne sia una sola e che questa sia lultima. Esempio 2.3.4 Mediante il codice seguente costruiamo una funzione che, presi in entrata i valori reali x e y, ritorna il loro valore medio x+y : 2 function Valore_Medio(x, y : FLOAT) return FLOAT is begin return (x + y)/2.0; end Valore_Medio; Una volta denita una funzione essa pu essere (ri)chiamata allinterno di un programma: ci avviene indicando il nome della funzione con, tra parentesi, il numero di argomenti necessari. In Ada 95 una funzione deve essere dichiarata alla ne della parte dichiarativa di un programma. Esempio 2.3.5 Nel seguente programma si denisce la funzione Valore_Medio: ------Nome del file: CALCOLA_MEDIA.ADB Autore: Claudio Marsan Data dellultima modifica: 27 febbraio 2002 Scopo: definizione di una funzione per calcolare la media di due numeri reali Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Float_Text_IO; procedure Calcola_Media is numero1, numero2, media : FLOAT; function Valore_Medio(x, y : FLOAT) return FLOAT is begin return (x + y)/2.0; end Valore_Medio; begin Ada.Text_IO.Put_Line(Item => "Dare due numeri reali"); Ada.Float_Text_IO.Get(Item => numero1); Ada.Float_Text_IO.Get(Item => numero2); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

68

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

media := Valore_Medio(x => numero1, y => numero2); -- (*) Ada.Text_IO.Put(Item => "La media e: "); Ada.Float_Text_IO.Put(Item => media, Fore => 0, aft => 4, Exp => 0); end Calcola_Media; Ecco ci che succede una volta giunti alla riga (*): la funzione Valore_Medio viene richiamata e, al posto dei parametri formali x e y vengono inseriti i valori numero1 e, rispettivamente, numero2 (questo per ogni occorrenza di x e y); il risultato viene poi assegnato alla variabile media. Si dice che numero1 e numero2 sono i parametri attuali della funzione. La chiamata ad una funzione da considerare unespressione e come tale pu essere utilizzata in altre espressioni. Esempio 2.3.6 Le seguenti istruzioni sono lecite: ... Ada.Float_Text_IO.Put(Item => Valore_Medio(x => numero1, y => numero2)); ... k := Valore_Medio(x => numero1, y => numero2)/numero1 * 100.0; ... k := Valore_Medio(x => numero1 + numero2, y => 12.0 + numero2/10.0); ... Esempio 2.3.7 La seguente funzione serve per stabilire quale fra due interi il maggiore: function MAX (x, y : INTEGER) return INTEGER is begin if x > y then return x; else return y; end if; end MAX; Se abbiamo tre interi A, B e C allora una chiamata come la seguente permette di memorizzare nella variabile k di tipo INTEGER il maggiore fra i tre: ... k := MAX(MAX(A, B), C); ... Esempio 2.3.8 La funzione seguente ritorna TRUE (vero) se largomento (di tipo CHARACTER) passato alla funzione una lettera dellalfabeto inglese: function Lettera (char : CHARACTER) return BOOLEAN is begin case char is when a..z | A..Z => return TRUE; when others => return FALSE; end case; end Lettera; Un frammento di programma che usa la funzione appena denita: Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.3. FUNZIONI ... c : CHARACTER; ... Ada.Text_IO.Get(Item => c); if Lettera(char => c) then Ada.Text_IO.Put_Line(Item => "E una lettera dellalfabeto"); else Ada.Text_IO.Put_Line(Item => "Non e una lettera dellalfabeto"); end if; ...

69

La funzione Lettera pu essere denita elegantemente, senza dover ricorrere allistruzione case, nel modo seguente: function Lettera (char : CHARACTER) return BOOLEAN is return ((char in a..z) OR (char in A..Z)); -- (**) end Lettera; Ci funziona perch in (**), dopo return, presente unespressione booleana composta. Esempio 2.3.9 La funzione Somma, denita poco pi sotto, ritorna la somma delle componenti di un vettore dello spazio, descritto dal tipo di dati seguente: type VETTORE_3D is array(1..3) of FLOAT; function Somma(v : VETTORE_3D) return FLOAT is s : FLOAT := 0.0; begin for i in 1..3 loop s := s + v(i); end loop; return s; end Somma; La variabile s una variabile locale: essa dichiarata allinterno della funzione (subito dopo la specicazione) ed esiste solo allinterno della funzione. Esercizio 2.3.1 Costruire la funzione Segno che, inserito un numero reale x, restituisce 1 se x < 0, 0 se x = 0, 1 se x > 0. Esercizio 2.3.2 Costruire un programma nel quale siano denite: una funzione per calcolare la lunghezza di un vettore dello spazio; una funzione per calcolare il prodotto scalare di due vettori dello spazio; una funzione per calcolare langolo fra due vettori dello spazio e che permetta di controllare la correttezza delle funzioni denite. Esercizio 2.3.3 Costruire una funzione che implementi la seguente funzione reale denita a tratti: f (x) = x2 , se x < 0; f (x) = x, se x [0, 10]; f x) = 1 + x + x2 , se x > 10. Liceo cantonale di Mendrisio, 2002 Claudio Marsan -- accumulatore

70

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

2.4

Procedure

Al contrario delle funzioni le procedure non restituiscono un valore quando sono chiamate ma eseguono un certo numero di istruzioni e, eventualmente, modicano il valore di una o pi variabili. Per le procedure non bisogna indicare un tipo per il risultato; inoltre esse non hanno bisogno di unistruzione return e terminano, normalmente, quando raggiungono lend nale. Esempio 2.4.1 Nel seguente programma si denisce una procedura che permette di scrivere il numero di pagina in alto e al centro di una pagina e fra due trattini (ammettiamo che la larghezza di una pagina sia di 80 colonne). -----Nome del file: TEST_NUMERA_PAGINE.ADB Autore: Claudio Marsan Data dellultima modifica: 12 marzo 2002 Scopo: procedura per numerare le pagine Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Test_Numera_Pagine is N : INTEGER := 20; procedure Numera_Pagine(num_pagina : INTEGER) is begin Ada.Text_IO.New_Page; Ada.Text_IO.Set_Col(To => 38); Ada.Text_IO.Put(Item => "-"); Ada.Integer_Text_IO.Put(Item => num_pagina, Width => 1); Ada.Text_IO.Put(Item => "-"); end Numera_Pagine; begin Numera_Pagine(num_pagina => 9); Numera_Pagine(num_pagina => N); Numera_Pagine(num_pagina => N + 1); end Test_Numera_Pagine;

-- (1) -- (2) -- (3)

Come le funzioni, anche le procedure possono avere dei parametri (nel nostro caso la procedura Numera_Pagine ha il parametro formale num_pagina di tipo INTEGER). In (1), (2), (3) la procedura viene richiamata, con parametri attuali diversi. Quando la procedura viene richiamata viene dapprima creato un magazzino temporaneo num_pagina nel quale viene copiato il parametro attuale (per esempio 9, in (1)); poi vengono eseguite tutte le istruzioni del corpo della procedura e, inne, il controllo ritorna al programma principale (precisamente alla prima istruzione che segue la chiamata della procedura). Osservazione 2.4.1 La chiamata di una procedura unistruzione, mentre la chiamata di una funzione unespressione. Esempio 2.4.2 Il seguente programma contiene una procedura che permette di scrivere una stringa di caratteri data dallutente al centro di una riga. -- Nome del file: TEST_RIGA_CENTRATA.ADB Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.4. PROCEDURE ----Autore: Claudio Marsan Data dellultima modifica: 12 marzo 2002 Scopo: procedura per centrare un testo in una riga Testato con: Gnat 3.13p su Windows 2000

71

with Ada.Text_IO; -- Ada.Integer_Text_IO; procedure Test_Riga_Centrata is procedure Riga_Centrata(testo : STRING) is lunghezza_riga : constant INTEGER := 80; colonna : Ada.Text_IO.Count; begin colonna := Ada.Text_IO.Count((lunghezza_riga - testoLENGTH)/2); Ada.Text_IO.Set_Col(To => colonna); Ada.Text_IO.Put(Item => testo); end Riga_Centrata; begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(" 10 20 30 40"); Ada.Text_IO.Put(" 50 60 70 80"); Ada.Text_IO.Put("1234567890123456789012345678901234567890"); Ada.Text_IO.Put("1234567890123456789012345678901234567890"); Ada.Text_IO.New_Line; Riga_Centrata(testo => "Ciao, mondo!"); Ada.Text_IO.New_Line; Riga_Centrata(testo => "Hello, World!"); Ada.Text_IO.New_Line; Riga_Centrata(testo => "Tanti saluti a Bill Gates"); Ada.Text_IO.New_Line; Riga_Centrata(testo => "Liceo cantonale di Mendrisio"); Ada.Text_IO.New_Line; end Test_Riga_Centrata; Da notare che la costante lunghezza_riga e la variabile colonna, dichiarate allinterno della procedura, sono locali e hanno valore solo allinterno della procedura. possibile ottenere la lunghezza della stringa testo mediante lattributo LENGTH, con la sintassi testoLENGTH. Le procedure che abbiamo visto nora si comportano essenzialmente come scatole nere: ricevono dei valori, li elaborano, compiono alcune azioni ma non restituiscono variabili con il valore modicato. Le procedure possono essere utilizzate anche per trasferire parametri dal programma principale (in vari modi): si parla di associazione di parametri. Per illustrare lassociazione di parametri consideriamo la procedura seguente (essa non fa niente di particolarmente entusiasmante ma permette di vedere di quale modo possono essere i parametri formali di una procedura): procedure Nulla(A : in INTEGER; B : in out INTEGER; C : out INTEGER) is begin Liceo cantonale di Mendrisio, 2002 Claudio Marsan

72 B := B + A; C := 0; end Nulla;

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

I tre parametri formali di Nulla sono scritti su linee diverse solo per chiarezza. Questi parametri sono seguiti dalle parole riservate in e/o out (si dice che: A un parametro di modo in, B un parametro di modo in out e C un parametro di modo out). Possiamo dire che: A usata per introdurre valori nella procedura Nulla; B usata per introdurre e per ricevere valori dalla procedura Nulla; C usata per ricevere valori dalla procedura Nulla. Osservazione 2.4.2 Se non si specica alcun modo si sottointende il modo in (come fatto negli esempi 2.4.1 e 2.4.2). Inseriamo ora la procedura Nulla in un programma completo: -----Nome del file: PROVA_PROCEDURA.ADB Autore: Claudio Marsan Data dellultima modifica: 13 marzo 2002 Scopo: modi dei parametri formali di una procedura Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Prova_Procedura is x, y, z : INTEGER; procedure Nulla(A : in INTEGER; B : in out INTEGER; C : out INTEGER) is begin B := B + A; C := 0; end Nulla; begin x := 1; y := 5; z := 10; Ada.Text_IO.Put_Line(Item => "Prima di richiamare Nulla:"); Ada.Integer_Text_IO.Put(Item => x); Ada.Integer_Text_IO.Put(Item => y); Ada.Integer_Text_IO.Put(Item => z); Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "Dopo aver richiamato Nulla:"); Nulla(A => x, B => y, C => z); Ada.Integer_Text_IO.Put(Item => x); Ada.Integer_Text_IO.Put(Item => y); Ada.Integer_Text_IO.Put(Item => z); Ada.Text_IO.New_Line; end Prova_Procedura; Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.4. PROCEDURE Loutput del programma il seguente: Prima di richiamare Nulla: 1 5 Dopo aver richiamato Nulla: 1 6

73

10 0

Cerchiamo di spiegare cosa succede quando viene richiamata la procedura Nulla(A => x, B => y, C => z) nel nostro programma: Il parametro formale A di modo in: il valore del parametro attuale x (1) viene copiato in A. Il parametro formale B di modo in out: il valore del parametro attuale y (5) viene copiato in B. Il parametro formale C di modo out: non c nessuna operazione di copiatura per i parametri di modo out; cos allinizio della chiamata il valore di C resta indenito. Listruzione B := B + A; assegna a B il valore 6. Listruzione C := 0; assegna a C il valore 0. Le istruzioni di Nulla sono terminate: alluscita i valori dei parametri formali A, B, C sono, rispettivamente, 1, 6, 0; in particolare i valori 6 e 0 saranno ricevuti dalle variabili x e y del programma principale. Osservazione 2.4.3 importante notare che: 1. Allinterno della procedura Nulla il parametro formale A di modo in considerato come una costante: ogni tentativo di modicarne il valore porta ad un errore! 2. Un parametro di modo in out pu essere considerato come una normale variabile allinterno di una procedura (abbiamo potuto modicare B e inserirlo in espressioni). 3. Allinizio di una procedura un parametro di modo out indenito. Riassumendo le regole per i diversi tipi di parametri possiamo dire che: 1. dal punto di vista del programma chiamante: in: il parametro attuale pu essere una variabile o unespressione e deve avere un valore valido al momento della chiamata. Se il valore del parametro attuale una variabile, il suo valore non pu essere modicato durante la chiamata del sottoprogramma. Il suo valore sar sempre identico prima e dopo lesecuzione del sottoprogramma. in out: il parametro attuale deve essere una variabile e deve avere un valore valido al momento della chiamata. Il valore della variabile pu cambiare durante lesecuzione del sottoprogramma e quindi avere un valore diverso alla sua ne. out: il parametro attuale deve essere una variabile. Il suo valore al momento della chiamata non importante poich la procedura lo ignora. Alla ne della chiamata della procedura il parametro attuale avr un valore diverso da quello iniziale. 2. dal punto di vista del sottoprogramma chiamato: in: quando parte lesecuzione di un sottoprogramma il parametro formale ha un valore. Nel sottoprogramma il parametro formale trattato come una costante: pu essere usato ma il suo valore non pu essere modicato. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

74

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95 in out: quando parte lesecuzione della procedura il parametro formale ha un valore. Allinterno della procedura il parametro pu essere usato come una variabile ordinaria: il suo valore pu essere usato e modicato. out: quando parte lesecuzione della procedura il parametro formale indenito.

Esempio 2.4.3 Il programma seguente contiene la procedura Swap(n1, n2) che scambia i valori degli interi n1 e n2: -----Nome del file: PROVA_SCAMBIA.ADB Autore: Claudio Marsan Data dellultima modifica: 13 marzo 2002 Scopo: procedura per scambiare il valore di due interi Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Prova_Scambia is a : INTEGER := 5; b : INTEGER := 9; procedure Swap(n1, n2 : in out INTEGER) is temp : INTEGER; -- variabile temporanea begin temp := n1; n1 := n2; n2 := temp; end Swap; begin Ada.Text_IO.Put_Line(Item => Ada.Integer_Text_IO.Put(Item Ada.Integer_Text_IO.Put(Item Swap(n1 => a, n2 => b); Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => Ada.Integer_Text_IO.Put(Item Ada.Integer_Text_IO.Put(Item end Prova_Scambia; Ecco loutput del programma: Prima dello scambio: 5 9 Dopo lo scambio: 9 5

"Prima dello scambio: "); => a); => b);

"Dopo lo scambio: "); => a); => b);

molto importante che i parametri formali n1 e n2 siano di modo in out: se fossero di modo in non potremmo modicarne il valore. Esercizio 2.4.1 Scrivere un programma nel quale sono denite una procedura per leggere e una procedura per scrivere un vettore dello spazio usuale. Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.5. SOVRACCARICARE FUNZIONI E PROCEDURE

75

2.5

Sovraccaricare funzioni e procedure

Nel paragrafo precedente abbiamo costruito, per esempio, la funzione MAX(x, y) che calcolava il valore massimo fra gli interi x e y. Vorremmo costruire una funzione che calcoli il valore massimo tra i numeri reali x e y. In altri linguaggi di programmazione bisogna denire una funzione con un nome diverso da MAX, per esempio MAX_FLOAT, cosa che non sempre gradita. In Ada invece possibile sovraccaricare le funzioni (overloading di funzioni), ossia denire due funzioni con lo stesso nome nel medesimo programma. Esempio 2.5.1 Il seguente frammento di programma contiene la denizione di due funzioni MAX: una restituisce il pi grande fra due INTEGER, laltra il pi grande fra due FLOAT: ... function MAX (x, y : INTEGER) return INTEGER is begin if x > y then return x; else return y; end if; end MAX; function MAX (x, y : FLOAT) return FLOAT is begin if x > y then return x; else return y; end if; end MAX; ... Il compilatore si arranger poi a stabilire se deve prendere la funzione MAX per gli interi oppure quella per i reali controllando il tipo dei parametri attuali passati alle funzioni. Un simile comportamento non ci comunque nuovo: basta pensare, per esempio, alle procedure Put e Get che usiamo da tempo per loutput e linput di caratteri, interi, reali, . . . possibile sovraccaricare anche gli operatori aritmetici e relazionali: in tal caso il loro simbolo va racchiuso tra virgolette. Esempio 2.5.2 Nel seguente frammento di programma si sovraccarica loperatore + denendo la somma per vettori dello spazio usuale: ... type Vettore_3D is array(1..3) of FLOAT; ... vett_a, vett_b, vett_c : Vettore_3D; ... function "+"(v, w : Vettore_3D) return Vettore_3D is begin return (v(1)+w(1), v(2)+w(2), v(3)+w(3)); end "+"; ... begin ... Liceo cantonale di Mendrisio, 2002 Claudio Marsan

76

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95 vett_c := vett_a + vett_b; ... end ...

La possibilit di sovraccaricare anche gli operatori aumenta notevolmente la leggibilit e la comprensione immediata del codice, evitando, nellesempio sopra, nomi quali Add_Vector o simili. Se ci sono ambiguit, ossia se il compilatore non sa scegliere quale fra le funzioni sovraccaricate deve usare, verr restituito un messaggio derrore.

2.6

Parametri di default

In precedenza abbiamo sempre richiamato le procedure associando i parametri formali ai parametri attuali per posizione; tuttavia possibile fare tale associazione anche per nome, usando la sintassi parametro_formale => parametro_attuale. Esempio 2.6.1 Consideriamo il seguente frammento di programma: ... attX, attY, attZ : INTEGER; ... procedure Nulla(formX, formY, formZ : INTEGER) is begin ... end Nulla; ... begin ... ... Nulla(attX, attY, attZ); Nulla(formZ => attZ, formX => attX, formY => attY); ... end ... Le istruzioni (1) (per posizione) e (2) (per nome) sono equivalenti. possibile assegnare ai parametri formali di una procedura dei valori di default, usando una sintassi simile a quella per linizializzazione di una variabile in fase di dichiarazione della stessa. Esempio 2.6.2 Consideriamo il seguente frammento di programma: ... procedure Solo_un_esempio(primo secondo terzo quarto begin ... end Solo_un_esempio; ...

-- (1) -- (2)

: : : :

in in in in

INTEGER; FLOAT := 0.0; BOOLEAN := FALSE; out INTEGER) is

Nella procedura sopra sono stati assegnati dei valori di default ai parametri formali secondo e terzo: se richiamando la procedura uno o pi valori di default servono ai nostri scopi essi possono essere tralasciati nella lista degli argomenti (naturalmente gli altri parametri della procedura devono essere richiamati per nome!). La procedura Solo_un_esempio pu essere cos richiamata in vari modi: Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.7. SOTTOPROGRAMMI RICORSIVI ... Solo_un_esempio(a, b, c, d); -- per posizione Solo_un_esempio(m, n); -- primo => m, quarto => n Solo_un_esempio(primo => m, quarto => n); Solo_un_esempio(7, secondo => 3.14, k); Solo_un_esempio(4, terzo => TRUE, secondo => 14.3, k); ...

77

2.7

Sottoprogrammi ricorsivi

Un sottoprogramma detto ricorsivo se richiama se stesso. Luso di sottoprogrammi ricorsivi adatto per risolvere alcuni tipi particolari di problemi, soprattutto matematici e deniti n dallinizio in modo ricorsivo. Esempio 2.7.1 Consideriamo la funzione fattoriale, denita da: n! := 1 2 3 (n 1) n, nN, e 0! := 1 .

Ricorsivamente essa pu essere denita nel modo seguente: 0! := 1 n! := n (n 1)! , Infatti: 0! 1! 2! 3! 4! 5! ... = = = = = = 1 1 0! 2 1! 3 2! 4 3! 5 4! = = = = = 11 21 32 46 5 24 = = = = = 1 2 6 24 120 n = 1, 2, . . .

Traduciamo in Ada 95 la funzione fattoriale: function Factorial(n : NATURAL) return POSITIVE is begin if n = 0 then return 1; else return n * Factorial(n - 1); end if; end Factorial; Vediamo cosa succede quando si incontra unistruzione quale la seguente: k := Factorial(4); 1. 4 viene sostituito a n in Factorial e viene restituito il valore 4 * Factorial(3); 2. 3 viene sostituito a n in Factorial e viene restituito il valore 3 * Factorial(2); 3. 2 viene sostituito a n in Factorial e viene restituito il valore Liceo cantonale di Mendrisio, 2002 Claudio Marsan

78 2 * Factorial(1);

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

4. 1 viene sostituito a n in Factorial e viene restituito il valore 1 * Factorial(0) = 1 * 1 = 1; 5. al passo 3. abbiamo ora 2 * 1 = 2; 6. al passo 2. abbiamo ora 3 * 2 = 6; 7. al passo 1. abbiamo ora 4 * 6 = 24; 8. k varr cos 24. importante che ci sia una condizione di arresto (nellesempio 2.7.1: n = 0), altrimenti avremmo una ricorsione innita. Esercizio 2.7.1 Scrivere una versione non ricorsiva della funzione fattoriale. Non sempre luso della ricorsione raccomandabile. Consideriamo per esempio la successione di Fibonacci, denita nel modo seguente: F0 F1 Fn che fornisce la successione 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, . . . Possiamo scrivere il seguente codice in Ada 95: function Fibonacci(n : NATURAL) return NATURAL is begin if n = 0 then return 0; elsif n = 1 then return 1; else return Fibonacci(n-2) + Fibonacci(n-1); end if; end Fibonacci; Esercizio 2.7.2 Simulare listruzione k := Fibonacci(4); e provare poi a scrivere una nuova funzione Fibonacci che non faccia uso della ricorsione e che sia pi eciente. := 0 := 1 := Fn2 + Fn1 ,

n = 2, 3, . . .

2.8

Esempi di algoritmi

Un algoritmo una lista (nita) di passi da eseguire per risolvere un determinato problema. Molto spesso la codica di un algoritmo la parte pi complicata che si presenta nel processo di risoluzione di un problema posto. Nel seguito esamineremo alcuni semplici algoritmi tratti dalla teoria elementare dei numeri e dallanalisi numerica. Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI

79

2.8.1

Calcolo del massimo comune divisore

Il metodo classico imparato alle scuole medie per calcolare il massimo comune divisore di due numeri interi a e b, indicato nel seguito con gcd(a, b) (gcd = greatest common divisor ), richiede la scomposizione in fattori primi dei due numeri. Questa operazione tuttavia spesso problematica. Esempio 2.8.1 Vogliamo calcolare d := gcd(48, 600) con il metodo classico imparato alle scuole medie. Siccome 48 = 24 3 e 600 = 23 3 52 avremo: d = 23 3 = 24 . Esempio 2.8.2 Vogliamo calcolare d := gcd(34142814131413255784100, 25891499490118815) con il metodo classico imparato alle scuole medie. Siccome 34142814131413255784100 = 22 37 52 13 2393 5018390817527 e 25891499490118815 = 3 5 72 17 127 78571 207661 avremo: d = 3 5 = 15 . Da notare che per fare questi calcoli necessaria una calcolatrice in grado di fattorizzare numeri molto grandi rapidamente. Se prendessimo numeri con 40 cifre il procedimento adottato sopra sarebbe, in generale, dicilmente applicabile. Fortunamente possiamo far capo ad un altro metodo, noto come algoritmo di Euclide: Siano r0 = a e r1 = b due interi con a b > 0. Applicando ripetutamente lalgoritmo della divisione con resto si ottiene la successione rj = rj+1 qj+1 + rj+2 , con 0 < rj+2 < rj+1 , per j = 0, 1, 2, . . . , n 2 e rn+1 = 0, ossia: (0) (1) . . . (j 2) . . . (n 4) (n 3) (n 2) (n 1) r0 = r1 q 1 + r2 r1 = r2 q 2 + r3 . . . rj2 = rj1 qj1 + rj . . . rn4 = rn3 qn3 + rn2 rn3 = rn2 qn2 + rn1 rn2 = rn1 qn1 + rn rn1 = rn qn . 0 r2 < r1 , 0 r3 < r2 , . . . 0 rj < rj1 , . . . 0 rn2 < rn3 , 0 rn1 < rn2 , 0 rn < rn1 ,

Si pu dimostrare che vale: gcd(a, b) = rn . Liceo cantonale di Mendrisio, 2002 Claudio Marsan

80

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

Esempio 2.8.3 Vogliamo determinare il massimo comune divisore di 354 e 270. Applicando lalgoritmo di Euclide otteniamo: 354 = 270 = 84 = 18 = 12 = Quindi: gcd(354, 270) = 6 . Infatti: 354 = 2 3 59 da cui gcd(354, 270) = 2 3 = 6 . Esercizio 2.8.1 Scrivere le funzioni gcd(a, b) e lcm(a, b) (least common multiple = minimo comune multiplo), dove a e b sono di tipo INTEGER, e testarle in un programma. Per il calcolo di lcm(a, b) possibile sfruttare la relazione a b = gcd(a, b) lcm(a, b) (a, b N) . Esercizio 2.8.2 Siano a, b N . Sfruttando la seguente denizione gcd(a, b) = a , se a = b gcd(a, b) = gcd(a b, b) , se a > b gcd(a, b) = gcd(a, b a) , altrimenti costruire una funzione ricorsiva per il calcolo del massimo comune divisore degli interi positivi a e b. Esempio 2.8.4 Ecco una possibile soluzione dellesercizio 2.8.1: -----Nome del file: TEST_GCD.ADB Autore: Claudio Marsan Data dellultima modifica: 9 aprile 2002 Scopo: calcolo di "gcd(a,b)" e di "lcm(a,b)" Testato con: Gnat 3.13p su Windows 2000 e 270 = 2 33 5 , 1 270 + 84 3 84 + 18 4 18 + 12 1 12 + 6 2 6 + 0.

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Test_GCD is x, y : INTEGER;

function GCD(a, b : INTEGER) return INTEGER is ------------------------------------------------- Calcolo del massimo comune divisore di due --- numeri interi (versione non ricorsiva) ------------------------------------------------Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI

81

r0 : INTEGER := Abs(a); r1 : INTEGER := Abs(b); old_r1 : INTEGER; begin if a*b = 0 then return 0; end if; if r0 < r1 then r0 := r1; r1 := Abs(a); end if; while r1 /= 0 loop old_r1 := r1; r1 := r0 MOD r1; r0 := old_r1; end loop; return old_r1; end GCD;

function LCM(a, b : INTEGER) return INTEGER is ------------------------------------------------ Calcolo del minimo comune multiplo di due --- numeri interi -----------------------------------------------begin if (a = 0) or (b = 0) then return 0; else return Abs(a*b) / GCD(a, b); end if; end LCM; begin Ada.Text_IO.Put(Item => "Dare un intero: "); Ada.Integer_Text_IO.Get(Item => x); Ada.Text_IO.Put(Item => "Dare un altro intero: "); Ada.Integer_Text_IO.Get(Item => y); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "gcd("); Ada.Integer_Text_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => ","); Ada.Integer_Text_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => ") = "); Ada.Integer_Text_IO.Put(Item => GCD(x, y), Width => 0); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "lcm("); Ada.Integer_Text_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => ","); Ada.Integer_Text_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => ") = "); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

82

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

Ada.Integer_Text_IO.Put(Item => LCM(x, y), Width => 0); end Test_GCD; Esempio 2.8.5 Ecco una possibile soluzione dellesercizio 2.8.2: -----Nome del file: TEST_GCD_RICORSIVA.ADB Autore: Claudio Marsan Data dellultima modifica: 9 aprile 2002 Scopo: calcolo di "gcd(a,b)" (versione ricorsiva) Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Test_GCD_Ricorsiva is x, y : INTEGER; function GCD(a, b : INTEGER) return INTEGER is --------------------------------------------- Calcolo del massimo comune divisore di --- due numeri interi (versione ricorsiva) --------------------------------------------begin if a*b = 0 then return 0; end if; if a = b then return a; elsif a > b then return GCD(a - b, b); else return GCD(a, b - a); end if; end GCD; begin Ada.Text_IO.Put(Item => "Dare un intero: "); Ada.Integer_Text_IO.Get(Item => x); Ada.Text_IO.Put(Item => "Dare un altro intero: "); Ada.Integer_Text_IO.Get(Item => y); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "gcd("); Ada.Integer_Text_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => ","); Ada.Integer_Text_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => ") = "); Ada.Integer_Text_IO.Put(Item => GCD(x, y), Width => 0); end Test_GCD_ricorsiva;

2.8.2

Divisione per tentativi

Sia n N . n detto numero primo (o semplicemente primo) se: Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI n>1 gli unici divisori positivi di n sono 1 e n stesso.

83

Come noto n dai tempi di Euclide esistono inniti numeri primi. Ecco linizio della successione dei numeri primi: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, . . . Un numero n > 1 non primo detto numero composto (o semplicemente composto). La scomposizione di n > 1 in un prodotto di numeri primi detta fattorizzazione di n. noto dalla teoria elementare dei numeri che la fattorizzazione di n unica, a meno dellordine dei fattori. Vogliamo costruire un algoritmo per la fattorizzazione di n, noto come algoritmo della divisione per tentativi (trial division). Dapprima possiamo notare che se n = r s allora non possono essere contemporaneamente r > n e s > n; nel nostro algoritmo potremo cos limitarci ad esaminare come divisori di tentativo i numeri primi non superiori a n. Consideriamo una successione di numeri naturali d0 := 2 d1 d2 . . . dk . . . contenente tutti i numeri primi non superiori a n; tale successione pu essere denita ricorsivamente come segue: d0 := 2 dk+1 := dk + 1, se dk = 2 dk+1 := dk + 2, altrimenti Possiamo allora tentare successivamente la divisione di n per d0 , d1 , d2 , . . .: se n non divisibile per dk si passa ad esaminare dk+1 ;
n se n divisibile per dk si sostituisce n con dk e si tenta ancora la divisione per dk no ad ottenere un valore di n non pi divisibile per dk ; si prosegue allo stesso modo no ad esaurire tutti i divisori di tentativo non superiori a n.

Esempio 2.8.6 Vediamo un esempio con n = 300. d0 := 2: n = 300 divisibile per 2; 2 un fattore; n := n = 150 divisibile per 2; 2 un fattore; n := n = 75 non divisibile per 2; d1 := 3: n = 75 divisibile per 3; 3 un fattore; n := n = 25 non divisibile per 3; d2 := 5: n = 25 divisibile per 5; 5 un fattore; n := n = 25 divisibile per 5; 5 un fattore; n := FINE: n = 22 3 52 Un simile algoritmo presenta qualche inconveniente: se prendiamo, per esempio, n = 101 proveremo a dividere, senza successo, per 2, 3, 5, 7 e poi dovremmo ancora tentare la divisione per 9! Ci ha poco senso poich se un numero non divisibile per 3 non pu essere divisibile per 9. dunque conveniente eliminare dalla successione d0 , d1 , d2 , . . . i multipli di 3 e ottenere cos la successione di divisori di tentativo 2, 3, 5, 7, 11, 13, 17, 19, 23, 25, 29, . . . , che contiene come minimo numero composto il numero 25. Tale successione si genera considerando una variabile ausiliaria h che assume alternativamente i valori 2 e 4. Liceo cantonale di Mendrisio, 2002 Claudio Marsan
n 5 n 5 n 3 n 2 n 2

= 150;

= 75;

= 25;

= 5;

= 1;

84

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

Esempio 2.8.7 Il seguente programma implementa la trial division: -----Nome del file: TEST_TRIAL_DIVISION.ADB Autore: Claudio Marsan Data dellultima modifica: 10 aprile 2002 Scopo: algoritmo della divisione per tentativi Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Test_Trial_Division is N : POSITIVE; d : INTEGER := 2; h : INTEGER := 4; begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il numero da fattorizzare: "); Ada.Integer_Text_IO.Get(Item => N); Ada.Text_IO.New_Line(Spacing => 2); Ada.Integer_Text_IO.Put(Item => N, Width => 0); Ada.Text_IO.Put(Item => " = "); while d**2 <= N loop if (N mod d) > 0 then if d <= 3 then d := 2*d - 1; else h := 6 - h; d := d + h; end if; else Ada.Integer_Text_IO.Put(Item => d, Width => 0); Ada.Text_IO.Put(Item => " "); N := N/d; end if; end loop; if N > 1 then Ada.Integer_Text_IO.Put(Item => N, Width => 0); end if; end Test_Trial_Division; Osservazione 2.8.1 La fattorizzazione unoperazione molto dicile e complessa e lalgoritmo visto in precedenza (trial division) praticamente inutilizzabile gi per numeri con circa 30 cifre. Esistono degli algoritmi pi ecienti e complicati ma il problema dellesecuzione dellalgoritmo in un tempo accettabile si ripresenta gi con numeri di circa 6080 cifre. Il seguente passo, tratto da un discorso di H. W. Lenstra jr. al Congresso Internazionale di Matematica tenuto a Berkeley nel 1986, signicativo per quel che riguarda lenorme dicolt a cui si confrontati nel problema della fattorizzazione di numeri molto grandi: Supponiamo di aver dimostrato che due numeri p e q, di circa 100 cifre, sono primi; Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI con gli odierni test di primalit la cosa molto semplice. Supponiamo inoltre che, per sbaglio, i numeri p e q vadano perduti nella spazzatura e che ci rimanga invece il loro prodotto p q. Come fare per risalire a p e q? Deve essere sentito come una scontta della matematica il fatto che la cosa pi promettente che possiamo fare sia di andare a cercare nellimmondizia o di provare con tecniche mnemoipnotiche . . .

85

Questa dicolt nel fattorizzare numeri molto grandi alla base di alcuni sistemi crittograci moderni (RSA, per esempio) che vengono utilizzati per garantire la privacy nella trasmissione di documenti riservati, la segretezza degli email (vedi, per esempio, PGP), la sicurezza nel commercio elettronico, . . .

2.8.3

Il crivello di Eratostene

Un metodo antico per determinare tutti i numeri primi minori di un certo intero n dato dal crivello di Eratostene. Esso funziona come segue: si scrivano tutti i numeri naturali k 2 no al limite n; si cancellino tutti i multipli di 2, tranne il 2; si cancellino tutti i multipli del prossimo numero non cancellato (il 3), a parte il numero stesso; si ripeta tale procedimento nch il prossimo numero non cancellato risulta essere maggiore di n; i numeri non cancellati saranno tutti i primi minori di n. Esempio 2.8.8 I numeri non cancellati nella tabella 2.1 sono tutti i primi minori di 100 e sono stati ottenuti con il crivello di Eratostene. 0 10 20 30 40 50 60 70 80 90 1 11 21 31 41 51 61 71 81 91 2 12 22 32 42 52 62 72 82 92 3 13 23 33 43 53 63 73 83 93 4 14 24 34 44 54 64 74 84 94 5 15 25 35 45 55 65 75 85 95 6 16 26 36 46 56 66 76 86 96 7 17 27 37 47 57 67 77 87 97 8 18 28 38 48 58 68 78 88 98 9 19 29 39 49 59 69 79 89 99

Tabella 2.1: Crivello di Eratostene Teoricamente il crivello di Eratostene un metodo che pu essere applicato per trovare tutti i numeri primi minori di un certo numero n; in pratica questo metodo diventa inutilizzabile, anche per dei supercalcolatori, quando n ha qualche decina di cifre. Tuttavia perfezionando tale metodo, sono state gradualmente calcolate tavole complete di numeri primi no a circa 10000000 (nel 1909 D. N. Lehmer pubblic: List of Prime Numbers from 1 to 10006721 ), che forniscono molti dati empirici a proposito della distribuzione dei numeri primi. Sulla base di queste tavole si possono poi formulare molte ipotesi (come se la teoria dei numeri fosse una scienza sperimentale) del tutto plausibili, ma spesso estremamente dicili da dimostrare. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

86

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

Esercizio 2.8.3 Implementare in Ada 95 il crivello di Eratostene! Esempio 2.8.9 Il seguente programma contiene una possibile implementazione del crivello di Eratostene: -----Nome del file: ERATOSTENE.ADB Autore: Claudio Marsan Data dellultima modifica: 9 aprile 2002 Scopo: implementazione del crivello di Eratostene Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions;

procedure Eratostene is N_max : CONSTANT POSITIVE := 10_000; type PRIMI is array(1..N_max) of BOOLEAN; p : PRIMI := (others => TRUE); n : POSITIVE;

procedure Intestazione is --------------------------------- Intestazione del programma --------------------------------begin Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "Crivello di Eratostene"); Ada.Text_IO.Put_Line(Item => "----------------------"); Ada.Text_IO.New_Line; end Intestazione;

procedure Leggi_Limite(limite : out POSITIVE) is ---------------------------------------------- Lettura del limite superiore di ricerca ---------------------------------------------dummy : POSITIVE; begin Ada.Text_IO.Put(Item => "Dare il limite massimo di ricerca "); Ada.Text_IO.Put(Item => "(al massimo 10000): "); Ada.Integer_Text_IO.Get(Item => dummy); if dummy > 10_000 then Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI dummy := 10_000; end if; limite := dummy; Ada.Text_IO.New_Line(Spacing => 2); end Leggi_Limite;

87

procedure Crivello_di_Eratostene(lista : in out PRIMI; limite : in POSITIVE) is ----------------------------------------------------------- Il crivello di Eratostene: nellarray "lista" avremo --- TRUE per gli elementi di indice primo e "FALSE" per --- gli elementi di indice composto ----------------------------------------------------------Sqrt_n : POSITIVE := POSITIVE(Sqrt(FLOAT(limite))) + 1; next_prime : POSITIVE := 3; begin lista(1) := FALSE; -- Eliminiamo i multipli di 2 for i in 3..limite loop if (i MOD 2) = 0 then lista(i) := FALSE; end if; end loop; -- Eliminiamo i multipli dei numeri primi dispari while next_prime < Sqrt_n loop if lista(next_prime) then for i in (next_prime + 1)..limite loop if (i MOD next_prime) = 0 then lista(i) := FALSE; end if; end loop; end if; next_prime := next_prime + 2; end loop; end Crivello_di_Eratostene;

procedure Stampa_Primi(lista : in PRIMI; limite : in POSITIVE) is --------------------------------------------------------- Stampa la lista dei numeri primi minori del limite --- fissato e la lunghezza di tale lista --------------------------------------------------------counter : NATURAL := 0; begin for i in 1..limite loop Liceo cantonale di Mendrisio, 2002 Claudio Marsan

88

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95 if lista(i) then counter := counter + 1; Ada.Integer_Text_IO.Put(Item => i, Width => 7); if (counter MOD 10) = 0 then Ada.Text_IO.New_Line; end if; end if; end loop; Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Ci sono "); Ada.Integer_Text_IO.Put(Item => counter, Width => 0); Ada.Text_IO.Put(Item => " numeri primi non piu grandi di "); Ada.Integer_Text_IO.Put(Item => limite, Width => 0); Ada.Text_IO.New_Line; end Stampa_Primi;

begin Intestazione; Leggi_Limite(limite => n); Crivello_di_Eratostene(lista => p, limite => n); Stampa_Primi(lista => p, limite => n); end Eratostene;

2.8.4

Lalgoritmo di Sundaram

Oltre al crivello di Eratostene possiamo generare i numeri primi minori di un certo numero pressato usando lalgoritmo di Sundaram, descritto nel seguito: Siano date le successioni seguenti: a1k a2k a3k ... ank = 3k + 1 = 5k + 2 = 7k + 3 = (2n + 1)k + n : 4, 7, 10, 13, . . . : 7, 12, 17, 22, . . . : 10, 17, 24, 31, . . .

con k, n N e sia T linsieme degli elementi delle successioni denite sopra. Allora: 2z + 1 un numero primo z T.

Le considerazioni seguenti servono per dimostrare la validit di quanto aermato: Sia dapprima z T . Dobbiamo mostrare che 2z + 1 composto. Se z T allora esistono n, k N tali che z = (2n + 1)k + n e quindi si ottiene: 2z + 1 = 2[(2n + 1)k + n] + 1 = 2k(2n + 1) + 2n + 1 = (2n + 1) (2k + 1) , quindi 2z + 1 composto. Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI Sia 2z + 1 un numero composto. Dobbiamo mostrare che z T .

89

Ovviamente 2z + 1 un numero dispari e, essendo composto, sar scomponibile nella forma 2z + 1 = a b, ossia della forma a = 2k + 1 Allora: 2z + 1 = (2k + 1) (2n + 1) da cui 2z + 1 = 4kn + 2k + 2n + 1 , e quindi z = (2n + 1)k + n . Ma ci signica che z T . Esempio 2.8.10 Vogliamo ricercare i numeri primi dispari minori di un certo valore dispari q, per esempio q = 79. Dallequazione 2z + 1 = q ricaviamo il valore massimo che pu assumere z, nel nostro caso sar z = 39. Costruiremo linsieme T della dimostrazione del teorema come tabella e ad ogni riga ci arresteremo prima di arrivare a 39: a1k a2k a3k a4k a5k a6k a7k a8k : : : : : : : : 4 7 10 13 16 19 22 25 7 12 17 22 27 32 37 10 13 17 22 24 31 31 38 16 27 38 19 32 22 37 25 28 31 34 37 e b = 2n + 1 (k, n N ) . con a, b dispari,

( inutile continuare poich per n > 8 avremo an2 > 39 e gli altri numeri nella prima colonna appaiono gi nella prima riga). Nella tabella T mancano i numeri seguenti: 1, 2, 3, 5, 6, 8, 9, 11, 14, 15, 18, 20, 21, 23, 26, 29, 30, 33, 35, 36, 39 che, inseriti al posto di z nellespressione 2z + 1, forniscono tutti i numeri primi dispari minori o uguali a 79: 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79 . Esercizio 2.8.4 Trovare tutti i numeri primi minori di 120 applicando lalgoritmo di Sundaram. Esercizio 2.8.5 Implementare in Ada 95 lalgoritmo di Sundaram! Esempio 2.8.11 Il seguente programma contiene una possibile implementazione dellalgoritmo di Sundaram: -----Nome del file: SUNDARAM.ADB Autore: Claudio Marsan Data dellultima modifica: 9 aprile 2002 Scopo: implementazione dellalgoritmo di Sundaram Testato con: Gnat 3.13p su Windows 2000

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

90

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

with Ada.Text_IO, Ada.Integer_Text_IO;

procedure Sundaram is num_max : CONSTANT POSITIVE := 10_000; type PRIMI is array(1..num_max) of BOOLEAN; p : PRIMI := (others => TRUE); n : POSITIVE;

procedure Intestazione is --------------------------------- Intestazione del programma --------------------------------begin Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "Algoritmo di Sundaram"); Ada.Text_IO.Put_Line(Item => "---------------------"); Ada.Text_IO.New_Line; end Intestazione;

procedure Leggi_Limite(limite : out POSITIVE) is ---------------------------------------------- Lettura del limite superiore di ricerca ---------------------------------------------dummy : POSITIVE;

begin Ada.Text_IO.Put(Item => "Limite massimo di ricerca (tra 10 e 10000): "); Ada.Integer_Text_IO.Get(Item => dummy); if dummy > 10_000 then dummy := 10_000; elsif dummy < 10 then dummy := 10; end if; limite := dummy; Ada.Text_IO.New_Line(Spacing => 2); end Leggi_Limite;

procedure Algoritmo_di_Sundaram(lista : in out PRIMI; limite : in POSITIVE) is ------------------------------------------------- Implementazione dellalgoritmo di Sundaram ------------------------------------------------Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI

91

z_max : POSITIVE; k_max : POSITIVE; dummy : POSITIVE; begin z_max := (limite - 1)/2; k_max := (z_max - 1)/3; for i in 1..k_max loop for j in 1..k_max loop dummy := (2*i + 1)*j + i; if dummy <= z_max then lista(dummy) := FALSE; end if; end loop; end loop; end Algoritmo_di_Sundaram;

procedure Stampa_Primi(lista : in PRIMI; limite : in POSITIVE) is --------------------------------------------------------- Stampa la lista dei numeri primi minori del limite --- fissato e la lunghezza di tale lista --------------------------------------------------------counter : NATURAL := 1; z_max : NATURAL := (limite - 1)/2; begin Ada.Integer_Text_IO.Put(Item => 2, Width => 7); for i in 1..z_max loop if lista(i) then counter := counter + 1; Ada.Integer_Text_IO.Put(Item => 2*i + 1, Width => 7); if (counter MOD 10) = 0 then Ada.Text_IO.New_Line; end if; end if; end loop; Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Ci sono "); Ada.Integer_Text_IO.Put(Item => counter, Width => 0); Ada.Text_IO.Put(Item => " numeri primi non piu grandi di "); Ada.Integer_Text_IO.Put(Item => limite, Width => 0); Ada.Text_IO.New_Line; end Stampa_Primi;

begin Intestazione; Leggi_Limite(limite => n); Algoritmo_di_Sundaram(lista => p, limite => n); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

92

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

Stampa_Primi(lista => p, limite => n); end Sundaram;

2.8.5

Il metodo della fattorizzazione di Fermat

Descriviamo ora un metodo per scomporre un numero intero positivo dispari in un prodotto di due fattori, non necessariamente primi: tale metodo noto come metodo della fattorizzazione di Fermat e risale allinizio del XVII secolo. Sia N = a b un numero dispari composto. Se riuscissimo a scrivere N come N = x2 y 2 = (x y) (x + y) allora avremmo una scomposizione in fattori (non necessariamente primi ma pi piccoli di N e quindi pi facili da fattorizzare) di N . Le seguenti considerazioni ci permettono di giungere allalgoritmo: Ovviamente deve essere x > N . Calcoliamo dapprima m := [ N ] + 1 (con [k] intendiamo la parte intera di k), che il pi piccolo valore possibile di x (a meno che N sia un quadrato perfetto; in tal caso avremmo per N = x2 02 ). Consideriamo poi z := m2 N e controlliamo se esso un quadrato perfetto. Se z un quadrato perfetto allora abbiamo terminato: x = m e y = z. Se z non un quadrato perfetto proviamo con il prossimo valore di x, ossia m+1 e calcoliamo (m + 1)2 N = m2 + 2m + 1 N = z + 2m + 1 . Testiamo poi se esso un quadrato perfetto, eccetera. Esempio 2.8.12 Sia N = 13199. Allora: N 114.88 e quindi N non un quadrato perfetto. = Avremo cos: 1. m = 115, z = 1152 13199 = 26, che non un quadrato perfetto; 2. m = 116, z = 1162 13199 = 257, che non un quadrato perfetto; 3. m = 117, z = 1172 13199 = 490, che non un quadrato perfetto; 4. m = 118, z = 1182 13199 = 725, che non un quadrato perfetto; 5. . . .; 6. m = 131, z = 1312 13199 = 3962, che non un quadrato perfetto; 7. m = 132, z = 1322 13199 = 4225, che un quadrato perfetto (infatti: 4225 = 652 ). Dunque: x = 132 e quindi: 13199 = (132 65) (132 + 65) , ossia: 13199 = 67 197 . Il metodo di Fermat viene usato come metodo dappoggio ad altri metodi di fattorizzazione: con esso possibile ridurre la magnitudine del numero da fattorizzare. Esso , generalmente, poco eciente, tranne in alcuni casi particolari (per esempio quando N il prodotto di due numeri prossimi a N ). Claudio Marsan Liceo cantonale di Mendrisio, 2002 e y = 65

2.8. ESEMPI DI ALGORITMI Esercizio 2.8.6 Implementare in Ada 95 il metodo di fattorizzazione di Fermat!

93

Esempio 2.8.13 Il seguente programma contiene una possibile implementazione metodo di fattorizzazione di Fermat: -----Nome del file: FERMAT_FACTORIZATION.ADB Autore: Claudio Marsan Data dellultima modifica: 9 aprile 2002 Scopo: metodo di Fermat per fattorizzare un numero intero dispari Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Long_Integer_Text_IO, Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions;

procedure Fermat_Factorization is n, f1, f2 : LONG_INTEGER; procedure Intestazione is --------------------------------- Intestazione del programma --------------------------------begin Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "Fattorizzazione con il metodo di Fermat"); Ada.Text_IO.Put_Line(Item => "---------------------------------------"); Ada.Text_IO.New_Line; end Intestazione;

procedure Leggi_Numero(numero : out LONG_INTEGER) is -------------------------------------------------------------------- Lettura del numero dispari da fattorizzare (se il numero dato --- non e dispari vengono eliminate tutte le potenze di 2; se il --- numero dato minore di 5 verra posto uguale a 5) -------------------------------------------------------------------dummy : LONG_INTEGER;

begin Ada.Text_IO.Put(Item => "Dare un numero intero dispari: "); Ada.Long_Integer_Text_IO.Get(Item => dummy); while (dummy MOD 2) = 0 loop dummy := dummy / 2; end loop; if dummy < 5 then dummy := 5; end if; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

94

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95 numero := dummy; Ada.Text_IO.New_Line(Spacing => 2); end Leggi_Numero;

function Integer_Sqrt(x : LONG_INTEGER) return LONG_INTEGER is -------------------------------------------------------- Calcola la parte intera della radice di un intero -------------------------------------------------------begin return LONG_INTEGER(Sqrt(FLOAT(x)) - 0.5); end Integer_Sqrt;

procedure Fermat_Method(numero : in LONG_INTEGER; a, b : out LONG_INTEGER) is ----------------------------------------------------------------------------- Implementazione del metodo di Fermat: nei parametri "a" e "b" sono --- restituiti i due fattori trovati (essi non sono necessariamente primi) ----------------------------------------------------------------------------continua : BOOLEAN; m, z, zSqrt : LONG_INTEGER; begin m := Integer_Sqrt(x => numero); if numero = m**2 then a := m; b := m; continua := FALSE; else m := m + 1; continua := TRUE; end if; while continua loop z := m**2 - numero; zSqrt := Integer_Sqrt(x => z); if z = zSqrt**2 then continua := FALSE; a := m - zSqrt; b := m + zSqrt; else m := m + 1; end if; end loop; end Fermat_Method;

procedure Stampa_Fattori(numero, a, b : in LONG_INTEGER) is --------------------------- Stampa dei risultati -Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI -------------------------begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il numero "); Ada.Long_Integer_Text_IO.Put(Item => numero, Width => 0); Ada.Text_IO.Put(Item => " e il prodotto di: "); Ada.Long_Integer_Text_IO.Put(Item => a, Width => 0); Ada.Text_IO.Put(Item => " e "); Ada.Long_Integer_Text_IO.Put(Item => b, Width => 0); Ada.Text_IO.Put_Line(Item => "."); end Stampa_Fattori;

95

begin Intestazione; Leggi_Numero(numero => n); Fermat_Method(numero => n, a => f1, b => f2); stampa_Fattori(numero => n, a => f1, b => f2); end Fermat_Factorization;

2.8.6

Il metodo di bisezione
f: [a, b] x

Consideriamo la funzione reale R , y = f (x)

continua sullintervallo [a, b] e con f (a) f (b) < 0. Come noto dallanalisi esiste (almeno!) un valore x0 ]a, b[ tale che f (x0 ) = 0 (si dice che x0 uno zero di f ). Spesso dicile trovare il valore esatto degli zeri di f (per esempio quando f (x) = 0 unequazione trascendente oppure unequazione polinomiale di grado superiore al quarto) e dunque ci si deve accontentare di un valore approssimato per gli zeri di f . Un metodo abbastanza semplice per trovare uno zero di f il metodo di bisezione. Esso funziona nel modo seguente: 1. si pone x0 := a e x1 := b; 2. si calcola x2 :=
1 2

(x0 + x1 );

3. se f (x2 ) = 0 allora x2 uno zero di f e lalgoritmo termina; 4. si calcola K := f (x0 ) f (x2 ): se K < 0 allora uno zero della funzione sar nellintervallo ]x0 , x2 [; si pone poi x1 := x2 ; se K > 0 allora uno zero della funzione sar nellintervallo ]x2 , x1 [; si pone poi x0 := x2 ;
1 5. si calcola x2 := 2 (x0 + x1 ) e si procede come sopra ntanto che si trova uno zero di f oppure ntanto che lintervallo contenente lo zero di f ha unampiezza minore di una quantit predenita.

Esercizio 2.8.7 Scrivere un programma che permetta di calcolare con il metodo di bisezione lo zero reale (precisione desiderata: 105 ) della funzione, data tramite equazione, f (x) = x3 2x2 + 3x 7 . Liceo cantonale di Mendrisio, 2002 Claudio Marsan

96

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

Esempio 2.8.14 Il seguente programma presenta una possibile soluzione dellesercizio precedente: -----Nome del file: METODO_DI_BISEZIONE.ADB Autore: Claudio Marsan Data dellultima modifica: 9 aprile 2002 Scopo: implementazione del metodo di bisezione Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Float_Text_IO; procedure Metodo_di_bisezione is

function F(X : FLOAT) return FLOAT is ------------------------------------------------------ La funzione della quale si cerca uno zero reale -----------------------------------------------------begin return (X*X*X - 2.0*X*X + 3.0*X - 7.0); end F;

function Intervallo_Buono(x1, x2 : FLOAT) return BOOLEAN is ---------------------------------------- Ritorna "TRUE" se F(x1)*F(x2)<0.0 ---------------------------------------begin return (F(x1) * F(x2) < 0.0); end Intervallo_Buono;

procedure Leggi_Intervallo(sinistro : out FLOAT; destro : out FLOAT) is ----------------------------------------------------------------- Lettura dellintervallo nel quale si ricerca lo zero reale --- della funzione ----------------------------------------------------------------begin Ada.Text_IO.Put(Item => "Estremo sinistro dellintervallo: "); Ada.Float_Text_IO.Get(Item => sinistro); Ada.Text_IO.Put(Item => "Estremo destro dellintervallo: "); Ada.Float_Text_IO.Get(Item => destro); end Leggi_Intervallo;

procedure Stampa_Risultato(X, x1, x2: in FLOAT) is ------------------------------------------------------------------ Stampa il valore dello zero trovato nellintervallo [x1,x2] -Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI ----------------------------------------------------------------begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Nellintervallo ["); Ada.Float_Text_IO.Put(Item => x1, Fore => 0, Aft => 5, Exp => 0); Ada.Text_IO.Put(Item => ", "); Ada.Float_Text_IO.Put(Item => x2, Fore => 0, Aft => 5, Exp => 0); Ada.Text_IO.Put(Item => "] e stato trovato lo zero "); Ada.Float_Text_IO.Put(Item => X, Fore => 0, Aft => 5, Exp => 0); end Stampa_Risultato;

97

EPSILON ok a, b a1, a2 zero

: : : : :

constant FLOAT := 1.0E-5; BOOLEAN := FALSE; FLOAT; FLOAT; FLOAT;

-- errore tollerato

begin ---------------------------------------- Lettura di un intervallo adeguato ---------------------------------------while not(ok) loop Leggi_Intervallo(sinistro => a, destro => b); if a >= b then Ada.Text_IO.Put(Item => "Estremi non validi per un intervallo!"); Ada.Text_IO.New_Line(Spacing => 2); else if not(Intervallo_Buono(x1 => a, x2 => b)) then Ada.Text_IO.Put(Item => "Intervallo non adeguato!"); Ada.Text_IO.New_Line(Spacing => 2); else ok := TRUE; end if; end if; end loop;

a1 := a; a2 := b; ----------------------------- Il metodo di bisezione ----------------------------loop zero := (a1 + a2) / 2.0; if Intervallo_Buono(x1 => a1, x2 => zero) then a2 := zero; else a1 := zero; end if; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

98

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95 exit when (Abs(F(a1) - F(a2)) <= EPSILON) or (F(zero) = 0.0); end loop; Stampa_Risultato(X => zero, x1 => a, x2 => b);

end Metodo_di_bisezione; Ecco un esempio di output del programma: Estremo sinistro dellintervallo: -1.0 Estremo destro dellintervallo: 3.0

Nellintervallo [-1.00000, 3.00000] e stato trovato lo zero 2.13249

2.8.7

Il metodo di Newton

Un altro metodo per la ricerca di uno zero di una funzione f continua e derivabile in un intervallo [a, b] dato dal metodo di Newton, noto anche come metodo iterativo. Tale metodo, se fornisce una soluzione, converge verso questa pi rapidamente del metodo di bisezione visto nel paragrafo precedente. Lo schema delliterazione molto semplice: si parte da un valore iniziale x0 e si esegue literazione f (xn ) xn+1 = xn , per n = 0, 1, 2, . . . f (xn ) (con f si intende la funzione derivata di f ), che verr ripetuta ntanto che f (xn ) sucientemente vicino a zero. Esercizio 2.8.8 Scrivere un programma che permetta di calcolare con il metodo di Newton lo zero reale (precisione desiderata: 105 ) della funzione, data tramite equazione, f (x) = x3 2x2 + 3x 7 . Esempio 2.8.15 Il seguente programma presenta una possibile soluzione dellesercizio precedente: -----Nome del file: METODO_DI_NEWTON.ADB Autore: Claudio Marsan Data dellultima modifica: 9 aprile 2002 Scopo: implementazione del metodo di Newton Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Float_Text_IO; procedure Metodo_di_Newton is

function F(X : FLOAT) return FLOAT is --------------------------------------------------------------- La funzione per la quale si vuole trovare uno zero reale --------------------------------------------------------------begin return (X*X*X - 2.0*X*X + 3.0*X - 7.0); Claudio Marsan Liceo cantonale di Mendrisio, 2002

2.8. ESEMPI DI ALGORITMI end F;

99

function dF(X : FLOAT) return FLOAT is ------------------------------------------------------------------------------ La derivata della funzione per la quale si vuole trovare uno zero reale -----------------------------------------------------------------------------begin return (3.0*X*X - 4.0*X + 3.0); end dF;

procedure Leggi_Valore_Iniziale(x0 : out FLOAT) is -------------------------------------------------- Lettura del valore iniziale delliterazione -------------------------------------------------begin Ada.Text_IO.Put(Item => "Valore iniziale delliterazione: "); Ada.Float_Text_IO.Get(Item => x0); end Leggi_Valore_Iniziale;

procedure Stampa_Risultato(X : in FLOAT) is ------------------------------------------ Stampa il valore dello zero trovato -----------------------------------------begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "E stato trovato lo zero "); Ada.Float_Text_IO.Put(Item => X, Fore => 0, Aft => 5, Exp => 0); end Stampa_Risultato;

EPSILON ok x_iniziale zero

: : : :

constant FLOAT := 1.0E-5; BOOLEAN := FALSE; FLOAT; FLOAT;

-- errore tollerato

begin --------------------------------------------- Lettura di un valore iniziale adeguato --------------------------------------------while not(ok) loop Leggi_Valore_Iniziale(x0 => x_iniziale); if dF(X => x_iniziale) = 0.0 then Ada.Text_IO.Put(Item => "Valore iniziale non valido!"); Ada.Text_IO.New_Line(Spacing => 2); else ok := TRUE; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

100 end if; end loop;

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

-------------------------- Il metodo di Newton -------------------------loop zero := x_iniziale - F(X => x_iniziale) / dF(X => x_iniziale); exit when Abs(F(X => zero)) <= EPSILON; x_iniziale := zero; end loop; Stampa_Risultato(X => zero); end Metodo_di_Newton; Ecco un esempio di output del programma: Valore iniziale delliterazione: -1.17

E stato trovato lo zero 2.13249 Ecco un altro esempio di output del programma: Valore iniziale delliterazione: 3.1415

E stato trovato lo zero 2.13249 Per maggiori ragguagli sul metodo di Newton confronta le lezioni di matematica.

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 3 Tipi di dati in Ada 95


In questo capitolo riprendiamo a parlare dei tipi di dati, approfondendo la questione.

3.1

Astrazione dei dati

Abbiamo gi visto che il compito di un programma di manipolare dei dati e gli oggetti ad essi associati, che rappresentano spesso fenomeni del mondo reale. Quando parliamo di fenomeni del mondo reale usiamo una tecnica nota come astrazione. Con lastrazione si crea dunque un modello oppure un concetto di un fenomeno del mondo reale. Esempio 3.1.1 La parola camion unastrazione per un veicolo che pu essere utilizzato per trasportare delle cose. Possiamo parlare di un camion e dire che ha certe propriet: una capacit, una lunghezza, un costo di manutenzione al chilometro, . . . Lastrazione pu essere fatta a pi livelli. Esempio 3.1.2 Per un meccanico naturale pensare ad un camion come ad un oggetto composto da diverse componenti (per esempio: il sistema frenante, la scatola del cambio, . . . ). Scendendo di un altro livello possiamo dire che la scatola del cambio composta da tante parti (gli ingranaggi del cambio, lasse, . . . ): questo livello pi appropriato per la progettazione o per la riparazione della scatola del cambio. Il livello di astrazione scelto dipende quindi dal contesto nel quale il fenomeno deve essere studiato. Il vantaggio nel poter scegliere il livello di astrazione che permette di ignorare dettagli irrilevanti in favore di quelle propriet importanti per lo studio in corso. Esempio 3.1.3 Lautista del camion non interessato a come gli ingranaggi del cambio si muovono allinterno della scatola ma deve solo sapere come usare il cambio. In Ada 95 possibile applicare lastrazione dei dati usando tipi e pacchetti dichiarati dal programmatore. Ecco i vantaggi derivanti dallintrodurre dei nuovi tipi che rappresentano in modo specico le propriet di un fenomeno: il programma risulta pi chiaro poich pi legato alla realt; il programma risulta pi sicuro poich il compilatore controlla che non vengano mescolati illegalmente diversi tipi e che non vengano assegnati valori illegali a variabili; 101

102

CAPITOLO 3. TIPI DI DATI IN ADA 95

il programma risulta meno complesso poich possibile scegliere un adeguato livello di astrazione, evitando dettagli superui. In Ada 95 si distinguono i seguenti tipi di dati: tipi scalari, usati per descrivere oggetti che possono essere espressi con un unico numero (per esempio: temperatura, altezza, massa, . . . ); tra di essi distinguiamo: tipi numerici (interi e reali) tipi enumerativi (elenchi) tipi discreti, che comprendono i tipi numerici interi e i tipi enumerativi tipi composti o tipi strutturati, usati per descrivere oggetti di dati pi complessi (per esempio: le componenti di un vettore, i dati di una scheda clinica); tra di essi distinguiamo: array, per gestire dati complessi dello stesso tipo record, per gestire dati complessi di tipo diverso tipi accesso, noti come puntatori in altri linguaggi; servono alla gestione dinamica dei dati; tipi privati, usati per creare tipi di dati astratti che devono essere occultati allutente (data hiding: lutente sa che esistono questi dati e li pu usare, ma non sa come sono implementati). Il programmatore pu denire (anzi: in Ada 95 addirittura incoraggiato a farlo) nuovi tipi di dati. Ricordiamo che: un nuovo tipo si denisce con la sintassi seguente: type NOME_DEL_TIPO is DEFINIZIONE_DEL_TIPO; dove DEFINIZIONE_DEL_TIPO dipende dal tipo che si sta dichiarando; la dichiarazione di un nuovo tipo va inserita nella parte dichiarativa del programma; nessun oggetto del tipo viene creato con la dichiarazione del tipo; il nome di un nuovo tipo pu essere poi usato come quello di tipi predeniti in Ada 95, in particolare per denire variabili e costanti (nota: in questo caso la dichiarazione del tipo deve precedere quella di relative variabili e costanti). A volte risulta dicile distinguere tra il nome di una variabile e il nome di un tipo; per questo motivo diversi esperti di programmazione in Ada 95 consigliano di terminare il nome del tipo con _TYPE oppure di premettere al nome del tipo i caratteri T_. Esempio 3.1.4 perfettamente lecito denire il tipo type TEMPERATURA is ...; per questo non ci consente pi di usare TEMPERATURA come nome di variabile; le dichiarazioni type TEMPERATURA_TYPE is ...; type T_TEMPERATURA is ...; evidenziano subito che TEMPERATURA_TYPE e T_TEMPERATURA sono il nome di un tipo di dato e permette cos di utilizzare TEMPERATURA come nome di variabile. Altri programmatori consigliano invece di scrivere completamente in maiuscolo il nome del tipo e di non scrivere completamente in maiuscolo il nome delle variabili: cos importante scegliere (o meglio: imporsi) uno stile e rispettarlo scrupolosamente, almeno allinterno dello stesso progetto! Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.2. ANCORA SUI TIPI INTERI

103

3.2

Ancora sui tipi interi

Con la dichiarazione di un tipo intero sono dichiarati anche il valore minimo e il valore massimo che esso pu assumere. Esempio 3.2.1 Le seguenti dichiarazioni di tipi interi evidenziano chiaramente quali sono i valori minimo e massimo che variabili di questi tipi possono assumere: type NUMERI_DI_RIGA is range 1..66; type PUNTEGGIO is range 0..100; type NEGATIVO is range -100_000..-1; La sintassi generale per la denizione di un tipo intero la seguente: type NOME_DEL_TIPO is range valore_minimo..valore_massimo; dove valore_minimo e valore_massimo sono delle espressioni intere statiche (costanti), con valore_minimo <= valore_massimo. In ogni compilatore Ada 95 predenito, nel package STANDARD, il tipo INTEGER, che pu essere considerato come: type INTEGER is range least_integer..greatest_integer; dove least_integer e greatest_integer sono il pi piccolo e il pi grande intero rappresentabili con il compilatore Ada 95 in uso. In realt la cosa un po pi complicata (vedi root_integer, nel Language Reference Manual ). Osservazione 3.2.1 Attenzione! Compilatori diversi possono avere limiti diversi! Ci signica che per garantirsi la portabilit dei programmi su piattaforme diverse bisognerebbe evitare di usare il tipo INTEGER predenito. I limiti diversi sono dovuti a come il compilatore Ada 95 memorizza gli interi (ossia: quanti bytes occupa un intero quando viene memorizzato); se nel denire un nuovo tipo di intero non si rispettano i limiti il compilatore reclamer con un messaggio derrore. Esempio 3.2.2 Sono ammesse anche dichiarazioni di tipo come la seguente: ... Max_Line : constant := 25; Max_Col : constant := 80; type POSIZIONE_SCHERMO is range 1..(Max_Line * Max_Col); ... (infatti Max_Line e Max_Col, essendo costanti, sono statiche). Esempio 3.2.3 Se giocando a freccette si possono totalizzare da 0 a 100 punti, allora i risultati di un torneo fra tre persone possono essere descritti in modo chiaro con le seguenti dichiarazioni: type PUNTEGGIO Punti_Pippo : Punti_Pluto : Punti_Orazio : is range 0..100; PUNTEGGIO; PUNTEGGIO; PUNTEGGIO;

Ogni tentativo, anche casuale, di assegnare un punteggio negativo o superiore a 100 genererebbe un messaggio errore in fase di esecuzione: ci aiuta a trovare errori logici nel programma. La stessa cosa non potremmo dirla se usassimo semplicemente delle variabili di tipo INTEGER. Possiamo dire che: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

104

CAPITOLO 3. TIPI DI DATI IN ADA 95 il tipo INTEGER rappresenta il concetto matematico di numero intero, non ha nessuna connessione con particolari oggetti reali ed troppo vago per un modello reale del punteggio di un torneo di freccette.

Tutte le operazioni che si possono fare con il tipo INTEGER (assegnazione, confronto, addizione, . . . ) si possono fare anche con gli altri tipi interi, ma non permesso mescolare luso di dierenti tipi. Esempio 3.2.4 Consideriamo le dichiarazioni seguenti: type NUMERI_DI_RIGA is range 1..66; type PUNTEGGIO is range 0..100; riga_corrente, prossima_riga : NUMERI_DI_RIGA; Punti_Pippo : PUNTEGGIO; k : INTEGER; Le seguenti assegnazioni sono tutte non legali: riga_corrente := Punti_Pippo; k := prossima_riga; Punti_Pippo := k; cos come le seguenti espressioni: ... := riga_corrente + k; ... := Punti_Pippo * k; Sono invece legali: riga_corrente := prossima_riga; riga_corrente := NUMERI_DI_RIGA(k); ... := Punti_Pippo * PUNTEGGIO(K); -- stesso tipo -- conversione di tipo -- conversione di tipo -- errore! -- errore! -- errore! -- errore! -- errore!

La conversione di tipo (esplicita) permessa tra tutti i tipi di dati numerici, anche se in taluni casi pu comportare la perdita di informazioni. Esempio 3.2.5 Consideriamo il seguente caso particolare: type NUMERO_PAGINE is range 1..500; type INDICE is range 1..500; pagine : NUMERO_PAGINE; i : INDICE; Le variabili pagine e i sono di tipo diverso, sebbene siano dichiarate allo stesso modo: non possono essere cos mischiate. Sar tuttavia possibile utilizzare listruzione di conversione seguente: pagine := NUMERO_PAGINE(i); La conversione di un FLOAT in un INTEGER comporta larrotondamento allintero pi vicino al FLOAT. Esempio 3.2.6 Consideriamo il seguente programma: ----Nome del file: ROUND.ADB Autore: Claudio Marsan Data dellultima modifica: 7 maggio 2002 Scopo: conversione FLOAT -> INTEGER Liceo cantonale di Mendrisio, 2002

Claudio Marsan

3.2. ANCORA SUI TIPI INTERI -- Testato con: Gnat 3.13p su Windows 2000

105

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Round is x y z n : : : : FLOAT := 1.7; FLOAT := 1.3; FLOAT := 1.5; INTEGER;

begin Ada.Text_IO.New_Line; n := INTEGER(x); Ada.Integer_Text_IO.Put(Item Ada.Text_IO.New_Line; n := INTEGER(y); Ada.Integer_Text_IO.Put(Item Ada.Text_IO.New_Line; n := INTEGER(z); Ada.Integer_Text_IO.Put(Item Ada.Text_IO.New_Line; n := INTEGER(-x); Ada.Integer_Text_IO.Put(Item Ada.Text_IO.New_Line; n := INTEGER(-y); Ada.Integer_Text_IO.Put(Item Ada.Text_IO.New_Line; n := INTEGER(-z); Ada.Integer_Text_IO.Put(Item Ada.Text_IO.New_Line; end Round; Ecco loutput:

=> n, Width => 0);

=> n, Width => 0);

=> n, Width => 0);

=> n, Width => 0);

=> n, Width => 0);

=> n, Width => 0);

2 1 2 -2 -1 -2

Osservazione 3.2.2 In Ada 95 larrotondamento di un reale che si trova esattamente fra due interi, ossia avente la parte decimale uguale a 0.5, avviene verso il prossimo intero se il numero positivo o verso il precedente intero se il numero negativo, per esempio: INTEGER(4.5) vale 5 mentre INTEGER(-4.5) vale -5. Come gi visto, quando utilizziamo delle costanti intere per le quali non si esplicitato il tipo, esse sono considerate di tipo universal_integer e sono convertite automaticamente nel tipo intero corretto. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

106

CAPITOLO 3. TIPI DI DATI IN ADA 95

Esempio 3.2.7 Sono permesse le seguenti istruzioni: riga_corrente := 1; ... := Punti_Pippo + 5; ... := k * 27;

3.2.1

Input e output di interi

Quando si deniscono nuovi tipi interi non si hanno a disposizione le routines di input e output del package Ada.Integer_Text_IO. Tuttavia in esso denito lo scheletro per queste routines che diventeranno disponibili aggiungendo la riga seguente nella parte dichiarativa del programma (i dettagli e le spiegazioni seguiranno in futuro): package NOME_DEL_PACKAGE is new Ada.Text_IO.Integer_IO(NOME_DEL_TIPO); Esempio 3.2.8 Il seguente programma mostra luso delle procedure Put e Get per delle variabili di un tipo intero non predenito: -----Nome del file: PROVA_TIPI_INTERI.ADB Autore: Claudio Marsan Data dellultima modifica: 7 maggio 2002 Scopo: input e output per un tipo di dato intero definito dallutente Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Prova_Tipi_Interi is type PUNTEGGIO is range 0..100; Punti_Pippo Punti_Pluto Punti_Orazio Punti_Clarabella : : : : PUNTEGGIO := 30; PUNTEGGIO := 65; PUNTEGGIO := 45; PUNTEGGIO;

package Punteggio_IO is new Ada.Text_IO.Integer_IO(PUNTEGGIO); begin Ada.Text_IO.Put(Item => " Pippo: "); Punteggio_IO.Put(Item => Punti_Pippo, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => " Pluto: "); Punteggio_IO.Put(Item => Punti_Pluto, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Orazio: "); Punteggio_IO.Put(Item => Punti_Orazio, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Punteggio di Clarabella? "); Punteggio_IO.Get(Item => Punti_Clarabella); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Clarabella: "); Punteggio_IO.Put(Item => Punti_Clarabella, Width => 0); Ada.Text_IO.New_Line; end Prova_Tipi_Interi; Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.2. ANCORA SUI TIPI INTERI Ecco un esempio di output del programma: Pippo: 30 Pluto: 65 Orazio: 45 Punteggio di Clarabella? 23 Clarabella: 23

107

Osservazione 3.2.3 Il package Ada.Integer_Text_IO il frutto della seguente dichiarazione: package Ada.Integer_Text_IO is new Ada.Text_IO.Integer_IO(INTEGER); Discorso analogo per i packages: Ada.Long_Integer_Text_IO; Ada.Short_Integer_Text_IO; ... Si possono dichiarare anche dei sottotipi di interi (i sottotipi non rappresentano un nuovo tipo di dati ma esprimono solo un intervallo di possibili valori del tipo base, con il quale restano compatibili e del quale possono in particolare sfruttare le routines di input e output). Esempio 3.2.9 Il seguente frammento di programma mostra una denizione di sottotipo e la compatibilit delle variabili del sottotipo con quelle del tipo da cui deriva: ... type PUNTEGGIO is range 0..100; subtype POCHI is PUNTEGGIO range 0..60; ... Punti_Pippo : POCHI := 20; Punti_Pluto : PUNTEGGIO := 85; differenza : PUNTEGGIO; ... differenza := Punti_Pluto - Punti_Pippo; -- corretto; ... Riprenderemo la trattazione dei sottotipi pi avanti, sempre in questo capitolo.

3.2.2

Tipi interi non segnati

Talvolta pu essere comodo lavorare con degli interi non segnati, ossia con dei numeri interi il cui intervallo di denizione va da 0 a un limite superiore stabilito. Esempio 3.2.10 Volendo fare dei calcoli modulo n (n N , n > 1) si ha a che fare con i numeri interi 0, 1, 2, . . . n 1 e quindi sarebbe comodo avere degli interi non segnati. In Ada 95 permesso dichiarare un tipo interi non segnati (o tipo modulo) nel modo seguente: type NOME_DEL_TIPO is mod N; dove N un numero intero positivo (molto spesso una potenza di 2). Per denizione lintervallo dei valori ammessi del tipo sar 0..N-1. Esempio 3.2.11 Alcune dichiarazioni di tipi modulo: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

108 type BYTE is mod 256; type WORD is mod 65536; type INDICE is mod 1000; -- da 0 a 255 -- da 0 a 65535 -- da 0 a 999

CAPITOLO 3. TIPI DI DATI IN ADA 95

Questi tipi di interi sono detti non segnati perch nessuno dei bits necessari per rappresentare numeri di questi tipi viene utilizzato per impostare il segno. Le operazioni che si possono utilizzare con i tipi modulo sono quelle predenite per il tipo INTEGER, con la particolarit che il secondo operando delloperatore aritmetico ** (elevazione a potenza) deve essere sempre del sottotipo NATURAL. La principale caratteristica dei tipi modulo che calcoli con variabili di questo tipo non causeranno mai un overow o un underow del risultato poich tutta laritmetica viene eettutata modulo N. Inoltre possibile applicare, come nel caso dellaritmetica del processore, gli operatori and, or, xor e not a degli operandi di tipo modulo (in tal caso verranno considerati come una sequenza di bits). Per scrivere e leggere variabili di tipo modulo bisogna creare un apposito package; come modello si pu usare quanto scritto nella riga seguente: package NOME_DEL_PACKAGE is new Ada.Text_IO.Modular_IO(NOME_DEL_TIPO); Esercizio 3.2.1 Stabilire qual loutput del programma -----Nome del file: PROVA_TIPI_MODULO.ADB Autore: Claudio Marsan Data dellultima modifica: 7 maggio 2002 Scopo: esempio di tipo intero senza segno Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Prova_Tipi_Modulo is type BYTE is mod 256; -- da 0 a 255;

package Byte_IO is new Ada.Text_IO.Modular_IO(BYTE); x, y, z : BYTE; begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dare un intero tra 0 e 255: "); Byte_IO.Get(Item => x); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dare un altro intero tra 0 e 255: "); Byte_IO.Get(Item => y); Ada.Text_IO.New_Line; z := x and y; Byte_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => " and "); Byte_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => " = "); Byte_IO.Put(Item => z, Width => 0); Ada.Text_IO.New_Line; z := x or y; Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.2. ANCORA SUI TIPI INTERI Byte_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => " or "); Byte_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => " = "); Byte_IO.Put(Item => z, Width => 0); Ada.Text_IO.New_Line; z := x xor y; Byte_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => " xor "); Byte_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => " = "); Byte_IO.Put(Item => z, Width => 0); Ada.Text_IO.New_Line; z := not x; Ada.Text_IO.Put(Item => "not "); Byte_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => " = "); Byte_IO.Put(Item => z, Width => 0); Ada.Text_IO.New_Line; z := x + y; Byte_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => " + "); Byte_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => " = "); Byte_IO.Put(Item => z, Width => 0); Ada.Text_IO.New_Line; z := x - y; Byte_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => " - "); Byte_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => " = "); Byte_IO.Put(Item => z, Width => 0); Ada.Text_IO.New_Line; z := x * y; Byte_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => " * "); Byte_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => " = "); Byte_IO.Put(Item => z, Width => 0); Ada.Text_IO.New_Line; z := x / y; Byte_IO.Put(Item => x, Width => 0); Ada.Text_IO.Put(Item => " / "); Byte_IO.Put(Item => y, Width => 0); Ada.Text_IO.Put(Item => " = "); Byte_IO.Put(Item => z, Width => 0); Ada.Text_IO.New_Line;

109

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

110 end Prova_Tipi_Modulo; nei seguenti casi: 1. x = 23 e y = 45 2. x = 211 e y = 94 3. x = 128 e y = 128

CAPITOLO 3. TIPI DI DATI IN ADA 95

Esempio 3.2.12 Ecco un output del programma dellesercizio precedente: Dare un intero tra 0 e 255: 200 Dare un altro intero tra 0 e 255: 79 200 200 200 not 200 200 200 200 and 79 = 72 or 79 = 207 xor 79 = 135 200 = 55 + 79 = 23 - 79 = 121 * 79 = 184 / 79 = 2

Esercizio 3.2.2 Si denisce il seguente tipo modulo: type MOD10 is mod 10; -- da 0 a 9;

Costruire un programma che visualizzi, su richiesta, le tabelline per le operazioni +, -, *, /, and, or, xor e not tra le cifre. Denire anche un tipo di dato array adeguato che permetta di rappresentare in forma binaria il contenuto di una variabile di tipo MOD10 e costruire le necessarie funzioni di conversione tra numero decimale (compreso tra 0 e 9) e numero binario (tra 0 e 1001).

3.3

Ancora sui tipi reali

In Ada 95 esistono due tipi di numeri reali: i numeri reali a virgola mobile (oating point) che vengono utilizzati per rappresentare valori reali con una certa precisione, ossia con una accuratezza di un certo numero di cifre dopo la virgola; i numeri reali a virgola ssa (xed point), con precisione assoluta (due numeri reali a virgola ssa consecutivi sono distanti tra loro sempre della stessa quantit; da notare che in matematica non ha senso parlare di numeri reali consecutivi!).

3.3.1

Numeri reali a virgola mobile

Nel package Standard contenuta la denizione del tipo FLOAT, come tipo derivato dal tipo universal_float (nota: la precisione del tipo FLOAT dipendente dalle varie implementazioni di Ada 95; luso di FLOAT pu quindi essere pericoloso nel caso in cui il programma che si sta scrivendo deve essere portabile!). I seguenti sono altri tipi di dati reali che si possono trovare predeniti nel package STANDARD (essi dieriscono dal tipo FLOAT per la precisione e/o per i limiti inferiore e superiore): Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.3. ANCORA SUI TIPI REALI SHORT_FLOAT SHORT_SHORT_FLOAT LONG_FLOAT LONG_LONG_FLOAT Per denire nuovi tipi reali si user la sintassi seguente: type NOME_TIPO is digits NUMERO_CIFRE;

111

dove NUMERO_CIFRE esprime laccuratezza sotto forma di numero delle cifre signicative (deve essere unespressione intera statica). Il compilatore ha il compito di scegliere la rappresentazione binaria interna pi adatta tra quelle disponibili per soddisfare la precisione richiesta dallutente nella denizione dei suoi tipi di dati reali in virgola mobile. Esempio 3.3.1 Denizione di due tipi reali in virgola mobile (il primo con 4 cifre signicative, il secondo con 15 cifre signicative): type TEMPERATURA is digits 4; type PRECISIONE is digits 15; anche possibile indicare i limiti inferiore e superiore per un tipo di dati in virgola mobile che si sta denendo (in matematica ci corrisponderebbe alla denizione di un intervallo), usando la sintassi seguente: type NOME_TIPO is digits NUMERO_CIFRE range A..B; dove A e B sono espressioni reali statiche e A <= B. Esempio 3.3.2 Denizione di due tipi reali in virgola mobile delimitati: type PERCENTUALE is digits 4 range 0.0 .. 100.0; type PROB_ERRORE is digits 6 range 0.0 .. 1.0; La denizione data di PROB_ERRORE assicura che, quando un programma in esecuzione, variabili di questo tipo non assumano valori che giacciono al di fuori dellintervallo di denizione. Tutte le operazioni permesse con i FLOAT sono permesse anche per i reali deniti dallutente; come nel caso degli interi non tuttavia possibile mescolare luso di variabili reali di tipo diverso. invece permessa la conversione esplicita dei tipi. Esempio 3.3.3 Il seguente frammento di programma mostra la conversione esplicita di tipi reali: ... max_percento : PERCENTUALE; max_probab : PROB_ERRORE; ... max_probab := max_percento / 100.0; -- scorretto! max_probab := PROB_ERRORE(max_percento / 100.0); -- ok! ... Per linput e loutput di tipi di dati reali deniti dallutente necessario creare un nuovo package con listruzione seguente: package NOME_PACKAGE is new Ada.Text_IO.Float_IO(NOME_TIPO); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

112

CAPITOLO 3. TIPI DI DATI IN ADA 95

dove NOME_TIPO il nuovo tipo di dati reale denito dal programmatore. Esempio 3.3.4 Il seguente programma mostra linput e loutput di variabili di un tipo di dati reale denito dal programmatore: ------Nome del file: IO_REALI.ADB Autore: Claudio Marsan Data dellultima modifica: 7 maggio 2002 Scopo: input e output di variabili di un tipo di dati reale definito dal programmatore Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure IO_Reali is type PROB is digits 4 range 0.0 .. 1.0; -------------------------------------------------------- Tipo di dati adatto a contenere delle probabilit --- con 4 cifre significative (3 dopo la virgola) -------------------------------------------------------package Prob_IO is new Ada.Text_IO.Float_IO(PROB); p, q : PROB; begin Ada.Text_IO.Put_Line(Item => "Lancio di una moneta truccata"); Ada.Text_IO.Put(Item => "Probabilita di ottenere TESTA: "); Prob_IO.Get(p); q := 1.0 - p; Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "La probabilita di ottenere CROCE e: "); Prob_IO.Put(Item => q, Exp => 0, Fore => 0); end IO_Reali; Se si rideniscono dei tipi predeniti necessario denire anche il package per linput e loutput. Esempio 3.3.5 Nel programma seguente si ridenisce il tipo predenito FLOAT come un reale con 9 cifre signicative. Da notare che bisogna denire il package Float_Text_IO per linput e loutput (il package Ada.Float_Text_IO non utilizzabile e non possibile utilizzare questo nome per il package che si deve creare). -----Nome del file: RIDEFINIZIONE_FLOAT.ADB Autore: Claudio Marsan Data dellultima modifica: 7 maggio 2002 Scopo: ridefinizione del tipo FLOAT Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Ridefinizione_FLOAT is type FLOAT is digits 9; Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.3. ANCORA SUI TIPI REALI ---------------------------------------------- Ridefinizione del tipo FLOAT come reale --- con 9 cifre significative ---------------------------------------------package Float_Text_IO is new Ada.Text_IO.Float_IO(FLOAT); x : FLOAT; begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dammi un numero reale: "); Float_Text_IO.Get(Item => x); Ada.Text_IO.New_Line; Float_Text_IO.Put(Item => x, Exp => 0, Fore => 0); end Ridefinizione_FLOAT; Ecco un esempio di output del programma: Dammi un numero reale: 1.2345 1.23450000

113

Ecco un esempio nel quale il numero in entrata ha una precisione maggiore di quella supportata (da notare larrotondamento automatico): Dammi un numero reale: 1.23456789901234 1.23456790

3.3.2

Attributi per i numeri reali in virgola mobile

Siano: T un tipo di numeri reali in virgola mobile, x e y variabili di tipo T e s una variabile di tipo stringa. Si possono allora usare i seguenti attributi: TFIRST: il pi piccolo numero reale del tipo T che pu essere memorizzato; TLAST: il pi grande numero reale del tipo T che pu essere memorizzato; TPREC(x): il numero macchina che precede x; TSUCC(x): il numero macchina che segue x; TMIN(x, y): restituisce il valore minore tra x e y; TMAX(x, y): restituisce il valore maggiore tra x e y; TROUNDING(x): restituisce un numero di tipo T che rappresenta larrotondamento a numero intero di x; TTRUNCATION(x): restituisce un numero di tipo T che rappresenta la parte intera di x (troncamento di x); TFLOOR(x): restituisce un numero di tipo T che rappresenta il pi grande intero non maggiore di x; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

114

CAPITOLO 3. TIPI DI DATI IN ADA 95

TCEILING(x): restituisce un numero di tipo T che rappresenta il pi piccolo intero non minore di x; TDIGITS: fornisce il numero di cifre signicative del tipo T; TMANTISSA: fornisce la lunghezza della mantissa binaria di un numero di tipo T; TWIDTH: restituisce un numero intero indicante il numero di caratteri necessario per stampare sotto forma di stringa un qualsiasi valore di tipo T; TIMAGE(x): restituisce una stringa che contiene il valore di x; TVALUE(s): s una stringa che contiene un numero reale del tipo T e come risultato viene restituito il numero reale del tipo T rappresentato da s. Esempio 3.3.6 Il seguente programma mostra luso di questi attributi: -----Nome del file: ATTRIBUTI_FLOATING_POINT.ADB Autore: Claudio Marsan Data dellultima modifica: 7 maggio 2002 Scopo: attributi per il tipo FLOAT in Ada 95 Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Float_Text_IO; procedure Attributi_Floating_Point is x : FLOAT := 2.71828; s : STRING := "3.14159"; y : FLOAT; begin Ada.Text_IO.Put(Item => "FLOATFIRST: "); Ada.Float_Text_IO.Put(Item => FLOATFIRST, Fore => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "FLOATLAST: "); Ada.Float_Text_IO.Put(Item => FLOATLAST, Fore => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATDIGITS: "); Ada.Integer_Text_IO.Put(Item => FLOATDIGITS, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATMANTISSA: "); Ada.Integer_Text_IO.Put(Item => FLOATMANTISSA, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATWIDTH: "); Ada.Integer_Text_IO.Put(Item => FLOATWIDTH, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATIMAGE(x): "); Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.3. ANCORA SUI TIPI REALI Ada.Text_IO.Put(Item => FLOATIMAGE(x)); Ada.Text_IO.New_Line; y := FLOATVALUE(s); Ada.Text_IO.Put("y = FLOATVALUE(s): "); Ada.Float_Text_IO.Put(Item => y, Fore => 0, Exp => 0); Ada.Text_IO.New_Line;

115

Ada.Text_IO.Put("FLOATPRED(x): "); Ada.Float_Text_IO.Put(Item => FLOATPRED(x), Fore => 0, Aft => 10, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATSUCC(x): "); Ada.Float_Text_IO.Put(Item => FLOATSUCC(x), Fore => 0, Aft => 10, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATMIN(x, y): "); Ada.Float_Text_IO.Put(Item => FLOATMIN(x, y), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATMAX(x, y): "); Ada.Float_Text_IO.Put(Item => FLOATMAX(x, y), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATROUNDING(x): "); Ada.Float_Text_IO.Put(Item => FLOATROUNDING(x), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATTRUNCATION(x): "); Ada.Float_Text_IO.Put(Item => FLOATTRUNCATION(x), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATFLOOR(x): "); Ada.Float_Text_IO.Put(Item => FLOATFLOOR(x), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOATCEILING(x): "); Ada.Float_Text_IO.Put(Item => FLOATCEILING(x), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; end Attributi_Floating_Point; Loutput del programma (Gnat 3.13p su Windows 2000) il seguente: FLOATFIRST: -3.40282E+38 FLOATLAST: 3.40282E+38 FLOATDIGITS: 6 FLOATMANTISSA: 21 FLOATWIDTH: 12 FLOATIMAGE(x): 2.71828E+00 y = FLOATVALUE(s): 3.14159 FLOATPRED(x): 2.7182798386 FLOATSUCC(x): 2.7182803154 FLOATMIN(x, y): 2.71828 FLOATMAX(x, y): 3.14159 Liceo cantonale di Mendrisio, 2002 Claudio Marsan

116 FLOATROUNDING(x): 3.00000 FLOATTRUNCATION(x): 2.00000 FLOATFLOOR(x): 2.00000 FLOATCEILING(x): 3.00000

CAPITOLO 3. TIPI DI DATI IN ADA 95

3.3.3

Numeri reali a virgola ssa

In questo paragrafo parliamo brevemente dei tipi di dati reali a virgola ssa, che useremo solo raramente nel seguito del nostro corso. Un tipo di dati reale a virgola ssa utile se necessario lavorare con dei numeri reali per i quali la dierenza tra due numeri consecutivi costante (errore assoluto), come per esempio nella misura del tempo oppure in applicazioni contabili. Un tipo di dati reale a virgola ssa si dichiara nel modo seguente: type NOME_DEL_TIPO is delta ERRORE A..B; dove: NOME_DEL_TIPO il nome che si vuole assegnare al tipo che si sta denendo; ERRORE rappresenta lerrore tollerato; A e B sono espressioni reali statiche e A <= B. Esempio 3.3.7 Nel seguito si dichiarano due tipi di dati reali a virgola ssa: type REALE_01 is delta 0.1 range -1.0 .. 1.0; type SECONDI is delta 0.001 range 0.0 .. 86_400.0; type INTERVALLO is delta 0.05 range 0.0 .. 1.0; Le operazioni per i tipi di dati reali a virgola ssa sono le stesse di quelle per i tipi di dati reali a virgola mobile. Sia REALE_FISSO un tipo di dati reale a virgola ssa. Se vogliamo scrivere istruzioni di input e output che coinvolgono variabili di tipo REALE_FISSO dobbiamo aggiungere nella parte dichiarativa del programma la seguente istruzione: package NOME_DEL_PACKAGE is new Ada.Text_IO.Fixed_IO(REALE_FISSO); dove NOME_DEL_PACKAGE il nome che si intende dare al package per linput e loutput di variabili di tipo REALE_FISSO. Esiste un solo tipo di dato reale a virgola ssa predenito: il tipo DURATION (assomiglia al tipo SECONDI dichiarato sopra), che viene utilizzato quando si programmano applicazioni nelle quali bisogna misurare il tempo. Osservazione 3.3.1 Consideriamo la dichiarazione type INTERVALLO is delta 0.05 range 0.0 .. 1.0; In teoria, cos facendo, si hanno a disposizione i numeri reali: 0.0, 0.05, 0.1, 0.15, ...; in pratica non garantita la rappresentazione esatta ma solo luso di un vincolo di precisione uguale o minore a quello richiesto. Se si denisce un sottotipo di un tipo reale in virgola ssa il vincolo di precisione del sottotipo deve essere maggiore o uguale al vincolo di precisione del tipo da cui deriva. Esempio 3.3.8 Dichiarazione di un tipo e di un sottotipo a virgola ssa: Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.4. TIPI DISCRETI type VOLT is delta 0.125 range 0.0 .. 255.0; subtype VOLT_APPR is VOLT delta 1.0; -- intervallo come sopra

117

Con i tipi di dati reali a virgola ssa si possono usare vari attributi, molti simili a quelli per i tipi di dati reali a virgola mobile. Esempio 3.3.9 Il seguente programma mostra luso dellinput e output e di qualche attributo per un tipo di dati reale a virgola ssa: -----Nome del file: REALI_FISSI.ADB Autore: Claudio Marsan Data dellultima modifica: 7 maggio 2002 Scopo: test per i reali a virgola fissa Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Reali_Fissi is type UNITARIO is delta 0.001 range 0.0 .. 1.0; package UNITARIO_IO is new Ada.Text_IO.Fixed_IO(UNITARIO); x, y : UNITARIO; begin Ada.Text_IO.Put(Item => "Dare un numero reale tra 0 e 1: "); UNITARIO_IO.Get(Item => x); Ada.Text_IO.New_Line; y := UNITARIODELTA; UNITARIO_IO.Put(Item => y); Ada.Text_IO.New_Line; y := x + y; UNITARIO_IO.Put(Item => y); end Reali_Fissi; Ecco loutput del programma: Dare un numero reale tra 0 e 1: 0.23 0.001 0.231 Un altro esempio di output (notare larrotondamento automatico del valore di input): Dare un numero reale tra 0 e 1: 0.2356 0.001 0.236

3.4

Tipi discreti

I tipi discreti sono formati, oltre che dai tipi interi, anche dai tipi enumerativi che sono deniti elencando esplicitamente tutte le costanti che rappresentano i relativi valori. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

118

CAPITOLO 3. TIPI DI DATI IN ADA 95

Esempio 3.4.1 Alcune denizioni di tipi enumerativi: type SEME is (QUADRI, CUORI, FIORI, PICCHE); type COLORI is (BIANCO, NERO, ROSSO, GIALLO, VERDE, BLU); subtype SEMAFORO is COLORE range ROSSO..VERDE; Ricordiamo che per i tipi enumerativi sono deniti gli operatori relazionali (=, /=, <, <=, >, >=) e gli operatori in e not in e che il primo elemento denito ha ordine 0. Per usare le procedure di input e output con i tipi enumerativi necessario inserire nella parte dichiarativa del programma la seguente istruzione: package NOME_DEL_PACKAGE is new Ada.Text_IO.Enumeration_IO(NOME_DEL_TIPO); Se T un tipo discreto qualsiasi, x una variabile di tipo T e n una variabile di tipo intero, allora possibile usare i seguenti attributi: TFIRST: il primo elemento di T; TLAST: lultimo elemento di T; TWIDTH: restituisce un intero indicante il numero di caratteri necessario per stampare sotto forma di stringa un qualsiasi valore di tipo T; TPOS(x): la posizione di x nella denizione di T; TVAL(n): il valore dellelemento di ordine n in T; TSUCC(x): il successore di x in T; TPRED(x): il predecessore di x in T; TIMAGE(x): la rappresentazione di x sotto forma di stringa; TVALUE(x): il valore di cui x la stringa immagine. Esempio 3.4.2 Il seguente programma mostra luso degli attributi per un tipo enumerativo denito dal programmatore: -----Nome del file: ATTRIBUTI_TIPI_DISCRETI.ADB Autore: Claudio Marsan Data dellultima modifica: 7 maggio 2002 Scopo: attributi per i tipi discreti Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Attributi_Tipi_Discreti is type SEMI is (QUADRI, CUORI, FIORI, PICCHE); package Semi_IO is new Ada.Text_IO.Enumeration_IO(SEMI); begin Ada.Text_IO.Put(Item => "SEMIFIRST: "); Semi_IO.Put(Item => SEMIFIRST); Ada.Text_IO.New_Line;

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

3.5. SOTTOTIPI Ada.Text_IO.Put(Item => "SEMILAST: "); Semi_IO.Put(Item => SEMILAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMIWIDTH: "); Ada.Integer_Text_IO.Put(Item => SEMIWIDTH, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMIPOS(CUORI): "); Ada.Integer_Text_IO.Put(Item => SEMIPOS(CUORI), Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMIVAL(2): "); Semi_IO.Put(Item => SEMIVAL(2)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMISUCC(CUORI): "); Semi_IO.Put(Item => SEMISUCC(CUORI)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMIPRED(CUORI): "); Semi_IO.Put(Item => SEMIPRED(CUORI)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMIIMAGE(CUORI): "); Ada.Text_IO.Put(Item => SEMIIMAGE(CUORI)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMIVALUE(""CUORI""): "); Semi_IO.Put(Item => SEMIVALUE("CUORI")); Ada.Text_IO.New_Line; end Attributi_Tipi_Discreti; Ecco loutput del programma: SEMIFIRST: QUADRI SEMILAST: PICCHE SEMIWIDTH: 6 SEMIPOS(CUORI): 1 SEMIVAL(2): FIORI SEMISUCC(CUORI): FIORI SEMIPRED(CUORI): QUADRI SEMIIMAGE(CUORI): CUORI SEMIVALUE("CUORI"): CUORI

119

3.5

Sottotipi

Quando si deve denotare un sottoinsieme di un certo tipo di dati T , a volte, comodo introdurre un sottotipo S di T (per esempio quando, cos facendo, si ottiene una migliore descrizione della realt oppure pi facile ricercare errori di logica nel programma). Per dichiarare il sottotipo S di T si deve usare la sintassi seguente: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

120 subtype S is T range A..B;

CAPITOLO 3. TIPI DI DATI IN ADA 95

dove con A..B si indicano gli estremi dellintervallo in cui si possono scegliere i valori di S. Esempio 3.5.1 Se denito il tipo GIORNI: type GIORNI is (LUN, MAR, MER, GIO, VEN, SAB, DOM); per riferirsi unicamente ai giorni lavorativi conveniente introdurre il sottotipo LAV_GIORNI mediante listruzione subtype LAV_GIORNI is GIORNI range LUN..VEN; Saranno valide le seguenti dichiarazioni di variabili: oggi : GIORNI; prossimo_lavorativo : LAV_GIORNI; Osservazione 3.5.1 La dichiarazione di un sottotipo non comporta la creazione di un nuovo tipo! Quindi: oggetti del sottotipo S sono oggetti del tipo T. Inoltre a variabili del tipo T potranno essere assegnate variabili del sottotipo S. Osservazione 3.5.2 Se dichiariamo un sottotipo S di un tipo T per il quale abbiamo a disposizione le routines di input e output, allora tali routines saranno utilizzabili anche da variabili del sottotipo S. Esempio 3.5.2 Listruzione oggi := prossimo_lavorativo; lecita. anche possibile unistruzione come prossimo_lavorativo := oggi; sebbene questa possa portare ad un errore di runtime se oggi assume un valore al di fuori dellintervallo LUN..VEN. Ricordiamo che nel package Standard sono predeniti i sottotipi: subtype NATURAL is INTEGER range 0..INTEGERLAST; subtype POSITIVE is INTEGER range 1..INTEGERLAST;

3.6

Array

Essenzialmente possiamo considerare gli array come limplementazione del concetto matematico di vettore ndimensionale, ossia di un oggetto con una struttura complessa le cui n componenti (elementi) sono tutte dello stesso tipo. Si distinguono due tipi di array: array vincolati (constrained array), le cui dimensioni sono denite in fase di denizione del tipo di dato; array non vincolati (unconstrained array), dei quali non si predenisce la dimensione (ci consente di trattare oggetti di dimensione diversa). Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.6. ARRAY

121

3.6.1

Array vincolati

Riprendiamo le cose essenziali sugli array vincolati, che abbiamo gi visto in precedenza. La dichiarazione di un tipo array avviene secondo la sintassi seguente: type NOME_TIPO is array(a..b) of TIPO_COMPONENTE; dove a e b sono di tipo enumerativo. Esempio 3.6.1 Denizione di due tipi di array: type VETTORE3D is array(1..3) of FLOAT; type NOTE_ESPE is array(1..20) of NATURAL; Esempio 3.6.2 anche possibile dichiarare un array con una sintassi come la seguente: type MISURE is array(INTEGER range 1..10) of FLOAT; Esempio 3.6.3 Il tipo enumerativo COLORE sia denito nel modo seguente: type COLORE is (ROSSO, GIALLO, BLU, VERDE, BIANCO); Allora il tipo NUMERO_COLORE potrebbe essere denito come type NUMERO_COLORE is array(COLORE range ROSSO..BIANCO) of INTEGER; oppure come type NUMERO_COLORE is array(COLORE) of INTEGER; Riassumendo: per denire un tipo di dati array vincolato si usa la sintassi seguente: type T is array(DEFINIZIONE_INDICI) of TIPO_COMPONENTI; dove: T il nome del tipo di dati array vincolato; TIPO_COMPONENTI il tipo (vincolato) delle componenti; DEFINIZIONE_INDICI pu avere una delle seguenti forme: (primo_indice..ultimo_indice) (TIPO_INDICE range primo_indice..ultimo_indice) (TIPO_INDICE) dove: primo_indice e ultimo_indice sono espressioni, non necessariamente costanti, di un tipo intero o di un tipo enumerativo (se primo_indice maggiore di ultimo_indice allora avremo un array senza componenti, detto array vuoto); TIPO_INDICE deve essere un tipo intero o un tipo enumerativo oppure un sottotipo di tali tipi. Ricordiamo che per accedere ad una determinata componente di un array bisogna indicare il nome della variabile seguito dallindice della componente, scritto tra parentesi tonde. Esempio 3.6.4 Consideriamo il seguente frammento di programma: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

122 ... type V3 ... v : V3; ... v(1) := v(2) := v(3) := ...

CAPITOLO 3. TIPI DI DATI IN ADA 95

is array(1..3) of FLOAT;

2.1; -- accesso alla 1a componente di v 0.3; -- accesso alla 2a componente di v v(1) - 2.0*v(2); -- accesso alla 3a componente di v

Come gi visto pure possibile inizializzare gli array in fase di dichiarazione di variabili. Esempio 3.6.5 Con il frammento seguente di programma si inizializzano i vettori della base canonica nello spazio usuale: ... type ... ... e1 : e2 : e3 : ...

V3 is array(1..3) of FLOAT;

V3 := (1.0, 0.0, 0.0); V3 := (0.0, 1.0, 0.0); V3 := (0.0, 0.0, 1.0);

Laggregato di array una lista nella quale ad ogni componente dellarray viene assegnato contemporaneamente un valore. Gli aggregati possono essere usati, per esempio, in assegnazioni o in comparazioni. Sono possibili le seguenti alternative: (valore_1, valore_2, ..., valore_N) (indice_i => valore_i, indice_j => valore_j, ...) (indice_k | indice_m => valore, ...) (indice_a .. indice_b => valore, ...)

(..., others => valore) In un aggregato di array ci deve essere esattamente un valore per ogni componente; se presente lalternativa others essa deve essere lultima. Esempio 3.6.6 Uso di aggregati di array: ... type MISURE is array(1..10) of FLOAT; ... serie_A, serie_B, serie_C : MISURE; ... serie_A := (1.0, 1.0, 1.0, 0.5, others => 0.0); serie_B := (others => 0.0); serie_C := MISURE(1..3 => 1.0, 4 => 0.5, others => 0.0); ... Da notare che nellultima riga di codice del frammento abbiamo usato una espressione qualicata che includeva il nome del tipo seguito da un apice: talvolta questo pu essere necessario (in particolare quando il compilatore non sa decidere la lunghezza dellaggregato a causa delluso di others). Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.6. ARRAY Esempio 3.6.7 Ricordiamo anche luso degli slice di array: ... serie_A(2..5) := serie_B(4..7); ...

123

possibile denire anche array di array di array di ... In matematica (in particolare nellalgebra lineare) si lavora molto con degli schemi rettangolari detti matrici : esse possono essere implementate in Ada 95 come array multidimensionali di reali. Esempio 3.6.8 La dichiarazione type MATRICE2x3 is array(1..2, 1..3) of FLOAT; permette di denire un tipo di dati adatto a trattare le matrici con due righe e tre colonne. Il frammento di programma che segue permette di vedere come si possono manipolare le matrici: ... A, B, C : MATRICE2x3; ... A := ((1.0, 3.1, 5.9), (0.0, 0.0, -1.1)); B(1,1) := 12.5; C := ((others => 1.5), (others => 2.9)); ... Esercizio 3.6.1 Scrivere una procedura che permetta di moltiplicare una matrice 2 3 con una matrice 3 4 e integrarla in un programma completo di test. Per il tipo di array vincolato T sono disponibili i seguenti attributi: TFIRST: restituisce il limite inferiore del primo indice; TFIRST(n): restituisce il limite inferiore dellnesimo indice; TLAST: restituisce il limite superiore del primo indice; TLAST(n): restituisce il limite superiore dellnesimo indice; TRANGE: restituisce lintervallo del primo indice; TRANGE(n): restituisce lintervallo dellnesimo indice; TLENGTH: restituisce il numero dei valori del primo indice; TLENGTH(n): restituisce il numero dei valori dellnesimo indice. Esempio 3.6.9 Il seguente programma mostra luso di alcuni attributi per un tipo di array vincolato: -----Nome del file: ATTRIBUTI_ARRAY.ADB Autore: Claudio Marsan Data dellultima modifica: 7 maggio 2002 Scopo: attributi per i tipi array Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO;

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

124 procedure Attributi_Array is

CAPITOLO 3. TIPI DI DATI IN ADA 95

type VETTORE is array(3..12,2..5) of INTEGER;

-- vincolato

begin Ada.Text_IO.Put(Item => "VETTOREFIRST: "); Ada.Integer_Text_IO.Put(Item => VETTOREFIRST, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTORELAST: "); Ada.Integer_Text_IO.Put(Item => VETTORELAST, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTORELENGTH: "); Ada.Integer_Text_IO.Put(Item => VETTORELENGTH, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTOREFIRST(2): "); Ada.Integer_Text_IO.Put(Item => VETTOREFIRST(2), Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTORELAST(2): "); Ada.Integer_Text_IO.Put(Item => VETTORELAST(2), Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTORELENGTH(2): "); Ada.Integer_Text_IO.Put(Item => VETTORELENGTH(2), Width => 0); Ada.Text_IO.New_Line; end Attributi_Array; Ecco loutput del programma: VETTOREFIRST: 3 VETTORELAST: 12 VETTORELENGTH: 10 VETTOREFIRST(2): 2 VETTORELAST(2): 5 VETTORELENGTH(2): 4

3.6.2

Array non vincolati

Lesercizio 3.6.1 mette in evidenza il limite degli array vincolati: siccome il prodotto di matrici denito solo tra matrici di tipo m p e p n (e d come risultato una matrice m n) nel caso volessimo denire una funzione o una procedura per il calcolo del prodotto di matrici sarebbe necessario denire ben tre tipi di matrici diverse! Con gli array non vincolati non necessario dare le dimensioni dellarray; dichiarando un array non vincolato si specica il tipo dellindice ma non i suoi limiti; al posto dei limiti si usa il simbolo <>, detto box. Vediamo alcuni esempi: Esempio 3.6.10 Alcune dichiarazioni di array non vincolati: ... type VETTORE is array(INTEGER range <>) of FLOAT; type INDICE is range 1..100; -- questo non un array type LISTA_NUMERI is array(INDICE range <>) of INTEGER; Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.6. ARRAY type CONTA_CARATTERI is array(CHARACTER range <>) of INTEGER; ...

125

Quando si dichiara una variabile di un tipo array non vincolato allora bisogna dichiarare anche i limiti per lindice. Esempio 3.6.11 Dichiarazione di variabili di tipo array non vincolato: ... v w my_list your_list counter contacifre ... : : : : : : VETTORE(-10..10); VETTORE(1..N); -- N una variabile LISTA_NUMERI(i .. 2*i); -- ok LISTA_NUMERI(90..100); CONTA_CARATTERI(A .. Z); CONTA_CARATTERI(0 .. 9);

anche possibile dichiarare sottotipi di un array non vincolato. Esempio 3.6.12 V3 un sottotipo del tipo VETTORE: ... type VETTORE is array(INTEGER range <>) of FLOAT; subtype V3 is VETTORE(1..3); punto : V3; ... Luso degli aggregati permesso anche con gli array non vincolati. Nel package Standard predenito il tipo STRING, che pu essere trattato come un array non vincolato di CHARACTER: type STRING is array(POSITIVE range <>) of CHARACTER; Esempio 3.6.13 Stringhe come array di CHARACTER: ... codice_prodotto : STRING(1..6); ... codice_prodotto := (k,a,p,p,a,2); ... codice_prodotto := "kappa2"; ... Da notare che (*) e (**) sono forme equivalenti. Per lassegnazione tra due variabili di tipo array bisogna prestare attenzione a quanto segue: se larray vincolato e le variabili sono dello stesso tipo non ci sono problemi; se larray non vincolato necessario che le due variabili abbiano lo stesso numero di componenti (ma non necessariamente la stessa numerazione). La comparazione tra array dello stesso tipo (ma non necessariamente con lo stesso numero di componenti, nel caso di array non vincolati) pu avvenire con gli operatori = e /=: due array sono uguali quando hanno lo stesso numero di componenti e le componenti omonime uguali, altrimenti sono disuguali. Esercizio 3.6.2 Costruire un insieme di routine per la gestione di vettori e matrici denendo dei tipi di array non vincolati. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

(*) (**)

126

CAPITOLO 3. TIPI DI DATI IN ADA 95

3.6.3

Array multidimensionali

Abbiamo gi visto nel paragrafo precedente, nel caso delle matrici, la dichiarazione di un particolare tipo di array multidimensionale. In generale la sintassi per la dichiarazione di un tipo T array multidimensionale la seguente: type T is array(index1,index2,...,indexN) of TIPO_ELEMENTI; dove: index1, index2, ..., indexN sono intervalli della forma a..b oppure nomi di tipi discreti; TIPO_ELEMENTI il nome di un tipo (vincolato). Esempio 3.6.14 Vogliamo implementare una tabella come la seguente per la distanza fra citt: Berlin 0 1101 2349 1092 1588 London 1101 0 1661 404 1870 Madrid 2349 1661 0 1257 2001 Paris 1092 404 1257 0 1466 Roma 1588 1870 2001 1466 0

Berlin London Madrid Paris Roma

Conviene denire i seguenti tipi di dati: type DISTANCE_TYPE is range 0..40077; -- in km type CITY is (BERLIN, LONDON, MADRID, PARIS, ROMA); type DISTANCE_TABLE is array(CITY, CITY) of DISTANCE_TYPE; Se deniamo la variabile distanza : DISTANCE_TABLE; e ammettiamo che sia stato denito il package DISTANCE_IO che permette loutput di variabili di tipo DISTANCE_TYPE, allora saranno ammesse istruzioni quali: distanza(BERLIN, ROMA) := 1588; DISTANCE_IO.Put(Item => distanza(PARIS, MADRID)); Se volessimo stampare la tabella dovremmo scrivere una parte di codice come la seguente: ... for DA in BERLIN..ROMA loop for A in BERLIN..ROMA loop DISTANCE_IO.Put(Item => distanza(DA, A), Width => 6); end loop; Ada.Text_IO.New_Line; end loop; ... Volendo attribuire valori alla variabile distanza si pu procedere in vari modi: distanza := ((0, (0, (0, (0, (0, Claudio Marsan 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 0), 0), 0), 0)); Liceo cantonale di Mendrisio, 2002

3.6. ARRAY oppure: distanza := (BERLIN LONDON MADRID PARIS ROMA oppure: distanza := ( (others (others (others (others (others o, pi brevemente: distanza := (others => (others => 0)); => => => => => 0), 0), 0), 0), 0)); => => => => => (0, (0, (0, (0, (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 0), 0), 0), 0));

127

possibile usare anche array multidimensionali non vincolati con una dichiarazione come la seguente: type T is array(T1 range <>, T2 range <>, ... TN range <>) of TIPO_ELEMENTO; Esempio 3.6.15 Denizione del tipo MATRICE come array bidimensionale non vincolato: type MATRICE is array(POSITIVE range <>, POSITIVE range <>) of FLOAT; Saranno cos possibili dichiarazioni di variabili come le seguenti: p35, q35 : MATRICE(1..3, 1..5); x24, y24 : MATRICE(1..2, 1..4); Esempio 3.6.16 La seguente funzione permette di sommare due matrici delle stesse dimensioni: function Add(a, b : MATRICE) return MATRICE is c : MATRICE(aRANGE(1), aRANGE(2)); begin for i in aRANGE(1) loop for j in aRANGE(2) loop c(i,j) := a(i,j) + b(i,j); end loop: end loop; return c; end Add;

3.6.4

Array di array

Potremmo denire le matrici anche come array di array. Esempio 3.6.17 Denizione alternativa per le matrici: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

128

CAPITOLO 3. TIPI DI DATI IN ADA 95 type MATRICE4x5 is array(1..4) of array(1..5) of FLOAT;

oppure anche: type RIGHE5 is array(1..5) of FLOAT; type MATRICE4x5 is array(1..4) of RIGHE5; Le denizioni date nellultimo esempio sono solo apparentemente equivalenti a quelle date con gli array multidimensionali. In particolare: per accedere ad una componente di un array di array bisogna usare una sintassi del tipo a(i)(j), mentre con gli array multidimensionali suciente scrivere a(i,j); con gli array multidimensionali si pu accedere solo alle componenti della matrice mentre con gli array di array possibile accedere anche alle righe della matrice con una sintassi del tipo a(i); con gli array di array non possibile utilizzare tipi non vincolati per le righe.

3.7

Record

Abbiamo visto che mediante gli array possiamo descrivere oggetti complicati con molte componenti. Una limitazione degli array consiste nel fatto che tutte le componenti devono essere dello stesso tipo: essi non possono cos essere utilizzati per descrivere oggetti formati da componenti di tipo diverso. Esempio 3.7.1 Consideriamo la descrizione di unautomobile in un ipotetico registro di automobili. Unauto pu essere caratterizzata da molte cose, per esempio: numero di registrazione; marca; anno di fabbricazione; peso; potenza. Avendo denito i tipi seguenti type T_ANNO is range 1_900..2_000; type T_PESO is range 100..10_000; -- in kg type T_POTENZA is digits 4; -- in kW linformazione pu essere raccolta usando la seguente dichiarazione di tipo record : type T_AUTO is record Numero_Registrazione Marca Anno_Modello Peso Potenza end record;

: : : : :

STRING(1..7); STRING(1..20); T_ANNO; T_PESO; T_POTENZA;

Possiamo dichiarare una variabile di tipo T_AUTO nel modo usuale: Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.7. RECORD mia_auto : T_AUTO;

129

Negli array per accedere ad una singola componente bastava specicare il suo numero tra parentesi dopo il nome della variabile; nei record le componenti, dette anche campi, non sono numerate ma hanno un nome esplicito. Per accedere ad una particolare componente di un record si usa la selezione: si scrive il nome della variabile, seguito da un punto e dal nome della componente a cui si vuole accedere. Esempio 3.7.2 Riferendosi allesempio 3.7.1 abbiamo: mia_auto.Numero_Registrazione := "ABC1234"; mia_auto.Marca := "Fiat 500 Turbo "; mia_auto.Anno_Modello := 1971; mia_auto.Peso := 630; mia_auto.Potenza := 44; In generale per denire un tipo record T si usa la sintassi seguente: type T is record componente_1 : tipo_1; componente_2 : tipo_2; ... componente_N : tipo_N; end record; Se ci sono pi componenti dello stesso tipo anche possibile scrivere: componente_i, ..., componente_j : tipo_k; Se inoltre v una variabile di tipo T la selezione della iesima componente del record avviene mediante la sintassi seguente: v.componente_i Con variabili di tipo record sono possibili le operazioni seguenti: assegnazione; comparazione. Esempio 3.7.3 Riferendosi sempre allesempio 3.7.1 abbiamo: ... Bill_Gates_auto : T_AUTO; ... Bill_Gates_auto := mia_auto; ... if Bill_Gates_auto = mia_auto then ... end if; ... if mia_auto /= Bill_Gates_auto then ... end if; ... Liceo cantonale di Mendrisio, 2002 Claudio Marsan

130

CAPITOLO 3. TIPI DI DATI IN ADA 95

Come nel caso degli array possibile usare gli aggregati per assegnare in una sola volta tutti i valori delle componenti di un record. Esempio 3.7.4 Riferendosi sempre allesempio 3.7.1 abbiamo: Bill_Gates_auto := ("$$$$$$$", "Rolls Royce Phantom ", 1998, 3650, 350); oppure, usando la notazione nominale: Bill_Gates_auto := (Numero_Registrazione => "$$$$$$$", Marca => "Rolls Royce Phantom ", Anno_Modello => 1998, Peso => 3650, Potenza => 350); Le componenti di un record possono essere di ogni tipo, anche di tipo record. Esempio 3.7.5 Denendo il tipo di dati type T_PERSONA is record Nome : STRING(1..20); Numero_AVS : STRING(1..14); end record; possiamo estendere il tipo T_AUTO denito nellesempio 3.7.1 nel modo seguente: type T_AUTO is record Numero_Registrazione Marca Anno_Modello Peso Potenza Proprietario end record;

: : : : : :

STRING(1..7); STRING(1..20); T_ANNO; T_PESO; T_POTENZA; T_PERSONA;

Se per esempio vogliamo stampare il numero AVS del proprietario di unauto useremo la sintassi seguente: Ada.Text_IO.Put(Item => mia_auto.Proprietario.Numero_AVS); Esercizio 3.7.1 Denire un record PUNTO, composto da due componenti x e y di tipo FLOAT che rappresentano le coordinate cartesiane di un punto del piano. Scrivere poi le funzioni necessarie per calcolare la lunghezza di un segmento e le coordinate del punto medio di un segmento di estremi noti. possibile fare in modo che un record T, in fase di dichiarazione, abbia una o pi componenti inizializzate: in tal caso ogni variabile del tipo T che sar denita in futuro avr delle componenti inizializzate. Esempio 3.7.6 Consideriamo il seguente frammento di programma: Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.7. RECORD ... anno_corrente : constant := 1999; ... type T_AUTO is record Numero_Registrazione : STRING(1..7) := " "; Marca : STRING(1..20); Anno_Modello : T_ANNO := anno_corrente; Peso : T_PESO; Potenza : T_POTENZA; Proprietario : T_PERSONA; end record; ... nuova_auto : T_AUTO; ...

131

Allora la variabile nuova_auto avr le componenti Numero_Registrazione e Anno_Modello inizializzate; le altre componenti di nuova_auto restano invece indenite. Esercizio 3.7.2 Denire un record adeguato per contenere i dati tipici di una scheda per un libro (autore, titolo, segnatura, casa editrice, luogo di pubblicazione, anno di pubblicazione, numero pagine, argomento principale, argomento secondario). Esercizio 3.7.3 Denire un record adeguato per contenere i dati tipici per la preparazione dello stipendio di un dipendente di una ditta (nome, cognome, anno di nascita, numero AVS, reparto, salario orario, ore mensili lavorate, deduzioni, stipendio).

3.7.1

Array di record

Talvolta necessario denire dei tipi di dati che sono array di record. Vediamo due esempi nel seguito. Esempio 3.7.7 Consideriamo la classica di una competizione sportiva, per esempio di una maratona. Per ogni partecipante viene registrato il numero di pettorale, il nome, il club di appartenenza e il tempo impiegato per terminare la gara. Potremmo denire la seguente struttura di dati: type T_NUMERO is range 1..1000; type T_TEMPO is digits 7 range 0.0 .. 300.0; type T_CORRIDORE is record Numero : T_NUMERO; Nome : STRING(1..10); Club : STRING(1..20); Tempo : T_TEMPO; end record;

-- in minuti

Per gestire la classica serve un array di record del tipo T_CORRIDORE, che possiamo denire tramite type T_CLASSIFICA is array(1..500) of T_CORRIDORE; (nota: abbiamo assunto che ci possono essere al massimo 500 partecipanti, ma i numeri possono arrivare no a 1000, quindi non tutti i numeri disponibili saranno utilizzati). Esempio 3.7.8 In questo esempio (elenco del telefono) trattiamo un array non vincolato di record: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

132

CAPITOLO 3. TIPI DI DATI IN ADA 95 type T_NUMERO_TELEFONO is range 0..9_999_999; type T_UTENTE is record Nome : STRING(1..20); Titolo : STRING(1..15); Indirizzo : STRING(1..20); Numero : T_NUMERO_TELEFONO; end record; type T_ELENCO_TELEFONO is array(INTEGER range <>) of T_UTENTE;

Quando si dichiara una variabile di tipo T_NUMERO_TELEFONO bisogna specicare il numero degli abbonati; per esempio: elenco_Ticino : T_ELENCO_TELEFONO(1..100_000); elenco_Zurigo : T_ELENCO_TELEFONO(1..650_000); Seguono alcuni esempi di assegnazioni: elenco_Ticino(123) := ("Gelsomini Ezechiele ", "Dottore FMH ", "6830 Chiasso ", 834_34_21); elenco_Ticino(892).Indirizzo := elenco_Ticino(123).Indirizzo; elenco_Zurigo(4567).Nome := "Mueller Franz "; elenco_Zurigo(4567).Numero := 898_11_02; Osservazione 3.7.1 Per gestire in modo ecace gli array di record necessario poter leggere e scrivere il contenuto dei campi su le, altrimenti quando si esce dal programma bisogna digitare nuovamente tutti i dati! Tratteremo i le in seguito.

3.7.2

Record con parte variante

Talvolta pu capitare di dover descrivere degli oggetti che hanno alcune propriet comuni e altre che variano. Per esempio potremmo voler descrivere un gruppo di persone che appartengono a categorie diverse (docenti, studenti, personale amministrativo, . . . ). Tutte queste persone hanno in comune la propriet di avere un nome e un indirizzo ma informazioni diverse potrebbero essere importanti per categorie diverse: per esempio per un docente potrebbe essere importante conoscere lo stipendio, mentre per uno studente potrebbe essere importante conoscere le note degli esami. Per descrivere questo tipo di dati Ada 95 mette a disposizione i record con parte variante. Esempio 3.7.9 Consideriamo una ditta che noleggia automobili. Possiamo denire i seguenti tipi di dati: type T_MODELLO is (BUSINESS, CARAVAN, LIMOUSINE, SPIDER); type T_VEICOLO is (AUTO_PRIVATA, CAMION, BUS, SCONOSCIUTO); type VEICOLO(tipo_di_veicolo : T_VEICOLO := SCONOSCIUTO) is record Targa : STRING(1..7); Tassa_giornaliera : POSITIVE; case tipo_di_veicolo is -- inizio della parte variante when AUTO_PRIVATA => Numero_posti : POSITIVE; Modello : T_MODELLO; when CAMION => Carico_massimo : POSITIVE; Claudio Marsan Liceo cantonale di Mendrisio, 2002

3.7. RECORD when BUS => Numero_passegeri : POSITIVE; Aria_condizionata : BOOLEAN; when SCONOSCIUTO => null; end case; -- fine della parte variante end record;

133

Nella prima riga della dichiarazione del tipo VEICOLO c una componente speciale chiamata tipo_di_veicolo. Una simile componente detta discriminante ed da considerare come una specie di parametro per il tipo VEICOLO. Quando si dichiara un record con parte variante si usa un discriminante per stabilire quale variante del record deve essere utilizzata. Un discriminante ha un nome, un tipo e, se possibile (come nellesempio visto), un valore di default. La parte della dichiarazione che segue la parola riservata record detta parte ssa del record : in essa vengono dichiarate le componenti che sono comuni a tutte le varianti. Esempio 3.7.10 Riferendosi allesempio 3.7.9 abbiamo: nome del discriminante: tipo_di_veicolo; tipo del discriminante: T_VEICOLO; valore di default: SCONOSCIUTO; parte ssa: composta da Targa e Tassa_giornaliera). La parte della dichiarazione che inizia con case detta parte variante del record : in essa si dichiarano le componenti che sono diverse per le dierenti varianti. Quando si dichiara una variabile di tipo record con parte variante viene messo a disposizione solo lo spazio suciente per memorizzare una sola variante del record: luso di record con parte variante consente cos di risparmiare memoria! Le variabili di tipo record con parte variante possono essere vincolate oppure non vincolate. Esempio 3.7.11 Ecco qualche dichiarazione di variabili di tipo record con parte variante: mia_auto : VEICOLO(AUTO_PRIVATA); mezzo_1 : VEICOLO(BUS); F1, F2 : VEICOLO; -- vincolata -- vincolata -- non vincolate

Le variabili vincolate non possono mai cambiare il tipo di variante durante la loro esistenza (per esempio: mia_auto non potr mai contenere informazioni diverse da quelle contenute in AUTO_PRIVATA), mentre una variabile non vincolata pu cambiare variante durante lesecuzione del programma. Quando una variabile dichiarata ed non vincolata, ad essa viene assegnato il valore di default del discriminante (F1 e F2 saranno cos, inizialmente, della variante SCONOSCIUTO): una condizione importante anch si possano denire delle variabili non vincolate di tipo record con parte variante che il record abbia un valore di default, altrimenti si provoca un errore di compilazione. Se non si usano variabili non vincolate non necessario denire un valore di default per il record. Le componenti della parte ssa di una variabile di tipo record con parte variante devono esistere per ogni variante e possono essere scritte e lette in ogni modo. Esempio 3.7.12 Riferendoci agli esempi precedenti: mia_auto.Tassa_giornaliera := 30; F1.Targa := "ABC123K"; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

134

CAPITOLO 3. TIPI DI DATI IN ADA 95

Per le componenti della parte variante sono, per esempio, permesse le istruzioni seguenti: mezzo_1.Aria_condizionata := TRUE; Ada.Integer_Text_IO.Put(Item => mia_auto.numero_di_posti); Non sono invece permesse istruzioni come le seguenti: Ada.Integer_Text_IO.Put(Item => mezzo_1.Carico_massimo); F1.Modello := SPIDER; poich mezzo_1 non un CAMION e F1 non unAUTO_PRIVATA. Un discriminante pu sempre essere letto. Esempio 3.7.13 Listruzione seguente legale: if F1.tipo_di_veicolo = BUS then ... end if; Il discriminante di una variabile vincolata non pu mai essere cambiato, mentre nel caso di una variabile non vincolata esso pu essere cambiato ma solo se allintero record viene dato un nuovo valore in un passaggio solo. Esempio 3.7.14 Sono istruzioni valide: F1 := (AUTO_PRIVATA, "SADQ12W", 30, 5, CARAVAN); F2 := F1; F1 := mezzo_1; anche possibile dichiarare array le cui componenti sono record con parte variante. Esempio 3.7.15 Consideriamo il seguente frammento di programma: ... type TABELLA_VEICOLI is array (POSITIVE range <>) of VEICOLI; veicolo_della_ditta : TABELLA_VEICOLI(1 .. 100); ... veicolo_della_ditta(21) := (CAMION, "783JK12", 75, 5000); ... for i in veicolo_della_dittaRANGE loop Print_Info(Item => veicolo_della_ditta(i)); end loop; ... Esercizio 3.7.4 Denire la procedura Print_Info(Item : in VEICOLO) usata nel precedente esempio. Suggerimento: utilizzare unistruzione case in corrispondenza delle parti varianti del record.

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 4 Files in Ada 95


Le strutture di dati che abbiamo visto nora hanno il seguente punto comune: esse risiedono tutte nella memoria principale del calcolatore. Ci signica che la cancellazione, volontaria o meno, della memoria provoca la distruzione di queste strutture e del loro contenuto, come daltra parte del programma che le utilizza. Potrebbe essere necessario conservare dei dati alla ne dellapplicazione che li ha creati, in previsione di un uso futuro come, per esempio, nelluso di un elaboratore di testi (esso porta alla manipolazione di contenuti letti e scritti su supporto magnetico o altro) o di programmi per la gestione di banche di dati. Queste considerazioni ci portano ad introdurre il concetto di le.

4.1

Il concetto di le

Possiamo tradurre in italiano la parola le con archivio o con schedario (in francese si usa il termine chier, in tedesco si usa il termine Datei ). Fuori dal mondo informatico ognuno di noi ha gi manipolato degli archivi (per esempio: lelenco del telefono, lo schedario di una biblioteca, . . . ). Per accedere ad un elemento particolare dellarchivio bisogna far passare tutti gli elementi dellarchivio o utilizzare una chiave daccesso se larchivio stato ordinato secondo questa chiave (per esempio: ordine alfabetico, ordine di acquisto dei libri, o altro ancora). In informatica un le una struttura composta di dati, il cui numero non noto a priori e che risiede in un dispositivo sico di memorizzazione (hard disk, oppy disk, . . . ). Laccesso a un elemento (a un dato) pu essere fatto in vari modi: sequenzialmente (sequential access): il le viene percorso elemento per elemento partendo dallinizio nch si trova lelemento desiderato; direttamente (direct access): si d la posizione dellelemento; secondo una chiave (access by key): ogni valore della chiave che designa un elemento permette di ottenere lelemento desiderato. Siccome i les devono essere memorizzati in un dispositivo di memorizzazione essi devono possedere un nome e degli attributi (per esempio: data di creazione, grandezza, estensione, permessi di accesso, . . . ). Distinguiamo due tipi di les: les di testo (text les) contenenti caratteri e leggibili da un essere umano (non solo leggibili ma anche editabili, stampabili, . . . ); 135

136

CAPITOLO 4. FILES IN ADA 95

les binari (binary les) contenenti del codice binario rappresentante ogni elemento (les di questo tipo dovrebbero essere manipolati solo da programmi!). Esempio 4.1.1 Per illustrare la dierenza fra le di testo e le binario consideriamo il caso semplice nel quale vogliamo memorizzare 75 in un le. Il valore 75 sar memorizzato nel le binario sottoforma di sequenza di bits come 00000000 01001011 (ammesso che ci vogliano due bytes per memorizzare tale valore) mentre in un le di testo sar rappresentato dai caratteri 7 e 5 accostati. In Ada 95, come in altri linguaggi di programmazione, si distinguono i termini le (le object) e le esterno (external le). Un le una variabile di un programma che serve per designare un le esterno. Un le esterno un oggetto esterno al programma, conservato in un dispositivo di memorizzazione e designato da un nome. Per abuso di linguaggio si parla sempre di le.

4.2

Files di testo

Se gli elementi di un le sono caratteri (ossia di tipo CHARACTER) il le detto le di testo. I les di testo sono organizzati in righe, ognuna delle quali termina con un carattere di ne riga; dopo lultima riga il le di testo terminato (ne del le). Possiamo distinguere due tipi di les di testo: 1. les di testo che corrispondono ai dispositivi di input e output del computer; 2. les di testo che sono memorizzati in le esterni. In Ada 95 entrambi i tipi di les di testo vengono trattati allo stesso modo: da un programma sono trattati logicamente come una serie di caratteri che devono essere letti o scritti. Ogni le di testo esiste sicamente nel computer ed ha un nome speciale (si parla anche di le sico e di nome sico). Il nome sico dipende dal sistema operativo in uso. Esempio 4.2.1 Il nome sico per la stampante (le speciale) collegata alla porta parallela LPT1: nei sistemi MSDOS , solitamente, LPR; in un sistema Unix potrebbe essere /dev/lp0 o qualcosa di simile. Per ovviare alla diversit dei nomi sici dovuti ai diversi sistemi operativi, un programma scritto in Ada 95 lavora con les logici. Per poter utilizzare i les logici di testo bisogna utilizzare il package Ada.Text_IO, nel quale denito il tipo FILE_TYPE che serve appunto per dichiarare i les logici di testo (ossia per dichiarare variabili di tipo le di testo). Esempio 4.2.2 Nel seguente frammento di programma si dichiara la variabile infile di tipo le di testo: with Ada.Text_IO; ... infile : Ada.Text_IO.FILE_TYPE; ... Da notare che, essendo FILE_TYPE dichiarato in Ada.Text_IO, necessario scrivere la forma completa Ada.Text_IO.FILE_TYPE. Volendo avere una notazione pi leggera dobbiamo utilizzare la clausola use: with Ada.Text_IO; use Ada.Text_IO; ... infile : FILE_TYPE; ... Claudio Marsan Liceo cantonale di Mendrisio, 2002

4.2. FILES DI TESTO

137

Nel package Ada.Text_IO il tipo FILE_TYPE dichiarato come privato limitato (limited private, spiegheremo pi avanti cosa signica questo) e quindi il programmatore non pu sapere come in realt appare una variabile come la infile dichiarata nellesempio 4.2.2. Lunica cosa che il programmatore pu fare con una simile variabile di passarla come parametro ad alcuni sottoprogrammi presenti in Ada.Text_IO. Non sar dunque possibile comparare due variabili di tipo FILE_TYPE oppure assegnare una variabile ad unaltra e neppure dichiarare costanti di tipo FILE_TYPE. Nel seguito useremo la seguente convenzione: con le intenderemo le logico in un programma; con le esterno intenderemo le sico in un supporto di memorizzazione.

4.2.1

Creazione, apertura e chiusura di un le di testo

In Ada 95 un le di testo pu essere letto, scritto o completato alla ne. Si dice che il le utilizzato, rispettivamente, in modo lettura, modo scrittura o in modo aggiunta. La lettura (read ) di un le consiste nella consultazione del suo contenuto senza apportare alcuna modica; la scrittura (write) di un le permette di creare un contenuto nuovo, cancellando il vecchio (se questo era presente); laggiunta (append ) permette di aggiungere alla ne di un le esistente dei nuovi dati, senza perdere il contenuto gi esistente. Lapertura (open) di un le consiste, tra le altre cose, nellassociare una variabile le a un le esterno e a scegliere il modo duso (lettura, scrittura, aggiunta) del le. Ci viene eseguito con la procedura Create se il le nuovo oppure con la procedura Open se il le esiste gi. Entrambe le procedure appartengono al package Ada.Text_IO e la loro intestazione la seguente: procedure Create(File Mode Name Form procedure Open(File Mode Name Form dove: File il nuovo le di testo creato oppure il le di testo che viene aperto se gi esistente; Mode il modo duso (o di accesso) del le, per default in scrittura nel caso della creazione del le; FILE_MODE un tipo enumerativo dichiarato in Ada.Text_IO che fornisce i valori seguenti: In_File (lettura); Out_File (scrittura); Append_File (aggiunta). Name il nome del le esterno designato tramite File; Form un parametro che permette di ssare le propriet del le esterno (per esempio il diritto daccesso). Il valore di default di Name della procedura Create permette di creare un le temporaneo che sar automaticamente cancellato alla ne del programma. Il valore di default di Form permette di utilizzare le opzioni correnti dellimplementazione, spesso quelle del sistema operativo in uso. Liceo cantonale di Mendrisio, 2002 Claudio Marsan : : : : : : : : in in in in out FILE_TYPE; FILE_MODE := Out_File; STRING := ""; STRING := "");

in in in in

out FILE_TYPE; FILE_MODE; STRING; STRING := "");

138 Esempio 4.2.3 Mediante

CAPITOLO 4. FILES IN ADA 95

... new_file : Ada.Text_IO.FILE_TYPE; ... Ada.Text_IO.Create(File => new_file, name => "my_file.txt"); ... viene creato, nel direttorio corrente, il le sico my_file.txt a cui associato il le logico new_file. Adesso possibile scrivere sul le new_file. Osservazione 4.2.1 Attenzione: se un le esistente viene aperto in scrittura (ossia con Mode => Out_File) il contenuto precedente verr sovrascritto e distrutto. Se un le associato ad un le esterno mediante una chiamata di Create o di Open, diremo che il le aperto. Quando si tenta di aprire un le potrebbero vericarsi degli errori, per esempio il le potrebbe gi essere aperto oppure non esiste alcun le esterno con il nome passato alla procedura. La funzione Is_Open pu essere utilizzata per controllare se un le aperto. Lintestazione di tale funzione, presente nel package Ada.Text_IO, la seguente: function Is_Open(File : FILE_TYPE) return BOOLEAN; La chiusura (close) di un le consiste nella soppressione dellassociazione, realizzata allapertura, tra un le e un le esterno. Essa si eettua tramite la procedura Close, la cui intestazione : procedure Close(File : in out FILE_TYPE); dove File il le che deve essere chiuso. obbligatorio chiudere i les aperti in precedenza, altrimenti potrebbero esserci comportamenti anomali (dipende da sistema a sistema)! Da notare che se un le di testo stato utilizzato per la scrittura, la procedura Close si preoccuper che la linea corrente e la pagina corrente siano terminate. Ricordiamo ancora che lunico posto in un programma dove possono essere trovati nomi di les esterni nella chiamata della procedura Create oppure della procedura Open; altrimenti sono sempre usati nomi logici di les.

4.2.2

Accesso agli elementi di un le di testo

Per linput e loutput con i les di testo sono disponibili le procedure Put e Get per la lettura e la scrittura di dati di un tipo scalare; sono inoltre disponibili Put_Line, Get_Line, New_Line e Skip_Line. Per usare le procedure elencate sopra con i le di testo suciente aggiungere il nome della variabile di tipo le di testo come primo parametro. Esempio 4.2.4 Il programma seguente mostra come si crea un le di testo e come se ne legge poi il contenuto. -----Nome del file: IO_FILE_DI_TESTO.ADB Autore: Claudio Marsan Data dellultima modifica: 14 maggio 2002 Scopo: scrittura e lettura di file di testo Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Float_Text_IO; Claudio Marsan Liceo cantonale di Mendrisio, 2002

4.2. FILES DI TESTO

139

procedure IO_File_di_Testo is file_di_lettura file_di_scrittura x n ch s lung_s : : : : : : : Ada.Text_IO.FILE_TYPE; Ada.Text_IO.FILE_TYPE; FLOAT; INTEGER; CHARACTER; STRING(1..5); INTEGER;

begin -- Creazione del file di testo C:\TEMP\PROVA.TXT Ada.Text_IO.Create(File => file_di_scrittura, Mode => Ada.Text_IO.Out_File, Name => "C:\TEMP\PROVA.TXT", Form => ""); -- Scriviamo nel file di testo il valore di alcune -- variabili di tipo diverso lette da tastiera Ada.Text_IO.Put(Item => "Dare un numero reale: "); Ada.Float_Text_IO.Get(Item => x); Ada.Float_Text_IO.Put(File => file_di_scrittura, Item => x); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dare un numero intero: "); Ada.Integer_Text_IO.Get(Item => n); Ada.Integer_Text_IO.Put(File => file_di_scrittura, Item => n); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dare un carattere: "); Ada.Text_IO.Get(Item => ch); Ada.Text_IO.Put(File => file_di_scrittura, Item => ch); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dare una stringa di 5 caratteri: "); Ada.Text_IO.Skip_Line; Ada.Text_IO.Get_Line(Item => s, Last => lung_s); Ada.Text_IO.Put(File => file_di_scrittura, Item => s); Ada.Text_IO.New_Line; -- Chiusura del file Ada.Text_IO.Close(File => file_di_scrittura); -- Ora riapriamo il file Ada.Text_IO.Open(File => Mode => Name => Form => in sola lettura file_di_lettura, Ada.Text_IO.In_File, "C:\TEMP\PROVA.TXT", "");

-- Leggiamo il contenuto del file e lo visualizziamo -- sullo schermo Ada.Float_Text_IO.Get(File => file_di_lettura, Item => x); Ada.Float_Text_IO.Put(Item => x); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

140 Ada.Text_IO.New_Line;

CAPITOLO 4. FILES IN ADA 95

Ada.Integer_Text_IO.Get(File => file_di_lettura, Item => n); Ada.Integer_Text_IO.Put(Item => n); Ada.Text_IO.New_Line; Ada.Text_IO.Get(File => file_di_lettura, Item => ch); Ada.Text_IO.Put(Item => ch); Ada.Text_IO.New_Line; Ada.Text_IO.Get_Line(File => file_di_lettura, Item => s, Last => lung_s); Ada.Text_IO.Put(Item => s); Ada.Text_IO.New_Line; -- Chiusura del file Ada.Text_IO.Close(File => file_di_lettura); end IO_File_di_Testo; Esempio 4.2.5 Ammettiamo che la stampante del nostro sistema abbia il nome LPR e che vogliamo inviare loutput di un programma direttamente alla stampante. Dovremo procedere come segue: stampante : Ada.Text_IO.FILE_TYPE; e poi associare il le appena dichiarato alla stampante mediante Ada.Text_IO.Open(File => stampante, Mode => Ada.Text_IO.Out_File, Name => "LPR"); Con il comando Ada.Text_IO.Put(File => stampante, Item => "Ciao, mondo!"); verr scritta su carta la frase Ciao, mondo!. La funzione End_Of_File del package Ada.Text_IO, la cui intestazione function End_of_File(File : FILE_TYPE) return BOOLEAN; restituisce il valore TRUE se ci troviamo alla ne del le di testo che si sta considerando. La funzione End_Of_Line del package Ada.Text_IO, la cui intestazione function End_of_Line(File : FILE_TYPE) return BOOLEAN; restituisce il valore TRUE se ci troviamo alla ne della riga corrente nel le di testo che si sta considerando. Esempio 4.2.6 Il programma seguente permette di fare una copia del le di testo (giacente nel corrente direttorio) sorgente.txt nel le di testo copia.txt: -----Nome del file: COPIA_FILE.ADB Autore: Claudio Marsan Data dellultima modifica: 14 maggio 2002 Scopo: fa una copia di un file di testo Testato con: Gnat 3.13p su Windows 2000 Liceo cantonale di Mendrisio, 2002

Claudio Marsan

4.2. FILES DI TESTO

141

with Ada.Text_IO; procedure Copia_File is infile, outfile : Ada.Text_IO.FILE_TYPE; linea : STRING(1..200); lunghezza_linea : NATURAL; begin -- Apertura dei files Ada.Text_IO.Open(File => infile, Mode => Ada.Text_IO.In_File, Name => "sorgente.txt"); Ada.Text_IO.Create(File => outfile, Name => "copia.txt"); -- Copia "infile" in "outfile" while not Ada.Text_IO.End_of_File(File => infile) loop Ada.Text_IO.Get_Line(File => infile, Item => linea, Last => lunghezza_linea); Ada.Text_IO.Put_Line(File => outfile, Item => linea(1..lunghezza_linea)); end loop; -- Chiusura dei files Ada.Text_IO.Close(File => infile); Ada.Text_IO.Close(File => outfile); Ada.Text_IO.Put(Item => "Fatto!"); end Copia_File; Esempio 4.2.7 Nel programma dellesempio 4.2.6 abbiamo ammesso che il numero massimo di caratteri per una riga sia 200. Se non fosse nota a priori la lunghezza massima di una riga dovremmo sostituire il ciclo while con il seguente codice, che legge un carattere alla volta: -- copia "infile" in "outfile" while not Ada.Text_IO.End_of_File(File => infile) loop while not Ada.Text_IO.End_of_Line(File => infile) loop Ada.Text_IO.Get(File => infile, Item => ch); Ada.Text_IO.Put(File => outfile, Item => ch); end loop; Ada.Text_IO.Skip_Line(File => infile); Ada.Text_IO.New_Line(File => outfile); end loop; Naturalmente ch una variabile di tipo CHARACTER. La procedura Skip_Line permette, quando si arrivati alla ne di una riga, di passare alla prossima riga. I les sono usati spesso per memorizzare valori che sono risultati di calcoli o di misurazioni di laboratorio, in modo tale che essi possano poi essere analizzati o elaborati in un secondo tempo, magari anche da un programma applicativo particolare. Esempio 4.2.8 Il seguente frammento di programma esegue 1000 calcoli dello stesso tipo e salva i risultati nel le di testo datafile. La natura dei calcoli non ha, qui, alcuna importanza e Liceo cantonale di Mendrisio, 2002 Claudio Marsan

142

CAPITOLO 4. FILES IN ADA 95

assumiamo quindi che essi siano svolti in una funzione Calcola della quale possiamo ignorare il funzionamento interno. Ecco il frammento di programma: ... -- apertura di "datafile" Ada.Text_IO.Put(Item => "Nome del nuovo file: "); Ada.Text_IO.Get_Line(Item => nome_file, Last => lung_nome); Ada.Text_IO.Create(File => datafile, Name => nome_file(1..lung_nome)); Ada.Text_IO.Set_Line_Length(File => datafile, 100); -- esegue 1000 calcoli e memorizza il risultato in "datafile" for i in 1..1000 loop valore := Calcola; -- valore di tipo FLOAT Ada.Float_Text_IO.Put(File => datafile, Item => valore); end loop; -- chiusura di "datafile" Ada.Text_IO.Close(File => datafile); ... Con listruzione Ada.Float_Text_IO.Put(File => datafile, Item => valore); il numero reale valore viene scritto nel le datafile nella forma esponenziale standard; volendo un altro formato bisogna usare i parametri Exp, Fore e Aft, per esempio: Ada.Float_Text_IO.Put(File => datafile, Item => valore, Fore => 2, Aft => 3, Exp => 0); Mediante listruzione Set_Line_Length(datafile, 100); abbiamo posto a 100 caratteri la lunghezza massima di una riga di datafile. La procedura Put parte automaticamente da una nuova riga ogni volta che non c spazio suciente per scrivere il prossimo valore sulla riga corrente. Se non avessimo specicato una lunghezza massima per le righe, allora tutti i valori sarebbero scritti su una stessa riga, in teoria senza ne. Esempio 4.2.9 Nel frammento di programma che segue vogliamo leggere numeri reali da un le di testo contenente numeri reali, per esempio dal le datafile creato in precedenza. Il programma calcola e scrive la media aritmetica dei numeri letti dal le. Ecco il codice: ... valore : FLOAT; conta_valori : NATURAL := 0; somma : FLOAT := 0.0; ... while not Ada.Text_IO.End_of_File(File => datafile) loop Ada.Float_Text_IO.Get(File => datafile, Item => valore); somma := somma + valore; conta_valori := conta_valori + 1; end loop; Ada.Text_IO.Put(Item => "Media aritmetica dei valori letti: "); Ada.Float_Text_IO.Put(Item => somma/FLOAT(conta_valori)); ... Claudio Marsan Liceo cantonale di Mendrisio, 2002

4.2. FILES DI TESTO

143

Esempio 4.2.10 Consideriamo un elenco telefonico memorizzato nel le di testo (giacente nel direttorio corrente) telefono.lst nella forma seguente: Bianchi Alberto 12-233-3321 Bianchi Edoardo 12-345-0944 Neri Giacinto 29-222-9001 Rossi Umberto 17-133-0011 Verdi Giuseppe 34-992-3232 ... (ossia: per ogni persona ci sono due righe: una per il nome, laltra per il numero di telefono). Vogliamo costruire un programma che, letto dal terminale il nome di una persona, va a ricercare nellelenco il numero di telefono. Useremo lalgoritmo pi semplice: il programma parte dallinizio della lista e legge un nome alla volta nch trova il nome richiesto oppure nch giunge alla ne del le. Per indicare che abbiamo trovato il nome daremo il valore TRUE alla variabile booleana trovato. Ecco il codice: -----Nome del file: TROVA_NUMERO_DI_TELEFONO.ADB Autore: Claudio Marsan Data dellultima modifica: 14 maggio 2002 Scopo: ricerca di un elemento in un file di testo Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Trova_Numero_di_Telefono is elenco req_pers, curr_pers tel_no req_l, curr_l, tel_no_l trovato : : : : : Ada.Text_IO.FILE_TYPE; STRING(1..50); STRING(1..15); NATURAL; BOOLEAN := FALSE;

begin -- legge il nome della persona richiesta Ada.Text_IO.Put(Item => "Nome della persona richiesta: "); Ada.Text_IO.Get_Line(Item => req_pers, Last => req_l); -- ricerca della persona richiesta nellelenco Ada.Text_IO.Open(File => elenco, Mode => Ada.Text_IO.In_File, Name => "telefono.lst"); while not trovato and not Ada.Text_IO.End_of_File(File => elenco) loop -- legge nome e numero di telefono Ada.Text_IO.Get_Line(File => elenco, Item => curr_pers, Last => curr_l); Ada.Text_IO.Get_Line(File => elenco, Item => tel_no, Last => tel_no_l); if curr_pers(1..curr_l) = req_pers(1..req_l) then Ada.Text_IO.Put(Item => "Numero di telefono: "); Ada.Text_IO.Put(Item => tel_no(1..tel_no_l)); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

144 trovato := TRUE; end if; end loop; Ada.Text_IO.Close(File => elenco);

CAPITOLO 4. FILES IN ADA 95

if not trovato then Ada.Text_IO.Put_Line("Questo nome non e nellelenco!"); end if; end Trova_Numero_di_Telefono; Abbiamo visto che possibile o leggere un le di testo, scrivere su un le di testo e appendere dei dati alla ne di un le di testo, ma non possibile alternare le operazioni di scrittura e lettura. In un le di testo la lettura e la scrittura avvengono in modo sequenziale, dallinizio alla ne del le: non si pu tornare indietro ad una determinata posizione nel mezzo del le; si pu solo tornare indietro allinizio del le mediante la procedura Reset ( unoperazione analoga al riavvolgimento del nastro di una cassetta). Ci sono due versioni di questa procedura: 1. procedure Reset(File : in out FILE_TYPE; Mode : in FILE_MODE); nella quale, oltre a specicare il nome del le, possibile stabilire se richiesta la lettura o la scrittura del le. Facendo il reset del le cos possibile cambiare anche il modo di accesso. 2. procedure Reset(File : in out FILE_TYPE); nella quale il le viene riportato allinizio e il modo daccesso non viene cambiato (ossia: se si stava leggendo si continuer a leggere, se si stava scrivendo si continuer a scrivere). Esempio 4.2.11 Riprendiamo lesempio 4.2.10 dellelenco telefonico e modichiamo il codice in modo tale da permettere il cambiamento di un numero di telefono dellelenco. Siccome non possibile scrivere e leggere contemporaneamente da uno stesso le, verr creata una copia temporanea del le telefono.lst. Se durante il processo di copia la persona a cui bisogna cambiare il numero di telefono viene trovata nel le telefono.lst, nella copia verr scritto il nuovo numero di telefono. Poi entrambi i les verrano resettati e il le temporaneo verr copiato su telefono.lst. Ecco il codice: -----Nome del file: CAMBIA_NUMERO_DI_TELEFONO.ADB Autore: Claudio Marsan Data dellultima modifica: 14 maggio 2002 Scopo: modifica di un file di testo Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO; procedure Cambia_Numero_di_Telefono is catalogo, tempfile : req_pers, curr_pers : new_no, tel_no : req_l, curr_l, new_no_l, tel_no_l : trovato : Ada.Text_IO.FILE_TYPE; STRING(1..50); STRING(1..15); NATURAL; BOOLEAN := FALSE;

begin -- legge il nome della persona richiesta e il -- suo nuovo numero di telefono Claudio Marsan Liceo cantonale di Mendrisio, 2002

4.2. FILES DI TESTO Ada.Text_IO.Put(Item => "Persona a cui bisogna cambiare il numero: "); Ada.Text_IO.Get_Line(Item => req_pers, Last => req_l); Ada.Text_IO.Put(Item => "Dare il nuovo numero di telefono: "); Ada.Text_IO.Get_Line(Item => new_no, Last => new_no_l); -- apertura di "catalogo" per la lettura Ada.Text_IO.Open(File => catalogo, Mode => Ada.Text_IO.In_File, Name => "telefono.lst"); -- apertura di un file temporaneo per la scrittura Ada.Text_IO.Create(File => tempfile);

145

-- copia di "catalogo" in "tempfile" e modifica -- del numero di telefono richiesto while not Ada.Text_IO.End_of_File(File => catalogo) loop -- legge nome e numero di telefono Ada.Text_IO.Get_Line(File => catalogo, Item => curr_pers, Last => curr_l); Ada.Text_IO.Get_Line(File => catalogo, Item => tel_no, Last => tel_no_l); -- scrive nome e numero di telefono in "tempfile" Ada.Text_IO.Put_Line(File => tempfile, Item => curr_pers(1..curr_l)); if curr_pers(1..curr_l) = req_pers(1..req_l) then Ada.Text_IO.Put_Line(File => tempfile, Item => new_no(1..new_no_l)); trovato := TRUE; else Ada.Text_IO.Put_Line(File => tempfile, Item => tel_no(1..tel_no_l)); end if; end loop; if trovato then -- torna allinizio dei files Ada.Text_IO.Reset(File => tempfile, Mode => Ada.Text_IO.In_File); Ada.Text_IO.Reset(File => catalogo, Mode => Ada.Text_IO.Out_File); -- copia "tempfile" in "catalogo" while not Ada.Text_IO.End_of_File(File => tempfile) loop Ada.Text_IO.Get_Line(File => tempfile, Item => curr_pers, Last => curr_l); Ada.Text_IO.Put_Line(File => catalogo, Item => curr_pers(1..curr_l)); Ada.Text_IO.Get_Line(File => tempfile, Item => tel_no, Last => tel_no_l); Ada.Text_IO.Put_Line(File => catalogo, Item => tel_no(1..tel_no_l)); end loop; else Ada.Text_IO.Put_Line(Item => "Questo nome non e nellelenco!"); end if; -- chiusura dei files Ada.Text_IO.Close(catalogo); Ada.Text_IO.Close(tempfile); end Cambia_Numero_di_Telefono;

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

146

CAPITOLO 4. FILES IN ADA 95

4.2.3

Altre manipolazioni possibili con i les di testo

Se usiamo le procedure e le funzioni presenti nel package Ada.Text_IO senza indicare il nome logico del le si intendono, per default, i dispositivi di input e output del terminale (tastiera e schermo). Il terminale infatti considerato come un insieme di due les di testo, detti standard input e standard output. Questi les sono aperti automaticamente quando un programma viene lanciato e sono associati automaticamente alla tastiera e allo schermo. Le funzioni function Standard_Input return FILE_TYPE; function Standard_Output return FILE_TYPE; restituiscono i nomi logici dei dispositivi standard. Mediante le procedure procedure Set_Input(File : in FILE_TYPE); procedure Set_Output(File : in FILE_TYPE); possibile cambiare i dispositivi standard di input e, rispettivamente, output. Mediante le funzioni function Current_Input return FILE_TYPE; function Current_Output return FILE_TYPE; possibile stabilire i correnti dispositivi standard di input e, rispettivamente, output. La funzione function Name(File : FILE_TYPE) return STRING; restituisce il nome del le esterno associato a File; la funzione function Mode(File : FILE_TYPE) return FILE_MODE; restituisce il modo duso di File (lettura, scrittura o aggiunta). Esempio 4.2.12 Il seguente programma mostra come si usano alcune delle funzioni e procedure viste sopra: ------Nome del file: MANIPOLAZIONE_DI_FILES.ADB Autore: Claudio Marsan Data dellultima modifica: 14 maggio 2002 Scopo: mostra luso di alcune funzioni e procedure per la manipolazione di files di testo Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO;

procedure Manipolazione_di_Files is saluti_logico : Ada.Text_IO.FILE_TYPE; begin -- File standard di input e output Ada.Text_IO.Put(Item => "File standard di input: "); Ada.Text_IO.Put_Line(Item => Ada.Text_IO.Name(Ada.Text_IO.Standard_Input)); Claudio Marsan Liceo cantonale di Mendrisio, 2002

4.3. FILES BINARI

147

Ada.Text_IO.Put(Item => "File standard di output: "); Ada.Text_IO.Put_Line(Item => Ada.Text_IO.Name(Ada.Text_IO.Standard_Output)); -- Reindirizziamo loutput sul file "saluti.txt" Ada.Text_IO.Create(File => saluti_logico, Name => "saluti.txt"); Ada.Text_IO.Set_Output(File => saluti_logico); -- Scriviamo qualcosa Ada.Text_IO.Put_Line(Item => "Ciao, mondo!"); Ada.Text_IO.Put_Line(Item => "Hello, world!"); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "File di input corrente: "); Ada.Text_IO.Put(Item => Ada.Text_IO.Name(Ada.Text_IO.Current_Input)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "File di output corrente: "); Ada.Text_IO.Put(Item => Ada.Text_IO.Name(Ada.Text_IO.Current_Output)); -- Chiusura del file Ada.Text_IO.Close(File => saluti_logico); end Manipolazione_di_Files La procedura procedure Delete(File : in FILE_TYPE); cancella il le esterno associato a File.

4.3

Files binari

I les binari comprendono tutti i les che non sono les di testo. Il loro contenuto pu essere visto come una successione di bits che costituiscono gli elementi del le, sistemati uno dopo laltro. Come per i les di testo la ne del le si situa dopo lultimo elemento. In Ada 95 i les binari si possono manipolare mediante i seguenti packages generici: Ada.Sequential_IO per un accesso sequenziale; Ada.Direct_IO per un accesso diretto. In questi packages sono deniti tutti i tipi, sottotipi, procedure, funzioni, . . . che sono necessari alla manipolazione dei les binari. Un le binario si dichiara come una variabile di tipo FILE_TYPE la cui struttura nascosta allutente (come daltronde succedeva per i les di testo). La manipolazione di un le binario richiede la conoscenza del tipo degli elementi del le. Esempio 4.3.1 Vogliamo costruire un le binario sequenziale di INTEGER. Per avere a disposizione le procedure e le funzioni per la manipolazione di simili les binari saranno necessarie le istruzioni seguenti: with Ada.Sequential_IO; ... procedure ... ... package Integer_Seq_IO is new Ada.Sequential_IO(INTEGER); ... file_di_interi : Integer_Seq_IO.FILE_TYPE; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

148 ... begin ... end ...;

CAPITOLO 4. FILES IN ADA 95

Esempio 4.3.2 Vogliamo costruire un le binario ad accesso diretto che permetta di memorizzare delle date in un formato particolare. Per avere a disposizione le procedure e le funzioni per la manipolazione di simili les binari saranno necessarie le istruzioni seguenti: with Ada.Direct_IO; ... procedure ... ... type T_GIORNO is range 1..31; type T_MESE is (gennaio, febbraio, marzo, aprile, maggio, giugno, luglio, agosto, settembre, ottobre, novembre, dicembre); type T_DATA is record Giorno : T_GIORNO; Mese : T_MESE; Anno : INTEGER; end record; ... package Data_Dir_IO is new Ada.Direct_IO(T_DATA); ... compleanni_3bcd : Data_Dir_IO.FILE_TYPE; ... begin ... end ...; Lunica cosa che il programmatore pu fare con una variabile di tipo FILE_TYPE di darla come parametro ad alcuni sottoprogrammi presenti nel package Ada.Sequential_IO oppure in Ada.Direct_IO. Non sar dunque possibile comparare due variabili di tipo FILE_TYPE oppure assegnare una variabile ad unaltra e neppure dichiarare costanti di tipo FILE_TYPE.

4.3.1

Creazione, apertura e chiusura di un le binario

In Ada 95 un le binario pu sempre essere letto oppure scritto. Inoltre pu essere ancora completato alla ne se il le sequenziale oppure letto e scritto contemporaneamente se il le ad accesso diretto. Si dir cos che il le binario aperto, rispettivamente, in modo lettura, in modo scrittura, in modo aggiunta oppure in modo letturascrittura. La lettura (read ) consiste nel consultare il contenuto senza modicarlo; la scrittura (write) permette di creare un nuovo contenuto (eliminando un eventuale vecchio contenuto); laggiunta (append ) lascia il contenuto attuale invariato e appende alla ne del le il nuovo contenuto; la lettura scrittura (read-write) permette di leggere, modicare o aggiungere informazioni in qualsiasi parte del le. Lapertura (open) di un le binario consiste, tra laltro, nellassociare una variabile di tipo le ad un le esterno e nello scegliere il modo duso (accesso sequenziale oppure diretto, lettura, scrittura, aggiunta, letturascrittura). Ci si fa tramite la procedura Create se il le nuovo oppure tramite la procedura Open se il le gi esistente. Ecco lintestazione per la procedura Create: nel caso di un le binario sequenziale procedure Create(File : in out FILE_TYPE; Claudio Marsan Liceo cantonale di Mendrisio, 2002

4.3. FILES BINARI Mode : in File_Mode := Out_File; Name : in STRING := ""; Form : in STRING := ""); nel caso di un le binario ad accesso diretto: procedure Create(File Mode Name Form : : : : in in in in out FILE_TYPE; File_Mode := InOut_File; STRING := ""; STRING := "");

149

e lintestazione della procedura Open: procedure Open(File : Mode Name Form dove: File il le che verr creato, se nuovo, oppure aperto, se gi esistente; Mode il modo duso del le; in fase di creazione il valore di default Out_File se il le sequenziale oppure InOut_File se il le ad accesso diretto; File_Mode un tipo enumerativo che ammette i valori: In_File (lettura), Out_File (scrittura) e Append_File (aggiunta) se il le sequenziale; In_File (lettura), Out_File (scrittura) e InOut_File (letturascrittura) se il le ad accesso diretto. Name il nome del le esterno associato a File (per default viene creato un le temporaneo che verr cancellato automaticamente alla ne del le); Form un parametro che permette di ssare le propriet del le esterno. La chiusura (close) di un le binario, come per un le di testo, consiste nella soppressione dellassociazione, attivata allapertura del le, tra un le e un le esterno. Essa avviene tramite la procedura Close, la cui intestazione : procedure Close(File : in out FILE_TYPE); al pi tardi alla chiusura del le che il contenuto viene modicato in funzione delle operazioni svolte. in out FILE_TYPE; : in File_Mode; : in STRING := ""; : in STRING := "");

4.3.2

Accesso agli elementi di un le binario sequenziale

Nel package Ada.Sequential_IO sono dichiarate le procedure Read e Write che servono per leggere e scrivere un le binario sequenziale di dati del tipo degli elementi del le. Ecco lintestazione della procedura Read: procedure Read(File : in FILE_TYPE; Item : out Element_Type); e lintestazione della procedura Write: procedure Write(File : in FILE_TYPE; Item : in Element_Type); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

150 dove: File il le sequenziale; Item lelemento letto o scritto; Element_Type il tipo di Item.

CAPITOLO 4. FILES IN ADA 95

Esempio 4.3.3 Il seguente programma mostra la lettura e la scrittura da un le binario sequenziale: -----Nome del file: ESEMPIO_FILE_BINARIO_SEQUENZIALE.ADB Autore: Claudio Marsan Data dellultima modifica: 14 maggio 2002 Scopo: input e output con un file binario sequenziale Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Sequential_IO;

procedure Esempio_File_Binario_Sequenziale is type T_MESE is (gennaio, febbraio, marzo, aprile, maggio, giugno, luglio, agosto, settembre, ottobre, novembre, dicembre); type T_GIORNO is range 1..31; type T_DATA is record Giorno : T_GIORNO; Mese : T_MESE; Anno : INTEGER; end record; procedure Scrivi_Data(Item : in T_DATA) is -- Scrive una data sullo schermo begin Ada.Text_IO.Put(Item => T_GIORNOIMAGE(Item.Giorno)); Ada.Text_IO.Put(Item => " "); Ada.Text_IO.Put(Item => T_MESEIMAGE(Item.Mese)); Ada.Text_IO.Put_Line(Item => INTEGERIMAGE(Item.Anno)); end Scrivi_Data; package Data_Seq_IO is new Ada.Sequential_IO(T_DATA); date_importanti inizio_millennio ch data_temporanea : : : : Data_Seq_IO.FILE_TYPE; T_DATA; CHARACTER; T_DATA;

begin -- Creazione e apertura "date.txt" del file in scrittura Data_Seq_IO.Create(File => date_importanti, Name => "date.txt"); inizio_millennio := (Giorno => 1, Mese => gennaio, Anno => 2001);

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

4.3. FILES BINARI -- Scrittura di qualche data sul file "date.txt" Data_Seq_IO.Write(File => date_importanti, Item => inizio_millennio); Data_Seq_IO.Write(File => date_importanti, Item => (Giorno => 15, Mese => maggio, Anno => 2002)); Data_Seq_IO.Write(File => date_importanti, Item => (Giorno => 19, Mese => giugno, Anno => 2002)); Data_Seq_IO.Write(File => date_importanti, Item => (Giorno => 14, Mese => luglio, Anno => 2002)); -- Chiusura del file "date.txt" Data_Seq_IO.Close(File => date_importanti); Ada.Text_IO.Put_Line(Item => "Il file date.txt e stato scritto!"); Ada.Text_IO.Put(Item => "Premere 1 per leggere il contenuto del file: "); Ada.Text_IO.Get(Item => ch); if ch = 1 then -- Apertura del file "date.txt" in lettura Data_Seq_IO.Open(File => date_importanti, Mode => Data_Seq_IO.In_File, Name => "date.txt"); -- Lettura del contenuto del file "date.txt" e -- visualizzazione sullo schermo dei dati letti Data_Seq_IO.Read(File => date_importanti, Item => Scrivi_Data(Item => data_temporanea); Data_Seq_IO.Read(File => date_importanti, Item => Scrivi_Data(Item => data_temporanea); Data_Seq_IO.Read(File => date_importanti, Item => Scrivi_Data(Item => data_temporanea); Data_Seq_IO.Read(File => date_importanti, Item => Scrivi_Data(Item => data_temporanea); -- Chiusura del file "date.txt" Data_Seq_IO.Close(File => date_importanti); end if; Ada.Text_IO.Put_Line(Item => "Ok!"); end Esempio_File_Binario_Sequenziale;

151

data_temporanea); data_temporanea); data_temporanea); data_temporanea);

4.3.3

Manipolazione di les binari sequenziali

La manipolazione di un le binario sequenziale consiste, essenzialmente, nella lettura, nelluso del contenuto e nella scrittura di un (nuovo) le binario. Qui il le termina dopo lultimo elemento (fine del file). Una lettura corretta deve tener conto della ne del le, anch si eviti di leggere oltre, provocando cos un errore. Nel package Ada.Sequential_IO denita la funzione End_Of_File che restituisce il valore TRUE se stata raggiunta la ne del le, altrimenti restituisce FALSE. Lintestazione di tale funzione : function End_Of_File(File : in FILE_TYPE) return BOOLEAN; Esempio 4.3.4 Il seguente programma mostra come si pu fare una copia di un le binario sequenziale:

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

152 ------

CAPITOLO 4. FILES IN ADA 95 Nome del file: COPIA_FILE_BINARIO_SEQUENZIALE.ADB Autore: Claudio Marsan Data dellultima modifica: 14 maggio 2002 Scopo: esegue una copia di un file binario sequenziale Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Sequential_IO;

procedure Copia_File_Binario_Sequenziale is type T_MESE is (gennaio, febbraio, marzo, aprile, maggio, giugno, luglio, agosto, settembre, ottobre, novembre, dicembre); type T_GIORNO is range 1..31; type T_DATA is record Giorno : T_GIORNO; Mese : T_MESE; Anno : INTEGER; end record; package Data_Seq_IO is new Ada.Sequential_IO(T_DATA); file_originale : Data_Seq_IO.FILE_TYPE; file_destinazione : Data_Seq_IO.FILE_TYPE; data_temporanea : T_DATA; begin -- Apertura di "date.txt" in lettura Data_Seq_IO.Open(File => file_originale, Mode => Data_Seq_IO.In_File, Name => "date.txt"); -- Creazione di "date_nuovo.txt" Data_Seq_IO.Create(File => file_destinazione, Name => "date_nuovo.txt"); -- Copia dei dati while not Data_Seq_IO.End_Of_File(File => file_originale) loop Data_Seq_IO.Read(File => file_originale, Item => data_temporanea); Data_Seq_IO.Write(File => file_destinazione, Item => data_temporanea); end loop; -- Chiusura dei due files Data_Seq_IO.Close(File => file_originale); Data_Seq_IO.Close(File => file_destinazione); Ada.Text_IO.Put(Item => "Copia eseguita!"); end Copia_File_Binario_Sequenziale;

4.3.4

Accesso agli elementi di un le binario ad accesso diretto

Anche nel package Ada.Direct_IO, dedicato ai les binari ad accesso diretto, sono denite delle procedure Read e Write per la lettura e la scrittura di dati del tipo degli elementi del le. QueClaudio Marsan Liceo cantonale di Mendrisio, 2002

4.3. FILES BINARI

153

ste operazioni possono essere sequenziali ma anche eettuarsi in funzione del valore di un indice. Questo indice un numero intero compreso tra 1 e un certo limite massimo, imposto dallimplementazione del compilatore Ada 95. Tale indice di tipo Positive_Count, che un sottotipo di Count; entrambi sono deniti nel package Ada.Direct_IO. Abbiamo cos: type Count is range 0..limite_massimo; subtype Positive_Count is Count range 1..CountLAST; procedure Read(File : in FILE_TYPE; Item : out Element_Type); procedure Read(File : in FILE_TYPE; Item : out Element_Type; From : in Positive_Count); procedure Write(File : in FILE_TYPE; Item : in Element_Type); procedure Write(File : in FILE_TYPE; Item : in Element_Type; To : in Positive_Count); dove: File il le ad accesso diretto; Item lelemento letto o scritto; Element_Type il tipo di Item; From il valore dellindice, ossia la posizione dellelemento da leggere; To il valore dellindice, ossia la posizione di scrittura dellelemento. In ogni caso lindice viene incrementato di 1 dopo ogni lettura o scrittura. Essendo arbitrario il valore dellindice possibile leggere o scrivere in una qualsiasi parte di un le binario ad accesso diretto. Lindice pu essere inoltre modicato senza dover necessariamente utilizzare le procedure Read e Write, basta infatti utilizzare la procedura Set_Index, la cui intestazione : procedure Set_Index(File : in FILE_TYPE; To : in Positive_Count); dove: File il le ad accesso diretto; To il nuovo valore dellindice. Osservazione 4.3.1 Il nuovo valore dellindice, assegnato tramite la procedura Set_Index, pu oltrepassare la posizione dellultimo elemento del le! Per sapere qual la posizione attuale dellindice si pu utilizzare la funzione Index, la cui intestazione : function Index(File : in FILE_TYPE) return Positive_Count; Sia Set_Index che Index sono denite nel package Ada.Direct_IO.

4.3.5

Manipolazione di les binari ad accesso diretto

Nel package Ada.Direct_IO sono presenti anche le funzioni End_Of_File, che funziona come nel caso di les binari sequenziali, e la funzione Size che restituisce il numero di elementi di un le binario ad accesso diretto. Lintestazione di questultima funzione : function Size(File : in FILE_TYPE) return Count; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

154

CAPITOLO 4. FILES IN ADA 95

Siccome possibile posizionare lindice dopo la ne del le possiamo avere i seguenti comportamenti in questa posizione: un errore, se si tenta di leggere; la creazione di un nuovo elemento, in scrittura. In un le non possono esserci buchi e quindi tra lelemento che era lultimo del le e il nuovo elemento verranno creati, se necessario, degli elementi il cui contenuto non denito. Un le ad accesso diretto quindi simile ad una tabella che pu aumentare la propria dimensione. Esempio 4.3.5 Il seguente programma mostra come si pu fare una copia di un le binario ad accesso diretto: -----Nome del file: COPIA_FILE_BINARIO_AD_ACCESSO_DIRETTO.ADB Autore: Claudio Marsan Data dellultima modifica: 14 maggio 2002 Scopo: crea un file binario ad accesso diretto e ne esegue poi una copia Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Direct_IO;

procedure Copia_File_Binario_ad_Accesso_Diretto is type T_MESE is (gennaio, febbraio, marzo, aprile, maggio, giugno, luglio, agosto, settembre, ottobre, novembre, dicembre); type T_GIORNO is range 1..31; type T_DATA is record Giorno : T_GIORNO; Mese : T_MESE; Anno : INTEGER; end record; procedure Scrivi_Data(Item : in T_DATA) is -- Scrive una data sullo schermo begin Ada.Text_IO.Put(Item => T_GIORNOIMAGE(Item.Giorno)); Ada.Text_IO.Put(Item => " "); Ada.Text_IO.Put(Item => T_MESEIMAGE(Item.Mese)); Ada.Text_IO.Put_Line(Item => INTEGERIMAGE(Item.Anno)); end Scrivi_Data; package Data_Dir_IO is new Ada.Direct_IO(T_DATA); date_importanti file_originale file_destinazione inizio_millennio ch data_temporanea : : : : : : Data_Dir_IO.FILE_TYPE; Data_Dir_IO.FILE_TYPE; Data_Dir_IO.FILE_TYPE; T_DATA; CHARACTER; T_DATA;

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

4.3. FILES BINARI begin -- Creazione e apertura "date.txt" del file in scrittura Data_Dir_IO.Create(File => date_importanti, Name => "date.txt"); inizio_millennio := (Giorno => 1, Mese => gennaio, Anno => 2001); -- Scrittura di qualche data sul file "date.txt" Data_Dir_IO.Write(File => date_importanti, Item => inizio_millennio); Data_Dir_IO.Write(File => date_importanti, Item => (Giorno => 15, Mese => maggio, Anno => 2000)); Data_Dir_IO.Write(File => date_importanti, Item => (Giorno => 19, Mese => giugno, Anno => 2000)); Data_Dir_IO.Write(File => date_importanti, Item => (Giorno => 14, Mese => luglio, Anno => 2000)); -- Chiusura del file "date.txt" Data_Dir_IO.Close(File => date_importanti); Ada.Text_IO.Put_Line(Item => "Il file date.txt e stato scritto!"); Ada.Text_IO.Put(Item => "Premere 1 per leggere il contenuto del file: "); Ada.Text_IO.Get(Item => ch); if ch = 1 then -- Apertura del file "date.txt" in lettura Data_Dir_IO.Open(File => date_importanti, Mode => Data_Dir_IO.In_File, Name => "date.txt"); -- Lettura del contenuto del file "date.txt" e -- visualizzazione sullo schermo dei dati letti Data_Dir_IO.Read(File => date_importanti, Item => Scrivi_Data(Item => data_temporanea); Data_Dir_IO.Read(File => date_importanti, Item => Scrivi_Data(Item => data_temporanea); Data_Dir_IO.Read(File => date_importanti, Item => Scrivi_Data(Item => data_temporanea); Data_Dir_IO.Read(File => date_importanti, Item => Scrivi_Data(Item => data_temporanea); -- Chiusura del file "date.txt" Data_Dir_IO.Close(File => date_importanti); end if; -- Apertura di "date.txt" in lettura Data_Dir_IO.Open(File => file_originale, Mode => Data_Dir_IO.In_File, Name => "date.txt"); -- Creazione di "date_nuovo.txt" Data_Dir_IO.Create(File => file_destinazione, Name => "date_nuovo.txt"); -- Copia dei dati for i in 1..Data_Dir_IO.Size(File => file_originale) loop Data_Dir_IO.Read(File => file_originale, Item => data_temporanea); Liceo cantonale di Mendrisio, 2002

155

data_temporanea); data_temporanea); data_temporanea); data_temporanea);

Claudio Marsan

156

CAPITOLO 4. FILES IN ADA 95 Data_Dir_IO.Write(File => file_destinazione, Item => data_temporanea); end loop; -- Chiusura dei due files Data_Dir_IO.Close(File => file_originale); Data_Dir_IO.Close(File => file_destinazione);

Ada.Text_IO.Put(Item => "Copia eseguita!"); end Copia_File_Binario_ad_Accesso_Diretto; Osservazione 4.3.2 Gli elementi di un le binario ad accesso diretto non possono essere array non vincolati e nemmeno record con parti varianti senza valori di default.

4.4

Altre osservazioni sulluso dei les

Per ognuna delle tre categorie di les (les di testo, les binari sequenziali e les binari ad accesso diretto) i packages corrispondenti mettono a disposizione le procedure Delete e Reset e le funzioni Is_Open, Mode e Name. La procedura Delete, la cui intestazione procedure Delete(File : in out FILE_TYPE); chiude il le File e cancella il le esterno ad esso associato dal dispositivo di memorizzazione. Lintestazione della procedura Reset ha due possibili forme: procedure Reset(File : in out FILE_TYPE); oppure procedure Reset(File : in out FILE_TYPE; Mode : in FILE_MODE); dove File il le da manipolare e Mode il nuovo modo duso. Tale procedura posiziona il le in modo tale che la lettura ricominci dal primo elemento per i modi lettura e letturascrittura, o che la scrittura parta dallinizio per i modi letturascrittura e scrittura, o che la scrittura riprenda dopo lultimo elemento per il modo aggiunta. La seconda forma dela procedura Reset permette di cambiare il modo del le, ci potrebbe essere utile, per esempio, per rileggere gli elementi di un le che sono appena stati scritti. Inoltre per un le binario ad accesso diretto, luso della procedura Reset comporta lassegnazione allindice del valore 1. La funzione Is_Open, la cui intestazione function Is_Open(File : in FILE_TYPE) return BOOLEAN; restituisce il valore TRUE se File aperto, FALSE altrimenti. La funzione Mode, la cui intestazione function Mode(File : in FILE_TYPE) return FILE_MODE; restituisce il modo con cui stato aperto File. La funzione Name, la cui intestazione function Name(File : in FILE_TYPE) return STRING; restituisce il nome del le esterno associato a File.

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 5 Gestione delle eccezioni in Ada 95


La nozione di errore in esecuzione (runtime error ) gi stata riscontrata in varie occasioni (per esempio superamento del limite inferiore o superiore di un intervallo oppure divisione per zero). Spesso un errore di esecuzione in unapplicazione provoca la sua ne brutale (macchina in stallo, les corrotti, bombe, errori irreversibili di sistema, . . . ). Un programma scitto in Ada 95 pu recuperare e trattare certi errori di esecuzione se chi concepisce lapplicazione prevede questa possibilit: Ada 95 mette infatti a disposizione un meccanismo di gestione degli errori, detto eccezioni (exceptions).

5.1

Introduzione

Quando un programma viene eseguito possono capitare, talvolta, delle situazioni impreviste, dette eccezioni. Esempio 5.1.1 Uneccezione pu essere il risultato di operazioni di tipo diverso, per esempio: divisione per zero; estrazione della radice quadrata di un numero negativo; uso di un indice di un array fuori dallintervallo permesso; dare un input errato a un programma o a un sottoprogramma; ... Quando si scrive un programma lalgoritmo deve essere il pi possibile chiaro e facile da eseguire. Se per ad ogni passo dellalgoritmo si fanno tutti i controlli necessari per prevenire ogni possibile tipo di errore e ogni tipo di evento anormale, allora lalgoritmo diventa molto complesso e dicile da seguire. Per questo motivo in Ada 95 c un meccanismo per gestire gli eventi eccezionali allesterno della parte di codice relativa alla programmazione dellalgoritmo.

5.2

Eccezioni predenite

Quando capita un errore nellesecuzione di un programma normalmente il programma termina e viene visualizzato un messaggio derrore. 157

158

CAPITOLO 5. GESTIONE DELLE ECCEZIONI IN ADA 95

Esempio 5.2.1 Il seguente il messaggio derrore che si ottiene (con Gnat 3.13p su Windows 2000) quando si tenta di dividere per zero: raised CONSTRAINT_ERROR : calcolatrice.adb:37 Il messaggio sopra ci dice che alla riga 37 del le calcolatrice.adb capitato un evento eccezionale di tipo (constraint error). In Ada 95 sono predeniti quattro tipi di eccezioni: CONSTRAINT_ERROR: provocata da ogni violazione di limiti (per esempio quando si oltrepassano i limiti di un intervallo oppure quando si tenta di dividere per zero) ed leccezione che si riscontra pi spesso; PROGRAM_ERROR: capita quando sussiste la violazione di una struttura di controllo (per esempio il raggiungimento dellend nale in una funzione senza che questa abbia restituito un valore); STORAGE_ERROR: capita quando la memoria accessibile non pi disponibile, per esempio se un programma ricorsivo ha una condizione darresto errata che provoca troppi livelli di ricorsione; TASKING_ERROR: pu capitare nella programmazione parallela (non verr trattato). Le quattro eccezioni elencate sopra sono denite nel package STANDARD e quindi sono automaticamente denite in ogni implementazione di Ada 95. Esempio 5.2.2 Consideriamo il seguente programma: -----Nome del file: ESEMPIO_ECCEZIONE_1.ADB Autore: Claudio Marsan Data dellultima modifica: 6 giugno 2002 Scopo: solleva uneccezione CONSTRAINT_ERROR Testato con: Gnat 3.13p su Windows 2000

procedure Esempio_Eccezione_1 is numero : POSITIVE; begin numero := 0; -- 0 non un POSITIVE end Esempio_Eccezione_1; Sullo schermo verr visualizzato il messaggio seguente:

raised CONSTRAINT_ERROR : esempio_eccezione_1.adb:12 Da notare che gi in fase di compilazione viene segnalato con un avvertimento (warning) che ci sar un CONSTRAINT_ERROR. Esempio 5.2.3 Consideriamo il seguente programma: -- Nome del file: ESEMPIO_ECCEZIONE_2.ADB -- Autore: Claudio Marsan -- Data dellultima modifica: 6 giugno 2002 Claudio Marsan Liceo cantonale di Mendrisio, 2002

5.2. ECCEZIONI PREDEFINITE -- Scopo: solleva uneccezione CONSTRAINT_ERROR -- Testato con: Gnat 3.13p su Windows 2000 procedure Esempio_Eccezione_2 is numero : INTEGER := 0; procedure P(Valore : in NATURAL) is ------------------------------------------------ Procedura che non fa nulla diverse volte! -----------------------------------------------begin for i in NATURAL range 0..Valore loop null; end loop; end P; begin P(Valore => numero - 3); end Esempio_Eccezione_2;

159

Siccome largomento della procedura P nel programma principale non un NATURAL viene visualizzato sullo schermo il messaggio seguente:

raised CONSTRAINT_ERROR : esempio_eccezione_2.adb:22 Al contrario di quanto accadeva nellesempio precedente, in fase di compilazione non viene segnalato alcun avvertimento di CONSTRAINT_ERROR. Quando capita uneccezione (tecnicamente si dice che viene sollevata uneccezione, in inglese: exception raising) lesecuzione del programma termina subito. Se non si prendono delle precauzioni nel programma, esso terminer in modo anormale con un messaggio derrore che cita il tipo deccezione che ha provocato larresto del programma. Esempio 5.2.4 Il seguente programma stampa le potenze di un intero, scelto dallutente, no ad un certo esponente, pure scelto dallutente: -----Nome del file: ESPONENTI_SENZA_ECCEZIONI.ADB Autore: Claudio Marsan Data dellultima modifica: 6 giugno 2002 Scopo: calcola le potenze di un intero senza trattare eccezioni Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Esponenti_Senza_Eccezioni is function Esponente_Massimo return NATURAL is esponente : NATURAL; begin Liceo cantonale di Mendrisio, 2002 Claudio Marsan

160

CAPITOLO 5. GESTIONE DELLE ECCEZIONI IN ADA 95 Ada.Text_IO.Put(Item => "Dare lesponente piu grande: "); Ada.Integer_Text_IO.Get(Item => esponente); -- (1) Ada.Text_IO.Skip_Line; return esponente; end Esponente_Massimo;

procedure Stampa_Potenze(numero : in INTEGER) is limite : POSITIVE := Esponente_Massimo; -- (2)

begin Ada.Text_IO.Put_Line(Item => "Ecco la lista delle potenze: "); for N in NATURALFIRST..limite loop if (N mod 5) = 0 then Ada.Text_IO.New_Line; end if; Ada.Integer_Text_IO.Put(Item => numero ** N); end loop; Ada.Text_IO.New_Line; end Stampa_Potenze; numero_intero : INTEGER;

-- (3)

begin Ada.Text_IO.Put(Item => "Calcolo delle potenze di un intero fino ad "); Ada.Text_IO.Put_Line(Item => "un esponente massimo."); Ada.Text_IO.Put(Item => "Dare un numero intero: "); Ada.Integer_Text_IO.Get(Item => numero_intero); -- (4) Ada.Text_IO.Skip_Line; Stampa_Potenze(numero => numero_intero); end Esponenti_Senza_Eccezioni; Eccezioni di tipo CONSTRAINT_ERROR possono essere sollevate in corrispondenza delle linee commentate con (1), (2), (3) e (4): se lutente d un intero fuori dallintervallo ammesso per gli INTEGER verr sollevata leccezione alla linea commentata con (4): Calcolo delle potenze di un intero fino ad un esponente massimo. Dare un numero intero: 123456789012

raised ADA.IO_EXCEPTIONS.DATA_ERROR : a-tiinio.adb:91 instantiated at a-inteio.ads:20 se lutente d un esponente negativo allora verr sollevata uneccezione alla linea commentata con (1) poich esponente del sottotipo NATURAL: Calcolo delle potenze di un intero fino ad un esponente massimo. Dare un numero intero: 5 Dare lesponente piu grande: -4

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

5.3. TRATTAMENTO DI UNECCEZIONE

161

raised CONSTRAINT_ERROR : esponenti_senza_eccezioni.adb:19 se lutente d un esponente nullo allora verr sollevata uneccezione alla linea commentata con (2) poich limite del sottotipo POSITIVE: Calcolo delle potenze di un intero fino ad un esponente massimo. Dare un numero intero: 5 Dare lesponente piu grande: 0

raised CONSTRAINT_ERROR : esponenti_senza_eccezioni.adb:27 se lesponente abbastanza grande verr sollevata uneccezione alla linea commentata con (3) poich il valore dellespressione numero ** N rischia di essere maggiore del pi grande INTEGER: Calcolo delle potenze di un intero fino ad un esponente massimo. Dare un numero intero: 12 Dare lesponente piu grande: 25 Ecco la lista delle potenze: 1 248832 12 2985984 144 35831808 1728 429981696 20736

raised CONSTRAINT_ERROR : s-expgen.adb:165

5.3

Trattamento di uneccezione

Non mai piacevole vedere unapplicazione che termina brutalmente a causa di un messaggio derrore (nemmeno quando il messaggio appare in una nestra colorata o con una bomba accesa e una musichetta!). Il trattamento di uneccezione (exception handling) in Ada 95 consiste in: intercettare leccezione per impedire che questa risalga no al sistema operativo; ristabilire una situazione normale sopprimendo o evitando la causa dellerrore. Osservazione 5.3.1 Attenzione! sempre meglio vedere un messaggio derrore piuttosto che ottenere dei risultati sbagliati nellesecuzione di unapplicazione. Bisogna reagire e sopprimere leccezione solo se lapplicazione capace di gestirla e di ritrovare alla ne uno stato coerente. A livello di codice il trattamento di uneccezione consiste nel completare la ne del corpo di una funzione, di una procedura o di un blocco immediatamente prima dellend nale con una parte, detta manipolatore di eccezioni (exception handler ). Il manipolatore di eccezioni ha la forma seguente, molto simile a quella dellistruzione case: exception when scelta_1 => trattamento_1; when scelta_2 => trattamento_2; ... when others => altro_trattamento; dove: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

162

CAPITOLO 5. GESTIONE DELLE ECCEZIONI IN ADA 95

la parola riservata exception inizia il manipolatore di eccezioni e va inserita dopo lultima istruzione del corpo e prima dellend nale; scelta_1, scelta_2, . . . sono formati da uno o pi valori deccezione separati da barre verticali; trattamento_1, trattamento_2, . . . sono composti da uno o pi istruzioni; la parola riservata others rappresenta tutti gli altri casi deccezione; questo ramo opzionale (se presente , ovviamente, lultimo della serie!). Osservazione 5.3.2 Bisogna evitare di utilizzare un ramo del tipo when others => null; poich, cos facendo, si fanno sparire un certo numero di eccezioni e la scoperta di un errore in un programma che non si comporta come desiderato diventa molto pi dicile. Quando uneccezione sollevata nelle istruzioni del corpo contenente un simile manipolatore di eccezioni, lesecuzione del programma viene trasferita al ramo identicato dal nome delleccezione oppure, se presente, dallultimo ramo che inizia con when others. Lesecuzione del programma prosegue poi normalmente nel trattamento del ramo e leccezione viene eliminata. Se il nome delleccezione non fa parte delle possibili scelte e se non c un ramo che inizia con when others, allora leccezione si propaga come se il manipolatore di eccezioni non fosse presente. Esempio 5.3.1 Il programma seguente la riscrittura del programma dellesempio 5.2.4 con il trattamento delle eccezioni: -----Nome del file: ESPONENTI_CON_ECCEZIONI.ADB Autore: Claudio Marsan Data dellultima modifica: 6 giugno 2002 Scopo: calcola le potenze di un intero trattando le eccezioni Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Esponenti_Con_Eccezioni is function Esponente_Massimo return NATURAL is esponente : NATURAL; begin Ada.Text_IO.Put(Item => "Dare lesponente piu grande: "); Ada.Integer_Text_IO.Get(Item => esponente); -- (1) Ada.Text_IO.Skip_Line; return esponente; exception when CONSTRAINT_ERROR => Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Esponente negativo! Posto uguale a "); Ada.Text_IO.Put_Line(Item => "1 arbitrariamente!"); return 1; when others => Claudio Marsan Liceo cantonale di Mendrisio, 2002

5.3. TRATTAMENTO DI UNECCEZIONE Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Esponente errato! Posto uguale a "); Ada.Text_IO.Put_Line(Item => "20 arbitrariamente!"); return 20; end Esponente_Massimo;

163

procedure Stampa_Potenze(numero : in INTEGER) is limite : POSITIVE := Esponente_Massimo; -- (2)

begin Ada.Text_IO.Put_Line(Item => "Ecco la lista delle potenze: "); for N in NATURALFIRST..limite loop if (N mod 5) = 0 then Ada.Text_IO.New_Line; end if; Ada.Integer_Text_IO.Put(Item => numero ** N); end loop; Ada.Text_IO.New_Line;

-- (3)

exception when CONSTRAINT_ERROR => Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "Potenza troppo grande! Fine!"); end Stampa_Potenze; numero_intero : INTEGER; begin Ada.Text_IO.Put(Item => "Calcolo delle potenze di un intero fino ad "); Ada.Text_IO.Put_Line(Item => "un esponente massimo."); Ada.Text_IO.Put(Item => "Dare un numero intero: "); Ada.Integer_Text_IO.Get(Item => numero_intero); -- (4) Ada.Text_IO.Skip_Line; Stampa_Potenze(numero => numero_intero); end Esponenti_Con_Eccezioni; Con le modiche eseguite leccezione sollevata alla linea commentata con (1) provoca il trasferimento dellesecuzione al manipolatore di eccezioni della funzione che provveder a visualizzare sullo schermo un messaggio e a restituire il valore 1 se lesponente dato negativo oppure 20 se maggiore del pi grande NATURAL oppure se contiene caratteri non ammessi: Calcolo delle potenze di un intero fino ad un esponente massimo. Dare un numero intero: 5 Dare lesponente piu grande: -4 Esponente negativo! Posto uguale a 1 arbitrariamente! Ecco la lista delle potenze: 1 5

Calcolo delle potenze di un intero fino ad un esponente massimo. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

164

CAPITOLO 5. GESTIONE DELLE ECCEZIONI IN ADA 95

Dare un numero intero: 2 Dare lesponente piu grande: 123456789012 Esponente troppo grande! Posto uguale a 20 arbitrariamente! Ecco la lista delle potenze: 1 32 1024 32768 1048576 2 64 2048 65536 4 128 4096 131072 8 256 8192 262144 16 512 16384 524288

La linea commentata con (3) provoca il trasferimento dellesecuzione al manipolatore di eccezioni della procedura che provveder a visualizzare un messaggio sullo schermo e a terminare normalmente il programma: Calcolo delle potenze di un intero fino ad un esponente massimo. Dare un numero intero: 12 Dare lesponente piu grande: 25 Ecco la lista delle potenze: 1 12 144 248832 2985984 35831808 Potenza troppo grande! Fine! 1728 429981696 20736

Leccezione che viene sollevata dalla linea commentata con (2) non viene trattata dal manipolatore di eccezioni della procedura: infatti uneccezione sollevata da una dichiarazione di un sottoprogramma si propaga sempre dal punto di richiamo del sottoprogramma, che questo abbia o non abbia un manipolatore di eccezioni (per risolvere il problema basterebbe, in questo caso, sostituire POSITIVE con NATURAL). Osservazione 5.3.3 Da notare che se lesecuzione di unistruzione allinterno del manipolatore di eccezioni solleva uneccezione allora anche questultima si propaga. Non possibile tornare al punto del programma dove si prodotta leccezione (a meno di non usare una struttura di blocco) poich il trattamento di un ramo del manipolatore di eccezioni sostituisce il resto del corpo terminando cos lesecuzione del corpo. Esempio 5.3.2 Sostituendo la funzione Esponente_Massimo nel programma dellesempio 5.3.1 con la seguente risolviamo il problema dellinput errato dellesponente (numero negativo oppure troppo grande oppure contenente un carattere proibito): function Esponente_Massimo return NATURAL is esponente : NATURAL; begin loop Ada.Text_IO.Put(Item => "Dare lesponente piu grande: "); begin -- inizio blocco Ada.Integer_Text_IO.Get(Item => esponente); Ada.Text_IO.Skip_Line; return esponente; exception Claudio Marsan Liceo cantonale di Mendrisio, 2002

-- (1)

5.4. DICHIARAZIONE DI UNECCEZIONE when CONSTRAINT_ERROR => Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Esponente negativo! Ricominciare!"); Ada.Text_IO.New_Line; when others => Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Esponente errato! Ricominciare!"); Ada.Text_IO.New_Line; end; -- fine blocco end loop; end Esponente_Massimo;

165

Da notare che senza luso del blocco (senza parte dichiarativa) non sarebbe stato possibile riportare lesecuzione del programma allinizio della funzione. Ecco un esempio di output: Calcolo delle potenze di un intero fino ad un esponente massimo. Dare un numero intero: 2 Dare lesponente piu grande: qw Esponente errato! Ricominciare! Dare lesponente piu grande: 123456789012 Esponente errato! Ricominciare! Dare lesponente piu grande: -5 Esponente negativo! Ricominciare! Dare lesponente piu grande: 12 Ecco la lista delle potenze: 1 32 1024 2 64 2048 4 128 4096 8 256 16 512

5.4

Dichiarazione di uneccezione

Uneccezione pu essere dichiarata nella parte dichiarativa, non importa in quale posizione; la sua forma : nome_eccezione : exception; dove: nome_eccezione un identicatore (nota: possibile usare anche pi identicatori, separati da virgole); la parola riservata exception qui usata per stabilire la natura dellidenticatore. Le regole di visibilit per uneccezione sono le stesse che si hanno per variabili e costanti; per esempio il nome di uneccezione non conosciuto allesterno del sottoprogramma in cui denito (in ogni caso se viene sollevata uneccezione i suoi eetti possono diondersi allesterno del sottoprogramma nel quale era stata denita). Esempio 5.4.1 Leccezione Time_Up pu essere dichiarata mediante listruzione Time_Up : exception; Questa una dichiarazione e dunque va messa insieme alle altre dichiarazioni, di variabili, per esempio. In eetti la forma simile alla dichiarazione di una variabile, con la sola dierenza che Time_Up non una variabile e quindi non pu avere un valore. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

166

CAPITOLO 5. GESTIONE DELLE ECCEZIONI IN ADA 95

Esempio 5.4.2 Con la seguente riga si dichiarano pi eccezioni: Table_Empty, Table_Full : exception; Esempio 5.4.3 Denendo un tipo RATIONAL bisogna prestare attenzione al fatto che le frazioni non possono avere denominatore nullo: ... type RATIONAL is record num : LONG_INTEGER; den : LONG_INTEGER; end record; ... ZERO : constant RATIONAL := (0, 1); ... Divisione_per_zero : exception; -- eccezione sollevata se si divide per 0 ...

5.5

Sollevare e propagare uneccezione

La propagazione di uneccezione avviene allo stesso modo, sia nel caso di uneccezione predenita che nel caso di uneccezione denita dal programmatore. Il sollevare uneccezione non predenita non pu avvenire automaticamente ma deve essere eseguito tramite una particolare istruzione, listruzione raise, la cui sintassi : raise nome_eccezione; dove nome_eccezione designa leccezione da sollevare. Esempio 5.5.1 La seguente funzione divide due numeri di tipo RATIONAL (vedi esempio 5.4.3) e solleva leccezione Divisione_per_zero se la seconda frazione nulla: function "/"(X, Y : RATIONAL) return RATIONAL is begin if Y.num <> 0 then return(X.num * Y.den, X.den * Y.num); else raise Divisione_per_zero; end if; end "/"; Nota: la funzione da migliorare perch il risultato non ridotto ai minimi termini!

5.6

Ancora sul trattamento delle eccezioni

Il trattamento di uneccezione identico per ogni tipo di eccezione; tuttavia quanto detto nel paragrafo 5.3 va completato. Dapprima esiste la forma semplicata raise;, senza menzione esplicita delleccezione. Questa forma pu essere usata solo in un manipolatore deccezioni e serve per sollevare di nuovo leccezione il cui trattamento in corso. Ci permette di passare dal manipolatore di eccezioni, per visualizzare per esempio un messaggio o per abbandonare il pi correttamente possibile la struttura attuale e di lasciare che leccezione si propaghi. Claudio Marsan Liceo cantonale di Mendrisio, 2002

5.6. ANCORA SUL TRATTAMENTO DELLE ECCEZIONI

167

Esempio 5.6.1 Siano E1 ed E2 due eccezioni. Consideriamo il seguente frammento di codice: ... exception when CONSTRAINT_ERROR => ... -- prepara luscita dalla struttura attuale raise; -- solleva di nuovo la CONSTRAINT_ERROR per propagarla when E1 | E2 => Ada.Text_IO.Put(Item => "Eccezione sollevata in questa struttura"); raise; -- solleva di nuovo leccezione che ha provocato il -- raggiungimento del manipolatore di eccezioni, ossia -- solleva E1 oppure E2 end ...; Il package Ada.Exceptions permette di ottenere delle precisazioni concernenti leccezione in corso grazie alle tre funzioni seguenti (tutte restituiscono un valore di tipo STRING): Exception_Name restituisce il nome delleccezione corrente; Exception_Message produce un messaggio di una riga correlato alleccezione; Exception_Information fornisce il nome delleccezione, il messaggio e altre informazioni ancora. Il messaggio e le altre informazioni dipendono dallimplementazione e devono servire a identicare la causa e il posto in cui stata sollevata leccezione. Queste tre funzioni hanno bisogno di un parametro che permetta di accedere alleccezione; tale parametro deve identicare un ramo del manipolatore di eccezioni. La sintassi sar: exception when PARAMETRO_1 : scelta_1 => trattamento_1; Ada.Text_IO.Put(Item => Exception_Information(PARAMETRO_1)); ... Esempio 5.6.2 Riprendiamo lesempio 5.6.1: ... exception when CONSTRAINT_ERROR => ... -- prepara luscita dalla struttura attuale raise; -- solleva di nuovo la CONSTRAINT_ERROR per propagarla when ERRORE : E1 | E2 => -- ERRORE dichiarata implicitament qui Ada.Text_IO.Put(Item => "Eccezione "); Ada.Text_IO.Put(Item => Ada.Exceptions.Exception_Name(ERRORE)); Ada.Text_IO.Put_Line(Item => "sollevata in questa struttura"); Ada.Text_IO.Put_Line(Item => "Informazioni supplementari"); Ada.Text_IO.Put(Item => Ada.Exceptions.Exception_Message(ERRORE)); Ada.Text_IO.Put_Line(Item => "Informazioni complete"); Ada.Text_IO.Put(Item => Ada.Exceptions.Exception_Information(ERRORE)); raise; -- solleva di nuovo leccezione che ha provocato Claudio Marsan

Liceo cantonale di Mendrisio, 2002

168

CAPITOLO 5. GESTIONE DELLE ECCEZIONI IN ADA 95 -- il raggiungimento del manipolatore di -- eccezioni, ossia solleva E1 oppure E2 end ...;

Esempio 5.6.3 Il programma che segue non fa nulla di particolarmente entusiasmante ma mostra alcune delle cose spiegate in questo paragrafo: -----Nome del file: INFO_SU_ECCEZIONI.ADB Autore: Claudio Marsan Data dellultima modifica: 6 giugno 2002 Scopo: informazioni sulle eccezioni Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Exceptions; procedure Info_su_eccezioni is function F(X : INTEGER) return INTEGER is begin return X/(X**3 - X); end F; ECCEZIONE_ZERO : exception; ECCEZIONE_UNO : exception; N : INTEGER; begin loop Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dare un intero: "); begin -- inizio blocco Ada.Integer_Text_IO.Get(Item => N); if N = 0 then raise ECCEZIONE_ZERO; elsif Abs(N) = 1 then raise ECCEZIONE_UNO; else Ada.Text_IO.Put(Item => "F("); Ada.Integer_Text_IO.Put(Item => N, Width => 0); Ada.Text_IO.Put(Item => ") = "); Ada.Integer_Text_IO.Put(Item => F(N), Width => 0); end if; exit; exception when CONSTRAINT_ERROR => Ada.Text_IO.Skip_Line; Claudio Marsan Liceo cantonale di Mendrisio, 2002 -- sollevata se X = 0 -- sollevata se X = 1 o se X = -1

5.6. ANCORA SUL TRATTAMENTO DELLE ECCEZIONI Ada.Text_IO.Put_Line(Item => "Input errato! Ricominciare!"); when ERRORE_0 : ECCEZIONE_ZERO => Ada.Text_IO.Put(Item => "F(0) = NaN"); Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "Informazioni sulleccezione:"); Ada.Text_IO.Put_Line(Item => Ada.Exceptions.Exception_Information(ERRORE_0)); raise; when ERRORE_1 : ECCEZIONE_UNO => if N = 1 then Ada.Text_IO.Put(Item => "F(1) = inf"); elsif N = -1 then Ada.Text_IO.Put(Item => "F(-1) = inf"); end if; Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "Informazioni sulleccezione:"); Ada.Text_IO.Put_Line(Item => Ada.Exceptions.Exception_Information(ERRORE_1)); raise; when others => Ada.Text_IO.Skip_Line; Ada.Text_IO.Put_Line(Item => "Input errato! Ricominciare!"); end; -- fine del blocco end loop; for i in 10..15 loop Ada.Integer_Text_IO.Put(Item => F(N), Width => 10); end loop; end Info_su_eccezioni; Ecco qualche esempio di output: Dare un intero: cosa? Input errato! Ricominciare! Dare un intero: 123456789012 Input errato! Ricominciare! Dare un intero: 5 F(5) = 0 0

169

Dare un intero: 0 F(0) = NaN Informazioni sulleccezione: Exception name: INFO_SU_ECCEZIONI.ECCEZIONE_ZERO Message: info_su_eccezioni.adb:32

raised INFO_SU_ECCEZIONI.ECCEZIONE_ZERO : info_su_eccezioni.adb:32 Dare un intero: 1 F(1) = inf Liceo cantonale di Mendrisio, 2002 Claudio Marsan

170

CAPITOLO 5. GESTIONE DELLE ECCEZIONI IN ADA 95 Informazioni sulleccezione: Exception name: INFO_SU_ECCEZIONI.ECCEZIONE_UNO Message: info_su_eccezioni.adb:34

raised INFO_SU_ECCEZIONI.ECCEZIONE_UNO : info_su_eccezioni.adb:34 Dare un intero: -1 F(-1) = inf Informazioni sulleccezione: Exception name: INFO_SU_ECCEZIONI.ECCEZIONE_UNO Message: info_su_eccezioni.adb:34

raised INFO_SU_ECCEZIONI.ECCEZIONE_UNO : info_su_eccezioni.adb:34

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 6 Packages in Ada 95


In questo capitolo parleremo della modularit di Ada 95, ossia vedremo come possibile costruire ed usare les che contengono denizioni di tipi, di costanti, . . . e sottoprogrammi, utilizzabili da pi programmi.

6.1

Introduzione

Come gi detto pi volte in precedenza quello di scrivere programmi lunghissimi un pessimo modo di procedere poich ci: diminuisce la leggibilit del programma; complica la fase di correzione (debugging) del programma; aumenta la dicolt nella manutenzione e nellaggiornamento del programma. In Ada 95, come daltronde nella maggior parte dei linguaggi di programmazione evoluti, esiste la possibilit di suddividere un programma in sottoprogrammi, ossia in unit di dimensioni pi piccole (a seconda del linguaggio di programmazione si parla di funzioni, procedure, routines, subroutines, . . . ). Utilizzando i sottoprogrammi si rimedia agli svantaggi citati sopra, si pu adottare la tecnica di programmazione detta top down (nascondere al programmatore, in un primo momento, dettagli ininuenti per concentrarsi su un problema alla volta) e si evita di scrivere pi volte la stessa parte di codice: questo modo di procedere detto astrazione procedurale. In Ada 95 esiste unaltra costruzione, oltre ai sottoprogrammi, che permette di raggruppare parti di programma in ununit logica: questa costruzione detta package (useremo il termine inglese piuttosto che la traduzione italiana pacchetto). Sottoprogrammi, tipi e oggetti che hanno una relazione logica tra loro possono essere raggruppati in un package. Quando si costruisce un package la sua interfaccia con il resto del programma o, in altre parole, la parte di package che deve essere visibile al programma, deve essere specicata. I dettagli che non sono essenziali per lutilizzatore del package possono essere nascosti nel package. Un package pu essere sviluppato e compilato separatamente. Esempio 6.1.1 Quando un prodotto complicato come unautomobile deve essere costruito necessario costruire le diverse parti separatamente e poi assemblarle in seguito; in caso contrario la fabbricazione diventerebbe troppo complicata. Anch le varie parti costruite separatamente possano essere assemblate necessaria una specicazione di come le diverse parti debbano essere fabbricate. 171

172

CAPITOLO 6. PACKAGES IN ADA 95

Ada 95 un linguaggio progettato non solo per risolvere piccoli problemi ma anche per risolvere grandi progetti di programmazione nei quali sono coinvolti diversi programmatori. Come nel caso della costruzione di unautomobile necessario che varie parti di un grande progetto siano scritte separatamente, magari anche da persone diverse, e assemblate in seguito. I packages di Ada 95 permettono di costruire un programma usando vari moduli separati, ognuno dei quali forma una unit logica. Con laiuto della specicazione del package si pu stabilire come un package deve essere inserito in unaltra parte del programma: cos facendo si evita di lavorare con programmi mostruosamente grandi e scomodi da manipolare. anche possibile costruire una libreria di packages generali che pu essere utilizzata in contesti diversi con programmi diversi. La libreria pu contenere un package di funzioni matematiche oppure un package di utensili per la visualizzazione di risultati in forma graca o altro ancora. Questi packages possono essere scritti da programmatori diversi oppure essere packages standard di unimplementazione di Ada 95. In Ada 95 ci sono anche dei packages detti child packages (traducibile con packages gli ): questi possono essere utilizzati per estendere dei packages esistenti senza ricompilazione oppure per dividere un sistema grande e complesso in sottosistemi.

6.2

Specicazione di un package

Ogni package ha una specicazione (package specication). Essa pu essere vista come lesposizione di ci che presente nel package e che viene oerto al potenziale utilizzatore. La specicazione del package specica dunque linterfaccia del package con altri parti del programma. La specicazione di un package introdotta dalla parola riservata package seguita dal nome del package; bisogna usare la sintassi seguente: package Nome_del_Package is dichiarazione_1; dichiarazione_2; ... dichiarazione_n; end Nome_del_Package; Nella specicazione di un package possono essere presenti: dichiarazioni di tipi; dichiarazioni di oggetti vari (variabili, costanti, . . . ) intestazioni di sottoprogrammi. Non pu essere invece presente il corpo di un sottoprogramma. Esempio 6.2.1 Vogliamo lavorare con gure geometriche del piano bidimensionale, per esempio con cerchi, rettangoli e triangoli. Sembra dunque sensato costruire un package, che chiameremo Planimetria, contenente gli strumenti necessari per eseguire vari calcoli con tali gure. La specicazione del package Planimetria la seguente: package Planimetria is type T_LUNGHEZZA is digits 5 range 0.0 .. 1.0E10; type T_AREA is digits 5 range 0.0 .. 1.0E20; function Area_Triangolo(base, altezza : T_LUNGHEZZA) return T_AREA; function Area_Rettangolo(base, altezza : T_LUNGHEZZA) return T_AREA; function Area_Cerchio(raggio : T_LUNGHEZZA) return T_AREA; Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.3. LAMBIENTE DI PROGRAMMAZIONE DI ADA 95 function Circonferenza(raggio : T_LUNGHEZZA) return T_LUNGHEZZA; end Planimetria;

173

La specicazione del package Planimetria mostra cos allutente che sono stati deniti i tipi di dati T_LUNGHEZZA e T_AREA e sono state dichiarate delle funzioni per il calcolo delle area di triangoli, rettangoli e cerchi e per la lunghezza della circonferenza. Da notare che non sappiamo nulla sul corpo di queste funzioni: esso andr scritto pi tardi nel corpo del package (package body). Usando il compilatore Gnat (non importa quale versione) bisogna salvare la specicazione di un package in un le con estensione .ads (ada specication). Esempio 6.2.2 Riferendoci allesempio 6.2.1 il le che conterr la specicazione del package Planimetria dovr chiamarsi planimetria.ads.

6.3

Lambiente di programmazione di Ada 95

Prima di vedere come si usano i packages e come appare il corpo di un package bene spendere qualche parola sullambiente di programmazione di Ada 95. Per compilare un programma (o una sua parte) si usa un compilatore: esso legge il testo del programma e come risultato fornisce il programma tradotto in codice macchina (tutti i compilatori funzionano cos, non solo i compilatori Ada 95). Un compilatore Ada 95, al contrario di tanti altri compilatori, non solo produce il codice macchina ma tiene conto anche di tutte le compilazioni che sono state eseguite, mantenendo quella che detta libreria Ada (Ada library). Quando una compilazione terminata il compilatore Ada 95 inserisce una descrizione del programma (o della parte di programma) che stato compilato nella libreria Ada. cos possibile fare riferimento a ci che stato compilato in precedenza in un programma. Il compilatore accede alla libreria Ada e cerca le informazioni riguardanti un certo oggetto permettendo cos di costruire gradualmente dei programmi complicati.

6.4

Uso dei packages

Un package, la cui specicazione gi stata compilata, pu essere usato in programmi o in parti di programmi che verranno compilati in seguito. Esempio 6.4.1 Se scriviamo il programma Calcola_Area e vogliamo usare le procedure contenute nel package Planimetria sar suciente scrivere allinizio del programma la riga with Planimetria; Il compilatore andr a cercare nella libreria Ada il package Planimetria e tutto ci che denito in esso sar utilizzabile nel nostro programma: Per esempio: -----Nome del file: CALCOLA_AREA.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: calcola larea di un rettangolo sfruttando il package PLANIMETRIA Testato con: Gnat 3.13p su Windows 2000

with Planimetria; procedure Calcola_Area is

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

174 area : Planimetria.T_AREA;

CAPITOLO 6. PACKAGES IN ADA 95

begin area := Planimetria.Area_Rettangolo(Base => 3.0, Altezza => 4.0); end Calcola_Area; Il programma compilabile ma non linkabile (e quindi non verr generato il le eseguibile) poich il corpo del package Planimetria non ancora stato costruito. tuttavia comodo procedere in questo modo perch facile correggere eventuali errori di progettazione, senza essere confrontati, in questo stadio primordiale, con errori di programmazione di algoritmi. Notiamo che il compilatore ha generato i les calcola_area.o e calcola_area.ali: il primo il codice oggetto del programma, il secondo un le di testo che contiene le informazioni della libreria Ada (lestensione .ali sta per Ada Library Information). A titolo di curiosit ne riportiamo il contenuto; per il signicato di ogni singola riga si rimanda alla consultazione dello GNAT for Windows NT: Users Guide: V M A A A P R "GNAT Lib v3.13 " P W=b -gnatwu -g -gnato nnnnnnnnnnnnnnnvnnnnnnnnnnnnnnnnnnnnnnnnnnn

U calcola_area%b calcola_area.adb 03BC44D7 NE SU W planimetria%s planimetria.adb planimetria.ali D calcola_area.adb 20020608115012 65EA058F D planimetria.ads 20020608115158 66564158 D system.ads 20000818203508 585631F3 X 1 calcola_area.adb 10U11*Calcola_Area 16r5 16t17 12f4 area 15m3 X 2 planimetria.ads 7K9*Planimetria 1|8r6 12r11 15r11 10F9*T_AREA 1|12r23 13V13*Area_Rettangolo 1|15r23 13f29 base 1|15r39 13f35 altezza 1|15r52 Se nella specicazione di un package abbiamo denito un oggetto, per fare riferimento ad esso in un programma dobbiamo premettere al nome delloggetto il nome del package seguito da un punto. Esempio 6.4.2 Nellesempio 6.4.1 abbiamo denito il tipo T_AREA nella specicazione del package Planimetria. Nel programma Calcola_Area abbiamo denito la variabile area di tipo T_AREA. Per far ci siamo stati costretti ad usare la sintassi seguente: area : Planimetria.T_AREA; Possiamo leggere la riga precedente nel modo seguente: la variabile area di tipo T_AREA e questo tipo denito nel package Planimetria. Quanto descritto sopra detto selezione. In generale se P un package e N un qualsiasi oggetto denito nella specicazione di P allora si potr accedere a N, fuori da P, mediante la selezione P.N. Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.4. USO DEI PACKAGES

175

Talvolta la selezione pu essere molto pesante, pensiamo per esempio ad un programma che fa un uso intensivo delle funzioni trigonometriche, denite nel package predenito delle funzioni matematiche elementari Ada.Numerics.Elementary_Functions. Impiegando la clausola use possibile omettere il nome del package quando si richiama un oggetto del package. Esempio 6.4.3 Il seguente frammento di programma with Ada.Text_IO, Ada.Float_Text_IO, Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions; ... x := Sin(0.1234); ... equivalente ma pi facile da leggere del seguente with Ada.Text_IO, Ada.Float_Text_IO, Ada.Numerics.Elementary_Functions; ... x := Ada.Numerics.Elementary_Functions.Sin(0.1234); ... La clausola use pu essere usata subito dopo la clausola with oppure ovunque nella parte dichiarativa di un sottoprogramma. Se essa messa allinizio di una unit di compilazione allora la sua validit sar estesa a tutta lunit di compilazione; se invece essa messa nella parte dichiarativa di un sottoprogramma la sua validit sar limitata al sottoprogramma in cui denita. Esempio 6.4.4 Consideriamo il seguente frammento di programma: with Ada.Text_IO, Ada.Float_Text_IO, Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions; procedure Prova_USE is procedure Test is use Ada.Text_IO; x : FLOAT; begin x := Sin(-0.123); Put_Line(Item => "Ciao, ciao!"); end Test; ... begin ... Liceo cantonale di Mendrisio, 2002 Claudio Marsan

176 -- Put_Line(Item => "Salve!"); Ada.Text_IO.Put_Line(Item => "Salve!"); ... end Prova_USE;

CAPITOLO 6. PACKAGES IN ADA 95 -- errore! -- corretto!

Nella procedura Test c la clausola use Ada.Text_IO;: la sua validit si estende per solo allinterno della procedura e dunque la riga Put_Line(Item => "Salve!"); nel programma principale genererebbe un errore. Osservazione 6.4.1 Lo svantaggio nelluso della clausola use consiste nel fatto che il programma diventa meno chiaro poich, se questo composto da pi unit separate, non si sa da quale package proviene un oggetto. Inoltre possibile utilizzare lo stesso nome per oggetti che provengono da packages dierenti: solo la selezione permette di stabilire con facilit la reale provenienza delloggetto. Esempio 6.4.5 Se avessimo i packages Geometria_Spaziale e Planimetria e nel nostro programma fosse presente il seguente frammento: ... use Geometria_Spaziale, Planimetria; ... area : T_AREA; ... non saremmo in grado di stabilire se T_AREA proviene da Geometria_Spaziale o da Planimetria. Si consiglia cos di usare la clausola use con molta cautela!

6.5

Il corpo di un package

In questo paragrafo vediamo come costruire il corpo di un package (package body), la parte del package nascosta allutente. Dettagli che lutente non deve vedere o conoscere sono messi nel corpo del package (tali dettagli potrebbero essere il corpo di sottoprogrammi e dati interni). Possiamo cos dire che: la specicazione linterfaccia del package; il corpo limplementazione del package. Il corpo di un package introdotto dalle parole riservate package body seguite dal nome del package; il resto del corpo del package ha la stessa struttura di un sottoprogramma (prima la parte dichiarativa, poi la eventuale sequenza delle istruzioni). Dunque: package body Nome_del_Package is dichiarazione_1; dichiarazione_2; ... dichiarazione_k; begin Claudio Marsan -- pu non esserci Liceo cantonale di Mendrisio, 2002

6.5. IL CORPO DI UN PACKAGE istruzione_1; -istruzione_2; -... -istruzione_m; -end Nome_del_Package; pu pu pu pu non non non non esserci esserci esserci esserci

177

Ogni tipo di dichiarazione ammessa nel corpo del package; bisogna tuttavia ricordarsi che gli oggetti qui dichiarati non sono accessibili dallesterno del package. Se c una sezione con delle istruzioni queste vengono eseguite una sola volta, pi precisamente quando il programma che usa il package inizia lesecuzione. Esempio 6.5.1 Il codice seguente il codice del corpo del package Planimetria (vedi esempio 6.2.1 per la specicazione del package) -----Nome del file: PLANIMETRIA.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: corpo del package PLANIMETRIA Testato con: Gnat 3.13p su Windows 2000

package body Planimetria is PI_GRECO : constant := 3.1415926536; function Area_Triangolo(base, altezza : T_LUNGHEZZA) return T_AREA is begin return 0.5 * T_AREA(base) * T_AREA(altezza); end Area_Triangolo; function Area_Rettangolo(base, altezza : T_LUNGHEZZA) return T_AREA is begin return T_AREA(base) * T_AREA(altezza); end Area_Rettangolo; function Area_Cerchio(raggio : T_LUNGHEZZA) return T_AREA is begin return PI_GRECO * (T_AREA(raggio) ** 2); end Area_Cerchio; function Circonferenza(raggio : T_LUNGHEZZA) return T_LUNGHEZZA is begin return 2.0 * PI_GRECO * raggio; end Circonferenza; end Planimetria; Alcune osservazioni riguardanti il codice appena scritto: 1. la costante PI_GRECO denita allinterno del corpo del package Planimetria e dunque non accessibile (e nemmeno visibile!) dallesterno del package, neanche usando la selezione Planimetria.PI_GRECO; 2. si sono fatte delle conversioni di tipo di variabili di tipo T_LUNGHEZZA in variabili di tipo T_AREA; 3. il corpo del package Planimetria non possiede una parte di istruzioni. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

178

CAPITOLO 6. PACKAGES IN ADA 95

Per quanto riguarda lordine di compilazione possiamo dire quanto segue: prima si compila la specicazione del package; poi si compilano, in un qualsiasi ordine, il corpo del package e le unit di compilazione che fanno riferimento al package in questione. Se il corpo di un package viene ricompilato non necessario ricompilare le procedure che usano tale pacchetto. Daltra parte se la specicazione di un package viene ricompilata allora bisogna ricompilare sia il corpo del package che le componenti del programma che usano il package. Un package potrebbe contenere anche solo la dichiarazione di tipi e di costanti; in tal caso esiste solo la specicazione e non il corpo del package. Esempio 6.5.2 Il seguente package (o meglio: specicazione del package) contiene la denizione di costanti atomiche: ------Nome del file: COSTANTI_ATOMICHE.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: specificazione del package COSTANTI_ATOMICHE; questo package non ha il corpo. Testato con: Gnat 3.13p su Windows 2000

package Costanti_Atomiche is Carica_Elettrone Massa_Elettrone Massa_Neutrone Massa_Protone : : : : constant constant constant constant := := := := 1.602E-19; 0.9108E-30; 1674.7E-30; 1672.4E-30; ----in in in in coulomb kg kg kg

end Costanti_Atomiche; Esercizio 6.5.1 Costruire un package Numeri_Complessi per trattare i numeri complessi. Nella specicazione del package devono essere dichiarati: 1. il tipo COMPLEX: type COMPLEX is record Re : FLOAT := 0.0; Im : FLOAT := 0.0; end record;

-- parte reale -- parte immaginaria

2. le intestazioni delle funzioni per poter eseguire le quattro operazioni aritmetiche elementari; 3. le intestazioni delle funzioni Norm (norma) e AbsC (modulo o valore assoluto); 4. le intestazioni delle procedure Put, Put_Line e Get il cui signicato ovvio; 5. la dichiarazone della costante i (unit immaginaria) Un numero complesso z = a + bi deve essere letto e scritto nella forma (a, b). Scrivere anche un programma per testare quanto stato dichiarato nella specicazione del package. Esempio 6.5.3 Soluzione dellesercizio 6.5.1. Dapprima vediamo la specicazione del package Numeri_Complessi: Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.5. IL CORPO DI UN PACKAGE -----Nome del file: NUMERI_COMPLESSI.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: specificazione del package NUMERI_COMPLESSI Testato con: Gnat 3.13p su Windows 2000

179

package Numeri_Complessi is type COMPLEX is record Re : FLOAT := 0.0; Im : FLOAT := 0.0; end record;

-- parte reale -- parte immaginaria

i : constant COMPLEX := (0.0, 1.0);

-- unit immaginaria

function Norm(z : COMPLEX) return FLOAT; function AbsC(z : COMPLEX) return FLOAT; function "+"(x, y : COMPLEX) return COMPLEX; function "-"(x, y : COMPLEX) return COMPLEX; function "*"(x, y : COMPLEX) return COMPLEX; function "/"(x, y : COMPLEX) return COMPLEX; procedure Put(z : in COMPLEX); procedure Put_Line(z : in COMPLEX); procedure Get(z : out COMPLEX); end Numeri_Complessi; Quello che segue il corpo del package Numeri_Complessi: -----Nome del file: NUMERI_COMPLESSI.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: corpo del package NUMERI_COMPLESSI Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Float_Text_IO, Ada.Numerics.Elementary_Functions; package body Numeri_Complessi is

function Norm(z : COMPLEX) return FLOAT is ------------------------------------------------ Calcola la norma del numero complesso "z" -----------------------------------------------begin return ((z.Re ** 2) + (z.Im ** 2)); end norm;

function AbsC(z : COMPLEX) return FLOAT is Liceo cantonale di Mendrisio, 2002 Claudio Marsan

180

CAPITOLO 6. PACKAGES IN ADA 95 ------------------------------------------------- Calcola il modulo del numero complesso "z" ------------------------------------------------begin return(Ada.Numerics.Elementary_Functions.Sqrt(Norm(z))); end AbsC;

function "+"(x, y : COMPLEX) return COMPLEX is ----------------------------------------------- Calcola la somma di due numeri complessi ----------------------------------------------begin return(x.Re + y.Re, x.Im + y.Im); end "+";

function "-"(x, y : COMPLEX) return COMPLEX is ---------------------------------------------------- Calcola la differenza di due numeri complessi ---------------------------------------------------begin return(x.Re - y.Re, x.Im - y.Im); end "-";

function "*"(x, y : COMPLEX) return COMPLEX is ------------------------------------------------- Calcolail prodotto di due numeri complessi ------------------------------------------------begin return(x.Re * y.Re - x.Im * y.Im, x.Re * y.Im + x.Im * y.Re); end "*";

function "/"(x, y : COMPLEX) return COMPLEX is --------------------------------------------------- Calcola il quoziente di due numeri complessi --------------------------------------------------begin return((x.Re * y.Re + x.Im * y.Im) / Norm(y), (y.Re * x.Im - x.Re * y.Im) / Norm(y)); end "/";

procedure Put(z : in COMPLEX) is ----------------------------------------------------- Scrive il numero complesso "z = a + b*i" nella --- forma "(a,b)" -Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.5. IL CORPO DI UN PACKAGE ---------------------------------------------------begin Ada.Text_IO.Put(Item => "("); Ada.Float_Text_IO.Put(Item => z.Re, Fore => 1, Aft => 2, Exp => 0); Ada.Text_IO.Put(Item => ", "); Ada.Float_Text_IO.Put(Item => z.Im, Fore => 1, Aft => 2, Exp => 0); Ada.Text_IO.Put(Item => ")"); end Put;

181

procedure Put_Line(z : in COMPLEX) is ----------------------------------------------------------------- Scrive il numero complesso "z = a + b*i" nella forma --- "(a,b)" e sposta il cursore allinizio della prossima riga ----------------------------------------------------------------begin Put(z => z); Ada.Text_IO.New_Line; end Put_Line;

procedure Get(z : out COMPLEX) is --------------------------------------------------- Legge il numero complesso "z = a + b*i" dato --- da tastiera nella forma "(a,b)" --------------------------------------------------ch : CHARACTER; begin Ada.Text_IO.Get(Item => ch); -- legge "(" Ada.Float_Text_IO.Get(Item => z.Re); Ada.Text_IO.Get(Item => ch); -- legge "," Ada.Float_Text_IO.Get(Item => z.Im); Ada.Text_IO.Get(Item => ch); -- legge ")" end Get; end Numeri_Complessi; Inne il programma di test: -----Nome del file: TEST_NUMERI_COMPLESSI.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: test del package NUMERI_COMPLESSI Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Float_Text_IO, Numeri_Complessi; use Numeri_Complessi; -- per usare senza problemi i simboli Claudio Marsan

Liceo cantonale di Mendrisio, 2002

182

CAPITOLO 6. PACKAGES IN ADA 95 -- delle operazioni aritmetiche

procedure Test_Numeri_Complessi is z1, z2, z3 : COMPLEX; x : FLOAT; begin Ada.Text_IO.Put_Line(Item => "Test del package Complex_Numbers"); Ada.Text_IO.Put_Line(Item => "================================"); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Dare un numero complesso z1 = (a,b): "); Numeri_Complessi.Get(z => z1); Ada.Text_IO.Put(Item => "Dare un numero complesso z2 = (c,d): "); Numeri_Complessi.Get(z => z2); z3 := z1 + z2; Ada.Text_IO.Put(Item => "z1 + z2 = "); Numeri_Complessi.Put_Line(z => z3); z3 := z1 - z2; Ada.Text_IO.Put(Item => "z1 - z2 = "); Numeri_Complessi.Put_Line(z => z3); z3 := z1 * z2; Ada.Text_IO.Put(Item => "z1 * z2 = "); Numeri_Complessi.Put_Line(z => z3); z3 := z1 / z2; Ada.Text_IO.Put(Item => "z1 / z2 = "); Numeri_Complessi.Put_Line(z => z3); x := Norm(z => z1); Ada.Text_IO.Put(Item => "Norma di z1 = "); Ada.Float_Text_IO.Put(Item => x, Fore => 0, Exp => 0); x := AbsC(z => z1); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Modulo di z1 = "); Ada.Float_Text_IO.Put(Item => x, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "i = "); Numeri_Complessi.Put_Line(z => i); end Test_Numeri_Complessi;

6.6

Un package per trattare i numeri razionali

In questo paragrafo vedremo di costruire delle unit di compilazione per la manipolazione dei numeri razionali. Tali unit di compilazione conterranno le normali operazioni aritmetiche fra numeri Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.6. UN PACKAGE PER TRATTARE I NUMERI RAZIONALI

183

razionali, le procedure di scrittura e lettura, le funzioni di confronto e altro ancora. Scriveremo poi un programma di test che permetta di controllare la correttezza di quanto stato implementato. Scegliamo di denire il tipo RATIONAL nel modo seguente: type RATIONAL is record num : LONG_INTEGER; den : LONG_INTEGER; end record;

6.6.1

Costruzione del package Long_Integer_Math_Lib

Nel nostro package per i numeri razionali dovremo sicuramente lavorare con il massimo comune divisore di due interi e anche con altre funzioni elementari per gli interi. Conviene dunque denire il package Long_Integer_Math_Lib che conterr tali funzioni. Ecco la specicazione del package: ------Nome del file: LONG_INTEGER_MATH_LIB.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: specificazione del package per funzioni matematiche su interi del tipo LONG_INTEGER Testato con: Gnat 3.13p su Windows 2000

package Long_Integer_Math_Lib is function GCD(x, y function LCM(x, y function Max(x, y function Min(x, y function Sign(x : procedure Swap(x, : LONG_INTEGER) return LONG_INTEGER; : LONG_INTEGER) return LONG_INTEGER; : LONG_INTEGER) return LONG_INTEGER; : LONG_INTEGER) return LONG_INTEGER; LONG_INTEGER) return LONG_INTEGER; y : in out LONG_INTEGER);

end Long_Integer_Math_Lib; e il corrispondente corpo del package: ------Nome del file: LONG_INTEGER_MATH_LIB.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: corpo del package per funzioni matematiche su interi del tipo LONG_INTEGER Testato con: Gnat 3.13p su Windows 2000

package body Long_Integer_Math_Lib is

function GCD(x, y : LONG_INTEGER) return LONG_INTEGER is ------------------------------------------------------------- Calcola il massimo comune divisore, positivo, di x e y --- utilizzando lalgoritmo di Euclide ------------------------------------------------------------m, n, r : LONG_INTEGER; begin Liceo cantonale di Mendrisio, 2002 Claudio Marsan

184 m := Abs(x); n := Abs(y); while n /= 0 loop r := m MOD n; m := n; n := r; end loop; return m; end GCD;

CAPITOLO 6. PACKAGES IN ADA 95

function LCM (x, y : LONG_INTEGER) return LONG_INTEGER is --------------------------------------------------------------- Calcola il minimo comune multiplo di x e y sfruttando la --- formula: x * y = GCD(x, y) * LCM(x, y) --------------------------------------------------------------begin if (x = 0) and (y = 0) then return 0; else return (x * y)/GCD(x, y); end if; end LCM;

function Max(x, y : LONG_INTEGER) return LONG_INTEGER is ------------------------------------------------- Restituisce il valore pi grande fra x e y ------------------------------------------------begin if x > y then return x; else return y; end if; end Max;

function Min(x, y : LONG_INTEGER) return LONG_INTEGER is -------------------------------------------------- Restituisce il valore pi piccolo fra x e y -------------------------------------------------begin if x < y then return x; else return y; end if; end Min;

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

6.6. UN PACKAGE PER TRATTARE I NUMERI RAZIONALI

185

function Sign(x : LONG_INTEGER) return LONG_INTEGER is -------------------------------------------- Ritorna 0 se x=0, 1 se x>0, -1 se x<0 -------------------------------------------begin if x > 0 then return 1; elsif x < 0 then return -1; else return 0; end if; end Sign;

procedure Swap(x, y : in out LONG_INTEGER) is --------------------------------------------- Scambia i valori delle variabili x e y --------------------------------------------z : LONG_INTEGER; begin z := x; x := y; y := z; end Swap; end Long_Integer_Math_Lib;

6.6.2

Il package Rational_Numbers

Siccome vogliamo poter gestire linput e loutput di un numero razionale anche da un le di testo decidiamo di non includere le procedure di input e output nel package Rational_Numbers ma di dedicare a queste operazioni un apposito package, che chiameremo Rational_Numbers_IO. Il package Rational_Numbers conterr quindi solo le operazioni aritmetiche elementari e qualche altra funzione; ecco la sua specicazione: -----Nome del file: RATIONAL_NUMBERS.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: specificazione del package per i numeri razionali Testato con: Gnat 3.13p su Windows 2000

package Rational_Numbers is type RATIONAL is record num : LONG_INTEGER; den : LONG_INTEGER; end record;

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

186 ZERO : constant RATIONAL := (0, 1); ONE : constant RATIONAL := (1, 1); function function function function function function function function function function function function function function function function function function function function

CAPITOLO 6. PACKAGES IN ADA 95

Simplify_Fraction(a : RATIONAL) return RATIONAL; Numerator(a : RATIONAL) return LONG_INTEGER; Denominator(a : RATIONAL) return LONG_INTEGER; Fraction(a, b : LONG_INTEGER) return RATIONAL; Fraction(s : STRING) return RATIONAL; "+"(a, b : RATIONAL) return RATIONAL; "-"(a, b : RATIONAL) return RATIONAL; "*"(a, b : RATIONAL) return RATIONAL; "/"(a, b : RATIONAL) return RATIONAL; "**"(a : RATIONAL; n : INTEGER) return RATIONAL; "="(a, b : RATIONAL) return BOOLEAN; "Abs"(a : RATIONAL) return RATIONAL; Inverse(a : RATIONAL) return RATIONAL; Approximate(a : RATIONAL) return FLOAT; "<"(a, b : RATIONAL) return BOOLEAN; "<="(a, b : RATIONAL) return BOOLEAN; ">"(a, b : RATIONAL) return BOOLEAN; ">="(a, b : RATIONAL) return BOOLEAN; Positive_Number(a : RATIONAL) return BOOLEAN; Negative_Number(a : RATIONAL) return BOOLEAN;

DIVISIONE_PER_ZERO : EXCEPTION; ERRORE_DI_INPUT : EXCEPTION; end Rational_Numbers; Quella che segue il corpo del package Rational_Numbers: -----Nome del file: RATIONAL_NUMBERS.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: corpo del package per i numeri razionali Testato con: Gnat 3.13p su Windows 2000

with Long_Integer_Math_Lib; package body Rational_Numbers is

function Simplify_Fraction(a : RATIONAL) return RATIONAL is --------------------------------------------------------- Semplifica la frazione "a" e fa in modo che in una --- frazione negativa il segno - stia a numeratore. --------------------------------------------------------d : LONG_INTEGER := Long_Integer_Math_Lib.GCD(a.num, a.den); b : RATIONAL := a; begin if b.num = 0 then return ZERO; Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.6. UN PACKAGE PER TRATTARE I NUMERI RAZIONALI elsif (b.num < 0) and (b.den < 0) then b.num := abs(b.num); b.den := abs(b.den); elsif (b.num > 0) and (b.den < 0) then b.num := -b.num; b.den := abs(b.den); end if; return (b.num / d, b.den / d); end Simplify_Fraction;

187

function Numerator(a : RATIONAL) return LONG_INTEGER is ------------------------------------------------- Restituisce il numeratore di una frazione. ------------------------------------------------begin return a.num; end Numerator;

function Denominator(a : RATIONAL) return LONG_INTEGER is --------------------------------------------------- Restituisce il denominatore di una frazione. --------------------------------------------------begin if a.den = 0 then raise DIVISIONE_PER_ZERO; end if; return a.den; end Denominator;

function Fraction(a, b : LONG_INTEGER) return RATIONAL is -------------------------------------------------------------------- Costruisce la frazione "a/b" partendo dagli interi "a" e "b". -------------------------------------------------------------------q : RATIONAL; begin if b = 0 then raise DIVISIONE_PER_ZERO; end if; q.num := a; q.den := b; return Simplify_Fraction(q); end Fraction;

function Fraction(s : STRING) return RATIONAL is -------------------------------------------------------------Liceo cantonale di Mendrisio, 2002 Claudio Marsan

188

CAPITOLO 6. PACKAGES IN ADA 95 -- Costruisce la frazione "a/b" partendo dalla stringa "s". --------------------------------------------------------------length_of_s slash_found number_of_slash q : : : : POSITIVE := sLENGTH; NATURAL := 0; NATURAL := 0; RATIONAL;

begin for i in POSITIVE range 1..length_of_s loop if s(i) = / then slash_found := i; number_of_slash := number_of_slash + 1; if number_of_slash > 1 then raise ERRORE_DI_INPUT; end if; end if; if (s(i) not in 0..9) AND (s(i) /= /) AND (s(i) /= -) AND (s(i) /= +) then raise ERRORE_DI_INPUT; end if; if (i /= 1) AND (i /= slash_found + 1) AND ((s(i) = -) OR (s(i) = +)) then raise ERRORE_DI_INPUT; end if; end loop; if slash_found = 0 then q.num := LONG_INTEGERVALUE(s(1..length_of_s)); q.den := 1; else q.num := LONG_INTEGERVALUE(s(1..(slash_found - 1))); q.den := LONG_INTEGERVALUE(s((slash_found + 1)..length_of_s)); end if; if q.den = 0 then raise DIVISIONE_PER_ZERO; end if; return Simplify_Fraction(q); end Fraction;

function "+"(a, b : RATIONAL) return RATIONAL is ------------------------------------------------------------- Calcola la somma di due frazioni "a" e "b" restituendo --- il risultato semplificato. ------------------------------------------------------------c : RATIONAL; begin c.num := a.num * b.den + a.den * b.num; c.den := a.den * b.den; return Simplify_Fraction(c); end "+";

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

6.6. UN PACKAGE PER TRATTARE I NUMERI RAZIONALI

189

function "-"(a, b : RATIONAL) return RATIONAL is ------------------------------------------------------ Calcola la differenza di due frazioni "a" e "b" --- restituendo il risultato semplificato. -----------------------------------------------------c : RATIONAL; begin c.num := a.num * b.den - a.den * b.num; c.den := a.den * b.den; return Simplify_Fraction(c); end "-";

function "*"(a, b : RATIONAL) return RATIONAL is ---------------------------------------------------- Calcola il prodotto di due frazioni "a" e "b" --- restituendo il risultato semplificato. ---------------------------------------------------c : RATIONAL; begin c.num := a.num * b.num; c.den := a.den * b.den; return Simplify_Fraction(c); end "*";

function "/"(a, b : RATIONAL) return RATIONAL is ------------------------------------------------- Calcola il quoto di due frazioni "a" e "b" --- restituendo il risultato semplificato. ------------------------------------------------c : RATIONAL; begin c.num := a.num * b.den; c.den := a.den * b.num; return Simplify_Fraction(c); end "/";

function "**"(a : RATIONAL; n : INTEGER) return RATIONAL is ------------------------------------------------------- Calcola la potenza "n"-esima della frazione "a". ------------------------------------------------------q : RATIONAL; begin Liceo cantonale di Mendrisio, 2002 Claudio Marsan

190 q.num := LONG_INTEGER(FLOAT(a.num)**n); q.den := LONG_INTEGER(FLOAT(a.den)**n); return Simplify_Fraction(q); end "**";

CAPITOLO 6. PACKAGES IN ADA 95

function "="(a, b : RATIONAL) return BOOLEAN is ---------------------------------------------------------------------- Restituisce TRUE se le due frazioni "a" e "b" sono equivalenti. ---------------------------------------------------------------------q1 : RATIONAL := Simplify_Fraction(a); q2 : RATIONAL := Simplify_Fraction(b); begin return (q1.num = q2.num and then q1.den = q2.den); end "=";

function "Abs"(a : RATIONAL) return RATIONAL is --------------------------------------------------------------- Restituisce il valore assoluto del numero razionale "a". --------------------------------------------------------------begin return (Abs(a.num), Abs(a.den)); end "Abs";

function Inverse(a : RATIONAL) return RATIONAL is --------------------------------------------------------- Restituisce il reciproco del numero razionale "a". --------------------------------------------------------begin return (ONE / a); end Inverse;

function Approximate(a : RATIONAL) return FLOAT is ------------------------------------------------------------- Restituisce il numero razionale "a" in forma decimale. ------------------------------------------------------------begin return (FLOAT(a.num) / FLOAT(a.den)); end Approximate;

function "<"(a, b : RATIONAL) return BOOLEAN is --------------------------------------------------------- Restituisce TRUE se la frazione "a" minore della --- frazione "b". --------------------------------------------------------Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.6. UN PACKAGE PER TRATTARE I NUMERI RAZIONALI

191

begin return (Approximate(a) < Approximate(b)); end "<";

function "<="(a, b : RATIONAL) return BOOLEAN is ------------------------------------------------------ Restituisce TRUE se la frazione "a" minore o --- uguale alla frazione "b". -----------------------------------------------------begin return (Approximate(a) <= Approximate(b)); end "<=";

function ">"(a, b : RATIONAL) return BOOLEAN is ----------------------------------------------------------- Restituisce TRUE se la frazione "a" maggiore della --- frazione "b". ----------------------------------------------------------begin return (Approximate(a) > Approximate(b)); end ">";

function ">="(a, b : RATIONAL) return BOOLEAN is ------------------------------------------------------ Restituisce TRUE se la frazione "a" maggiore --- o uguale alla frazione "b". -----------------------------------------------------begin return (Approximate(a) >= Approximate(b)); end ">=";

function Positive_Number(a : RATIONAL) return BOOLEAN is ------------------------------------------------------ Restituisce TRUE se la frazioni "a" positiva. -----------------------------------------------------begin return (a > ZERO); end Positive_Number;

function Negative_Number(a : RATIONAL) return BOOLEAN is ------------------------------------------------------ Restituisce TRUE se la frazioni "a" negativa. -----------------------------------------------------begin Liceo cantonale di Mendrisio, 2002 Claudio Marsan

192 return (a < ZERO); end Negative_Number; end Rational_Numbers;

CAPITOLO 6. PACKAGES IN ADA 95

Da notare che bisogna richiamare il package Long_Integer_Math_Lib poich esso contiene la funzione per il calcolo del massimo comune divisore.

6.6.3

Il package Rational_Numbers_IO

Ecco la specicazione del package Rational_Numbers_IO: -----Nome del file: RATIONAL_NUMBERS_IO.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: specificazione del package per linput e loutput di numeri razionali Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Rational_Numbers; use Rational_Numbers;

package Rational_Numbers_IO is

procedure Get(a : out RATIONAL; simplify : in NATURAL := 1); procedure Get(infile : in Ada.Text_IO.FILE_TYPE; a : out RATIONAL; simplify : in NATURAL := 1); procedure Put(a : in RATIONAL; simplify : in NATURAL := 1); procedure Put(infile : in Ada.Text_IO.FILE_TYPE; a : in RATIONAL; simplify : in NATURAL := 1); procedure Put_Line(a : in RATIONAL; simplify : in NATURAL := 1); procedure Put_Line(infile : in Ada.Text_IO.FILE_TYPE; a : in RATIONAL; simplify : in NATURAL := 1); end Rational_Numbers_IO; Da notare che necessario richiamare il package Ada.Text_IO perch esso contiene la dichiarazione del tipo FILE_TYPE e il package Rational_Numbers poich senza di esso non visibile la dichiarazione del tipo RATIONAL. Quello che segue il corpo del package Rational_Numbers_IO: -----Nome del file: RATIONAL_NUMBERS_IO.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: corpo del package per linput e loutput di numeri razionali Testato con: Gnat 3.13p su Windows 2000

with Ada.Long_Integer_Text_IO;

package body Rational_Numbers_IO is procedure Get(a Claudio Marsan : out RATIONAL; Liceo cantonale di Mendrisio, 2002

6.6. UN PACKAGE PER TRATTARE I NUMERI RAZIONALI simplify : in NATURAL := 1) is ------------------------------------------------------------------------- Memorizza in "a" una frazione data nella forma "x/y"; come input --- accettato anche un numero intero; la frazione letta come stringa --- e vengono eseguiti gli opportuni controlli; se "simplify = 1" la --- frazione viene semplificata prima di essere memorizzata. ------------------------------------------------------------------------a_as_string aa length_of_a slash_found number_of_slash : : : : : STRING(1..40); RATIONAL; POSITIVE; NATURAL := 0; NATURAL := 0;

193

begin Ada.Text_IO.Get_Line(Item => a_as_string, Last => length_of_a); for i in POSITIVE range 1..length_of_a loop if a_as_string(i) = / then slash_found := i; number_of_slash := number_of_slash + 1; if number_of_slash > 1 then raise ERRORE_DI_INPUT; end if; end if; if (a_as_string(i) not in 0..9) AND (a_as_string(i) /= /) AND (a_as_string(i) /= -) AND (a_as_string(i) /= +) then raise ERRORE_DI_INPUT; end if; if (i /= 1) AND (i /= slash_found + 1) AND ((a_as_string(i) = -) OR (a_as_string(i) = +)) then raise ERRORE_DI_INPUT; end if; end loop; if slash_found = 0 then aa.num := LONG_INTEGERVALUE(a_as_string(1..length_of_a)); aa.den := 1; else aa.num := LONG_INTEGERVALUE(a_as_string(1..(slash_found - 1))); aa.den := LONG_INTEGERVALUE(a_as_string((slash_found + 1) ..length_of_a)); end if; if aa.den = 0 then raise DIVISIONE_PER_ZERO; end if; if simplify = 1 then a := Simplify_Fraction(aa); else a := aa; end if; end Get;

procedure Get(infile : in Ada.Text_IO.FILE_TYPE; a : out RATIONAL; simplify : in NATURAL := 1) is Liceo cantonale di Mendrisio, 2002 Claudio Marsan

194

CAPITOLO 6. PACKAGES IN ADA 95 -------------------------------------------------------------------------- Memorizza in "a" una frazione letta dal file "infile"; la frazione --- nel file deve essere scritta nella forma "x/y" (il denominatore "1" --- obbligatorio per i numeri interi; se "simplify = 1" la frazione --- viene semplificata prima di essere memorizzata. -------------------------------------------------------------------------aa : RATIONAL; slash : CHARACTER; begin Ada.Long_Integer_Text_IO.Get(File => infile, Item => aa.num); Ada.Text_IO.Get(File => infile, Item => slash); Ada.Long_Integer_Text_IO.Get(File => infile, Item => aa.den); if aa.den = 0 then raise DIVISIONE_PER_ZERO; end if; if simplify = 1 then a := Simplify_Fraction(aa); else a := aa; end if; end Get;

procedure Put(a : in RATIONAL; simplify : in NATURAL := 1) is ----------------------------------------------------------------------- Scrive una frazione nella forma "x/y"; se la frazione negativa --- il segno verr scritto a numeratore; se il denominatore 1 non --- verr scritto se "simplify = 1" (default). ----------------------------------------------------------------------a_semplificato : RATIONAL := Simplify_Fraction(a); begin if simplify = 1 then if a_semplificato.den /= 1 then Ada.Long_Integer_Text_IO.Put(Item => a_semplificato.num, Width => 0); Ada.Text_IO.Put(Item => "/"); Ada.Long_Integer_Text_IO.Put(Item => a_semplificato.den, Width => 0); else Ada.Long_Integer_Text_IO.Put(Item => a_semplificato.num, Width => 0); end if; else Ada.Long_Integer_Text_IO.Put(Item => a.num, Width => 0); Ada.Text_IO.Put(Item => "/"); Ada.Long_Integer_Text_IO.Put(Item => a.den, Width => 0); end if; end Put;

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

6.6. UN PACKAGE PER TRATTARE I NUMERI RAZIONALI procedure Put(infile : in Ada.Text_IO.FILE_TYPE; a : in RATIONAL; simplify : in NATURAL := 1) is ---------------------------------------------------------------------- Scrive una frazione nella forma "x/y" sul file "infile"; se la --- frazione negativa il segno verr scritto a numeratore; se il --- denominatore 1 non verr scritto se "simplify = 1" (default). ---------------------------------------------------------------------a_semplificato : RATIONAL := Simplify_Fraction(a); begin if simplify = 1 then if a_semplificato.den /= 1 then Ada.Long_Integer_Text_IO.Put(File => infile, Item => a_semplificato.num, Width => 0); Ada.Text_IO.Put(File => infile, Item => "/"); Ada.Long_Integer_Text_IO.Put(File => infile, Item => a_semplificato.den, Width => 0); else Ada.Long_Integer_Text_IO.Put(File => infile, Item => a_semplificato.num, Width => 0); end if; else Ada.Long_Integer_Text_IO.Put(File => infile, Item => a.num, Width => 0); Ada.Text_IO.Put(File => infile, Item => "/"); Ada.Long_Integer_Text_IO.Put(File => infile, Item => a.den, Width => 0); end if; end Put;

195

procedure Put_Line(a : in RATIONAL; simplify : in NATURAL := 1) is ---------------------------------------------------------------------------- Come la procedura "Put", ma sposta poi il cursore in una nuova linea. ---------------------------------------------------------------------------begin Put(a, simplify); Ada.Text_IO.New_Line; end Put_Line;

procedure Put_Line(infile : in Ada.Text_IO.FILE_TYPE; a : in RATIONAL; simplify : in NATURAL := 1) is ---------------------------------------------------------------------------- Come la procedura "Put", ma sposta poi il cursore in una nuova linea. ---------------------------------------------------------------------------begin Put(infile, a, simplify); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

196 Ada.Text_IO.New_Line(File => infile); end Put_Line; end Rational_Numbers_IO;

CAPITOLO 6. PACKAGES IN ADA 95

Da notare che non pi necessario richiamare i packages Ada.Text_IO e Rational_Numbers poich essi sono gi stati richiamati nella specicazione del package.

6.6.4

Il programma di test

Il codice seguente relativo al programma di test per i packages costruiti pi sopra: -----Nome del file: TEST_RATIONAL_NUMBERS.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: programma di test per il package per i numeri razionali Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Rational_Numbers, Rational_Numbers_IO; use Rational_Numbers;

procedure Test_Rational_Numbers is a, b, c q ch operazione infile : : : : : RATIONAL; RATIONAL; CHARACTER := s; CHARACTER; Ada.Text_IO.FILE_TYPE;

begin Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Create(File => infile, Name => "SOLUZIONI.TXT"); while (ch = s or ch = S) loop Ada.Text_IO.Put(Item => "Dare una frazione: "); Rational_Numbers_IO.Get(a); Ada.Text_IO.Put(Item => "Dare unoperazione: "); Ada.Text_IO.Get(Item => operazione); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Dare unaltra frazione: "); Rational_Numbers_IO.Get(b); case operazione is when + => c := a + b; when - => c := a - b; when * => c := a * b; when / => c := a / b; when others => exit; end case; Rational_Numbers_IO.Put(a); Ada.Text_IO.Put(" " & operazione & " "); Rational_Numbers_IO.Put_Line(b);

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

6.7. I PACKAGES DISPONIBILI IN ADA 95 Rational_Numbers_IO.Put(infile, a, 0); Ada.Text_IO.Put(infile, " " & operazione & " "); Rational_Numbers_IO.Put(infile, b, 0); Ada.Text_IO.Put(infile, " = "); Rational_Numbers_IO.Put_Line(infile, c, 0); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Ancora un esempio? (s/n): "); Ada.Text_IO.Get(Item => ch); Ada.Text_IO.Skip_Line; Ada.Text_IO.New_Line; end loop; Ada.Text_IO.Close(File => infile); Ada.Text_IO.Open(File => infile, Mode => Ada.Text_IO.In_File, Name => "SOLUZIONI.TXT"); while not Ada.Text_IO.End_of_File(File => infile) loop Rational_Numbers_IO.Get(infile, q); Rational_Numbers_IO.Put(q); Ada.Text_IO.Get(File => infile, Item => ch); Ada.Text_IO.Get(File => infile, Item => ch); Ada.Text_IO.Put( & ch & ); Ada.Text_IO.Get(File => infile, Item => ch); Rational_Numbers_IO.Get(infile, q); Rational_Numbers_IO.Put(q); Ada.Text_IO.Get(File => infile, Item => ch); Ada.Text_IO.Get(File => infile, Item => ch); Ada.Text_IO.Put( & ch & ); Ada.Text_IO.Get(File => infile, Item => ch); Rational_Numbers_IO.Get(infile, q); Rational_Numbers_IO.Put(q); Ada.Text_IO.Skip_line(File => infile); Ada.Text_IO.New_Line; end loop; Ada.Text_IO.Close(File => infile); exception when DIVISIONE_PER_ZERO => Ada.Text_IO.Put_Line(Item => "Denominatore nullo non ammesso!"); end Test_Rational_Numbers;

197

6.7

I packages disponibili in Ada 95

Quello che segue lalbero gerarchico dei packages disponibili nellimplementazione Gnat (versione 3.11p del 2 marzo 1999) di Ada 95. A D A package -------------------------

+- Asynchronous_Task_Control | Liceo cantonale di Mendrisio, 2002 Claudio Marsan

198 +| | | | | +| | | +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| | | | | Claudio Marsan Calender -+- Delay_Objects | +- Delays

CAPITOLO 6. PACKAGES IN ADA 95

+- Handling | Characters -+- Latin_1 | +- Wide_Latin_1 Command_Line -+- Environment Decimal Direct_IO -+- C_Streams Dynamic_Priorities Exceptions Finalization -+- List_Controller Float_Text_IO Float_Wide_Text_IO Integer_Text_IO Interrupts -+- Names Integer_Wide_Text_IO Long_Float_Text_IO Long_Float_Wide_Text_IO Long_Integer_Text_IO Long_Integer_Wide_Text_IO Long_Long_Float_Text_IO Long_Long_Float_Wide_Text_IO Long_Long_Integer_Text_IO Long_Long_Integer_Wide_Text_IO IO_Exceptions +- Aux (Private?) | +- Complex_Elementry_Functions | Liceo cantonale di Mendrisio, 2002

6.7. I PACKAGES DISPONIBILI IN ADA 95 | | | | | | | | | | | Ada -+| | | | | | | | | | | | | | | | | | | | | +| +| +| +| +| +| +| +| | | | | | +| +| +| +| +| +Numerics -+ +| +| +| +| +| +| +| +| +| +Complex_Types Discrete_Random Elementary_Functions Float_Random Generic_Complex_Elementry_Functions Generic_Complex_Types Generic_Elementary_Functions Long_Complex_Types Long_Elementary_Functions Long_Complex_Elementary_Functions Long_Long_Complex_Elementary_Functions Long_Long_Complex_Types Long_Long_Elementary_Functions Short_Complex_Elementary_Functions Short_Complex_Types Short_Elementary_Functions

199

Real_Time -+- Delays Sequential_IO -+- C_Streams Short_Float_Text_IO Short_Float_Wide_Text_IO Short_Integer_Text_IO Short_Integer_Wide_Text_IO Storage_IO Streams -+- Stream_IO -+- C_Streams +- Bounded | +- Fixed | +- Maps -+- Constants Claudio Marsan

Liceo cantonale di Mendrisio, 2002

200 | +| | | | | | | | | | | | +| +| +| +| +| +| | | | | | | | | | | +| | | | | | | | | | | | | | +| +Claudio Marsan | Strings -+| | | +| +| +| +-

CAPITOLO 6. PACKAGES IN ADA 95

Unbounded -+- Aux (Private?) | +- Text_IO Wide_bounded Wide_Fixed Wide_Maps -+- Wide_Constants Wide_Unbounded

Short_Short_Integer_Text_IO Short_Short_Integer_Wide_Text_IO Synchronous_Task_Control Tags Task_Attributes Task_Identification +| +| +| +| +| Text_IO -+| +| +| +| +| +| +Complex_IO Editing Text_Streams Complex_Aux (Private?) Decimal_IO Fixed_IO Float_IO Enumeration_IO Integer_IO Modular_IO C_Streams Text_Streams

Unchecked_Conversion Unchecked_Deallocation Liceo cantonale di Mendrisio, 2002

6.7. I PACKAGES DISPONIBILI IN ADA 95 | | +| | | +| | | +| | | +| | | +| | +- Wide_Text_IO -+| +| +| +| +| +| +| +-

201

Complex_IO Editing Text_Streams Complex_Aux (Private?) Decimal_IO Fixed_IO Float_IO Enumeration_IO Integer_IO Modular_IO C_Streams Text_Streams Generic_Aux (Private?)

I N T E R F A C E S Package -----------------------------------------+- Pointers | +- C -+- Strings | | | +- Extensions | | | +- Sthreads | Interfaces -+- COBOL | +- Fortran | +- CPP | +- OS2Lib -+- Errors | | | +- Synchronization | | | +- Threads | +- Packed_Decimal Liceo cantonale di Mendrisio, 2002 Claudio Marsan

202

CAPITOLO 6. PACKAGES IN ADA 95

G N A T package ----------------------------------+| +| +| +| +| +| +| GNAT -+| +| +| +| +| | | | | +| | | +| +Bubble_Sort_A Bubble_Sort_G Case_Util Command_Line Debug_Utilities Directory_Operations Heap_Sort_A Heap_Sort_G HTable IO -+- IO_Aux OS_Lib Regexp +| +| Spitbol -+| +Table Task_Lock Patterns Table_Boolean Table_Integer Table_VString

S Y S T E M package ------------------------------------

+- Address_To_Access_Conversion | +- Machine_Code Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.7. I PACKAGES DISPONIBILI IN ADA 95 | +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +-

203

RPC OS_Interface Program_Info Parameters Arith_64 Assertions AST_Handling Aux_DEC Bit_Ops Checked_Pools Debug_Pools Direct_IO Error_Reporting Exceptions Exception_Table Exn_Flt Exn_Gen Exn_Int Exn_LFlt Exn_LInt Exn_LLF Exn_LLI Exn_SFlt Exn_SInt Exn_SSI Exp_Flt Exp_Gen Claudio Marsan

Liceo cantonale di Mendrisio, 2002

204 | +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +Claudio Marsan

CAPITOLO 6. PACKAGES IN ADA 95

Exp_Int Exp_LFlt Exp_LInt Exp_LLF Exp_LLI Exp_LLU Exp_Mod Exp_SFlt Exp_SIn Exp_SSI Exp_Uns Fat_Flt Fat_Gen Fat_LFlt Fat_LLF Fat_SFlt File_Control_Block File_IO Finalization_Implementation Finalization_Root Fore Img_BIU Img_Bool Img_Char Img_Dec Img_Int Img_LLB Liceo cantonale di Mendrisio, 2002

6.7. I PACKAGES DISPONIBILI IN ADA 95 | +| +| +| +| +| +| +| +| +| +| +| +| +| +-

205

Img_LLD Img_LLI Img_LLU Img_LLW Img_Real Img_Uns Img_WChar Img_WIU Interrupt_Management -+- Operations Interrupts IO Mantissa OS_Primitives

Pack_03 . . +- Pack_63 | +- Parameters | +- Partition_Interface | +- Pool_Global | +- Pool_Local | +- Pool_Size | +- Powten_Table | +- Secondary_Stack | +- Sequential_IO | +- String_Ops_Concat_3 | +- String_Ops_Concat_4 | +- String_Ops_Concat_5 | Liceo cantonale di Mendrisio, 2002 Claudio Marsan

206 +| +| +| +| +| | | | | | | | | | -+| | | | | | | | | | | | +| +| +| +| +| +| +| +| +| +| +| Standard_Library Storage_Elements Storage_Pools Stream_Attributes String_Ops

CAPITOLO 6. PACKAGES IN ADA 95

System

+| +| +| +| Tasking -+| +| +| +| +| +-

Task_Attributes Utilities Initialization Entry_Calls Protected_Objects Abortion Debug Queuing Rendezvous Stages

Task_Primitives -+- Operations Task_Info Tasking_Soft_Links Task_Specific_Data Task_Timer Unsigned_Types Vax_Float_Operations Val_Bool Val_Char Val_Dec Val_Enum

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

6.7. I PACKAGES DISPONIBILI IN ADA 95 +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +Val_Int Val_LLD Val_LLI Val_LLU Val_Real Val_Uns Val_Util Val_WChar Version_Control VMS_Exception_Table WCh_Cnv WCh_Con WCh_JIS WCh_StW WCh_WtS Wid_Bool Wid_Char Wid_Enum Wid_LLI Wid_LLU Wid_WChar WWd_Char WWd_Enum Wwd_WChar

207

Lalbero contiene quattro packages principali: 1. Ada, contenente 111 packages; 2. Interfaces contenente 13 packages; 3. GNAT contenente 20 nodi di packages; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

208 4. System contenente 190 packages.

CAPITOLO 6. PACKAGES IN ADA 95

Esso stato redatto da Nasser Abbasi ed eventuali aggiornamenti possono essere trovati allindirizzo web http://home.pacbell.net/nma123 Per avere ragguagli sui diversi packages e sul loro contenuto basta consultare la guida online fornita con il compilatore Gnat.

6.8

Sottoprogrammi separati e in biblioteca

In un programma con sottoprogrammi separati il programma principale e i sottoprogrammi si trovano in les separati che andranno compilati separatamente. Nel programma principale (ammettiamo che, per comodit, si chiami Main) bisogna indicare lintestazione del sottoprogramma e farla seguire dalla parola riservata separate; in generale la sintassi : function XYZ(...) return ... is separate; oppure procedure XYZ(...) is separate; Nel le che contiene la denizione del sottoprogramma bisogna scrivere, prima dellintestazione del sottoprogramma, separate(Main). Il sottoprogramma sar poi utilizzabile solo dallunit a cui collegato. Osservazione 6.8.1 Se vogliamo memorizzare in un le separato la routine XYZ e usarla poi nel programma principale Main i les dovranno avere, nellimplementazione Gnat del compilatore Ada 95, i nomi seguenti: MAIN.ADB per il le contenente il programma principale e MAIN-XYZ.ADB per il le contenente il codice della routine XYZ. Esempio 6.8.1 Riprendiamo lesempio 2.4.3, scrivendo la procedura Swap in un le separato. Il le SCAMBIA_SEPARATI.ADB contiene il programma principale: -----Nome del file: SCAMBIA_SEPARATI.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: richiama una procedura definita in un file separato Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Scambia_Separati is a : INTEGER := 5; b : INTEGER := 9; procedure Swap(n1, n2 : in out INTEGER) is separate; -- la procedura definita in un file separato! begin Ada.Text_IO.Put_Line(Item => "Prima dello scambio: "); Ada.Integer_Text_IO.Put(Item => a); Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.8. SOTTOPROGRAMMI SEPARATI E IN BIBLIOTECA Ada.Integer_Text_IO.Put(Item Swap(n1 => a, n2 => b); Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => Ada.Integer_Text_IO.Put(Item Ada.Integer_Text_IO.Put(Item end Scambia_Separati; => b);

209

"Dopo lo scambio: "); => a); => b);

mentre il le SCAMBIA_SEPARATI-SWAP.ADB contiene la denizione della procedura Swap: -----Nome del file: SCAMBIA_SEPARATI-SWAP.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: dichiara una procedura che verr utilizzata in un altro file Testato con: Gnat 3.13p su Windows 2000

separate(Scambia_Separati) procedure Swap(n1, n2 : in out INTEGER) is temp : INTEGER; begin temp := n1; n1 := n2; n2 := temp; end Swap; Per creare il le eseguibile con il compilatore GNAT 3.13p suciente compilare il le sorgente SCAMBIA_SEPARATI.ADB. Se volessimo fare in modo che un sottoprogramma sia disponibile per pi programmi allora quanto appena visto non adeguato; bisogner invece compilare il sottoprogramma in modo autonomo nella biblioteca (si parla di sottoprogramma in biblioteca). Esempio 6.8.2 Riprendiamo lesempio 6.8.1. Il le SCAMBIA_LIBRERIA.ADB contiene il programma principale mentre il le SWAP.ADB contiene la denizione della procedura Swap. Ecco il contenuto di SCAMBIA_LIBRERIA.ADB -----Nome del file: SCAMBIA_LIBRERIA.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: richiama una procedura definita in un file separato Testato con: Gnat 3.13p su Windows 2000 -- variabile temporanea

with Ada.Text_IO, Ada.Integer_Text_IO, Swap; procedure Scambia_Libreria is a : INTEGER := 5; b : INTEGER := 9;

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

210 begin Ada.Text_IO.Put_Line(Item => Ada.Integer_Text_IO.Put(Item Ada.Integer_Text_IO.Put(Item Swap(n1 => a, n2 => b); Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => Ada.Integer_Text_IO.Put(Item Ada.Integer_Text_IO.Put(Item end Scambia_Libreria; e il contenuto del le SWAP.ADB: ------

CAPITOLO 6. PACKAGES IN ADA 95

"Prima dello scambio: "); => a); => b);

"Dopo lo scambio: "); => a); => b);

Nome del file: SWAP.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: definisce una procedura in un file separato Testato con: Gnat 3.13p su Windows 2000

procedure Swap(n1, n2 : in out INTEGER) is temp : INTEGER; begin temp := n1; n1 := n2; n2 := temp; end Swap; Da notare che nel programma principale (Scambia_Libreria) deve apparire listruzione with Swap. Listruzione use Swap non ha senso poich la procedura Swap conosciuta direttamente con il suo nome di biblioteca. Esercizio 6.8.1 Costruire un programma che usi la funzione MAX, denita nellesempio 2.3.7; modicarlo poi in modo tale da avere una compilazione separata nei due modi visti (sottoprogramma separato e sottoprogramma in biblioteca). -- variabile temporanea

6.9

Paradigma modulare

I packages di Ada 95 permettono la realizzazione del paradigma modulare, in particolare: occultamento dei dati e delle informazioni (information hiding), ossia il nascondere allutente i dettagli dellimplementazione (in pratica il corpo del package); incapsulamento dei dati (data encapsulation), ossia il fornire allutente le funzioni di accesso ai dati senza permettergli di accedere ad essi direttamente. Esempio 6.9.1 Consideriamo la struttura di dati detta stack (o pila o coda di tipo FILO, rst in last out). Il package che costruiremo deve fornire allutente: una pila in uno stato noto (per esempio vuota); aggiungere un elemento in cima alla pila (Push); leggere e togliere lelemento in cima alla pila (Pop); Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.9. PARADIGMA MODULARE leggere, senza togliere, lelemento in cima alla pila (Peek ); svuotare la pila (Clear ); gestione degli errori con le eccezioni. Costruiremo una pila di CHARACTER. Questa la specicazione del package: -----Nome del file: PILA.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: interfaccia per una pila di CHARACTER Testato con: Gnat 3.13p su Windows 2000

211

package Pila is pila_piena, pila_vuota : exception; procedure Push(ch : in CHARACTER); procedure Pop; function Peek return CHARACTER; procedure Clear; end Pila; Ecco il le del corpo del package (implementazione): -----Nome del file: PILA.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: implementazione per una pila di CHARACTER Testato con: Gnat 3.13p su Windows 2000

package body Pila is dim_pila : constant INTEGER := 100; p : array(1..dim_pila) of CHARACTER; cima : INTEGER := 0; -- inizializzazione

procedure Push(ch : in CHARACTER) is begin if cima = dim_pila then raise pila_piena; -- eccezione! else cima := cima + 1; p(cima) := ch; end if; end Push;

procedure Pop is begin if cima = 0 then Liceo cantonale di Mendrisio, 2002 Claudio Marsan

212 raise pila_vuota; else cima := cima - 1; end if; end Pop; -- eccezione!

CAPITOLO 6. PACKAGES IN ADA 95

function Peek return CHARACTER is begin if cima = 0 then raise pila_vuota; -- eccezione! else return p(cima); end if; end Peek;

procedure Clear is begin cima := 0; end Clear; end Pila; Il seguente un programma che permette di testare il package appena scritto: -----Nome del file: TEST_PILA.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: programma di test del package PILA Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO, Pila; procedure Test_Pila is begin Ada.Text_IO.Put_Line(Item => "Costruisco una pila con a->f"); Ada.Text_IO.New_Line; for ch in CHARACTER range a .. f loop Pila.Push(ch); end loop; for i in 1..7 loop Ada.Integer_Text_IO.Put(Item => i); Ada.Text_IO.Put(Item => " "); Ada.Text_IO.Put(Item => Pila.Peek); Pila.Pop; Ada.Text_IO.New_Line; end loop; exception Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.10. ASTRAZIONE DEI TIPI DI DATI when Pila.pila_piena => Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "La pila e piena!"); when Pila.pila_vuota => Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "La pila e vuota!"); end Test_Pila; Si compilano, nellordine i le seguenti: 1. PILA.ADS; 2. PILA.ADB; 3. TEST_PILA.ADB;

213

e poi si linkano i les, in modo da ottenere il le eseguibile TEST_PILA.EXE. Notiamo nuovamente che i dettagli dellimplementazione sono nascosti allutente. In particolare egli non sa se la sua pila costruita mediante un array oppure mediante un puntatore. In ogni caso egli pu accedere ai dati desiderati.

6.10

Astrazione dei tipi di dati

Un tipo di dato astratto un tipo di dato denito dallutente: con esso vengono specicati sia i valori che il tipo pu assumere sia le operazioni associate al tipo. Non tutti i linguaggi di programmazione tradizionali supportano bene il paradigma dellastrazione dei dati; con Ada 95 il supporto di tale paradigma facilitato grazie alla possibilit di denire tipi di dati privati o riservati. Nella specicazione di un package possono essere utilizzati i cosiddetti tipi privati, la cui implementazione appare in unapposita sezione della specicazione, invisibile allesterno del package, detta parte privata. I tipi privati servono per impedire allutente di eseguire operazioni scorrette su oggetti di tipi esportati dal package, scavalcando i sottoprogrammi esportati dal package e agendo direttamente sulla rappresentazione degli oggetti. La sintassi per dichiarare un tipo privato T nel package P la seguente: -- interfaccia (specificazione) di P package P is type T is private; ... private type T is ... ... end P; Pur non conoscendone la struttura, lutente pu utilizzare il tipo privato T per denire oggetti con i quali compiere le operazioni specicate dal package stesso (mediante i sottoprogrammi). Quando si denisce un tipo privato T le uniche operazioni disponibili su oggetti di tipo T sono: lassegnazione ( := ); luguaglianza ( = e /= ); lappartenenza ( in e not in ); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

214 il passaggio di parametri;

CAPITOLO 6. PACKAGES IN ADA 95

le operazioni denite nella parte non privata del package. Osservazione 6.10.1 Se si denisce il tipo T come limited private allora le istruzioni di assegnazione e di uguaglianza non saranno disponibili! Non sar cos possibile denire costanti di un tipo limited private e nemmeno creare direttamente copie di oggetti del tipo T (il programmatore deve mettere a disposizione una funzione di accesso specica per questo scopo). Esempio 6.10.1 Riprendiamo lesempio dello stack visto nel paragrafo precedente. Il codice che avevamo scritto in precedenza ci permetteva di operare con un unico stack; denendo invece un nuovo tipo di dato privato PILA nel nuovo package Pile potremo costruire pi stacks, occultando comunque sempre i dettagli dellimplementazione. Ecco linterfaccia del nuovo package: ------Nome del file: PILE.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: interfaccia di PILE, contenente la dichiarazione del tipo astratto di dati PILA Testato con: Gnat 3.13p su Windows 2000

package Pile is type PILA is private; procedure Push(p : in out PILA; ch : in CHARACTER); procedure Pop(p : in out PILA); function Peek(p : PILA) return CHARACTER; pila_piena, pila_vuota : exception; private dim_pila : constant INTEGER := 100; type VETT_PILA is array(1..dim_pila) of CHARACTER; type PILA is record vp : VETT_PILA; cima : INTEGER := 0; -- inizializzazione end record; end Pile; Tutto quello che appare prima della parola riservata private pubblico e accessibile dallesterno (si dice che si trova nella parte visibile della specicazione), mentre quello che segue private non accessibile dallesterno del package; in particolare: Push, Pop, Peek, pila_piena e pila_vuota sono accessibili dallesterno del package Pile; dim_pila, VETT_PILA, vp e cima non sono accessibili dallesterno del package Pile; PILA utilizzabile per dichiarare oggetti di tipo PILA (e per operazioni di uguaglianza, assegnazione, appartenenza e passaggio di parametri) ma non possibile accedere alla sua struttura interna (sebbene essa sia leggibile). Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.10. ASTRAZIONE DEI TIPI DI DATI

215

Da notare che stato necessario introdurre il tipo di dati VETT_PILA poich Ada 95 proibisce luso di tipi array anonimi allinterno di un record. Ecco limplementazione del nuovo package: -----Nome del file: PILE.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: implementazione di PILE Testato con: Gnat 3.13p su Windows 2000

package body Pile is procedure Push(p : in out PILA; ch : in CHARACTER) is begin if p.cima = dim_pila then raise pila_piena; -- eccezione! else p.cima := p.cima + 1; p.vp(p.cima) := ch; end if; end Push;

procedure Pop(p : in out PILA) is begin if p.cima = 0 then raise pila_vuota; -- eccezione! else p.cima := p.cima - 1; end if; end Pop;

function Peek(p : in PILA) return CHARACTER is begin if p.cima = 0 then raise pila_vuota; -- eccezione! else return p.vp(p.cima); end if; end Peek; end Pile; Quello che segue un programma di test per il package Pile: -----Nome del file: TEST_PILE.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: progrmma di test del package PILE Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO, Pile; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

216

CAPITOLO 6. PACKAGES IN ADA 95

procedure Test_Pile is p : Pile.PILA; begin Ada.Text_IO.Put_Line(Item => "Costruisco una pila con a->f"); Ada.Text_IO.New_Line; for ch in CHARACTER range a .. f loop Pile.Push(p, ch); end loop; for i in 1..7 loop Ada.Integer_Text_IO.Put(Item => i); Ada.Text_IO.Put(Item => " "); Ada.Text_IO.Put(Item => Pile.Peek(p)); Pile.Pop(p); Ada.Text_IO.New_Line; end loop; exception when Pile.pila_piena => Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "La pila e piena!"); when Pile.pila_vuota => Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line(Item => "La pila e vuota!"); end Test_Pile; Esempio 6.10.2 In precedenza avevamo costruito un package per manipolare i numeri complessi (vedi esercizio 6.5.1), che aveva linterfaccia seguente: -----Nome del file: NUMERI_COMPLESSI.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: specificazione del package NUMERI_COMPLESSI Testato con: Gnat 3.13p su Windows 2000

package Numeri_Complessi is type COMPLEX is record Re : FLOAT := 0.0; Im : FLOAT := 0.0; end record;

-- parte reale -- parte immaginaria

i : constant COMPLEX := (0.0, 1.0); function function function function function Norm(z AbsC(z "+"(x, "-"(x, "*"(x, : : y y y

-- unit immaginaria

COMPLEX) return FLOAT; COMPLEX) return FLOAT; : COMPLEX) return COMPLEX; : COMPLEX) return COMPLEX; : COMPLEX) return COMPLEX; Liceo cantonale di Mendrisio, 2002

Claudio Marsan

6.10. ASTRAZIONE DEI TIPI DI DATI function "/"(x, y : COMPLEX) return COMPLEX; procedure Put(z : in COMPLEX); procedure Put_Line(z : in COMPLEX); procedure Get(z : out COMPLEX); end Numeri_Complessi;

217

Un utente del package potrebbe manipolare il tipo COMPLEX in modo poco pulito, facendo uso della sua particolare rappresentazione per costruire i valori del tipo. Per esempio, ammettendo che x sia una variabile di tipo COMPLEX, potrebbe scrivere unespressione del tipo x.Im := x.Im + 1.0; -- "+" un operatore per i FLOAT

piuttosto che unespressione del tipo x := x + i; -- "+" un operatore per i COMPLEX

Denendo invece il tipo COMPLEX come privato e fornendo le necessarie funzioni di accesso, possiamo ovviare al problema esposto sopra. Ecco la nuova interfaccia: ------Nome del file: NUMERI_COMPLESSI_PRIVATE.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: specificazione del package NUMERI_COMPLESSI_PRIVATE Testato con: Gnat 3.13p su Windows 2000

package Numeri_Complessi_Private is type COMPLEX is private; i : constant COMPLEX; function Norm(z : COMPLEX) return FLOAT; function AbsC(z : COMPLEX) return FLOAT; function "+"(x, y : COMPLEX) return COMPLEX; function "-"(x, y : COMPLEX) return COMPLEX; function "*"(x, y : COMPLEX) return COMPLEX; function "/"(x, y : COMPLEX) return COMPLEX; procedure Put(z : in COMPLEX); procedure Put_Line(z : in COMPLEX); procedure Get(z : out COMPLEX); function Reale(x : COMPLEX) return FLOAT; function Immaginaria(x : COMPLEX) return FLOAT; function Crea_Complesso(x, y : FLOAT) return COMPLEX; private type COMPLEX is record Re : FLOAT := 0.0; Im : FLOAT := 0.0; end record;

-- parte reale -- parte immaginaria

i : constant COMPLEX := (0.0, 1.0); end Numeri_Complessi_Private; Liceo cantonale di Mendrisio, 2002

-- unit immaginaria

Claudio Marsan

218

CAPITOLO 6. PACKAGES IN ADA 95

La funzione Reale restituisce la parte reale di un numero complesso, la funzione Immaginaria restituisce la parte immaginaria di un numero complesso, la funzione Crea_Complesso crea un numero complesso partendo da due numeri reali. Ecco limplementazione di queste funzioni allinterno del corpo del package Numeri_Complessi_Private: ... package body Numeri_Complessi_Private is ... function Reale(z : COMPLEX) return FLOAT is -------------------------------------------------------- Restituisce la parte reale del numero complesso z -------------------------------------------------------begin return z.Re; end Reale;

function Immaginaria(z : COMPLEX) return FLOAT is -------------------------------------------------------------- Restituisce la parte immaginaria del numero complesso z -------------------------------------------------------------begin return z.Im; end Immaginaria;

function Crea_Complesso(x, y : FLOAT) return COMPLEX is ------------------------------------------------------------ Costruisce il numero complesso "z = x + i*y = (x, y)" -----------------------------------------------------------begin return (x, y); end Crea_Complesso; ... end Numeri_Complessi_Private; Grazie a questa nuova implementazione istruzioni quali x.Im := x.Im + 1.0; non sono pi ammesse poich Im privato. Ammettiamo che x sia di tipo Complex. Unistruzione mista come la seguente non corretta: x := 5.3 * x; Grazie alla funzione Crea_Complesso possiamo per rimediare elegantemente: x := Crea_Complesso(5.3, 0.0) * x; Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.10. ASTRAZIONE DEI TIPI DI DATI Osservazione 6.10.2 Notiamo che:

219

1. Un vantaggio nella nuova implementazione del package Numeri_Complessi il seguente: se si decidesse di cambiare la denizione del tipo COMPLEX, per esempio in type COMPLEX is array(1..2) of FLOAT; questo non avrebbe nessuna conseguenza per i nostri programmi (a parte la necessit di doverli ricompilare!) poich ci stata vietata la possibilit di accedere direttamente alla struttura del tipo COMPLEX. 2. Nella parte visibile della specica possibile dichiarare delle costanti, come per esempio i, ma non possibile inizializzarle poich non sono ancora noti i dettagli del tipo. 3. Se nella parte visibile non avessimo denito le funzioni di accesso Reale, Immaginaria e Crea_Complesso allora non avremmo alcuna possibilit di accedere alle parti reale e immaginaria di un numero complesso e nemmeno quella di costruire un numero complesso partendo da due numeri reali dati!

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

220

CAPITOLO 6. PACKAGES IN ADA 95

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 7 Genericit in Ada 95


In Ada 95 possibile scrivere sottoprogrammi e packages generici, detti unit generiche (generic units). Le unit generiche sono modelli che, tramite apposite dichiarazioni, vengono attualizzati (o istanziati) in seguito per ottenere specici sottoprogrammi o specici packages. Una unit generica possiede parametri, in modo che istanze diverse della stessa unit generica possono contenere entit diverse. Le istanze di una unit generica vengono eettuate durante la fase di compilazione.

7.1

Dichiarazioni e attualizzazioni

Uno dei problemi di un linguaggio (fortemente) tipizzato come Ada 95 costituito dal fatto che i tipi di dati devono essere determinati al momento della compilazione. Questo signica, evidentemente, che non possiamo eettuarne il passaggio, cos come si fa con i parametri, al momento dellesecuzione. Spesso ci troviamo per nella situazione in cui la logica di una parte di programma indipendente dai tipi di dati coinvolti e pertanto ci sembra inutile doverla riesplicitare con tutti i dati ai quali vogliamo si applichi. Esempio 7.1.1 Consideriamo la procedura Swap per scambiare il valore di due variabili di tipo FLOAT: procedure Swap(x, y : in out FLOAT) is temp : FLOAT; begin temp := x; x := y; y := temp; end Swap; chiaro che la logica indipendente dal tipo dei valori scambiati. Se volessimo scambiare anche dei valori interi o booleani, potremmo naturalmente scrivere altre procedure ma sarebbe dispendioso e ridondante. Il meccanismo generalizzato ci consente di evitare ci perch possiamo dichiarare: generic type ITEM is private; 221

222

CAPITOLO 7. GENERICIT IN ADA 95

procedure Exchange(x, y : in out ITEM); procedure Exchange(x, y : in out ITEM) is temp : ITEM; begin temp := x; x := y; y := temp; end Exchange; La procedura Exchange generica e agisce come se fosse una specie di maschera. La specicazione di un sottoprogramma preceduta dalla parte generica formale che consiste della parola riservata generic seguita da una lista, eventualmente vuota, di parametri generici formali. Il corpo viene scritto nel modo usuale ma, con un sottoprogramma generico, il corpo e la specicazione sono indicati separatamente. La specicazione e il corpo possono appartenere a due unit di compilazione diverse. La procedura generica non pu essere richiamata direttamente ma serve per dare origine ad unulteriore procedura eettiva tramite un meccanismo detto attualizzazione (o istanziazione) generica (generic istantiation). Si dice anche che una particolare versione di una unit generica una attualizzazione o una istanziazione dellunit generica. Esempio 7.1.2 Riprendiamo lesempio 7.1.1. Lattualizzazione avviene mediante: procedure Swap is new Exchange(ITEM => FLOAT); Possiamo interpretare la riga sopra nel modo seguente: si ottiene la procedura Swap dalla maschera descritta dalla procedura Exchange sostituendo in questultima tutte le occorrenze di ITEM con FLOAT. Una volta che abbiamo creato la procedura Swap che agisce sul tipo FLOAT la possiamo richiamare nel solito modo. Si possono fare anche altre attualizzazioni, per esempio: procedure Swap is new Exchange(ITEM => INTEGER); procedure Swap is new Exchange(ITEM => LONG_INTEGER); procedure Swap is new Exchange(ITEM => BOOLEAN); eccetera. Abbiamo cos dato alla procedura Swap altri signicati (tecnicamente si dice che essa stata sovraccaricata (overloaded )) che si dierenziano tra loro grazie al tipo dei loro parametri. Naturalmente non era necessario sovraccaricare la procedura Swap; era anche possibile (sebbene meno elegante) dare nomi diversi alle varie procedure: procedure Swap_Integer is new Exchange(ITEM => INTEGER); procedure Swap_Long_Integer is new Exchange(ITEM => LONG_INTEGER); procedure Swap_Boolean is new Exchange(ITEM => BOOLEAN); Se la procedura Exchange deve essere usata in un programma, allora il programma deve inziare con with Exchange; Esempio 7.1.3 Vogliamo ora implementare praticamente la procedura generica Exchange dellesempio 7.1.1. Ecco il le di specicazione: Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.1. DICHIARAZIONI E ATTUALIZZAZIONI -----Nome del file: EXCHANGE.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: contiene la specificazione di una procedura di scambio generica Testato con: Gnat 3.13p su Windows 2000

223

generic type ITEM is private; procedure Exchange(x, y : in out ITEM); e il le di implementazione: -----Nome del file: EXCHANGE.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: contiene limplementazione di una procedura di scambio generica Testato con: Gnat 3.13p su Windows 2000

procedure Exchange(x, y : in out ITEM) is temp : ITEM; begin temp := x; x := y; y := temp; end Exchange; Quello che segue un programma completo per testare la procedura generica Exchange: -----Nome del file: TEST_SWAP.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: test di una procedura di scambio generica Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Float_Text_IO, Exchange;

procedure Test_Swap is procedure Swap is new Exchange(ITEM => INTEGER); procedure Swap is new Exchange(ITEM => FLOAT); i1 i2 f1 f2 : : : : INTEGER := 1; INTEGER := 2; FLOAT := 1.1; FLOAT := 2.2;

begin Ada.Text_IO.Put_Line(Item => "Prima dello scambio: "); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

224

CAPITOLO 7. GENERICIT IN ADA 95 Ada.Text_IO.Put(Item => "i1 = "); Ada.Integer_Text_IO.Put(Item => i1, Width => 0); Ada.Text_IO.Put(Item => " i2 = "); Ada.Integer_Text_IO.Put(Item => i2, Width => 0); Ada.Text_IO.New_Line(Spacing => 2); Swap(x => i1, y => i2); Ada.Text_IO.Put_Line(Item => "Dopo lo scambio: "); Ada.Text_IO.Put(Item => "i1 = "); Ada.Integer_Text_IO.Put(Item => i1, Width => 0); Ada.Text_IO.Put(Item => " i2 = "); Ada.Integer_Text_IO.Put(Item => i2, Width => 0); Ada.Text_IO.New_Line(Spacing => 5);

Ada.Text_IO.Put_Line(Item => "Prima dello scambio: "); Ada.Text_IO.Put(Item => "f1 = "); Ada.Float_Text_IO.Put(Item => f1, Fore => 0, Aft => 3, Ada.Text_IO.Put(Item => " f2 = "); Ada.Float_Text_IO.Put(Item => f2, Fore => 0, Aft => 3, Ada.Text_IO.New_Line(Spacing => 2); Swap(x => f1, y => f2); Ada.Text_IO.Put_Line(Item => "Dopo lo scambio: "); Ada.Text_IO.Put(Item => "f1 = "); Ada.Float_Text_IO.Put(Item => f1, Fore => 0, Aft => 3, Ada.Text_IO.Put(Item => " f2 = "); Ada.Float_Text_IO.Put(Item => f2, Fore => 0, Aft => 3, end Test_Swap; Loutput del programma: Prima dello scambio: i1 = 1 i2 = 2 Dopo lo scambio: i1 = 2 i2 = 1

Exp => 0); Exp => 0);

Exp => 0); Exp => 0);

Prima dello scambio: f1 = 1.100 f2 = 2.200 Dopo lo scambio: f1 = 2.200 f2 = 1.100 Come visto negli esempi listanziazione (ossia la concretizzazione) avviene passando i parametri generici attuali. Tali parametri non devono essere confusi con i parametri della procedura! Abbiamo visto come denire funzioni e procedure generiche; ora vogliamo fare la stessa cosa per linsieme dei sottoprogrammi di un package, denendo cos un package generico. Esempio 7.1.4 Riprendiamo lesempio degli stacks (vedi 6.10.1) denendo ora un tipo PILA generico e parametrizzato nel numero degli elementi. Ecco il codice relativo alla specicazione: -- Nome del file: PILA_GENERICA.ADS -- Autore: Claudio Marsan Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.1. DICHIARAZIONI E ATTUALIZZAZIONI -- Data dellultima modifica: 8 giugno 2002 -- Scopo: contiene la specificazione per la pila generica -- Testato con: Gnat 3.13p su Windows 2000 generic type ELEMENT is private; package Pila_Generica is type PILA(dim : POSITIVE) is private; procedure Push(p : in out PILA; x : in ELEMENT); procedure Pop(p : in out PILA); function Peek(p : PILA) return ELEMENT; pila_piena, pila_vuota : exception; private type VETT_PILA is array(POSITIVE range <>) of ELEMENT; type PILA(dim : POSITIVE) is record vp : VETT_PILA(1..dim); cima : NATURAL := 0; -- inizializzazione end record; end Pila_Generica; e il codice relativo allimplementazione: -----Nome del file: PILA_GENERICA.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: contiene limplementazione per la pila generica Testato con: Gnat 3.13p su Windows 2000

225

package body Pila_Generica is procedure Push(p : in out PILA; x : in ELEMENT) is begin p.cima := p.cima + 1; p.vp(p.cima) := x; exception when CONSTRAINT_ERROR => raise pila_piena; end Push;

procedure Pop(p : in out PILA) is begin p.cima := p.cima - 1; exception Liceo cantonale di Mendrisio, 2002 Claudio Marsan

226

CAPITOLO 7. GENERICIT IN ADA 95 when CONSTRAINT_ERROR => raise pila_vuota; end Pop;

function Peek(p : PILA) return ELEMENT is begin return p.vp(p.cima); exception when CONSTRAINT_ERROR => raise pila_vuota; end Peek; end Pila_Generica; Il seguente un programma di test completo: -----Nome del file: TEST_PILA_GENERICA.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: programma di test per la pila generica Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Float_Text_IO, Pila_Generica; procedure Test_Pila_Generica is -- istanziazione del package generico per il tipo INTEGER package Integer_Stack is new Pila_Generica(ELEMENT => INTEGER); -- istanziazione del package generico per il tipo FLOAT package Float_Stack is new Pila_Generica(ELEMENT => FLOAT); p1 p2 N i f : : : : : Integer_Stack.PILA(8); Float_Stack.PILA(6); INTEGER; INTEGER := 11; FLOAT := 21.0;

begin Ada.Text_IO.Put(Item => "Interi e reali da inserire negli stacks: "); Ada.Integer_Text_IO.Get(Item => N); for j in 1..N loop Integer_Stack.Push(p => p1, x => i); i := i + 1; Float_Stack.Push(p => p2, x => f); f := f + 1.0; end loop; Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Interi e reali da togliere dagli stacks: "); Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.1. DICHIARAZIONI E ATTUALIZZAZIONI

227

Ada.Integer_Text_IO.Get(Item => N); for j in 1..N loop Ada.Integer_Text_IO.Put(Item => j); Ada.Text_IO.Put(Item => " "); Ada.Integer_Text_IO.Put(Item => Integer_Stack.Peek(p => p1), Width => 5); Integer_Stack.Pop(p => p1); Ada.Text_IO.Put(" "); Ada.Float_Text_IO.Put(Item => Float_Stack.Peek(p => p2), Exp => 0); Float_Stack.Pop(p => p2); Ada.Text_IO.New_Line; end loop; exception when Integer_Stack.pila_piena => Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line("ERRORE: when Integer_Stack.pila_vuota => Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line("ERRORE: when Float_Stack.pila_piena => Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line("ERRORE: when Float_Stack.pila_vuota => Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line("ERRORE: end Test_Pila_Generica; Ecco qualche esempio di output: ------------------------------------------Interi e reali da inserire negli stacks: 6

stack di INTEGER pieno!");

stack di INTEGER vuoto!");

stack di FLOAT pieno!");

stack di FLOAT vuoto!");

Interi e reali da togliere dagli stacks: 5 1 16 26.00000 2 15 25.00000 3 14 24.00000 4 13 23.00000 5 12 22.00000 ------------------------------------------Interi e reali da inserire negli stacks: 9 ERRORE: stack di FLOAT pieno! ------------------------------------------Interi e reali da inserire negli stacks: 4

Interi e reali da togliere dagli stacks: 5 Liceo cantonale di Mendrisio, 2002 Claudio Marsan

228 1 2 3 4 5 ERRORE: stack di 14 13 12 11 24.00000 23.00000 22.00000 21.00000

CAPITOLO 7. GENERICIT IN ADA 95

INTEGER vuoto!

------------------------------------------Da notare che nel programma di test il package generico stato istanziato due volte e quindi i due packages istanziati esportano gli stessi nomi. Il compilatore in grado di risolvere correttamente la sovrapposizione (overloading) di sottoprogrammi in base al tipo degli argomenti dei sottoprogrammi. In taluni casi pu per essere necessario ricorrere alla forma qualicata per evitare conitti (in questo esempio sempre stato fatto). Osservazione 7.1.1 Da notare che non solo possibile istanziare una unit con parametri generici riferiti ai tipi di dati ma anche a valori che saranno poi ssati nella fase di istanziazione. Per esempio nel package Pila_Generica la dimensione dei vettori veniva ssata in fase di vincolo del tipo PILA; unaltra possibilit potrebbe essere quella di ssare la dimensione dellarray durante la fase di istanziazione. Ecco come: generic max_dim : in POSITIVE; type ELEMENT is private; package Pila_Generica is ... vp : array(1..max_dim) of ELEMENT; ... Il parametro generico pu essere della classe in (sottointesa) oppure della classe in out (vedi manuali); la classe out non prevista. Listanziazione del package generico avviene, per esempio, tramite: package Float_Stack is new Pila_Generica(max_dim => 100, ELEMENT => FLOAT);

7.2

Tipi parametri

Nel paragrafo precedente abbiamo accennato ai tipi in qualit di parametri generici. Gli esempi mostravano il parametro formale nella forma type T is private; In questo caso, allinterno di un sottoprogramma o di un package generico, si pu dare per scontato unicamente che per il tipo T siano denite luguaglianza e lassegnazione. Da questo momento nellunit generica, il tipo T si comporta proprio come un tipo riservato esterno al package che lo denisce. Oltre a poter denire un parametro tipo generico della forma private possibile denire anche altre forme. Ecco lelenco: type T is (<>); per il tipo discreto; type T is range <>; per il tipo intero; type T is digits <>; per il tipo reale a virgola mobile; Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.2. TIPI PARAMETRI type T is delta <>; per il tipo reale a virgola ssa; type T is array(INDICE) of TIPO; per il tipo array vincolato; type T is array(INDICE range <>) of TIPO; per il tipo array non vincolato; type T is access TIPO; per il tipo puntatore, che vedremo in seguito; type T is limited private; per il tipo privato limitato.

229

Da notare che i parametri tipo generici possono riferirsi anche ad altri parametri tipo generici dichiarati in precedenza. Grazie ai tipi generici si possono programmare funzioni e procedure che, prima di essere utilizzate, devono essere istanziate. Esempio 7.2.1 Vogliamo costruire una funzione che restituisca il valore successivo di un oggetto di tipo discreto. Ecco il le di specicazione: ------Nome del file: NEXT.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: contiene la specificazione di una funzione generica valida per i tipi discreti Testato con: Gnat 3.13p su Windows 2000

generic type DISCRETE is (<>); function Next(x : DISCRETE) return DISCRETE; e il le di implementazione: ------Nome del file: NEXT.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: contiene limplementazione di una funzione generica valida per i tipi discreti Testato con: Gnat 3.13p su Windows 2000

function Next(x : DISCRETE) return DISCRETE is begin if x = DISCRETELAST then return DISCRETEFIRST; else return DISCRETESUCC(x); end if; end Next; Ecco un programma completo che fa uso della funzione generica Next: ----Nome del file: TEST_NEXT.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: test di una procedura di scambio generica Claudio Marsan

Liceo cantonale di Mendrisio, 2002

230 -- Testato con: Gnat 3.13p su Windows 2000 with Ada.Text_IO, Ada.Integer_Text_IO, Next;

CAPITOLO 7. GENERICIT IN ADA 95

procedure Test_Next is type SETTIMANA is (LUN, MAR, MER, GIO, VEN, SAB, DOM); package Settimana_IO is new Ada.Text_IO.Enumeration_IO(SETTIMANA); function Domani is new Next(DISCRETE => SETTIMANA); function Prossimo is new Next(DISCRETE => INTEGER); oggi dopo_oggi n m : : : : SETTIMANA := GIO; SETTIMANA; INTEGER := 4; INTEGER;

begin dopo_oggi := Domani(x => oggi); Ada.Text_IO.Put(Item => "Il successore di "); Settimana_IO.Put(Item => oggi); Ada.Text_IO.Put(Item => " e "); Settimana_IO.Put(Item => dopo_oggi); Ada.Text_IO.New_Line(Spacing => 3); m := Prossimo(x => n); Ada.Text_IO.Put(Item => "Il successore di "); Ada.Integer_Text_IO.Put(Item => n, Width => 0); Ada.Text_IO.Put(Item => " e "); Ada.Integer_Text_IO.Put(Item => m, Width => 0); end Test_Next; Ecco loutput del programma: Il successore di GIO e VEN

Il successore di 4 e 5

7.3

Parametri funzionali

I parmetri generici possono anche essere costituiti da sottoprogrammi. In alcuni linguaggi (per esempio Algol e Pascal) gli stessi parametri di un sottoprogramma possono essere dei sottoprogrammi. Questa possibilit utile in varie applicazioni matematiche, per esempio nellintegrazione di funzioni. In Ada 95 lunica possibilit di passare un sottoprogramma come parametro ad unaltro sottoprogramma quello di usare i sottoprogrammi come parametri generici. Per far ci lintestazione (ossia la specicazione) del sottoprogramma deve essere preceduta dalla parola riservata with (da non confondere con listruzione di importazione dei package). Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.3. PARAMETRI FUNZIONALI

231

Esempio 7.3.1 Consideriamo la seguente funzione generica che permette di integrare numericamente una funzione: generic with function F(x : FLOAT) return FLOAT; function Integrale(sinistro, destro : FLOAT; numero_passi : POSITIVE) return FLOAT; Se volessimo integrare la funzione f (x) = sin x nellintervallo [0, 1] con 10 passi dintegrazione dovremo dapprima istanziare la funzione Integrale mediante function Integra_Sin is new Integrale(F => Sin); e poi richiamare la funzione Integra_Sin nel programma principale: ... area := Integra_Sin(sinistro => 0.0, destro => 1.0, numero_passi => 10); ... Siccome il problema appena esposto fa intervenire, nella sua implementazione pratica, un algoritmo numerico (per esempio il metodo di Simpson) bene aumentare la genericit lasciando allutente la possibilit di scegliere la precisione che desidera. Introduciamo cos un tipo generico REAL: generic type REAL is digits <>; with function F(x : REAL) return REAL; function Integrale(sinistro, destro : REAL; numero_passi : POSITIVE) return REAL; Una possibile istanziazione sar allora: function Integra_Sin is new Integrale(REAL => FLOAT, F => Sin); Come programma di test segue una semplice implementazione del metodo di integrazione di Simpson; dapprima la specicazione: ------Nome del file: INTEGRAZIONE.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: interfaccia per una funzione generica di integrazione (metodo di Simpson) Testato con: Gnat 3.13p su Windows 2000

generic type REAL is digits <>; with function F(x : REAL) return REAL; function Integrazione(sinistro, destro : REAL) return REAL; quindi limplementazione -----Nome del file: INTEGRAZIONE.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: implementazione per una funzione generica di integrazione (metodo di Simpson) Claudio Marsan

Liceo cantonale di Mendrisio, 2002

232 -- Testato con: Gnat 3.13p su Windows 2000

CAPITOLO 7. GENERICIT IN ADA 95

function Integrazione(sinistro, destro : REAL) return REAL is x0 : REAL := (sinistro + destro)/2.0; h : REAL := x0 - sinistro; begin return h*(F(x0 - h) + 4.0*F(x0) + F(x0 + h))/3.0; end Integrazione; ed inne il programma di test: ------Nome del file: TEST_INTEGRAZIONE.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: test della funzione generica di integrazione (metodo di Simpson) Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Float_Text_IO, Ada.Numerics.Elementary_Functions, Integrazione; use Ada.Numerics.Elementary_Functions; procedure Test_Integrazione is function Integra_Sin is new Integrazione(REAL => FLOAT, F => Sin); function Integra_Cos is new Integrazione(REAL => FLOAT, F => Cos); function Integra_Exp is new Integrazione(REAL => FLOAT, F => Exp); function g(x : FLOAT) return FLOAT is begin return (x**2 + x + 1.0); end g; function Integra_g is new Integrazione(REAL => FLOAT, F => g); begin Ada.Text_IO.Put(Item => "Integrale di sin(x) su [0,1]: "); Ada.Float_Text_IO.Put(Item => Integra_Sin(sinistro => 0.0, destro => 1.0), Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("Integrale di cos(x) su [0,1]: "); Ada.Float_Text_IO.Put(Item => Integra_Cos(sinistro => 0.0, destro => 1.0), Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("Integrale di exp(x) su [0,1]: "); Ada.Float_Text_IO.Put(Item => Integra_Exp(sinistro => 0.0, destro => 1.0), Exp => 0); Ada.Text_IO.New_Line; Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.4. UN PACKAGE PER I VETTORI DELLO SPAZIO

233

Ada.Text_IO.Put("Integrale di g(x) = x^2 + x + 1 su [0,1]: "); Ada.Float_Text_IO.Put(Item => Integra_g(sinistro => 0.0, destro => 1.0), Exp => 0); end Test_Integrazione; Ecco loutput del programma: Integrale Integrale Integrale Integrale di di di di sin(x) cos(x) exp(x) g(x) = su [0,1]: 0.45986 su [0,1]: 0.84177 su [0,1]: 1.71886 x^2 + x + 1 su [0,1]:

1.83333

7.4

Un package per i vettori dello spazio

Esercizio 7.4.1 Costruire un package per la manipolazione di vettori dello spazio, ossia di vettori con tre componenti. Le componenti possono essere di qualsiasi tipo reale (bisogna quindi ricorrere alla genericit); denire come privato il tipo base VETTORE_3D e fornire le funzioni di accesso alle singole componenti. Costruire poi un programma di test. Esempio 7.4.1 Ecco una possibile soluzione dellesercizio precedente. Dapprima il le di specicazione: ------Nome del file: VETTORI3D.ADS Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: interfaccia del package per la manipolazione di vettori a 3 componenti (il tipo VETTORE_3D dichiarato privato!) Testato con: Gnat 3.13p su Windows 2000

generic type ELEMENT is digits <>; package Vettori3D is type VETTORE_3D is private; function Ascissa(v : VETTORE_3D) return ELEMENT; function Ordinata(v : VETTORE_3D) return ELEMENT; function Quota(v : VETTORE_3D) return ELEMENT; function "+"(v, w : VETTORE_3D) return VETTORE_3D; function "-"(v, w : VETTORE_3D) return VETTORE_3D; function "*"(lambda : ELEMENT; v : VETTORE_3D) return VETTORE_3D; function "/"(v : VETTORE_3D; lambda : ELEMENT) return VETTORE_3D; function Vettore_Opposto(v : VETTORE_3D) return VETTORE_3D; function Prodotto_Scalare(v, w : VETTORE_3D) return ELEMENT; function Prodotto_Vettoriale(v, w : VETTORE_3D) return VETTORE_3D; function Prodotto_Misto(u, v, w : VETTORE_3D) return ELEMENT; procedure Get(v : out VETTORE_3D); procedure Put(v : in VETTORE_3D; prima : in INTEGER := 0; dopo : in INTEGER := 6; esponente : in INTEGER := 0); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

234 procedure Put_Line(v prima dopo esponente procedure Put(x : in prima : in dopo : in esponente : in procedure Put_Line(x prima dopo esponente Vettore_Nullo Versore_asse_x Versore_asse_y Versore_asse_z : : : : constant constant constant constant

CAPITOLO 7. GENERICIT IN ADA 95 : in VETTORE_3D; : in INTEGER := 0; : in INTEGER := 6; : in INTEGER := 0); ELEMENT; INTEGER := 0; INTEGER := 6; INTEGER := 0); : in ELEMENT; : in INTEGER := 0; : in INTEGER := 6; : in INTEGER := 0);

VETTORE_3D; VETTORE_3D; VETTORE_3D; VETTORE_3D;

private type VETTORE_3D is array(1..3) of ELEMENT; Vettore_Nullo Versore_asse_x Versore_asse_y Versore_asse_z end Vettori3D; quindi il le di implementazione: ------Nome del file: VETTORI3D.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: implementazione del package per la manipolazione di vettori a 3 componenti Testato con: Gnat 3.13p su Windows 2000 : : : : constant constant constant constant VETTORE_3D VETTORE_3D VETTORE_3D VETTORE_3D := := := := (0.0, (1.0, (0.0, (0.0, 0.0, 0.0, 1.0, 0.0, 0.0); 0.0); 0.0); 1.0);

with Ada.Text_IO; package body Vettori3D is

package Elementi_IO is new Ada.Text_IO.Float_IO(ELEMENT); ----------------------------------- Restituisce lascissa di "v" ----------------------------------function Ascissa(v : VETTORE_3D) return ELEMENT is begin return v(1); end Ascissa; Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.4. UN PACKAGE PER I VETTORI DELLO SPAZIO

235

------------------------------------ Restituisce lordinata di "v" -----------------------------------function Ordinata(v : VETTORE_3D) return ELEMENT is begin return v(2); end Ordinata;

---------------------------------- Restituisce la quota di "v" ---------------------------------function Quota(v : VETTORE_3D) return ELEMENT is begin return v(3); end Quota;

--------------------------- Somma di due vettori --------------------------function "+"(v, w : VETTORE_3D) return VETTORE_3D is begin return (v(1) + w(1), v(2) + w(2), v(3) + w(3)); end "+";

-------------------------------- Differenza di due vettori -------------------------------function "-"(v, w : VETTORE_3D) return VETTORE_3D is begin return (v(1) - w(1), v(2) - w(2), v(3) - w(3)); end "-";

---------------------------------------------------- Moltiplicazione di uno scalare per un vettore ---------------------------------------------------function "*"(lambda : ELEMENT; v : VETTORE_3D) return VETTORE_3D is begin return (lambda * v(1), lambda * v(2), lambda * v(3)); end "*";

---------------------------------------------- Divisione di un vettore per uno scalare -Liceo cantonale di Mendrisio, 2002 Claudio Marsan

236

CAPITOLO 7. GENERICIT IN ADA 95 --------------------------------------------function "/"(v : VETTORE_3D; lambda : ELEMENT) return VETTORE_3D is begin return (v(1)/lambda, v(2)/lambda, v(3)/lambda); end "/"; ---------------------- Vettore opposto ---------------------function Vettore_Opposto(v : VETTORE_3D) return VETTORE_3D is begin return (-v(1), -v(2), -v(3)); end Vettore_Opposto;

-------------------------------------- Prodotto scalare di due vettori -------------------------------------function Prodotto_Scalare(v, w : VETTORE_3D) return ELEMENT is begin return (v(1)*w(1) + v(2)*w(2) + v(3)*w(3)); end Prodotto_Scalare;

----------------------------------------- Prodotto vettoriale di due vettori ----------------------------------------function Prodotto_Vettoriale(v, w : VETTORE_3D) return VETTORE_3D is begin return (v(2)*w(3) - v(3)*w(2), v(3)*w(1) - v(1)*w(3), v(1)*w(2) - v(2)*w(1)); end Prodotto_Vettoriale;

------------------------------------ Prodotto misto di tre vettori -----------------------------------function Prodotto_Misto(u, v, w : VETTORE_3D) return ELEMENT is begin return (u(1)*v(2)*w(3) + v(1)*w(2)*u(3) + w(1)*u(2)*v(3) w(1)*v(2)*u(3) - u(1)*w(2)*v(3) - v(1)*u(2)*w(3)); end Prodotto_Misto;

----------------------------------------------------------- Procedura per leggere un vettore nella forma [x,y,z] ----------------------------------------------------------procedure Get(v : out VETTORE_3D) is Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.4. UN PACKAGE PER I VETTORI DELLO SPAZIO

237

ch : CHARACTER; begin Ada.Text_IO.Get(Item Elementi_IO.Get(Item Ada.Text_IO.Get(Item Elementi_IO.Get(Item Ada.Text_IO.Get(Item Elementi_IO.Get(Item Ada.Text_IO.Get(Item end Get;

=> => => => => => =>

ch); v(1)); ch); v(2)); ch); v(3)); ch);

------------------------------------------------------------ Procedura per scrivere un vettore nella forma [x,y,z] -----------------------------------------------------------procedure Put(v : in VETTORE_3D; prima : in INTEGER := 0; dopo : in INTEGER := 6; esponente : in INTEGER := 0) is begin Ada.Text_IO.Put(Item => "["); Elementi_IO.Put(Item => v(1), Fore => prima, Aft => dopo, Exp => esponente); Ada.Text_IO.Put(Item => ","); Elementi_IO.Put(Item => v(2), Fore => prima, Aft => dopo, Exp => esponente); Ada.Text_IO.Put(Item => ","); Elementi_IO.Put(Item => v(3), Fore => prima, Aft => dopo, Exp => esponente); Ada.Text_IO.Put(Item => "]"); end Put;

------------------------------------------------------------ Procedura per scrivere un vettore nella forma [x,y,z] --- e spostare in seguito il cursore in una nuova riga -----------------------------------------------------------procedure Put_Line(v : in VETTORE_3D; prima : in INTEGER := 0; dopo : in INTEGER := 6; esponente : in INTEGER := 0) is begin Put(v => v, prima => prima, dopo => dopo, esponente => esponente); Ada.Text_IO.New_Line; end Put_Line;

----------------------------------------------------------- Procedura per scrivere una variabile di tipo ELEMENT ----------------------------------------------------------procedure Put(x : in ELEMENT; prima : in INTEGER := 0; dopo : in INTEGER := 6; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

238

CAPITOLO 7. GENERICIT IN ADA 95 esponente : in INTEGER := 0) is begin Elementi_IO.Put(Item => x, Fore => prima, Aft => dopo, Exp => esponente); end Put;

----------------------------------------------------------- Procedura per scrivere una variabile di tipo ELEMENT --- e spostare in seguito il cursore in una nuova riga ----------------------------------------------------------procedure Put_Line(x : in ELEMENT; prima : in INTEGER := 0; dopo : in INTEGER := 6; esponente : in INTEGER := 0) is begin Elementi_IO.Put(Item => x, Fore => prima, Aft => dopo, Exp => esponente); Ada.Text_IO.New_Line; end Put_Line; end Vettori3D; ed inne un programma di test: ------Nome del file: TEST_VETTORI3D.ADB Autore: Claudio Marsan Data dellultima modifica: 8 giugno 2002 Scopo: programma di test per il package per la manipolazione di vettori a 3 componenti Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Vettori3D;

procedure Test_Vettori3D is type FLOAT4 is digits 4; -- nuovo tipo di FLOAT package Float4_IO is new Ada.Text_IO.Float_IO(FLOAT4); package V3D_Float4 is new Vettori3D(ELEMENT => FLOAT4); use V3D_Float4; -- serve per poter usare i simboli di operazione a, b, c : V3D_Float4.VETTORE_3D; k : FLOAT4;

begin Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.4. UN PACKAGE PER I VETTORI DELLO SPAZIO Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il vettore nullo: "); V3D_Float4.Put_Line(v => V3D_Float4.Vettore_Nullo); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "I versori degli assi cartesiani: "); V3D_Float4.Put(v => V3D_Float4.Versore_asse_x, dopo => 0); Ada.Text_IO.Put(Item => " "); V3D_Float4.Put(v => V3D_Float4.Versore_asse_y, dopo => 0); Ada.Text_IO.Put(Item => " "); V3D_Float4.Put_Line(v => V3D_Float4.Versore_asse_z, dopo => 0); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Dare un vettore a: "); V3D_Float4.Get(v => a); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Dare un vettore b: "); V3D_Float4.Get(v => b); Ada.Text_IO.New_Line; Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "a + b = "); V3D_Float4.Put_Line(v => a + b); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "a - b = "); V3D_Float4.Put_Line(v => a - b); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Dare un numero reale k: "); Float4_IO.Get(Item => k); Ada.Text_IO.New_Line; Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "k * a = "); V3D_Float4.Put_Line(v => k * a); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "a/k = "); V3D_Float4.Put_Line(v => a/k); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Il vettore opposto di a: "); V3D_Float4.Put_Line(v => V3D_Float4.Vettore_Opposto(a)); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Il prodotto scalare di a e b: "); V3D_Float4.Put(x => V3D_Float4.Prodotto_Scalare(a, b), esponente => 0, prima => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Il prodotto vettoriale di a e b: "); V3D_Float4.Put_Line(v => V3D_Float4.Prodotto_Vettoriale(a, b)); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Dare un vettore c: "); V3D_Float4.Get(v => c); Ada.Text_IO.New_Line; Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Il prodotto misto di a, b e c: "); V3D_Float4.Put(x => V3D_Float4.Prodotto_Misto(a, b, c), esponente => 0, prima => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Skip_Line; Liceo cantonale di Mendrisio, 2002

239

Claudio Marsan

240 Ada.Text_IO.Put(Item => "Fine!"); end Test_Vettori3D;

CAPITOLO 7. GENERICIT IN ADA 95

Osservazione 7.4.1 Non purtroppo possibile implementare un tipo VETTORE_3D che consenta di manipolare contemporaneamente sia elementi di tipo intero che elementi di tipo in virgola mobile. Si potrebbe pensare di denire, nella specicazione, il tipo generico ELEMENT come segue: generic type ELEMENT is private; Purtroppo ci non funziona: basti pensare che per linput e loutput sono necessarie delle procedure che provengono da package diversi. Inoltre se il tipo generico ELEMENT privato, allora sono ammesse sue attualizzazioni anche con tipi come CHARACTER o con tipi composti deniti dal programmatore: non allora pi evidente il signicato di taluni operatori (per esempio quelli aritmetici). Lunica soluzione consiste dunque nellimplementare vari packages!

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 8 Strutture di dati dinamiche


I puntatori rappresentano un argomento tra i pi dicili, soprattutto per i programmatori neoti, a causa dei numerosi trabocchetti insiti nella loro manipolazione. In un suo libro su Ada 95 John Barnes ha scritto che: Manipolare i puntatori un po come giocare con il fuoco. Il fuoco senza dubbio molto importante per luomo. Impiegato con precauzione considerevolmente utile; ma che disastro se non pi controllabile! Il tipo accesso permette la dichiarazione di puntatori in Ada 95.

8.1

Introduzione

Per ogni oggetto ordinario, variabile o costante, possiamo assumere che c uno spazio nella memoria di lavoro del computer nel quale memorizzato il valore delloggetto. Potremmo dire che il nome delloggetto il nome dello spazio in memoria. Lo spazio in memoria viene creato quando loggetto viene dichiarato ed esiste no alla ne dellesecuzione dellunit di programma nel quale appare la sua dichiarazione. Un simile oggetto detto statico; esso non pu n essere creato n essere distrutto durante lesecuzione del programma. In talune applicazioni il numero di oggetti necessari non noto a priori. In questo caso sono necessarie delle strutture di dati dinamiche, che possono aumentare o diminuire di numero durante lesecuzione del programma; quindi necessario poter creare nuovi oggetti in fase di esecuzione del programma. Esempio 8.1.1 Un tipico esempio di struttura di dati dinamica una lista nella quale possono essere aggiunti e tolti elementi dinamicamente (lalternativa statica un array sucientemente grande, necessariamente sovradimensionato, con evidente spreco di memoria!). Altri esempi di strutture di dati dinamiche sono le code, gli alberi, i gra, . . . Le strutture di dati statiche hanno i seguenti vantaggi: realizzazione semplice; accesso, in generale, ecace dal punto di vista del tempoprocessore. Il loro principale inconveniente risiede nella loro natura statica: limpossibilit di aumentare o di diminuire di grandezza durante lesecuzione poich questa ssata durante la compilazione (si parla di allocazione statica della memoria, static memory allocation). 241

242

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

Lallocazione dinamica della memoria (dynamic memory allocation) permette di eliminare questa restrizione. Lidea semplice: consentire al programma di riservare una porzione di memoria quando ne ha bisogno. Ovviamente esiste anche una restrizione a questa tecnica: la quantit di memoria disponibile nel computer in uso; sar cos importante considerare la possibilit di liberare, restituire la memoria (memory deallocation) quando alcune parti diventano inutilizzate. Questa restituzione pu essere automatica tramite un garbage collector (letteralmente: ramazza spazzatura) associato al programma in esecuzione oppure pu essere prevista nel programma mediante alcune procedure. Possiamo dire che lallocazione dinamica della memoria una tecnica di basso livello, nel senso che il programmatore che ne fa uso deve lavorare con porzioni di memoria e comprendere che queste sono denite da un indirizzo e da una grandezza. Generalmente, nei linguaggi di programmazione la gestione degli indirizzi di memoria si eettua con i puntatori (pointer ), ossia con variabili che conterranno indirizzi di memoria. Osservazione 8.1.1 Ogni dichiarazione di variabili nella parte dichiarativa tradotta dal compilatore in codice che provoca, in fase di esecuzione, lallocazione di una parte di memoria sucientemente grande per contenere questa variabile. Questa regola valida anche per le variabili puntatore: in questo caso la parte di memoria allocata quella necessaria per contenere un indirizzo di memoria.

8.2

Dichiarazione di tipi puntatore

Un tipo puntatore in Ada 95 si dichiara per mezzo della parola riservata access, da cui deriva il termine tipo accesso per designare i tipi puntatore. Oltre alla parola riservata access bisogna ancora indicare a quale contenuto le variabili puntatore di questo tipo fanno riferimento; questo per permettere al compilatore le veriche (di tipo) classiche in caso di unassegnazione o di un passaggio di parametro. La forma pi semplice di una dichiarazione di tipo accesso : type NOME_TIPO_ACCESSO is access NOME_TIPO; dove: NOME_TIPO_ACCESSO il nome del tipo accesso denito; NOME_TIPO il nome del tipo (o del sottotipo) del contenuto delle porzioni di memoria a cui fanno riferimento le variabili del tipo NOME_TIPO_ACCESSO. Il tipo (o sottotipo) NOME_TIPO detto tipo puntato (o sottotipo puntato). Esempio 8.2.1 Con la dichiarazione type P_Integer is access INTEGER; si denisce un tipo puntatore sul tipo puntato INTEGER. Le variabili di questo tipo conterranno un indirizzo di una porzione di memoria prevista per un numero del tipo puntato INTEGER. Esempio 8.2.2 Con la dichiarazione type P_Float is access FLOAT; si denisce un tipo puntatore sul tipo puntato FLOAT. Le variabili di questo tipo conterranno un indirizzo di una porzione di memoria prevista per un numero del tipo puntato FLOAT. Esempio 8.2.3 Siano dati i tipi di dati Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.3. LALLOCATORE NEW type T_Vettore_9 is array(1..9) of INTEGER; type T_Articolo is record numero : INTEGER; tabella : T_Vettore_9; end record; Allora con le dichiarazioni type P_T_Vettore_9 is access T_Vettore_9; type P_T_Articolo is access T_Articolo;

243

si deniscono un tipo puntatore sul tipo puntato T_Vettore_9 e un tipo puntatore sul tipo puntato T_Articolo. Le variabili di questi tipi conterranno un indirizzo di una porzione di memoria prevista per un vettore del tipo puntato T_Vettore_9 e, rispettivamente, per un record del tipo puntato T_Articolo. Osservazione 8.2.1 La porzione di memoria a cui fa riferimento un puntatore sono dette variabili puntate. Esiste ununica costante puntatore, chiamata null che, assegnata ad una variabile puntatore, indica che questa variabile non fa riferimento ad alcuna variabile puntata. Una variabile puntatore si dichiara come le altre variabili ed ha la particolarit di essere inizializzata automaticamente al valore null. Le operazioni possibili con i puntatori sono: assegnazione; passaggio di parametri; uso dellallocatore new (vedi prossimo paragrafo); accesso alla variabile puntata (dereferenziazione). Le espressioni si limitano a: 1. costanti; 2. variabili; 3. attributi applicabili ai puntatori; 4. uso dellallocatore new.

8.3

Lallocatore new

La porzione di memoria necessaria per una variabile puntatore prevista, come per le variabile di un qualsiasi tipo, dal compilatore. Un allocatore di memoria (memory allocator ) responsabile, in fase di esecuzione del programma, della creazione di una variabile puntatore. In Ada 95 questo allocatore designato dalla parola riservata new; esso si usa come una funzione operatore e in due modi diversi: new NOME_TIPO; oppure new Espressione_Qualificata; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

244 dove:

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

NOME_TIPO indica il tipo (o il sottotipo) puntato della variabile puntata creata; Espressione_Qualificata indica non solamente il tipo (o il sottotipo) della variabile puntata creata ma assegna anche un valore iniziale secondo la sintassi seguente: new NOME_TIPO(valore_iniziale); Esempio 8.3.1 Ecco alcune allocazioni: new INTEGER (viene creata una variabile puntata di tipo INTEGER); new FLOAT (viene creata una variabile puntata di tipo FLOAT); new T_Vettore_9 (viene creata una variabile puntata di tipo T_Vettore_9); new T_Articolo (viene creata una variabile puntata di tipo T_Articolo); new INTEGER(10) (viene creata una variabile puntata di tipo INTEGER e il suo valore iniziale posto uguale a 10); new FLOAT(10.0) (viene creata una variabile puntata di tipo FLOAT e il suo valore iniziale posto uguale a 10.0); new T_Vettore_9(1..5 => 1, others => 0) (viene creata una variabile puntata di tipo T_Vettore_9 e le componenti della variabile sono inizializzate: le prime cinque a 1, le altre a 0); new T_Articolo(1, (others => 0)) (una variabile puntata di tipo T_Articolo viene creata e il suo valore iniziale posto uguale a (1, (others => 0))). Visto che lallocatore new utilizzato come una funzione, quale risultato restituisce? Il risultato comprende lindirizzo della variabile puntata creata poich bisogner poter accedere ad essa. A causa di ci questo indirizzo dovr essere sistemato in una variabile puntatore di tipo adeguato mediante unassegnazione. Cos facendo una variabile puntatore far riferimento o punter ad una variabile puntata. Esempio 8.3.2 Alcune assegnazioni del risultato dellallocatore new (si usano i tipi di dati deniti negli esempi precedenti): ... -- Dichiarazione dei puntatori ip : P_Integer; -- variabile puntatore ad un INTEGER fp : P_Float; -- variabile puntatore ad un FLOAT vp : P_T_Vettore_9; -- variabile puntatore ad un T_Vettore_9 ap : P_T_Articolo; -- variabile puntatore ad un T_Articolo ... -- Uso dellallocatore ip := new Integer; -- ip punta alla variabile puntata creata fp := new Float(10.0); -- fp punta alla variabile puntata creata -- di valore iniziale 10.0 vp := new T_Vettore_9; -- vp punta alla variabile puntata creata ap := new T_Articolo(1, (others => 0)); -- ap punta alla -- variabile puntata creata di valore -- iniziale (1, (others => 0)) ... Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.4. RAPPRESENTAZIONE SCHEMATICA DEI PUNTATORI

245

8.4

Rappresentazione schematica dei puntatori

Lesperienza mostra che il lavoro con i puntatori pi facile se si usano di schemi che illustrano la gestione dei puntatori e delle variabili puntate. Questi schemi sono composti da: rettangoli, che indicano le variabili; frecce, che sono simboli per gli indirizzi delle variabili puntate: linizio di una simile freccia indica dov registrato lindirizzo mentre la ne designa la variabile puntata che possiede questo indirizzo; il valore particolare null rappresentato da una linea obliqua. Esempio 8.4.1 Nella gura 8.1 si pu vedere la rappresentazione schematica delle dichiarazioni fatte nellesempio 8.3.2 mentre nella gura 8.2 si pu vedere la rappresentazione schematica delle assegnazioni fatte nellesempio 8.3.2. ip fp vp p

Figura 8.1: Dichiarazione di puntatori

Puntatori ip

Variabili puntate

fp 10.0

vp ? ? ? ? ? ? ? ? ?

ap 1 0 0 0 0 0 0 0 0 0

Figura 8.2: Assegnazione di puntatori

Esempio 8.4.2 La gura 8.3 rappresenta schematicamente la seguente istruzione: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

246 intP

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

Figura 8.3: Assegnazione di un valore iniziale

intP := new INTEGER(5);

Esempio 8.4.3 Consideriamo il tipo di dato seguente: type PERSONA is record nome : STRING(1 .. 20); altezza : INTEGER; -- in cm peso : FLOAT; -- in kg end record; La gura 8.4 rappresenta schematicamente la seguente istruzione: pPers := new PERSONA(nome => "Ezechiele Gelsomini ", altezza => 175, peso => 72.5); pPers Ezechiele Gelsomini 175 72.5

Figura 8.4: Assegnazione di un valore iniziale

8.5

Accesso alle variabili puntate

Laccesso ad una variabile puntata possibile solo per mezzo di una variabile puntatore che funge da intermediaria o, indirettamente, per mezzo di unaltra variabile puntata. Questo accesso pu essere fatto sulla totalit della variabile puntata oppure su una delle sue componenti se essa di tipo composto (per esempio un array o un record). Laccesso alla totalit della variabile puntata si eettua aggiungendo il susso all alla variabile puntatore che la individua, mentre laccesso ad una componente si fa usando la notazione abituale di accesso a questa componente, usando il nome della variabile puntatore come presso (seguita o non seguita da all). Ovviamente lespressione derivante dallaccesso del tipo della variabile puntata oppure della componente a cui si ha avuto accesso. Esempio 8.5.1 Consideriamo il seguente frammento di programma (i tipi di dati sono stati deniti in esempi precedenti): Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.5. ACCESSO ALLE VARIABILI PUNTATE ... ip : P_Integer; -- variabile puntatore ad un INTEGER fp : P_Float; -- variabile puntatore ad un FLOAT vp : P_T_Vettore_9; -- variabile puntatore ad un T_Vettore_9 ap : P_T_Articolo; -- variabile puntatore ad un T_Articolo N : INTEGER; ... begin ... ip := new Integer; -- ip punta alla variabile puntata creata fp := new Float(10.0); -- fp punta alla variabile puntata creata -- di valore iniziale 10.0 vp := new T_Vettore_9; -- vp punta alla variabile puntata creata ap := new T_Articolo(1, (others => 0)); -- ap punta alla -- variabile puntata creata di valore -- iniziale (1, (others => 0)) ... -- Accesso alle variabili puntate create dallallocatore ip.all := 5; -- 1 N := 3 * ip.all - 1; -- 2 Ada.Integer_Text_IO.Put(Item => ip.all); -- 3 vp.all := (1..4 => 0, 5|7|9 => 1, others => 2); -- 4 ... -- Accesso alle componenti vp(1) := 5; -- 5 vp.all(1) := 5; -- identica allistruzione sopra! for i in vp.allRANGE loop -- 6 Ada.Integer_Text_IO.Put(Item => vp(i)); end loop; ap.numero := 10; -- 7 ap.all.numero := 10; -- identica allistruzione sopra! ap.tabella := vp.all; -- 8 for i in ap.tabellaRANGE loop -- 9 Ada.Integer_Text_IO.Put(Item => ap.tabella(i)); end loop; ...

247

Per esercizio rappresentare schematicamente o spiegare cosa succede con le istruzioni commentate con i numeri da 1 a 9. Esempio 8.5.2 Consideriamo la variabile pPers denita nellesempio 8.4.3. Con essa possiamo accedere al nuovo oggetto che stato creato (pPers punta alloggetto) e modicare per esempio alcuni campi o fare altre operazioni utilizzando la notazione dellesempio seguente: pPers.peso := 75.0; Ada.Text_IO.Put(Item => pPers.nome); Se richiesto lintero oggetto a cui punta pPers si pu utilizzare la parola riservata all: pPers.all := ("Evaristo Bianchi ", 190, 98.0);

Anche gli array possono essere allocati dinamicamente. Un puntatore ad un array pu essere utilizzato come se fosse il nome di un array ordinario; non quindi necessario usare la parola riservata all per accedere alloggetto verso il quale punta il puntatore. Esempio 8.5.3 Consideriamo la denizione di tipo Liceo cantonale di Mendrisio, 2002 Claudio Marsan

248

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE type TABELLA is array (INTEGER range <>) of INTEGER; type TABELLA_POINTER is access TABELLA; tp1 := new TABELLA(1..10); tp2 := new TABELLA(7, 19, 68);

Allora la variabile tp1 punta ad un array non inizializzato con 10 componenti, mentre la variabile tp2 punta ad un array inizializzato con 3 componenti. Potremo poi scrivere, per esempio: tp1(5) := 94; for i in tp2RANGE loop Ada.Text_IO.Put(Item => tp2(i)); end loop; Esercizio 8.5.1 Scrivere un programma che somma due numeri interi dati da tastiera usando i puntatori.

8.6

Assegnazioni

Lassegnazione tra variabili di tipo puntatore si eettua come di consueto; la sola limitazione che la variabile puntatore e lespressione devono essere dello stesso tipo puntatore. Attenzione a non confondere lassegnazione di puntatori con lassegnazione di variabili puntate: lassegnazione di un puntatore sostituisce lindirizzo contenuto nella variabile assegnata mentre lassegnazione di una variabile puntata cambia il valore di questultima senza modicarne lindirizzo. Per listruzione di assegnazione tra variabili di tipo accesso p1 := p2; dove p1 e p2 sono dello stesso tipo notiamo che: il valore di accesso di p2 assegnato a p1; p1 e p2 puntano allo stesso oggetto; il valore delloggetto non toccato da questa istruzione. Esempio 8.6.1 Consideriamo il seguente frammento di programma: ... type P_Integer is access INTEGER; type P_Float is access FLOAT; ... -- Si definiscono tre variabili puntatore ip : P_Integer := new Integer(5); jp : P_Integer; fp : P_Float; -- 1 : situazione iniziale ... jp := ip; -- 2 ip.all := 1; -- 3 -- quanto vale "jp.all"? -- 4 jp := new Integer; -- 5 jp.all := ip.all; -- 6 fp := ip; -- 7 : errore di tipo fp.all := 0.0; -- 8 : solleva leccezione Constraint_Error ... Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.6. ASSEGNAZIONI

249

Nelle gure 8.5, 8.6, 8.7, 8.8 e 8.9 visualizzato ci che succede nelle righe numerate da 1 a 6.

Puntatori

Variabili puntate

ip 5

jp

fp

Figura 8.5: Riga 1: le tre dichiarazioni

Puntatori

Variabili puntate

ip 5

jp

Figura 8.6: Riga 2 Liceo cantonale di Mendrisio, 2002 Claudio Marsan

250 Puntatori

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE Variabili puntate

ip 1

jp

Figura 8.7: Riga 3 Variabili puntate

Puntatori jp

Figura 8.8: Riga 5 Variabili puntate

Puntatori jp

Figura 8.9: Riga 6 Listruzione in 7 non valida poich le due variabili puntatore sono di tipo diverso. Listruzione in 8 potrebbe essere valida ma solleverebbe uneccezione di tipo Constraint_Error poich il puntatore fp possiede il valore null e quindi non designa alcuna variabile puntata. Osservazione 8.6.1 Listruzione della riga 8 nellesempio 8.6.1 rivela che lassegnazione e, generalmente, ogni tentativo di modicare dei puntatori o delle variabili puntate cela delle trappole nelle quali cascano anche i programmatori pi esperti. Queste trappole sono tra le pi perde poich il compilatore non le scopre: esse si rivelano infatti solo in fase di esecuzione del codice. Tra le trappole classiche, le pi diuse sono: tentativo daccesso ad una variabile puntata quando questa non esiste (come nellistruzione della riga 8 nellesempio 8.6.1); la confusione tra assegnazione di puntatori e di variabili puntate (come nelle istruzioni delle righe 2 e 6 nellesempio 8.6.1 ); la creazione di variabili puntate inaccessibili. Un oggetto dinamico cessa di esistere appena lesecuzione abbandona la parte del programma nella quale il tipo accesso alloggetto stato dichiarato. Come gi detto i compilatori Ada 95 possono Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.6. ASSEGNAZIONI

251

riutilizzare automaticamente lo spazio di memoria cos rilasciato; cos il fatto di dichiarare la durata per la quale un oggetto dinamico pu esistere signica che non accadr mai che un puntatore possa puntare ad una parte di memoria che stata liberata (pu invece capitare, durante lesecuzione, che esistano degli oggetti dinamici verso i quali non punta alcun puntatore). Una variabile puntata diventa inaccessibile se non pi referenziata da unaltra variabile. Essa non sar allora pi utilizzabile dal programma e occuper inutilmente dello spazio in memoria. Questo pu provocare uno o pi errori di programmazione se linformazione contenuta nella variabile inaccessibile doveva ancora servire allapplicazione. Esempio 8.6.2 Consideriamo il seguente framento di programma: ... type P_Integer is access INTEGER; ... ip : P_Integer := new Integer(5); jp : P_Integer; -- 1 : situazione iniziale ... ip := jp; -- 2 Puntatori Variabili puntate

ip 5

jp

Figura 8.10: Situazione iniziale Puntatori Variabili puntate diventate inaccessibili

ip

5 jp

Figura 8.11: Variabile puntata inaccessibile Bisogna quindi evitare le trappole generate dai puntatori. Inoltre, in casi di comportamenti anomali durante lesecuzione di un programma, bisogna ricordarsi che tali trappole esistono! Liceo cantonale di Mendrisio, 2002 Claudio Marsan

252

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

In Ada 95 possibile avere puntatori a variabili ordinarie: bisogna dichiarare, con una sintassi particolare, quelli che sono chiamati tipi accesso generale (general access types). Esempio 8.6.3 Vogliamo dichiarare un tipo accesso generale che pu puntare a qualsiasi oggetto di tipo FLOAT. Ecco la dichiarazione del tipo puntatore: type FLOAT_POINTER is access all FLOAT; La parola riservata all stabilisce che FLOAT_POINTER un tipo accesso generale. Una variabile di tipo FLOAT_POINTER pu essere dichiarata nel modo usuale, per esempio: fp : FLOAT_POINTER; Se invece vogliamo poter puntare ad una particolare variabile questa deve essere resa accessibile aggiungendo la parola riservata aliased alla sua dichiarazione, per esempio: x : aliased FLOAT; Quindi, per fare in modo che un puntatore possa puntare ad una particolare variabile, dobbiamo utilizzare lattributo ACCESS, per esempio: fp := xACCESS; Quando si usa lattributo ACCESS il compilatore controlla che la variabile e il tipo accesso non siano dichiarati in modo tale che puntatori ad una variabile che cessa desistere possano continuare ad esistere (nel nostro esempio la variabile x cessa di esistere quando lesecuzione abbandona la parte di programma nella quale essa era stata dichiarata; per evitare di lasciare dei puntatori a x necessario che il tipo FLOAT_POINTER non sia noto al di fuori di questa parte di programma). Se, per un qualche motivo, questo controllo non desiderato, si pu usare lattributo UNCHECKED_ACCESS. Ovviamente un puntatore generale pu puntare anche ad un oggetto che stato allocato mediante lallocatore new. Un puntatore generale non pu invece puntare ad un oggetto costante poich questo potrebbe essere cambiato usando il puntatore. Esempio 8.6.4 Consideriamo le seguenti dichiarazioni: type FLOAT_POINTER is access all FLOAT; fp : FLOAT_POINTER; PI : constant FLOAT := 3.14; Allora la seguente istruzione proibita: fp : PIACCESS; Esiste una variante del tipo accesso generale che permette di puntare ad un oggetto costante: bisogna utilizzare la parola constant nella dichiarazione. Esempio 8.6.5 La seguente una dichiarazione di tipo accesso generale costante che permette di puntare a costanti di tipo FLOAT: type CONSTANT_FLOAT_POINTER is access constant FLOAT; Con un simile puntatore il valore di una costante non potr essere cambiato ma potr essere letto tramite un puntatore. Osservazione 8.6.2 Puntatori generali costanti possono puntare ad oggetti costanti e a oggetti non costanti. Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.7. LISTE CONCATENATE Esempio 8.6.6 Il seguente programma mostra luso di puntatori generali: -----Nome del file: PUNTATORI_GENERALI.ADB Autore: Claudio Marsan Data dellultima modifica: 9 giugno 2002 Scopo: uso dei puntatori generali Testato con: Gnat 3.13p su Windows 2000

253

with Ada.Text_IO, Ada.Float_Text_IO; procedure Puntatori_Generali is type FLOAT_POINTER is access all FLOAT; type CONSTANT_FLOAT_POINTER is access constant FLOAT; fp fpc x PI : : : : FLOAT_POINTER; CONSTANT_FLOAT_POINTER; aliased FLOAT := 9.55; aliased constant FLOAT := 3.14;

begin fp := xACCESS; Ada.Text_IO.Put(Item => "Il valore a cui punta fp: "); Ada.Float_Text_IO.Put(Item => fp.all, Fore => 0, Aft => 2, Exp => 0); Ada.Text_IO.New_Line; -- fp := PIACCESS; -- errore! fpc := PIACCESS; Ada.Text_IO.Put(Item => "Il valore a cui punta fpc: "); Ada.Float_Text_IO.Put(Item => fpc.all, Fore => 0, Aft => 2, Exp => 0); -- fpc.all := 6.28; end Puntatori_Generali; -- errore!

Ecco loutput del programma: Il valore a cui punta fp: 9.55 Il valore a cui punta fpc: 3.14

8.7

Liste concatenate

Una lista concatenata (in inglese: linked list) o, semplicemente, lista, una struttura dinamica con molte applicazioni in vari campi della programmazione. Esempio 8.7.1 Nella gura 8.12 illustrata una lista di 3 interi: LIST 4 7 2

Figura 8.12: Lista concatenata Liceo cantonale di Mendrisio, 2002 Claudio Marsan

254

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

Ogni elemento della lista contiene un valore (nel nostro caso un numero intero) e un puntatore al prossimo elemento della lista. Il primo elemento della lista detto testa (head ) ed puntato da un puntatore speciale (LIST nella gura). Notiamo che: relativamente facile aggiungere elementi nuovi nella lista, in qualunque posizione (spesso, nelle applicazioni, si aggiungono allinizio oppure alla ne della lista); non necessario conoscere, in fase di programmazione, la lunghezza della lista. ovviamente possibile costruire una lista in un programma usando gli array, ma il modo pi naturale quello di utilizzare oggetti dinamici e puntatori. Esempio 8.7.2 Vogliamo denire un tipo di dati adatto allesempio 8.7.1 che abbiamo trattato sopra, nel quale una lista composta da un valore intero e da un puntatore al prossimo elemento della lista. Potremmo dare la dichiarazione seguente: type LIST_ELEMENT is record prossimo : LINK; valore : INTEGER; end record; La prima parte della lista sar un collegamento al prossimo elemento nella lista, un puntatore. Ma il tipo LINK non ancora stato dichiarato. La dichiarazione sar: type LINK is access LIST_ELEMENT; A questo punto dobbiamo rispondere al seguente quesito: in che posizione deve essere sistemata la dichiarazione sopra? Se la sistemiamo dopo la dichiarazione del tipo LIST_ELEMENT c un problema poich il tipo LINK resta indenito quando il tipo LIST_ELEMENT viene dichiarato; daltra parte se sistemiamo la dichiarazione di LINK prima di quella di LIST_ELEMENT, allora sar LIST_ELEMENT a restare indenito quando LINK dichiarato. La soluzione consiste nel partire con una dichiarazione di tipo incompleta, nella quale si dice soltanto che LIST_ELEMENT un tipo: type LIST_ELEMENT; Quando stata fatta una dichiarazione di tipo incompleta il nome del tipo pu essere usato nella dichiarazione di altri tipi. In seguito, nel programma, dovr essere presente una dichiarazione di tipo completa; solo allora sar possibile dichiarare delle variabili. Le dichiarazioni necessarie per la nostra lista sono dunque: type LIST_ELEMENT; type LINK is access LIST_ELEMENT; type LIST_ELEMENT is record prossimo : LINK; valore : INTEGER; end record; Vediamo ora come una lista concatenata viene costruita partendo dalle dichiarazioni fatte sopra. Dapprima dichiariamo una variabile di tipo accesso: LIST : LINK; Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.7. LISTE CONCATENATE

255

Questa variabile assume automaticamente il valore null in fase di dichiarazione, cosa che descrive che la lista vuota. Possiamo creare un nuovo elemento e aggiungerlo alla lista: LIST := new LIST_ELEMENT; LIST punta ora ad un nuovo elemento della lista; mediante LIST.valore := 5; assegnamo il valore 5 al primo intero della lista. Automaticamente il valore di prossimo posto uguale a null quando lelemento della lista viene creato. La situazione attuale illustrata nella gura 8.13. LIST null 5

Figura 8.13: Costruzione della lista concatenata: primo passo Naturalmente era pi semplice inizializzare lelemento in fase di creazione con listruzione LIST := new LIST_ELEMENT(null, 5); Ammettiamo ora di voler aggiungere, allinizio della lista, un nuovo elemento che conterr il valore 3. Un modo di procedere consiste nel dichiarare una nuova variabile di tipo accesso: NEW_LIST : LINK; e di usare le istruzioni seguenti: NEW_LIST := new LIST_ELEMENT; NEW_LIST.prossimo := LIST; NEW_LIST.valore := 3; LIST := NEW_LIST; La situazione illustrata nella gura 8.14. LIST null 3 5

Figura 8.14: Costruzione della lista concatenata: secondo passo Era naturalmente possibile aggiungere un elemento alla lista con listruzione LIST := new LIST_ELEMENT(LIST, 3); Un modo ancora pi elegante per aggiungere elementi ad una lista quello di costruire una procedura, come la seguente: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

256

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE procedure Put_First(Item : in INTEGER; L : in out LINK) is begin L := new LIST_ELEMENT(L, Item); end Put_First;

Assumendo che allinizio la variabile LIST abbia valore null, allora possiamo costruire la lista mediante le istruzioni Put_First(Item => 5, L => LIST); Put_First(Item => 3, L => LIST); Se vogliamo far passare tutti gli elementi di una lista basta partire dal primo elemento e continuare ntanto che lultimo elemento sia stato raggiunto. Esempio 8.7.3 Il seguente frammento di programma permette di stampare una lista di interi: ... p := LIST; while p /= null loop Ada.Integer_Text_IO.Put(Item => p.valore); p := p.prossimo; end loop; ... Esempio 8.7.4 Consideriamo ora le seguenti dichiarazioni di tipi: type PERSON; type PERSON_LINK is access PERSON; subtype NAME_TYPE is STRING(1 .. 20); type PERSON is record prossimo : PERSON_LINK; nome : NAME_TYPE; altezza : INTEGER; peso : FLOAT; end record; Con queste dichiarazioni possiamo lavorare con un registro nel quale ogni elemento contiene informazioni a proposito di una data persona. Nel seguito costruiamo una funzione che cerca se una data persona nella lista: se la persona nella lista la funzione restituisce un puntatore al corrispondente elemento della lista, in caso contrario restituisce null. Come argomenti la funzione richiede il nome della persona e un puntatore alla testa della lista che deve essere analizzata. Ecco il codice: function Trova_Persona(richiesto : NAME_TYPE; L : PERSON_LINK) return PERSON_LINK is p : PERSON_LINK := L; begin while p /= null and then p.nome /= richiesto loop p := p.prossimo; end loop; return p; end Trova_Persona; Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.7. LISTE CONCATENATE

257

Nella funzione il puntatore p viene fatto scorrere no alla ne della lista (p ha il valore null) oppure ntanto che la persona richiesta stata trovata. Da notare che importante luso delloperatore booleano cortocircuitato and then nellespressione booleana dellistruzione while ... loop. Se la lista viene fatta passare completamente allora p avr il valore null; se il valore di p.name fosse valutato allora verrebbe causato un errore di runtime poich p non punta da nessuna parte. Come visto in precedenza facile inserire un nuovo elemento allinizio della lista. Se invece vogliamo inserire un nuovo elemento alla ne della lista dobbiamo attraversare la lista nch non raggiungiamo la sua ne. Esempio 8.7.5 La seguente procedura crea un nuovo elemento e lo sistema alla ne della lista (per la dichiarazione dei tipi vedi esempio 8.7.2): procedure Put_Last(Item : in INTEGER; list : in out LINK) is p1, p2 : LINK; begin if list = null then -- lista vuota, inserisci prima un nuovo elemento list := new LIST_ELEMENT(null, Item); else p1 := list; while p1 /= null loop p2 := p1; p1 := p1.prossimo; end loop; -- ora p2 punta allultimo elemento -- inseriamo il nuovo elemento dopo p2 p2.prossimo := new LIST_ELEMENT(null, Item); end if; end Put_Last; Se vogliamo inserire un nuovo elemento dopo un dato elemento della lista possiamo usare la seguente procedura: procedure Put_After(NewItem : in INTEGER; -- nuovo elemento Item : in INTEGER; list : in out LINK) is p1, p2 : LINK; begin p1 := list; while p1 /= null and then p1.valore /= Item loop p1 := p1.prossimo; end loop; if p1 /= null then p2 := new LIST_ELEMENT(p1.prossimo, NewItem); p1.prossimo := p2; end if; end Put_After; La procedura seguente serve invece per cancellare da una lista un certo elemento: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

258

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE procedure Delete(Item : in INTEGER; list : in out LINK) is p1, p2 : LINK; begin p2 := list; p1 := p2; while p1 /= null and then p1.valore /= Item loop p2 := p1; p1 := p1.prossimo; end loop; if p1 = list and p1 /= null then -- si elimina il primo elemento list := p1.prossimo; elsif p1 /= null then p2.prossimo := p1.prossimo; end if; end Delete;

La seguente funzione serve per stabilire se un certo elemento appartiene o meno ad una lista: function Find_Value(Item : INTEGER; list : LINK) return BOOLEAN is p : LINK; begin p := list; while p /= null and then p.valore /= Item loop p := p.prossimo; end loop; if p /= null then return TRUE; else return FALSE; end if; end Find_Value; Possiamo considerare una lista come un tipo di dato ricorsivo composto da due componenti, una testa (il valore del primo elemento della lista) e una coda (una lista contenente tutti gli elementi tranne la testa). La coda pu cos essere vista come una lista pi corta di un elemento rispetto alla lista originale. In precedenza abbiamo visto come scrivere semplicemente (mediante un loop) una procedura per stampare gli elementi di una lista, dal primo allultimo. Se volessimo stampare gli elementi della lista in ordine inverso sarebbe molto pi complicato. Usando la ricorsione questo problema pu per essere risolto elegantemente. Esempio 8.7.6 La seguente una procedura ricorsiva per la stampa degli elementi di una lista in ordine inverso: procedure Write_Reverse(list : in LINK) is begin if list /= null then Write_Reverse(list.prossimo); Ada.Integer_Text_IO.Put(list.valore); end if; end Write_Reverse; Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.8. ALBERI

259

Riprendiamo nuovamente il problema di aggiungere un elemento alla ne di una lista. Sfruttando la ricorsione si pu usare il seguente algoritmo: se la lista vuota allora il nuovo elemento lunico elemento della lista ed cos messo allinizio della lista, altrimenti il nuovo elemento deve essere messo alla ne della coda. Esempio 8.7.7 La seguente una procedura ricorsiva per aggiungere un elemento alla ne di una lista: procedure Put_Last(Item : in INTEGER; list : in out LINK) is begin if list = null then list := new LIST_ELEMENT(null, Item); else Put_Last(Item, list.prossimo); end if; end Put_Last; Esercizio 8.7.1 Scrivere un programma, gestito da un menu, che permetta di testare tutte le procedure e funzioni viste per la gestione delle liste concatenate.

8.8

Alberi

Gli alberi sono una delle strutture dinamiche non lineari pi importanti usate nella programmazione. Una struttura ad albero facilmente riconoscibile poich i vari elementi che compongono lalbero sono collegati da rami. Esempio 8.8.1 Gli alberi genealogici, i diagrammi ad albero nel calcolo delle probabilit, lorganigramma di unazienda, . . ., sono tipiche strutture ad albero.

8.8.1

Denizione

Un albero un insieme nito di uno o pi nodi, tale che: 1. esiste un nodo speciale, detto radice; 2. i nodi restanti sono divisi in n 0 insiemi disgiunti T1 , T2 , . . . , Tn , ognuno dei quali un sottoalbero della radice. Nodi che sono radici di alberi senza sottoalberi sono detti foglie o nodi terminali. Osservazione 8.8.1 La denizione di albero che abbiamo dato sopra una denizione ricorsiva. Esempio 8.8.2 Consideriamo lalbero della gura 8.15. Allora A la radice dellalbero, mentre E, F, G, I e J sono le foglie dellalbero.

8.8.2

Alberi binari

Un albero si dice albero binario se da ogni suo nodo partono al massimo due rami (se ce ne sono due, si parler di ramo sinistro e ramo destro). Esempio 8.8.3 Non possibile scambiare il ramo destro e il ramo sinistro di un albero binario. Gli alberi binari della gura 8.16 sono cos da considerare diversi. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

260

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

Figura 8.15: Albero

Figura 8.16: Alberi binari diversi Esempio 8.8.4 Lalbero della gura 8.17 un albero binario. Lattraversamento di un albero pu essere denito in modo ricorsivo; ad ogni nodo si possono fare tre cose: 1. visitare il nodo (ossia: applicare una certa operazione al nodo); 2. attraversare il sottoalbero sinistro; 3. attraversare il sottoalbero destro. Queste operazioni possono essere svolte in 3! = 6 modi diversi. Convenendo che il sottoalbero sinistro debba essere attraversato prima del sottoalbero destro tali modi si riducono a tre: Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.8. ALBERI

261

Figura 8.17: Albero binario modo prex (preordine): nodo, sottoalbero sinistro, sottoalbero destro; modo inx (inordine): sottoalbero sinistro, nodo, sottoalbero destro; modo postx (postordine): sottoalbero sinistro, sottoalbero destro, nodo. Esercizio 8.8.1 Attraversare lalbero dellesempio 8.8.4 nei tre modi. Osservazione 8.8.2 Le espressioni aritmetiche possono essere ridotte ad alberi binari da attraversare in modo inx; nelle calcolatrici HewlettPackard con notazione polacca inversa le stesse espressioni possono essere ridotte ad alberi binari da attraversare in modo postx; nel linguaggio di programmazione Lisp le stesse espressioni possono essere ridotte ad alberi binari da attraversare in modo prex.

8.8.3

Ordinamento con albero binario

Vediamo ora come ordinare, mediante un albero binario, una lista di numeri interi introdotti da tastiera dallutente. Deniamo dapprima i tipi di dati necessari: type TREE_ELEMENT; type TREE is access TREE_ELEMENT; type TREE_ELEMENT is record valore : INTEGER; sinistro : TREE; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

262 destro : TREE; end record;

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

Sono dunque deniti due puntatori in ogni record di tipo TREE_ELEMENT. La prima procedura che trattiamo serve per inserire un nuovo elemento nellalbero: se il nuovo elemento che vogliamo inserire minore del primo elemento dellalbero esso verr sistemato nel ramo sinistro dellalbero, altrimenti nel ramo destro. La procedura che presentiamo ricorsiva: procedure Insert (Item : in INTEGER; t : in out TREE) is begin if t = null then t := new TREE_ELEMENT(Item, null, null); -- si crea un nuovo nodo dellalbero elsif Item < t.valore then Insert(Item, t.sinistro); -- si va a sinistra else Insert(Item, t.destro); -- si va a destra end if; end Insert; La seguente procedura, sempre ricorsiva, visualizza invece il contenuto dellalbero binario: procedure Put_Tree(t : in TREE) is begin if t /= null then Put_Tree(t.sinistro); Ada.Integer_Text_IO.Put(Item => t.valore); Ada.Text_IO.New_Line; Put_Tree(t.destro); end if; end Put_Tree; Da notare che si attraversa lalbero in modo inx, ossia prima si attraversa il ramo sinistro, poi si stampa il valore, poi si attraversa il ramo destro (tenendo naturalmente sempre conto della ricorsione). Ecco un programma completo di test: -----Nome del file: ALBERO_BINARIO.ADB Autore: Claudio Marsan Data dellultima modifica: 9 giugno 2002 Scopo: ordinamento con albero binario Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO; procedure Albero_Binario is type TREE_ELEMENT; type TREE is access TREE_ELEMENT; type TREE_ELEMENT is record valore : INTEGER; Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.8. ALBERI sinistro : TREE; destro : TREE; end record; albero : TREE; n : INTEGER; procedure Insert (Item : in INTEGER; t : in out TREE) is begin if t = null then t := new TREE_ELEMENT(Item, null, null); -- si crea un nuovo nodo dellalbero elsif Item < t.valore then Insert(Item, t.sinistro); -- si va a sinistra else Insert(Item, t.destro); -- si va a destra end if; end Insert;

263

procedure Put_Tree(t : in TREE) is begin if t /= null then Put_Tree(t.sinistro); Ada.Integer_Text_IO.Put(Item => t.valore); Ada.Text_IO.New_Line; Put_Tree(t.destro); end if; end Put_Tree; begin Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Ordinamento di interi "); Ada.Text_IO.Put(Item => "tramite albero binario"); Ada.Text_IO.New_Line(Spacing => 2); loop Ada.Text_IO.Put(Item => "Dare un intero (Ctrl-Z per terminare): "); exit when Ada.Text_IO.End_Of_File; Ada.Integer_Text_IO.Get(Item => n); Insert(Item => n, t => albero); end loop; Ada.Text_IO.New_Line(Spacing => 3); Ada.Text_IO.Put(Item => "Ecco gli interi in ordine crescente:"); Ada.Text_IO.New_Line(Spacing => 2); Put_Tree(t => albero); end Albero_Binario; Ecco un esempio di output del programma:

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

264

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

Ordinamento di interi tramite albero binario Dare Dare Dare Dare Dare Dare Dare Dare un un un un un un un un intero intero intero intero intero intero intero intero (Ctrl-Z (Ctrl-Z (Ctrl-Z (Ctrl-Z (Ctrl-Z (Ctrl-Z (Ctrl-Z (Ctrl-Z per per per per per per per per terminare): terminare): terminare): terminare): terminare): terminare): terminare): terminare): 5 9 3 7 2 1 4

Ecco gli interi in ordine crescente: 1 2 3 4 5 7 9 Esercizio 8.8.2 Visualizzare gracamente lalbero binario, se i dati di input sono: 9, 5, 6, 12, 4, 2, 5, 1, 23, 15, 17.

8.9

Pile di interi dinamiche

Nei capitoli precedenti abbiamo studiato a pi riprese la struttura di dati detta stack o pila o coda di tipo FILO (vedi paragra 6.9, 6.10, 7.1). Con gli stacks possiamo compiere le seguenti operazioni: Push: inserire un nuovo elemento in cima allo stack; Pop: eliminare lelemento in cima allo stack; Peek: leggere lelemento in cima allo stack; Clear: svuotare lo stack; Display: visualizzare gli elementi dello stack; Find: trovare un elemento nello stack. Vogliamo ora implementare queste operazioni per il tipo privato STACK che ha la struttura seguente: type NODE; type STACK is access NODE; type NODE is record valore : INTEGER; prossimo : STACK; end record; Esercizio 8.9.1 Scrivere un package per la gestione degli stacks usando la struttura privata denita sopra; scrivere inoltre un programma di test.

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 9 Alcuni metodi di ordinamento


In questo capitolo vogliamo vedere brevemente alcuni algoritmi per ordinare una lista di dati. Per semplicit tratteremo unicamente il caso dellordinamento di un vettore di numeri interi deniti dal tipo di dato seguente: TYPE VETTORE is array(INTEGER range <>) of INTEGER; Ordineremo la lista di interi in ordine crescente (ossia dal pi piccolo al pi grande).

9.1

Ordinamento per selezione

Lordinamento per selezione (selection sort) forse il metodo pi semplice per ordinare una lista di n interi x1 , x2 , . . . , xn . Esso funziona nel modo seguente: 1. Si cerca il pi piccolo elemento della lista e lo si scambia (se necessario) con il primo elemento della lista. La nuova lista sar ora y1 , y2 , . . . , yn , con y1 yk per k = 2, 3, . . . , n. 2. Si considera ora la sottolista y2 , y3 , . . . , yn . Si cerca il pi piccolo elemento della sottolista e lo si scambia (se necessario) con il primo elemento della sottolista. Avremo cos una nuova lista y1 , z2 , z3 , z4 , . . . , zn con y1 z2 zk per k = 3, 4, . . . , n. 3. Questo procedimento va poi ripetuto nch la lista risulta ordinata (in totale al massimo n volte). Esempio 9.1.1 Consideriamo la lista 3 5 2 1 4.

Ecco come procedere mediante lordinamento per selezione: Si cerca il pi piccolo elemento della lista e lo si scambia con il primo; otterremo: 1 5 2 3 4.

Si cerca il pi piccolo elemento della sottolista 5 e lo si scambia con il primo; otterremo: 2 5 265 3 4. 2 3 4

266

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO Tornando alla lista completa avremo i primi due elementi gi ordinati: 1 2 5 3 4.

Si cerca il pi piccolo elemento della sottolista 5 e lo si scambia con il primo; otterremo: 3 5 4. 3 4

Tornando alla lista completa avremo i primi tre elementi gi ordinati: 1 2 3 5 4.

necessario ancora uno scambio per terminare lordinamento della lista. Esercizio 9.1.1 La lista di interi 10 29 23 30 72 9 6

da ordinare usando lordinamento per selezione. Esempio 9.1.2 Quella che segue una possibile implementazione in Ada 95 del metodo di ordinamento per selezione: procedure Selection_Sort(v_input : in VETTORE; v_output : out VETTORE) is -------------------------------- Ordinamento per selezione -------------------------------v_temp : VETTORE := v_input; p : INTEGER; begin for i in 1..v_inputLAST-1 loop p := i; for j in i+1..v_inputLAST loop if v_temp(j) < v_temp(p) then p := j; end if; end loop; Swap(v_temp(p), v_temp(i)); end loop; v_output := v_temp; end Selection_Sort; Da notare che la procedura Swap scambia il valore di due interi e che il tipo VETTORE denito come allinizio del capitolo. Claudio Marsan Liceo cantonale di Mendrisio, 2002

9.2. ORDINAMENTO A BOLLE

267

9.2

Ordinamento a bolle

Lordinamento a bolle (bubble sort) o ordinamento per scambio (exchance sort) funziona nel modo seguente (ordinamento di una lista di n interi): 1. si considerano i primi due elementi della lista da ordianre: se il primo maggiore del secondo si scambia la loro posizione; 2. si considerano ora il secondo e il terzo elemento della lista: se il secondo maggiore del terzo si scambia la loro posizione; 3. dopo n 1 passaggi (al massimo) saremo sicuri di avere il pi grande elemento della lista nellultima posizione della lista; 4. si ripete ora lo stesso procedimento per i primi n 1 elementi della lista: alla ne avremo i due pi grandi elementi della lista in fondo alla lista; 5. . . . Esempio 9.2.1 Consideriamo la lista 3 5 2 1 4.

Ecco come procedere mediante lordinamento a bolle: Confrontiamo il 3 e il 5; otterremo la lista 3 5 2 1 4;

confrontiamo il 5 e il 2; otterremo la lista 3 2 5 1 4;

confrontiamo il 5 e l1; otterremo la lista 3 2 1 5 4;

confrontiamo il 5 e il 4; otterremo la lista 3 2 1 4 5;

a questo punto il 5 lelemento pi grande della lista. Confrontiamo il 3 e il 2; otterremo la lista 2 ... Esercizio 9.2.1 La lista di interi 10 29 23 30 72 9 6 3 1 4 5;

da ordinare usando lordinamento a bolle. Esempio 9.2.2 Quella che segue una possibile implementazione in Ada 95 del metodo di ordinamento a bolle: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

268

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO procedure Bubble_Sort(v_input : in VETTORE; v_output : out VETTORE) is -------------------------- Ordinamento a bolle -------------------------v_temp : VETTORE := v_input; begin for i in reverse 2..v_inputLAST loop for j in 1..i-1 loop if v_temp(j) > v_temp(j + 1) then Swap(v_temp(j), v_temp(j + 1)); end if; end loop; end loop; v_output := v_temp; end Bubble_Sort;

Da notare che la procedura Swap scambia il valore di due interi e che il tipo VETTORE denito come allinizio del capitolo. Lordinamento a bolle si pu migliorare aggiungendo una variabile booleana che controlla se ci sono stati scambi in un passaggio (si parla di improved bubble sort). Esempio 9.2.3 Quella che segue una possibile implementazione in Ada 95 del metodo improved bubble sort: procedure Improved_Bubble_Sort(v_input : in VETTORE; v_output : out VETTORE) is ------------------------------------- Ordinamento a bolle migliorato ------------------------------------v_temp : VETTORE := v_input; i : INTEGER := v_inputLAST; bubble : BOOLEAN := TRUE; begin while (i >= 1) and bubble loop bubble := FALSE; for j in 1..i-1 loop if v_temp(j) > v_temp(j + 1) then Swap(v_temp(j), v_temp(j + 1)); bubble := TRUE; end if; end loop; if i > 1 then i := i - 1; end if; end loop; v_output := v_temp; end Improved_Bubble_Sort; Claudio Marsan Liceo cantonale di Mendrisio, 2002

9.3. ORDINAMENTO PER INSERZIONE

269

Da notare che la procedura Swap scambia il valore di due interi e che il tipo VETTORE denito come allinizio del capitolo.

9.3

Ordinamento per inserzione

Lordinamento per inserzione (insertion sort) ricorda il metodo adottato dai giocatori di carte per ordinare le carte man mano che le ricevono. Esempio 9.3.1 Consideriamo la lista 3 5 2 1 4.

Ecco come procedere mediante lordinamento per inserzione: 1. Si prendono i primi due elementi della lista e si ordinano; si ottiene la lista 3 5 2 1 4.

2. Si considera il terzo elemento della lista e lo si inserisce nella posizione corretta rispetto ai primi due elementi della lista; si ottiene: 2 3 5 1 4.

3. Si considera il quarto elemento della lista e lo si inserisce nella posizione corretta rispetto ai primi tre elementi della lista; si ottiene: 1 2 3 5 4.

4. Si considera il quinto elemento della lista e lo si inserisce nella posizione corretta rispetto ai primi quattro elementi della lista; si ottiene: 1 la lista ora ordinata. Esercizio 9.3.1 La lista di interi 10 29 23 30 72 9 6 2 3 4 5;

da ordinare usando lordinamento per inserzione. Esempio 9.3.2 Quella che segue una possibile implementazione in Ada 95 del metodo di ordinamento per inserzione: procedure Insertion_Sort(v_input : in VETTORE; v_output : out VETTORE) is ---------------------------------- Ordinamento per inserimento ---------------------------------v_temp : VETTORE := v_input; found : BOOLEAN; j, p : INTEGER; begin Liceo cantonale di Mendrisio, 2002 Claudio Marsan

270

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO for i in 2..v_inputLAST loop p := v_temp(i); j := i; found := FALSE; while (j > 1) and not found loop if v_temp(j - 1) < p then found := TRUE; else v_temp(j) := v_temp(j - 1); j := j - 1; end if; end loop; v_temp(j) := p; end loop; v_output := v_temp; end Insertion_Sort;

Da notare che il tipo VETTORE denito come allinizio del capitolo.

9.4

Quick sort

Lalgoritmo noto con il nome quick sort ritenuto uno dei pi veloci ed di tipo ricorsivo. Ecco come funziona: 1. Se la lista non ha elementi o ne ha uno solo, allora la lista ordinata. Altrimenti eseguire i passi che seguono. 2. Scegliere un elemento k qualsiasi della lista. 3. Spostare gli elementi della lista in modo tale da formare due gruppi. k deve separare i due gruppi nel senso seguente: tutti gli elementi minori o ugali a k devono stare nel gruppo a sinistra di k, gli altri in quello a destra. 4. Ordinare il gruppo sinistro con questo algoritmo. 5. Ordinare il gruppo destro con questo algoritmo. Esempio 9.4.1 Quella che segue una possibile implementazione in Ada 95 del metodo bubble sort. Dapprima la procedura ausiliaria Split che serve per formare i due gruppi di cui si parlava sopra: procedure Split(v_input : in out VETTORE; first, last : in INTEGER; middle : in out INTEGER) is left, right, x : INTEGER; begin x := v_input(last); left := first; right := last; while left < right loop while (left < right) and (v_input(left) <= x) loop left := left + 1; end loop; if left < right then v_input(right) := v_input(left); Claudio Marsan Liceo cantonale di Mendrisio, 2002

9.5. EFFICIENZA right := right - 1; end if; while (left < right) and (x <= v_input(right)) loop right := right - 1; end loop; if left < right then v_input(left) := v_input(right); left := left + 1; end if; end loop; middle := right; v_input(middle) := x; end Split; e quindi la procedura ricorsiva di ordinamento: procedure Quick_Sort(v_input : in VETTORE; v_output : out VETTORE) is ----------------- Quick sort ----------------procedure Aux_Quick_Sort (x : in out VETTORE; first, last : in INTEGER) is middle : INTEGER; begin if first < last then Split(x, first, last, middle); Aux_Quick_Sort(x, first, middle - 1); Aux_Quick_Sort(x, middle + 1, last); end if; end Aux_Quick_Sort; v_temp : VETTORE := v_input; begin Aux_Quick_Sort(v_temp, 1, v_inputLAST); v_output := v_temp; end Quick_Sort; Esercizio 9.4.1 Ordinare la lista 17 36 22 41 12 24

271

mediante il metodo quick sort. Provare poi a seguire passo a passo lalgoritmo presentato sopra.

9.5

Ecienza

Lecienza di un algoritmo di ordinamento dipende dal numero di scambi e dal numero di confronti fra elementi della lista da ordinare. Qui ci accontentiamo unicamente di mostrare, con un programma di test, che lecienza dipende anche dalla quantit di elementi da ordinare e dallo stato iniziale degli elementi. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

272

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO

Esempio 9.5.1 Per chiarezza le procedure di ordinamento sono state scritte in un package. Ecco linterfaccia del package: -----Nome del file: SORT.ADS Autore: Claudio Marsan Data dellultima modifica: 9 giugno 2002 Scopo: interfaccia per alcuni algoritmi di ordinamento Testato con: Gnat 3.13p su Windows 2000

package Sort is TYPE VETTORE is array(INTEGER range <>) of INTEGER; procedure Selection_Sort(v_input : in VETTORE; v_output : out VETTORE); procedure Bubble_Sort(v_input : in VETTORE; v_output : out VETTORE); procedure Improved_Bubble_Sort(v_input : in VETTORE; v_output : out VETTORE); procedure Insertion_Sort(v_input : in VETTORE; v_output : out VETTORE); procedure Quick_Sort(v_input : in VETTORE; v_output : out VETTORE); end Sort; e la sua implementazione: -----Nome del file: SORT.ADB Autore: Claudio Marsan Data dellultima modifica: 9 giugno 2002 Scopo: implementazione di alcuni algoritmi di ordinamento Testato con: Gnat 3.13p su Windows 2000

package body Sort is

procedure Swap(a, b : in out INTEGER) is ----------------------------------------------- Scambia i valori di due variabili intere ----------------------------------------------c : INTEGER := a; begin a := b; b := c; end Swap;

procedure Selection_Sort(v_input : in VETTORE; v_output : out VETTORE) is

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

9.5. EFFICIENZA -------------------------------- Ordinamento per selezione -------------------------------v_temp : VETTORE := v_input; p : INTEGER; begin for i in 1..v_inputLAST-1 loop p := i; for j in i+1..v_inputLAST loop if v_temp(j) < v_temp(p) then p := j; end if; end loop; Swap(v_temp(p), v_temp(i)); end loop; v_output := v_temp; end Selection_Sort;

273

procedure Bubble_Sort(v_input : in VETTORE; v_output : out VETTORE) is -------------------------- Ordinamento a bolle -------------------------v_temp : VETTORE := v_input; begin for i in reverse 2..v_inputLAST loop for j in 1..i-1 loop if v_temp(j) > v_temp(j + 1) then Swap(v_temp(j), v_temp(j + 1)); end if; end loop; end loop; v_output := v_temp; end Bubble_Sort;

procedure Improved_Bubble_Sort(v_input : in VETTORE; v_output : out VETTORE) is ------------------------------------- Ordinamento a bolle migliorato ------------------------------------v_temp : VETTORE := v_input; i : INTEGER := v_inputLAST; bubble : BOOLEAN := TRUE; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

274

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO

begin while (i >= 1) and bubble loop bubble := FALSE; for j in 1..i-1 loop if v_temp(j) > v_temp(j + 1) then Swap(v_temp(j), v_temp(j + 1)); bubble := TRUE; end if; end loop; if i > 1 then i := i - 1; end if; end loop; v_output := v_temp; end Improved_Bubble_Sort;

procedure Insertion_Sort(v_input : in VETTORE; v_output : out VETTORE) is ---------------------------------- Ordinamento per inserimento ---------------------------------v_temp : VETTORE := v_input; found : BOOLEAN; j, p : INTEGER; begin for i in 2..v_inputLAST loop p := v_temp(i); j := i; found := FALSE; while (j > 1) and not found loop if v_temp(j - 1) < p then found := TRUE; else v_temp(j) := v_temp(j - 1); j := j - 1; end if; end loop; v_temp(j) := p; end loop; v_output := v_temp; end Insertion_Sort;

procedure Split(v_input : in out VETTORE; first, last : in INTEGER; middle : in out INTEGER) is left, right, x : INTEGER; Claudio Marsan Liceo cantonale di Mendrisio, 2002

9.5. EFFICIENZA begin x := v_input(last); left := first; right := last; while left < right loop while (left < right) and (v_input(left) <= x) loop left := left + 1; end loop; if left < right then v_input(right) := v_input(left); right := right - 1; end if; while (left < right) and (x <= v_input(right)) loop right := right - 1; end loop; if left < right then v_input(left) := v_input(right); left := left + 1; end if; end loop; middle := right; v_input(middle) := x; end Split;

275

procedure Quick_Sort(v_input : in VETTORE; v_output : out VETTORE) is ----------------- Quick sort ----------------procedure Aux_Quick_Sort (x : in out VETTORE; first, last : in INTEGER) is middle : INTEGER; begin if first < last then Split(x, first, last, middle); Aux_Quick_Sort(x, first, middle - 1); Aux_Quick_Sort(x, middle + 1, last); end if; end Aux_Quick_Sort; v_temp : VETTORE := v_input; begin Aux_Quick_Sort(v_temp, 1, v_inputLAST); v_output := v_temp; end Quick_Sort; end Sort; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

276

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO

Per poter misurare la velocit desecuzione di una procedura dobbiamo far partire un cronometro prima dellesecuzione della procedura e arrestare lo stesso appena la procedura terminata. Nel package Ada.Calendar esiste la funzione Clock che restituisce lora e pu quindi essere usata per costruirsi un cronometro (vedi la documentazione del package). Ecco il programma di test -----Nome del file: TEST_SORT.ADB Autore: Claudio Marsan Data dellultima modifica: 9 giugno 2002 Scopo: programma di test per i metodi di ordinamento Testato con: Gnat 3.13p su Windows 2000

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Float_Text_IO, Ada.Numerics.Discrete_Random, Ada.Calendar, Sort; use Ada.Calendar;

procedure Test_Sort is package Random_Integer is new Ada.Numerics.Discrete_Random(INTEGER);

procedure Selection_Sort_Time(v : in Sort.VETTORE; dim : in INTEGER; ripetizioni : INTEGER) is tempo_trascorso : DURATION; prima, dopo : Ada.Calendar.TIME; x1, x : Sort.VETTORE(1..dim); begin x1 := v(1..dim); prima := Ada.Calendar.Clock; for j in 1..ripetizioni loop Sort.Selection_Sort(v_input => x1, v_output => x); end loop; dopo := Ada.Calendar.Clock; tempo_trascorso := dopo - prima; Ada.Float_Text_IO.Put(Item => FLOAT(tempo_trascorso), Fore => 7, Aft => 7, Exp => 0); end Selection_Sort_Time;

procedure Bubble_Sort_Time(v : in Sort.VETTORE; dim : in INTEGER; ripetizioni : INTEGER) is tempo_trascorso : DURATION; Claudio Marsan Liceo cantonale di Mendrisio, 2002

9.5. EFFICIENZA prima, dopo x1, x : Ada.Calendar.TIME; : Sort.VETTORE(1..dim);

277

begin x1 := v(1..dim); prima := Ada.Calendar.Clock; for j in 1..ripetizioni loop Sort.Bubble_Sort(v_input => x1, v_output => x); end loop; dopo := Ada.Calendar.Clock; tempo_trascorso := dopo - prima; Ada.Float_Text_IO.Put(Item => FLOAT(tempo_trascorso), Fore => 7, Aft => 7, Exp => 0); end Bubble_Sort_Time;

procedure Improved_Bubble_Sort_Time(v : in Sort.VETTORE; dim : in INTEGER; ripetizioni : INTEGER) is tempo_trascorso : DURATION; prima, dopo : Ada.Calendar.TIME; x1, x : Sort.VETTORE(1..dim); begin x1 := v(1..dim); prima := Ada.Calendar.Clock; for j in 1..ripetizioni loop Sort.Improved_Bubble_Sort(v_input => x1, v_output => x); end loop; dopo := Ada.Calendar.Clock; tempo_trascorso := dopo - prima; Ada.Float_Text_IO.Put(Item => FLOAT(tempo_trascorso), Fore => 7, Aft => 7, Exp => 0); end Improved_Bubble_Sort_Time;

procedure Insertion_Sort_Time(v : in Sort.VETTORE; dim : in INTEGER; ripetizioni : INTEGER) is tempo_trascorso : DURATION; prima, dopo : Ada.Calendar.TIME; x1, x : Sort.VETTORE(1..dim); begin x1 := v(1..dim); prima := Ada.Calendar.Clock; for j in 1..ripetizioni loop Sort.Insertion_Sort(v_input => x1, v_output => x); end loop; dopo := Ada.Calendar.Clock; tempo_trascorso := dopo - prima; Liceo cantonale di Mendrisio, 2002 Claudio Marsan

278

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO Ada.Float_Text_IO.Put(Item => FLOAT(tempo_trascorso), Fore => 7, Aft => 7, Exp => 0); end Insertion_Sort_Time;

procedure Quick_Sort_Time(v : in Sort.VETTORE; dim : in INTEGER; ripetizioni : INTEGER) is tempo_trascorso : DURATION; prima, dopo : Ada.Calendar.TIME; x1, x : Sort.VETTORE(1..dim); begin x1 := v(1..dim); prima := Ada.Calendar.Clock; for j in 1..ripetizioni loop Sort.Quick_Sort(v_input => x1, v_output => x); end loop; dopo := Ada.Calendar.Clock; tempo_trascorso := dopo - prima; Ada.Float_Text_IO.Put(Item => FLOAT(tempo_trascorso), Fore => 7, Aft => 7, Exp => 0); end Quick_Sort_Time;

G dim1 dim2 dim3 dim4 dim5 a1, a2, a3, a4, a5 b1, b2, b3, b4, b5 c1, c2, c3, c4, c5 d1, d2, d3, d4, d5 e1, e2, e3, e4, e5 r file_dei_risultati

: : : : : : : : : : : : :

Random_Integer.Generator; INTEGER := 10; INTEGER := 50; INTEGER := 100; INTEGER := 500; INTEGER := 1_000; Sort.VETTORE(1..dim1); Sort.VETTORE(1..dim2); Sort.VETTORE(1..dim3); Sort.VETTORE(1..dim4); Sort.VETTORE(1..dim5); INTEGER := 100; Ada.Text_IO.FILE_TYPE;

begin Ada.Text_IO.Create(File => file_dei_risultati, Name => "RISULTATI.TXT"); Ada.Text_IO.Set_Output(File => file_dei_risultati); -- (1) Lista con 10 elementi Ada.Text_IO.Put(Item => "(1) Lista con "); Ada.Integer_Text_IO.Put(Item => dim1, Width => 0); Ada.Text_IO.Put_Line(Item => " elementi"); Random_Integer.Reset(G);

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

9.5. EFFICIENZA for i in 1..dim1 loop a1(i) := i; a2(i) := dim1 - i; a3(i) := 1; if (i mod 2) = 0 then a4(i) := i; else a4(i) := -i; end if; a5(i) := Random_Integer.Random(G); end loop; Ada.Text_IO.Put(Item => "SEL:"); Selection_Sort_Time(v => a1, dim Selection_Sort_Time(v => a2, dim Selection_Sort_Time(v => a3, dim Selection_Sort_Time(v => a4, dim Selection_Sort_Time(v => a5, dim Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "BUB:"); Bubble_Sort_Time(v => a1, dim => Bubble_Sort_Time(v => a2, dim => Bubble_Sort_Time(v => a3, dim => Bubble_Sort_Time(v => a4, dim => Bubble_Sort_Time(v => a5, dim => Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "IBU:"); Improved_Bubble_Sort_Time(v => a1, Improved_Bubble_Sort_Time(v => a2, Improved_Bubble_Sort_Time(v => a3, Improved_Bubble_Sort_Time(v => a4, Improved_Bubble_Sort_Time(v => a5, Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "INS:"); Insertion_Sort_Time(v => a1, dim Insertion_Sort_Time(v => a2, dim Insertion_Sort_Time(v => a3, dim Insertion_Sort_Time(v => a4, dim Insertion_Sort_Time(v => a5, dim Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => => "QUI:"); a1, dim => dim1, a2, dim => dim1, a3, dim => dim1, a4, dim => dim1, a5, dim => dim1,

279

=> => => => =>

dim1, dim1, dim1, dim1, dim1,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

dim1, dim1, dim1, dim1, dim1,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

dim dim dim dim dim

=> => => => =>

dim1, dim1, dim1, dim1, dim1,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

=> => => => =>

dim1, dim1, dim1, dim1, dim1,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r); Claudio Marsan

Liceo cantonale di Mendrisio, 2002

280

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO

Ada.Text_IO.New_Line(Spacing => 2);

-- (2) Lista con 50 elementi Ada.Text_IO.Put(Item => "(2) Lista con "); Ada.Integer_Text_IO.Put(Item => dim2, Width => 0); Ada.Text_IO.Put_Line(Item => " elementi"); Random_Integer.Reset(G); for i in 1..dim2 loop b1(i) := i; b2(i) := dim2 - i; b3(i) := 1; if (i mod 2) = 0 then b4(i) := i; else b4(i) := -i; end if; b5(i) := Random_Integer.Random(G); end loop; Ada.Text_IO.Put(Item => "SEL:"); Selection_Sort_Time(v => b1, dim Selection_Sort_Time(v => b2, dim Selection_Sort_Time(v => b3, dim Selection_Sort_Time(v => b4, dim Selection_Sort_Time(v => b5, dim Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "BUB:"); Bubble_Sort_Time(v => b1, dim => Bubble_Sort_Time(v => b2, dim => Bubble_Sort_Time(v => b3, dim => Bubble_Sort_Time(v => b4, dim => Bubble_Sort_Time(v => b5, dim => Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "IBU:"); Improved_Bubble_Sort_Time(v => b1, Improved_Bubble_Sort_Time(v => b2, Improved_Bubble_Sort_Time(v => b3, Improved_Bubble_Sort_Time(v => b4, Improved_Bubble_Sort_Time(v => b5, Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "INS:"); Insertion_Sort_Time(v => b1, dim => dim2, ripetizioni => r); Insertion_Sort_Time(v => b2, dim => dim2, ripetizioni => r); Insertion_Sort_Time(v => b3, dim => dim2, ripetizioni => r); Claudio Marsan Liceo cantonale di Mendrisio, 2002

=> => => => =>

dim2, dim2, dim2, dim2, dim2,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

dim2, dim2, dim2, dim2, dim2,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

dim dim dim dim dim

=> => => => =>

dim2, dim2, dim2, dim2, dim2,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

9.5. EFFICIENZA Insertion_Sort_Time(v => b4, dim => dim2, ripetizioni => r); Insertion_Sort_Time(v => b5, dim => dim2, ripetizioni => r); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => => "QUI:"); b1, dim => dim2, b2, dim => dim2, b3, dim => dim2, b4, dim => dim2, b5, dim => dim2,

281

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

Ada.Text_IO.New_Line(Spacing => 2);

-- (3) Lista con 100 elementi Ada.Text_IO.Put(Item => "(3) Lista con "); Ada.Integer_Text_IO.Put(Item => dim3, Width => 0); Ada.Text_IO.Put_Line(Item => " elementi"); Random_Integer.Reset(G); for i in 1..dim3 loop c1(i) := i; c2(i) := dim3 - i; c3(i) := 1; if (i mod 2) = 0 then c4(i) := i; else c4(i) := -i; end if; c5(i) := Random_Integer.Random(G); end loop; Ada.Text_IO.Put(Item => "SEL:"); Selection_Sort_Time(v => c1, dim Selection_Sort_Time(v => c2, dim Selection_Sort_Time(v => c3, dim Selection_Sort_Time(v => c4, dim Selection_Sort_Time(v => c5, dim Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "BUB:"); Bubble_Sort_Time(v => c1, dim => Bubble_Sort_Time(v => c2, dim => Bubble_Sort_Time(v => c3, dim => Bubble_Sort_Time(v => c4, dim => Bubble_Sort_Time(v => c5, dim => Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "IBU:"); Improved_Bubble_Sort_Time(v => c1, dim => dim3, ripetizioni => r); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

=> => => => =>

dim3, dim3, dim3, dim3, dim3,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

dim3, dim3, dim3, dim3, dim3,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

282 Improved_Bubble_Sort_Time(v Improved_Bubble_Sort_Time(v Improved_Bubble_Sort_Time(v Improved_Bubble_Sort_Time(v Ada.Text_IO.New_Line;

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO => => => => c2, c3, c4, c5, dim dim dim dim => => => => dim3, dim3, dim3, dim3, ripetizioni ripetizioni ripetizioni ripetizioni => => => => r); r); r); r);

Ada.Text_IO.Put(Item => "INS:"); Insertion_Sort_Time(v => c1, dim Insertion_Sort_Time(v => c2, dim Insertion_Sort_Time(v => c3, dim Insertion_Sort_Time(v => c4, dim Insertion_Sort_Time(v => c5, dim Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v =>

=> => => => =>

dim3, dim3, dim3, dim3, dim3,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

=> "QUI:"); c1, dim => dim3, c2, dim => dim3, c3, dim => dim3, c4, dim => dim3, c5, dim => dim3,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

Ada.Text_IO.New_Line(Spacing => 2);

-- (4) Lista con 500 elementi Ada.Text_IO.Put(Item => "(4) Lista con "); Ada.Integer_Text_IO.Put(Item => dim4, Width => 0); Ada.Text_IO.Put_Line(Item => " elementi"); Random_Integer.Reset(G); for i in 1..dim4 loop d1(i) := i; d2(i) := dim4 - i; d3(i) := 1; if (i mod 2) = 0 then d4(i) := i; else d4(i) := -i; end if; d5(i) := Random_Integer.Random(G); end loop; Ada.Text_IO.Put(Item => "SEL:"); Selection_Sort_Time(v => d1, dim Selection_Sort_Time(v => d2, dim Selection_Sort_Time(v => d3, dim Selection_Sort_Time(v => d4, dim Selection_Sort_Time(v => d5, dim Ada.Text_IO.New_Line;

=> => => => =>

dim4, dim4, dim4, dim4, dim4,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

9.5. EFFICIENZA Ada.Text_IO.Put(Item => "BUB:"); Bubble_Sort_Time(v => d1, dim => Bubble_Sort_Time(v => d2, dim => Bubble_Sort_Time(v => d3, dim => Bubble_Sort_Time(v => d4, dim => Bubble_Sort_Time(v => d5, dim => Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "IBU:"); Improved_Bubble_Sort_Time(v => d1, Improved_Bubble_Sort_Time(v => d2, Improved_Bubble_Sort_Time(v => d3, Improved_Bubble_Sort_Time(v => d4, Improved_Bubble_Sort_Time(v => d5, Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "INS:"); Insertion_Sort_Time(v => d1, dim Insertion_Sort_Time(v => d2, dim Insertion_Sort_Time(v => d3, dim Insertion_Sort_Time(v => d4, dim Insertion_Sort_Time(v => d5, dim Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => => "QUI:"); d1, dim => dim4, d2, dim => dim4, d3, dim => dim4, d4, dim => dim4, d5, dim => dim4,

283

dim4, dim4, dim4, dim4, dim4,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

dim dim dim dim dim

=> => => => =>

dim4, dim4, dim4, dim4, dim4,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

=> => => => =>

dim4, dim4, dim4, dim4, dim4,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

Ada.Text_IO.New_Line(Spacing => 2);

-- (5) Lista con 10 elementi Ada.Text_IO.Put(Item => "(5) Lista con "); Ada.Integer_Text_IO.Put(Item => dim5, Width => 0); Ada.Text_IO.Put_Line(Item => " elementi"); Random_Integer.Reset(G); for i in 1..dim5 loop e1(i) := i; e2(i) := dim5 - i; e3(i) := 1; if (i mod 2) = 0 then e4(i) := i; else e4(i) := -i; end if; e5(i) := Random_Integer.Random(G); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

284 end loop;

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO

Ada.Text_IO.Put(Item => "SEL:"); Selection_Sort_Time(v => e1, dim Selection_Sort_Time(v => e2, dim Selection_Sort_Time(v => e3, dim Selection_Sort_Time(v => e4, dim Selection_Sort_Time(v => e5, dim Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "BUB:"); Bubble_Sort_Time(v => e1, dim => Bubble_Sort_Time(v => e2, dim => Bubble_Sort_Time(v => e3, dim => Bubble_Sort_Time(v => e4, dim => Bubble_Sort_Time(v => e5, dim => Ada.Text_IO.New_Line;

=> => => => =>

dim5, dim5, dim5, dim5, dim5,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

dim5, dim5, dim5, dim5, dim5,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

Ada.Text_IO.Put(Item => "IBU:"); Improved_Bubble_Sort_Time(v => e1, Improved_Bubble_Sort_Time(v => e2, Improved_Bubble_Sort_Time(v => e3, Improved_Bubble_Sort_Time(v => e4, Improved_Bubble_Sort_Time(v => e5, Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "INS:"); Insertion_Sort_Time(v => e1, dim Insertion_Sort_Time(v => e2, dim Insertion_Sort_Time(v => e3, dim Insertion_Sort_Time(v => e4, dim Insertion_Sort_Time(v => e5, dim Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v => Quick_Sort_Time(v =>

dim dim dim dim dim

=> => => => =>

dim5, dim5, dim5, dim5, dim5,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

=> => => => =>

dim5, dim5, dim5, dim5, dim5,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

=> "QUI:"); e1, dim => dim5, e2, dim => dim5, e3, dim => dim5, e4, dim => dim5, e5, dim => dim5,

ripetizioni ripetizioni ripetizioni ripetizioni ripetizioni

=> => => => =>

r); r); r); r); r);

Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Close(File => end Test_Sort; e un esempio di output (programma eseguito su un personal computer con processore Intel Pentium III a 733 MHz e 512 MB di RAM, sotto Windows 2000): (1) Lista con 10 elementi Claudio Marsan Liceo cantonale di Mendrisio, 2002 file_dei_risultati);

9.5. EFFICIENZA SEL: BUB: IBU: INS: QUI: 0.0004816 0.0004663 0.0001659 0.0002263 0.0006604 0.0006216 0.0011566 0.0012940 0.0010985 0.0006671 elementi 0.0078471 0.0291886 0.0322848 0.0258622 0.0089564 0.0004758 0.0004596 0.0001564 0.0010683 0.0006540 0.0005196 0.0007998 0.0008945 0.0006034 0.0004861 0.0005246 0.0007836 0.0008241 0.0005872 0.0004649

285

(2) Lista con 50 SEL: 0.0072928 BUB: 0.0101717 IBU: 0.0005383 INS: 0.0009700 QUI: 0.0095473

0.0072789 0.0101921 0.0005339 0.0246651 0.0094384

0.0077728 0.0206012 0.0222296 0.0128782 0.0037692

0.0079259 0.0219883 0.0229468 0.0138699 0.0033907

(3) Lista con 100 elementi SEL: 0.0288595 0.0295579 BUB: 0.0414148 0.1181298 IBU: 0.0009937 0.1234014 INS: 0.0018399 0.0963558 QUI: 0.0323077 0.0322644 (4) Lista con 500 elementi SEL: 0.6445233 0.6896809 BUB: 1.0153635 2.9504504 IBU: 0.0046967 3.0918632 INS: 0.0088140 2.4119148 QUI: 0.7384006 0.7433152 (5) Lista con 1000 elementi SEL: 2.5646710 2.7462809 BUB: 4.0576677 11.9022322 IBU: 0.0092573 12.3649406 INS: 0.0175735 9.6450586 QUI: 2.9308367 2.9302557 Da notare che:

0.0280589 0.0407747 0.0009890 0.0963067 0.0323418

0.0294828 0.0788969 0.0863110 0.0487707 0.0086838

0.0286430 0.0825650 0.0839274 0.0488509 0.0078552

0.6444705 1.0161297 0.0046539 2.4118488 0.7394926

0.6614243 1.9873992 2.0643501 1.2081052 0.0621775

0.6552783 2.1059475 2.1600103 1.2108022 0.0540108

2.5686097 4.0575728 0.0092467 9.6492891 2.9299533

2.6078508 8.2518663 8.2571678 4.8266063 0.1295788

2.5909073 8.6069269 8.7873230 4.9298496 0.1124805

la prima colonna si riferisce allordinamento della lista 1, 2, 3, . . . , n; la seconda colonna si riferisce allordinamento della lista n 1, n 2, n 3, . . . , 2, 1, 0; la terza colonna si riferisce allordinamento della lista 1, 1, 1, . . . , 1, 1; la quarta colonna si riferisce allordinamento della lista 1, 2, 3, 4, . . . , (n 1), n; la quinta colonna si riferisce allordinamento di una lista casuale di n elementi con n = 10, 50, 100, 500, 1000 e la ripetizione di 100 volte degli algoritmi.

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

286

CAPITOLO 9. ALCUNI METODI DI ORDINAMENTO

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

BIBLIOGRAFIA

[1] Ada 95 Rationale: The Language. The Standard Libraries. Intermetrics Inc., 1995. [2] Annotated Ada Reference Manual: Language and Standard Libraries. Intermetrics Inc., 1995. [3] Barnes John G. P. Programmare in Ada. Zanichelli, Bologna, 1984. [4] Berg J.M., Donzelle L.O., Olive V., Rouillard J. Ada avec le sourire. Presse Polytechniques Romandes, Lausanne, 1989. [5] Breguet Pierre, Zaffalon Luigi. Programmation squentiell avec Ada 95. Presse Polytechniques Romandes, Lausanne, 1999. [6] Burtch Ken O. The Big Online Book of Linux Ada Programming. http://www.vaxxine.com/pegasoft/homes/book.html, 2000. [7] Fayard Didier, Rousseau Martine. Vers Ada 95 par lexample. De Boeck & Larcier, Paris, 1999(2). [8] Feldmann Michael B., Koffman Elliot B. Ada 95 Problem Solving and Program Design. AddisonWesley,Reading, 1997(2). [9] Frosini Graziano, Lazzerini Beatrice. Ada: un linguaggio per la programmazione avanzata. AddisonWesley, Milano, 1990. [10] Guerid Abdelali, Breguet Pierre, Rthlisberger Henri. Algorithmes et structures de donnes avec Ada, C++ et Java. Presse Polytechniques Romandes, Lausanne, 2002. [11] Marsan Claudio. Informatica di base. DIE-SUPSI, Manno, 2000. [12] Molliet J.P. Introduction Ada 83. EIEV (cole dIngnieurs de ltat de Vaud). [13] Ogor Robert, Rannou Robert. Language Ada et algorithmique. Hermes, Paris, 1993(2). [14] Pamini Renato. Informatica di base. DIE-SUPSI, Manno, 1998. [15] Skansholm Jan. Ada 95 from the Beginning. AddisonWesley, Harlow, 1997(3). [16] Smith James F., Frank Thomas S. Introduction to Programming Concepts and Methods with Ada. McGrawHill, New York, 1994. [17] Zaffalon Luigi, Breguet Pierre. Programmation concurrente et temps rel avec Ada 95. Presse Polytechniques Romandes, Lausanne, 1999.

287