You are on page 1of 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 dall’autore con L TEX2ε.

Claudio Marsan Liceo cantonale di Mendrisio Via Agostino Maspoli CH–6850 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 Specificazioni 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 Identificatori . . . . . . . . . . . . . . . . . . . . . . . . 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 L’istruzione if ... then ... end if; . . . . . . . . 1.4.2 L’istruzione if ... then ... else ... end if; . 1.4.3 Operatori relazionali . . . . . . . . . . . . . . . . . . . . 1.4.4 Operatori logici . . . . . . . . . . . . . . . . . . . . . . . 1.4.5 L’istruzione 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 L’istruzione exit . . . . . . . . . . . . . . . . . . . . . . 1.5.5 Cicli nidificati . . . . . . . . . . . . . . . . . . . . . . . . 1.6 L’istruzione null . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7 L’istruzione 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 predefinite . . . . . . . . . 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 L’algoritmo 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 fissa . 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 file . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Files di testo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Creazione, apertura e chiusura di un file di testo . . . . . 4.2.2 Accesso agli elementi di un file di testo . . . . . . . . . . . 4.2.3 Altre manipolazioni possibili con i files di testo . . . . . . 4.3 Files binari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Creazione, apertura e chiusura di un file binario . . . . . 4.3.2 Accesso agli elementi di un file binario sequenziale . . . . 4.3.3 Manipolazione di files binari sequenziali . . . . . . . . . . 4.3.4 Accesso agli elementi di un file binario ad accesso diretto 4.3.5 Manipolazione di files binari ad accesso diretto . . . . . . 4.4 Altre osservazioni sull’uso dei files . . . . . . . . . . . . . . . . . 5 Gestione delle eccezioni in Ada 95 Claudio Marsan

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

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

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

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

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

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

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

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

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

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

INDICE 5.1 5.2 5.3 5.4 5.5 5.6 Introduzione . . . . . . . . . . . . . . . Eccezioni predefinite . . . . . . . . . . Trattamento di un’eccezione . . . . . . Dichiarazione di un’eccezione . . . . . Sollevare e propagare un’eccezione . . 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 Specificazione di un package . . . . . . . . . . . . . . . . . 6.3 L’ambiente 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 L’allocatore 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 Definizione . . . . . . . . . . . . . . 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 Efficienza . . . . . . . . . . . Bibliografia

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

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 nell’area 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 l’equivalente per il calcolo scientifico 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 (scientifiche, 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ì classificabili: • 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

2

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Delle 17 proposte pervenute dagli Stati Uniti e dall’Europa ne vennero scelte quattro, alle quali vennero assegnate dei colori per garantire l’anonimato: • 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 (1815–1851), figlia di Lord Byron e assistente di Charles Babbage. Nel 1980, dopo vari miglioramenti in alcune parti, venne rilasciata la prima versione definitiva del linguaggio: essa fu proposta all’ANSI (American National Standards Institute) come standard. Per il lavoro di standardizzazione ci vollero due anni e varie modifiche di piccola entità. Nel gennaio 1983 fu pubblicato il manuale di riferimento (norma ANSI) che definì così uno standard, accettato nel 1987 anche dall’ISO (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 l’estrema portabilità dei programmi Ada, ossia la possibilità di compilare lo stesso programma con un altro compilatore o su un’altra macchina senza dover riscrivere o modificare 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ì definito un nuovo standard: Ada95 (norme ANSI e ISO).

1.1.2

Dove viene usato Ada?

Ada non è rimasto confinato nel DoD, ma è usato anche: • nei computer di bordo di quasi tutti gli aerei commerciali; • in quasi tutti i sistemi di controllo del traffico 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

Specificazioni di Ada

Le specificazioni di Ada furono fissate per: • rendere i programmi leggibili e affidabili; Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.2. ALCUNE CARATTERISTICHE DI ADA • facilitare il loro sviluppo e la loro manutenzione; • fare della programmazione un’attività umana; • rendere i programmi efficaci; • consentire una grande portabilità dei programmi.

3

Queste specificazioni furono fissate nello standard del 1983 (si parla così di Ada 83). Questo standard definisce: • ciò che è permesso in Ada; • ciò che non è permesso in Ada; • ciò che è lasciato libero al compilatore, fissando comunque dei limiti a tale libertà. Ogni compilatore deve essere sottoposto ad un processo di validazione, processo che ha l’intenzione di controllare che: • i programmi Ada siano tradotti ed eseguiti correttamente; • i programmi non conformi allo standard Ada siano rifiutati; • gli scarti di un programma rispetto allo standard facciano parte degli scarti autorizzati e siano realizzati nella maniera descritta nello standard; • le unità predefinite (input/output, . . . ) siano fornite e siano conformi allo standard. Lo standard assicura così che l’effetto 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 qualifica 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ù specifiche a certi campi particolari: • le istruzioni di controllo: – if, case, while, for, loop, exit, return, goto; – richiamo di procedure; – richiesta di rendez–vous 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 ; – rendez–vous tra due task. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

4 • la gestione delle eccezioni;

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

• l’organizzazione 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à predefinite. 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

5

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 dell’ultima 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 01–05 iniziano con due trattini (−−): esse sono trattate come linee di commento. È possibile usare i commenti anche dopo un’istruzione. Un commento si estende fino alla fine di una riga. • Le linee 06 e 08 sono bianche: il compilatore Ada permettere l’uso di linee bianche per aumentare la leggibilità di un programma. • La linea 07 è la linea d’importazione: essa, in questo caso, serve per richiamare il package Ada.Text_IO che contiene le procedure necessarie per l’input e l’output 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 specificato in fase di link (consiglio: fare in modo che tale nome coincida con il nome del file, per non avere inutili complicazioni). • La linea 10 indica l’inizio del corpo della procedura che contiene le istruzioni eseguibili del programma. • La linea 11 è l’istruzione 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 file che contiene il programma, il nome dell’autore, cosa fa il programma, la data dell’ultima modifica 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

6

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 MS–Windows, 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 file ASCII, genera codice oggetto (ossia del codice comprensibile alla CPU) oppure, se vi sono errori di sintassi, genera dei messaggi d’errore. 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 file 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 file eseguibile (in ambiente MS–Windows un file con l’estensione .exe). 4. La fase di esecuzione: per eseguire il programma basta, solitamente, scrivere il nome del file eseguibile creato dal linker nella linea di comando di una finestra 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 è efficiente, 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 diffusi 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 perfino 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 l’ambiente di sviluppo integrato, magari cliccando su delle icone (solitamente gli IDE hanno un’interfaccia grafica e consentono l’uso del mouse). Noi programmeremo in Ada 95 in ambiente MS–Windows, 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 MS–Windows. Ecco le operazioni da eseguire: 1. lanciare l’ambiente 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 dell’ambiente MS–Windows); 3. salvare il file con il nome HELLO.ADB; 4. compilare il file cliccando sull’apposita icona (verranno creati il file di testo HELLO.ALI e il file oggetto HELLO.O); 5. correggere eventuali errori finché il compilatore non segnala più errori; 6. lanciare il linker cliccando sull’apposita icona (verrà creato il file HELLO.EXE); Claudio Marsan Liceo cantonale di Mendrisio, 2002

1.3. PRIMI PASSI CON ADA 95 7. eseguire il programma cliccando sull’apposita icona.

7

Visto che AdaGIDE crea diversi file è 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 file che contiene il programma deve avere lo stesso nome del programma; il nome del file sarà poi completato dall’estensione .ADB (Ada body) (altri compilatori richiedono l’estensione .ADA). Con il compilatore GNAT è ammessa anche l’estensione .ADS (Ada specification), 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 l’input e l’output 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 all’inizio 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 l’istruzione 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 l’uso di alcune procedure del package Ada.Text_IO. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

8

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 un’istruzione 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 un’istruzione 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 qualificheremo ogni volta le procedure e le funzioni premettendo il nome del package da cui esse provengono! Molti programmatori Ada esperti ritengono più vantaggioso qualificare 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 e–mail, uno per riga, iniziando dalla decima colonna della quinta riga e mantenendo l’allineamento a sinistra. Alla fine 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 dell’ultima 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 l’output del programma:

9

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 l’equivalenza 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 dell’ultima 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 definizione 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ò modificare il suo valore nel programma. Le righe 16 e 17 definiscono due variabili di tipo FLOAT: esse servono per contenere dei valori reali e possono modificare il loro contenuto nel programma. Con la riga 25 si memorizza nella variabile pollici il numero reale digitato dall’utente. La riga 30 contiene un’istruzione di assegnazione: alla variabile centimetri viene assegnato il valore del prodotto della costante cm_per_pollice con la variabile pollici. Il simbolo := è l’operatore 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 d’uso 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

Identificatori

Un identificatore è 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 identificatore deve iniziare con una lettera;

11

2. dopo la lettera iniziale l’identificatore 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 l’ultimo 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 identificatore, ma ogni identificatore deve poter essere scritto su un’unica 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 identificatore. Esercizio 1.3.3 Dire quali fra i seguenti identificatori è 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 identificatori vanno scelti in modo sensato: essi dovrebbero avere un nome, possibilmente non troppo lungo (gli identificatori si scrivono più volte all’interno di un programma!), che ricordi lo scopo o l’azione dell’identificatore. Attenzione alle 69 parole riservate in Ada 95 poiché esse non possono essere usate come nomi di identificatori: 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 l’iniziale 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 l’iniziale di ogni parola di cui sono composte in maiuscolo e tutte le altre lettere in minuscolo; • i nomi di funzioni sono scritti con l’iniziale di ogni parola di cui sono composte in maiuscolo e tutte le altre lettere in minuscolo; • i nomi di package sono scritti con l’iniziale di ogni parola di cui sono composte in maiuscolo e tutte le altre lettere in minuscolo; • i nomi di librerie sono scritti con l’iniziale 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 dell’ultima modifica: 21 gennaio 2002 Scopo: esercizio sull’output 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; Verificare 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 identificatori); • un valore. Possiamo immaginare una variabile come un contenitore: sul contenitore c’è un’etichetta (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 l’intestazione 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 definito la variabile ch di tipo CHARACTER; nella riga 02 abbiamo definito le variabili lato, area e perimetro, tutte di tipo FLOAT (numeri reali); nella riga 03 abbiamo definito la variabile numero_avv_postale di tipo INTEGER (numero intero). All’inizio, quando si dichiara una variabile, essa ha un valore indefinito (alcuni compilatori tuttavia pongono le variabili numeriche uguali a 0); si resta in questo stato fino a che non le si dà un valore all’interno 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 definito la variabile lato di tipo FLOAT e ad essa abbiamo assegnato il valore iniziale 12.3; nella riga 02 abbiamo definito la variabile perimetro e ad essa abbiamo assegnato il valore iniziale 4*lato; nella riga 03 abbiamo definito le variabili n e m di tipo INTEGER e le abbiamo inizializzate con il valore 0. Da notare che l’ordine della dichiarazione delle variabili è importante: nell’esempio sopra è necessario definire prima lato di perimetro! Una costante può pure essere vista come un contenitore con un’etichetta e un contenuto: rispetto ad una variabile però il contenuto deve essere specificato nella dichiarazione e poi non può più essere modificato (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 dell’inizializzazione, 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 definito la costante NAP_Mendrisio di tipo INTEGER con il valore 6850; nella riga 02 abbiamo definito la costante PI_greco di tipo universal_real con il valore 3.1415926; nella riga 03 abbiamo definito 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 differenti. Spesso un oggetto rappresenta, in un programma, qualcosa che avviene nel mondo reale. Oggetti differenti hanno proprietà differenti: 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 l’addizione e la moltiplicazione. Osservazione 1.3.5 Bisogna prestare molta attenzione al fatto seguente: Ada è un linguaggio che controlla molto attentamente il tipo dei differenti 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, dall’altra parte aiuta a costruire programmi migliori e più affidabili. Ada è un linguaggio fortemente tipizzato: oltre ai tipi standard predefiniti (essi sono definiti 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ò definire 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 l’insieme Z := {. . . , −3, −2, −1, 0, 1, 2, 3, . . .} dei numeri interi è infinito ma, essendo la memoria di un computer finita, potremo rappresentare solo un sottoinsieme finito 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 nell’esempio 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 dell’ultima 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 => INTEGER’FIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu’ grande INTEGER e’: "); Ada.Integer_Text_IO.Put(Item => INTEGER’LAST); Ada.Text_IO.New_Line; end Limiti_Integer; L’ouput 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 (l’occupazione 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 predefiniti nel package Standard (riferiti al vostro compilatore Ada 95): ------Nome del file: LIMITI_INTERI_PREDEFINITI.ADB Autore: Claudio Marsan Data dell’ultima 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_INTEGER’FIRST); 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_INTEGER’LAST); 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_INTEGER’FIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu’ grande SHORT_INTEGER e’: "); Ada.Short_Integer_Text_IO.Put(Item => SHORT_INTEGER’LAST); Ada.Text_IO.New_Line(Spacing => 2); Ada.Text_IO.Put(Item => "Il piu’ piccolo INTEGER e’: "); Ada.Integer_Text_IO.Put(Item => INTEGER’FIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu’ grande INTEGER e’: "); Ada.Integer_Text_IO.Put(Item => INTEGER’LAST); 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_INTEGER’FIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu’ grande LONG_INTEGER e’: "); Ada.Long_Integer_Text_IO.Put(Item => LONG_INTEGER’LAST); 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_INTEGER’FIRST); 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_INTEGER’LAST); Ada.Text_IO.New_Line; end Limiti_Interi_Predefiniti; Ecco l’output 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: l’esponente 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 = −2·7+(−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 = −3·7+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 dell’ultima 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 L’output 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 l’intero 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 l’argomento opzionale Width che permette di formattare l’output 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 l’output di una variabile di tipo INTEGER secondo varie formattazioni: -----Nome del file: OUTPUT_INTEGER_FORMATTATO.ADB Autore: Claudio Marsan Data dell’ultima modifica: 21 gennaio 2002 Scopo: mostra la formattazione dell’output 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

L’output seguente mostra chiaramente che, qualora lo spazio previsto dal programmatore per visualizzare il valore di una variabile di tipo INTEGER è insufficiente, 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 l’uso 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 definire un proprio tipo di dato, con le limitazioni chiare. Ada permette di definire 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; definiamo un tipo per manipolare le cifre: tale tipo potrà assumere solo valori interi compresi tra 0 e 9 inclusi. Nel package Standard sono definiti i sottotipi interi seguenti: subtype NATURAL is INTEGER range 0..INTEGER’LAST; subtype POSITIVE is INTEGER range 1..INTEGER’LAST; In generale un sottotipo si definisce 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 è l’intervallo 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 all’utente 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 dell’esercizio proposto: -----Nome del file: SOMMA_CUBI_DELLE_CIFRE.ADB Autore: Claudio Marsan Data dell’ultima 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 d’errore:

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 definizione subtype Tre_Cifre possiamo usare, senza problemi, le procedure Get e Put presenti nel package Integer_Text_IO. Se invece avessimo usato la definizione type Tre_Cifre avremmo dovuto fornire delle procedure per l’input e l’output 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 l’apice 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 l’insieme dei numeri reali R è l’unione dell’insieme dei numeri razionali Q (l’insieme dei numeri che si possono rappresentare sotto forma di frazione con numeratore e denominatore interi) con l’insieme 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). L’insieme R è infinito (più che numerabile) e denso (ossia: tra due qualsiasi numeri reali ne esistono infiniti altri); è dunque impensabile poter rappresentare esattamente R con un tipo di dati. In Ada il tipo FLOAT (per floating point numbers, numeri in virgola mobile) rappresenta un’approssimazione 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 l’input e l’output di variabili di tipo FLOAT. Come già visto nel paragrafo 1.3.5 l’output 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 l’esponente; 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 scientifica (si dice che un numero reale positivo è in notazione scientifica quando il numero è scritto nella forma k · 10n , con k ∈ [1, 10[); uno spazio è riservato per il segno dell’esponente (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 l’esponente: nella 01 i parametri non sono qualificati 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 qualificazione è chiarificante e permette di scrivere i parametri nell’ordine voluto. Esempio 1.3.13 Il seguente programma illustra l’uso della formattazione dell’output con una variabile di tipo FLOAT: -----Nome del file: FLOAT_OUTPUT.ADB Autore: Claudio Marsan Data dell’ultima 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; L’output 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 definiti 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 identificare il più piccolo numero rappresentabile; • LAST, per identificare il più grande numero rappresentabile; • DIGITS, per indicare il numero di cifre significative del numero; • SIZE, per stabilire l’occupazione di memoria in bit. L’uso di questi attributi è analogo a quello per i numeri interi. Esempio 1.3.14 Il seguente programma illustra l’uso 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 dell’ultima 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_FLOAT’FIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu’ grande SHORT_FLOAT e’: "); Ada.Short_Float_Text_IO.Put(Item => SHORT_FLOAT’LAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Cifre significative di SHORT_FLOAT: "); Ada.Integer_Text_IO.Put(Item => SHORT_FLOAT’DIGITS, 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_FLOAT’SIZE, 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 => FLOAT’FIRST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Il piu’ grande FLOAT e’: "); Ada.Float_Text_IO.Put(Item => FLOAT’LAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Cifre significative di FLOAT: "); Ada.Integer_Text_IO.Put(Item => FLOAT’DIGITS, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Bit occupati da un FLOAT: "); Ada.Integer_Text_IO.Put(Item => FLOAT’SIZE, 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_FLOAT’FIRST); 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_FLOAT’LAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Cifre significative di LONG_FLOAT: "); Ada.Integer_Text_IO.Put(Item => LONG_FLOAT’DIGITS, 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_FLOAT’SIZE, 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_FLOAT’FIRST); 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_FLOAT’LAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "Cifre significative di LONG_LONG_FLOAT: "); Ada.Integer_Text_IO.Put(Item => LONG_LONG_FLOAT’DIGITS, 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_FLOAT’SIZE, Width => 0); Ada.Text_IO.New_Line; end Limiti_Float; Ecco l’output 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 significative dei tipi reali dipende dalla macchina e/o dal compilatore, è preferibile definire 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 definiscono: • 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 l’intervallo dei numeri in virgola mobile che vogliamo definire. La sintassi è la seguente: type Nome_Tipo is digits numero range minimo..massimo; Esempio 1.3.16 Le seguenti sono definizioni di tipi reali definiti 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 fissati, il compilatore reclama: questo aiuta a scrivere programmi più affidabili 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 l’operatore ** e l’esponente deve necessariamente essere un intero; • la priorità dell’esecuzione delle operazioni è quella dell’algebra; la priorità è modificabile usando le parentesi (sono ammesse solo parentesi tonde!); • nel package Ada.Numerics sono definite le costanti Pi per π ed e per il numero di Eulero e; • nel package Ada.Numerics.Elementary_Functions sono definite 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 nell’esempio 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 definiti da parole piuttosto che da numeri, per esempio i giorni della settimana, i mesi dell’anno, i colori, i semi delle carte da gioco, . . . . Per rappresentare fenomeni come quelli elencati in un programma i numeri non sono sufficienti: 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 definizione di un tipo enumerativo è la seguente: type Nome_Tipo is (val1, val2, ..., valN); All’interno 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 definizione del tipo, segue val) e PRED(val) (restituisce il valore che, nella definizione del tipo, precede val). Esempio 1.3.21 Il seguente frammento di programma è riferito agli esempi precedenti: ... domani := GIORNO’PRED(SABATO); -- VENERDI seme_giocato := SEME’SUCC(QUADRI); -- CUORI colore_auto := COLORE’SUCC(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). Affinché si possano utilizzare le procedure di input e output con i tipi enumerativi bisogna inserire all’interno della procedura una riga simile alla seguente (il significato 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 dell’ultima 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 := SEME’PRED(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 dell’ultima 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 := GIORNO’PRED(oggi); Ada.Text_IO.Put(Item => "Ieri era "); Giorno_IO.Put(Item => ieri); Ada.Text_IO.New_Line; domani := GIORNO’SUCC(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 fine settimana ci sono dei problemi che potranno essere risolti appena avremo studiato l’istruzione if ... then .... Vediamo comunque il messaggio d’errore 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 predefinito 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 definizione è: type BOOLEAN is (FALSE, TRUE); Anche il tipo CHARACTER è un tipo enumerativo predefinito. 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’); L’insieme dei caratteri stampabili con Ada 95 è definito 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 sull’alfabeto 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 specificati nello standard ISO 10646 BMP). Come già visto l’input e l’output 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 CHARACTER’POS(ch) si ottiene il codice ASCII del carattere ch; se N è un intero allora con CHARACTER’VAL(N) si ottiene il carattere avente il codice ASCII N. Esempio 1.3.24 CHARACTER’POS(’a’) restituirà 97; CHARACTER’VAL(97) restituirà ’a’. Gli attributi VALUE e IMAGE sono utilizzabili con i tipi discreti (interi ed enumerativi): • T’VALUE(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); • T’IMAGE(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 dell’esempio 1.3.22 nel modo seguente, evitando di “definire” 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 dell’ultima 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 := SEME’VALUE(s(1..lung)); seme_giocato := SEME’PRED(seme_giocato); Ada.Text_IO.Put(Item => SEME’IMAGE(seme_giocato)); end Carte2;

Il programma necessita di qualche spiegazione: • nella riga 15 si definisce la variabile s come una stringa di (al massimo) sei caratteri, sufficienti per memorizzare i valori del tipo enumerativo SEME; • nella riga 16 si definisce la variabile lung: in essa verrà memorizzato il numero di caratteri digitato dall’utente; • nella riga 21 si legge la stringa s e si memorizza la sua lunghezza in lung; • nella riga 22 il compito di SEME’VALUE è 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 SEME’IMAGE è 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 definiti. In Ada 95 l’uso di tali attributi è permesso anche con i numeri in virgoli mobile. Esempio 1.3.26 Il programma seguente mostra l’uso di IMAGE con un tipo di dati in virgola mobile definito dal programmatore: -----Nome del file: MY_FLOAT.ADB Autore: Claudio Marsan Data dell’ultima 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 => REAL’IMAGE(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 dell’ultima 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 := FLOAT’VALUE(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 definire l’insieme dei numeri in virgola mobile positivi dovremmo conoscere qual è il più piccolo FLOAT maggiore di 0. Questo valore si ottiene usando l’attributo SMALL: per esempio FLOAT’SMALL ritorna il più piccolo FLOAT positivo. Potremo così definire i FLOAT positivi nel modo seguente: type POSITIVE_FLOAT is digits 6 range FLOAT’SMALL..FLOAT’LAST; 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 finora 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, . . . fino a che l’ultima istruzione che appare nel programma è stata eseguita. Ma i computer possono fare di più: possono eseguire un’azione 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 flusso di controllo nell’esecuzione 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 paragrafi ci occuperemo di apprendere le istruzioni necessarie per modificare il flusso di un programma.

1.4

Istruzioni condizionali

I linguaggi di programmazione solitamente permettono al programmatore di far decidere al computer se un’istruzione 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

L’istruzione if ...

then ...

end if;

La forma più semplice è la seguente: if condizione then istruzione_1; istruzione_2; ... istruzione_N; end if; dove condizione è un’espressione booleana, ossia un’espressione 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; ... L’ultimo frammento di programma non è molto efficiente poiché se x è maggiore di y verrà ugualmente eseguito il blocco delle istruzioni che seguono, anche se tale azione è perfettamente inutile!

1.4.2

L’istruzione if ...

then ...

else ...

end if;

Mediante l’istruzione condizionale if ... then ... else ... end if;

(se ... allora ... altrimenti ...) si può rimediare a situazioni analoghe a quella dell’esempio 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 l’esempio 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 dell’esempio 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 modificarlo.

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 un’espressione 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 l’unica differenza che viene valutato prima l’operando di sinistra; se questo è FALSE il secondo operando non viene valutato); • or else : unione cortocircuitata (fornisce lo stesso risultato di or con l’unica differenza che viene valutato prima l’operando 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

L’istruzione if ...

then ...

elsif ...

end if;

Vogliamo migliorare l’esempio 1.4.6, riferito alla valutazione di un lavoro scritto, perché le categorie “sufficiente” e “insufficiente” 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 fino al primo elsif, altrimenti se seconda_condizione è vera vengono eseguite le istruzioni che la seguono fino 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 nidificate). Esercizio 1.4.1 Sia data l’equazione quadratica a coefficienti 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 all’utente i coefficienti A, B e C e risolva l’equazione 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 fi-

Osservazione 1.4.3 L’istruzione 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 l’istruzione 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 l’istruzione case (quest’ultima soluzione è da preferire). La sintassi dell’istruzione 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 l’istruzione 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 l’alternativa others essa deve essere l’ultima 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 d’errore. 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 (l’utente deve dare, per esempio, un’espressione del tipo 5*4 e il programma deve restituire il risultato). Osservazione 1.4.4 L’istruzione case è comoda da usare quando bisogna preparare l’interfaccia 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 specificare che un gruppo di operazioni possa essere ripetuto. L’esecuzione 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 infinito di volte; • una istruzione loop con for per scrivere una parte di programma che deve essere eseguita un numero prefissato di volte; • una istruzione loop con while per scrivere una parte di programma che deve essere eseguita finché 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 infinite 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 infinite 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 finestre, 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 definite dal programmatore, t_min e t_max sono costanti definite 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 dell’ultima 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 identificatore 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 modificato all’interno del ciclo; inoltre alla fine 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) dell’alfabeto 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 modificato all’interno 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 è un’espressione booleana. Il funzionamento di un ciclo while è il seguente: dapprima viene valutata l’espressione 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ì finché 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

L’output 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; ... L’output relativo al frammento di programma sarà: 10.00 5.00 2.50 1.25

Da notare che, se in un ciclo while, l’espressione booleana controllata all’inizio non diventa mai falsa avremo un ciclo infinito che potrà essere interrotto solo brutalmente con una combinazione di tasti tipica del sistema operativo in uso. È dunque importante assicurarsi che le istruzioni all’interno del ciclo while modifichino il valore dell’espressione booleana iniziale. Esempio 1.5.7 Il seguente frammento di programma genera un ciclo infinito: ... 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 dall’utente 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

L’istruzione exit

Il ciclo semplice loop ... end loop serve per ripetere un certo numero di istruzioni infinite volte. Mediante l’istruzione 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 dell’istruzione exit: 1. exit; 2. exit when condizione; (condizione è un’espressione 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 l’istruzione exit che comporta l’uscita dal ciclo e l’esecuzione del programma passerà alla prima istruzione dopo end loop. Esempio 1.5.10 L’esempio precedente usando però la costruzione exit when: ... loop ... x := y - a**2; exit when x < 1.0; ... end loop; ... Osservazione 1.5.2 Attenzione all’uso delle istruzioni exit e di exit when poiché con esse si possono creare facilmente programmi poco chiari e difficili da capire; se possibile usare invece un ciclo while. L’esercizio seguente permette di esercitare l’uso delle costruzioni for ... loop e delle istruzioni condizionali. Esercizio 1.5.3 Scrivere un programma che: 1. chieda all’utente 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 fintanto che la differenza fra due somme parziali successive è minore, in valore assoluto, di 10−12 ); 3. chieda all’utente, 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 l’equivalenza dei vari cicli: -----Nome del file: CICLI.ADB Autore: Claudio Marsan Data dell’ultima modifica: 20 febbraio 2002 Scopo: mostrare l’equivalenza 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 l’output del programma:

49

Valore di k (for): -53

Valore di k (while): -53

Valore di k (loop): -53

1.5.5

Cicli nidificati

La sequenza delle istruzioni all’interno 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 nidificati. Esempio 1.5.12 Il seguente frammento di programma richiede all’utente 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 l’output 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 l’algoritmo 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 dell’ultima 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 nidificati 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. L’ISTRUZIONE 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 dell’ultima 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 l’output 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

L’istruzione null

L’istruzione null è l’istruzione 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 l’istruzione nulla. In fase di progettazione può essere utile ricorrere all’istruzione null per testare una parte di programma del quale è già pronta la struttura principale ma non la codifica completa (mancano cioé i dettagli dell’implementazione). Questo modo di procedere è tipico della programmazione top–down. 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 L’istruzione null può essere utile anche nella clausola when others in una costruzione case.

1.7

L’istruzione goto

Una delle istruzioni più utilizzate nel linguaggio di programmazione Basic è l’istruzione di salto, ossia l’istruzione goto. L’uso (o meglio: l’abuso) di questa istruzione rende praticamente illeggibili i programmi (gli americani parlano di spaghetti code). Nei linguaggi di programmazione strutturata (Pascal, Modula–2, Ada) l’istruzione 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 l’uso (sembra che in Ada si sia introdotta l’istruzione goto per poter tradurre automaticamente gli innumerevoli programmi scritti in altri linguaggi, soprattutto in Fortran, già esistenti). Per poter utilizzare l’istruzione goto bisogna dichiarare anche un’etichetta (label ), ossia un identificatore 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 l’istruzione goto, si passerà all’istruzione 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 dell’istruzione goto: • essa non può trasferire il controllo del flusso del programma al di fuori di una funzione, di una procedura, di un package, di un task; • essa non può trasferire il controllo del flusso del programma all’interno 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 identificatore 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 identificatore 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 all’interno del blocco in cui essa è stata dichiarata. Esempio 1.8.2 Il seguente programma illustra la differenza fra variabili globali e variabili locali: -----Nome del file: VARIABILI.ADB Autore: Claudio Marsan Data dell’ultima 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 l’output 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 all’interno 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 flusso 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, l’esempio 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 definite in fase di definizione del tipo di dato; • gli array non vincolati (unconstrained array), dei quali non si predefinisce 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 dell’array; • a..b rappresenta il rango su cui è possibile scegliere gli indici dell’array. Esempio 1.9.1 Con type VETTORE3D is array(1..3) of FLOAT; si definisce 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 definisce 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 fisica. 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ì definire 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 definito 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, l’indice 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 effetti. 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 specificato per gli indici. Esempio 1.9.5 Nel seguente frammento di programma si mostra l’assegnazione 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 l’esempio 1.9.4 dell’esperimento di laboratorio. Ammettiamo di non volere fare più solo 10 misurazioni ma 20. Se avessimo scritto un programma per l’esempio 1.9.4 dovremmo ricercare tutte le occorrenze di 10 (quando questi ha il significato di indice finale di una variabile di tipo SERIE_DI_MISURE) e sostituirle con 20: poco pratico! Conviene invece modificare la definizione 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 definizione della costante nella riga apposita! Facendo come appena descritto sarà più facile mantenere e aggiornare i programmi. Quando si definisce 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 definiamo un array di 18 componenti di tipo NATURAL; la prima componente ha indice -10, l’ultima 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 definizioni 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 definizione del tipo ORE_DI_LAVORO non mette bene in risalto di che tipo sono gli indici. Per specificare il tipo degli indici si può ricorrere alla seguente definizione alternativa: type ORE_DI_LAVORO is array(GIORNO range LUNEDI..VENERDI) of FLOAT; Esempio 1.9.8 Riferendoci all’esempio 1.9.4 potremmo dare la seguente definizione, più meticolosa: type SERIE_DI_MISURE is array(INTEGER range 1..10) of FLOAT; Esempio 1.9.9 Dovendo costruire un programma che conta l’occorrenza di ogni singola lettera dell’alfabeto in un testo sarà conveniente definire 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 definizione 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ò significa che la grandezza dell’array 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 l’indice iniziale assuma un valore più grande del’indice finale avremo un’array vuoto, senza componenti. Esercizio 1.9.1 Costruire un programma che definisca 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 dall’utente. È possibile assegnare valori a tutte le componenti di un array con un’istruzione sola invece che assegnare i valori componente per componente: basta usare gli aggregati. Esempio 1.9.13 Definiamo 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 nell’aggregato, 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 definire 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 l’indice di riga e l’indice 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, definizione 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 (top–down) permette, in una prima fase, di ignorare dettagli ininfluenti del programma e di concentrarsi sull’algoritmo vero e proprio. In Ada 95 è possibile definire dei sottoprogrammi composti da più istruzioni di base; questi sottoprogrammi possono essere utilizzati come passi algoritmici di livello superiore quando l’algoritmo è 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 un’azione 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 predefinite 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 predefinite

Nel package Ada.Numerics.Elementary_Functions sono definite 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 significato di Sin. Osservazione 2.2.2 L’argomento delle funzioni trigonometriche è, per difetto, in radianti. Se si vuole, per esempio lavorare con i gradi sessadecimali bisogna specificare il parametro Cycle => 360.0, come nell’esempio 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 dell’ultima modifica: 27 febbraio 2002 Scopo: mostrare l’uso 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 dell’angolo 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 all’interno 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 specificazione della funzione (function specification) (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 identificatori, 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 specificare il tipo di dato del valore restituito dalla funzione. Esempio 2.3.2 Nel paragrafo 2.2 sono elencate le specificazioni delle funzioni presenti nel package Ada.Numerics.Elementary_Functions. Esempio 2.3.3 La seguente è la specificazione della funzione Massimo, non predefinita 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 l’algoritmo 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 ... -- l’algoritmo return risultato; -- il valore in uscita della funzione end Nome_Funzione;

67

Solitamente l’utente 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 specificazione della funzione. Quando l’istruzione return viene eseguita termina anche l’esecuzione 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 l’ultima. 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 definita una funzione essa può essere (ri)chiamata all’interno 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 fine della parte dichiarativa di un programma. Esempio 2.3.5 Nel seguente programma si definisce la funzione Valore_Medio: ------Nome del file: CALCOLA_MEDIA.ADB Autore: Claudio Marsan Data dell’ultima 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 un’espressione 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 l’argomento (di tipo CHARACTER) passato alla funzione è una lettera dell’alfabeto 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 definita: 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 dell’alfabeto"); else Ada.Text_IO.Put_Line(Item => "Non e’ una lettera dell’alfabeto"); end if; ...

69

La funzione Lettera può essere definita elegantemente, senza dover ricorrere all’istruzione 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 un’espressione booleana composta. Esempio 2.3.9 La funzione Somma, definita 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 all’interno della funzione (subito dopo la specificazione) ed esiste solo all’interno 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 definite: • 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 l’angolo fra due vettori dello spazio e che permetta di controllare la correttezza delle funzioni definite. Esercizio 2.3.3 Costruire una funzione che implementi la seguente funzione reale definita 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, modificano il valore di una o più variabili. Per le procedure non bisogna indicare un tipo per il risultato; inoltre esse non hanno bisogno di un’istruzione return e terminano, normalmente, quando raggiungono l’end finale. Esempio 2.4.1 Nel seguente programma si definisce 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 dell’ultima 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, infine, 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 è un’istruzione, mentre la chiamata di una funzione è un’espressione. Esempio 2.4.2 Il seguente programma contiene una procedura che permette di scrivere una stringa di caratteri data dall’utente 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 dell’ultima 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 - testo’LENGTH)/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 all’interno della procedura, sono locali e hanno valore solo all’interno della procedura. È possibile ottenere la lunghezza della stringa testo mediante l’attributo LENGTH, con la sintassi testo’LENGTH. Le procedure che abbiamo visto finora si comportano essenzialmente come “scatole nere”: ricevono dei valori, li elaborano, compiono alcune azioni ma non restituiscono variabili con il valore modificato. Le procedure possono essere utilizzate anche per trasferire parametri dal programma principale (in vari modi): si parla di associazione di parametri. Per illustrare l’associazione 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 specifica 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 dell’ultima 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 L’output 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ì all’inizio della chiamata il valore di C resta indefinito. • L’istruzione B := B + A; assegna a B il valore 6. • L’istruzione C := 0; assegna a C il valore 0. • Le istruzioni di Nulla sono terminate: all’uscita 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. All’interno della procedura Nulla il parametro formale A di modo in è considerato come una costante: ogni tentativo di modificarne il valore porta ad un errore! 2. Un parametro di modo in out può essere considerato come una normale variabile all’interno di una procedura (abbiamo potuto modificare B e inserirlo in espressioni). 3. All’inizio di una procedura un parametro di modo out è indefinito. 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 un’espressione e deve avere un valore valido al momento della chiamata. Se il valore del parametro attuale è una variabile, il suo valore non può essere modificato durante la chiamata del sottoprogramma. Il suo valore sarà sempre identico prima e dopo l’esecuzione 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 l’esecuzione del sottoprogramma e quindi avere un valore diverso alla sua fine. • out: il parametro attuale deve essere una variabile. Il suo valore al momento della chiamata non è importante poiché la procedura lo ignora. Alla fine 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 l’esecuzione 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 modificato. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

74

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95 • in out: quando parte l’esecuzione della procedura il parametro formale ha un valore. All’interno della procedura il parametro può essere usato come una variabile ordinaria: il suo valore può essere usato e modificato. • out: quando parte l’esecuzione della procedura il parametro formale è indefinito.

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 dell’ultima 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 l’output 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 modificarne il valore. Esercizio 2.4.1 Scrivere un programma nel quale sono definite 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 definire 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 definire due funzioni con lo stesso nome nel medesimo programma. Esempio 2.5.1 Il seguente frammento di programma contiene la definizione di due funzioni MAX: una restituisce il più grande fra due INTEGER, l’altra 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 l’output e l’input 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 l’operatore + definendo 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, nell’esempio 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 d’errore.

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 l’inizializzazione 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. L’uso di sottoprogrammi ricorsivi è adatto per risolvere alcuni tipi particolari di problemi, soprattutto matematici e definiti fin dall’inizio in modo ricorsivo. Esempio 2.7.1 Consideriamo la funzione fattoriale, definita da: n! := 1 · 2 · 3 · · · (n − 1) · n, n∈N, e 0! := 1 .

Ricorsivamente essa può essere definita 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! = = = = = 1·1 2·1 3·2 4·6 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 un’istruzione 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 (nell’esempio 2.7.1: n = 0), altrimenti avremmo una ricorsione infinita. Esercizio 2.7.1 Scrivere una versione non ricorsiva della funzione fattoriale. Non sempre l’uso della ricorsione è raccomandabile. Consideriamo per esempio la successione di Fibonacci, definita 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 l’istruzione k := Fibonacci(4); e provare poi a scrivere una nuova funzione Fibonacci che non faccia uso della ricorsione e che sia più efficiente. := 0 := 1 := Fn−2 + Fn−1 ,

n = 2, 3, . . .

2.8

Esempi di algoritmi

Un algoritmo è una lista (finita) di passi da eseguire per risolvere un determinato problema. Molto spesso la codifica 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 dall’analisi 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, difficilmente 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 l’algoritmo 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 . . . rj−2 = rj−1 · qj−1 + rj . . . rn−4 = rn−3 · qn−3 + rn−2 rn−3 = rn−2 · qn−2 + rn−1 rn−2 = rn−1 · qn−1 + rn rn−1 = rn · qn . 0 ≤ r2 < r1 , 0 ≤ r3 < r2 , . . . 0 ≤ rj < rj−1 , . . . 0 ≤ rn−2 < rn−3 , 0 ≤ rn−1 < rn−2 , 0 ≤ rn < rn−1 ,

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 l’algoritmo 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 definizione 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 dell’esercizio 2.8.1: -----Nome del file: TEST_GCD.ADB Autore: Claudio Marsan Data dell’ultima 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 dell’esercizio 2.8.2: -----Nome del file: TEST_GCD_RICORSIVA.ADB Autore: Claudio Marsan Data dell’ultima 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 fin dai tempi di Euclide esistono infiniti numeri primi. Ecco l’inizio 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 dell’ordine 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 definita 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 fino ad ottenere un valore di n non più divisibile per dk ; √ • si prosegue allo stesso modo fino 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 dell’ultima 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 è un’operazione molto difficile e complessa e l’algoritmo visto in precedenza (trial division) è praticamente inutilizzabile già per numeri con circa 30 cifre. Esistono degli algoritmi più efficienti e complicati ma il problema dell’esecuzione dell’algoritmo in un tempo accettabile si ripresenta già con numeri di circa 60–80 cifre. Il seguente passo, tratto da un discorso di H. W. Lenstra jr. al Congresso Internazionale di Matematica tenuto a Berkeley nel 1986, è significativo per quel che riguarda l’enorme difficoltà 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 sconfitta della matematica il fatto che la cosa più “promettente” che possiamo fare sia di andare a cercare nell’immondizia o di provare con tecniche mnemoipnotiche . . .

85

Questa difficoltà nel fattorizzare numeri molto grandi è alla base di alcuni sistemi crittografici moderni (RSA, per esempio) che vengono utilizzati per garantire la privacy nella trasmissione di documenti riservati, la segretezza degli e–mail (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 fino 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 finché 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 fino a circa 10’000’000 (nel 1909 D. N. Lehmer pubblicò: List of Prime Numbers from 1 to 10’006’721 ), 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 difficili 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 dell’ultima 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 10’000): "); 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: nell’array "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

L’algoritmo di Sundaram

Oltre al crivello di Eratostene possiamo generare i numeri primi minori di un certo numero prefissato usando l’algoritmo 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 l’insieme degli elementi delle successioni definite sopra. Allora: 2z + 1 è un numero primo ⇐⇒ z ∈ T.

Le considerazioni seguenti servono per dimostrare la validità di quanto affermato: • 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ò significa che z ∈ T . Esempio 2.8.10 Vogliamo ricercare i numeri primi dispari minori di un certo valore dispari q, per esempio q = 79. Dall’equazione 2z + 1 = q ricaviamo il valore massimo che può assumere z, nel nostro caso sarà z = 39. Costruiremo l’insieme 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 nell’espressione 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 l’algoritmo di Sundaram. Esercizio 2.8.5 Implementare in Ada 95 l’algoritmo di Sundaram! Esempio 2.8.11 Il seguente programma contiene una possibile implementazione dell’algoritmo di Sundaram: -----Nome del file: SUNDARAM.ADB Autore: Claudio Marsan Data dell’ultima modifica: 9 aprile 2002 Scopo: implementazione dell’algoritmo 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 10’000): "); 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 dell’algoritmo 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 all’inizio 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 all’algoritmo: √ • 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 d’appoggio ad altri metodi di fattorizzazione: con esso è possibile ridurre la magnitudine del numero da fattorizzare. Esso è, generalmente, poco efficiente, 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 dell’ultima 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 sull’intervallo [a, b] e con f (a) · f (b) < 0. Come noto dall’analisi esiste (almeno!) un valore x0 ∈]a, b[ tale che f (x0 ) = 0 (si dice che x0 è uno zero di f ). Spesso è difficile trovare il valore esatto degli zeri di f (per esempio quando f (x) = 0 è un’equazione trascendente oppure un’equazione 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 l’algoritmo termina; 4. si calcola K := f (x0 ) · f (x2 ): • se K < 0 allora uno zero della funzione sarà nell’intervallo ]x0 , x2 [; si pone poi x1 := x2 ; • se K > 0 allora uno zero della funzione sarà nell’intervallo ]x2 , x1 [; si pone poi x0 := x2 ;
1 5. si calcola x2 := 2 · (x0 + x1 ) e si procede come sopra fintanto che si trova uno zero di f oppure fintanto che l’intervallo contenente lo zero di f ha un’ampiezza minore di una quantità predefinita.

Esercizio 2.8.7 Scrivere un programma che permetta di calcolare con il metodo di bisezione lo zero reale (precisione desiderata: 10−5 ) 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 dell’esercizio precedente: -----Nome del file: METODO_DI_BISEZIONE.ADB Autore: Claudio Marsan Data dell’ultima 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 dell’intervallo nel quale si ricerca lo zero reale --- della funzione ----------------------------------------------------------------begin Ada.Text_IO.Put(Item => "Estremo sinistro dell’intervallo: "); Ada.Float_Text_IO.Get(Item => sinistro); Ada.Text_IO.Put(Item => "Estremo destro dell’intervallo: "); 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 nell’intervallo [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 => "Nell’intervallo ["); 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 dell’intervallo: -1.0 Estremo destro dell’intervallo: 3.0

Nell’intervallo [-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 dell’iterazione è molto semplice: si parte da un valore iniziale x0 e si esegue l’iterazione f (xn ) xn+1 = xn − , per n = 0, 1, 2, . . . f (xn ) (con f si intende la funzione derivata di f ), che verrà ripetuta fintanto che f (xn ) è sufficientemente vicino a zero. Esercizio 2.8.8 Scrivere un programma che permetta di calcolare con il metodo di Newton lo zero reale (precisione desiderata: 10−5 ) della funzione, data tramite equazione, f (x) = x3 − 2x2 + 3x − 7 . Esempio 2.8.15 Il seguente programma presenta una possibile soluzione dell’esercizio precedente: -----Nome del file: METODO_DI_NEWTON.ADB Autore: Claudio Marsan Data dell’ultima 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 dell’iterazione -------------------------------------------------begin Ada.Text_IO.Put(Item => "Valore iniziale dell’iterazione: "); 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 dell’iterazione: -1.17

E’ stato trovato lo zero 2.13249 Ecco un altro esempio di output del programma: Valore iniziale dell’iterazione: 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 l’astrazione si crea dunque un modello oppure un concetto di un fenomeno del mondo reale. Esempio 3.1.1 La parola camion è un’astrazione 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, . . . L’astrazione 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, l’asse, . . . ): 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 L’autista del camion non è interessato a come gli ingranaggi del cambio si muovono all’interno della scatola ma deve solo sapere come usare il cambio. In Ada 95 è possibile applicare l’astrazione dei dati usando tipi e pacchetti dichiarati dal programmatore. Ecco i vantaggi derivanti dall’introdurre dei nuovi tipi che rappresentano in modo specifico 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 superflui. 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 all’utente (data hiding: l’utente sa che esistono questi dati e li può usare, ma non sa come sono implementati). Il programmatore può definire (anzi: in Ada 95 è addirittura incoraggiato a farlo) nuovi tipi di dati. Ricordiamo che: • un nuovo tipo si definisce 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 predefiniti in Ada 95, in particolare per definire variabili e costanti (nota: in questo caso la dichiarazione del tipo deve precedere quella di relative variabili e costanti). A volte risulta difficile 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 definire 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 all’interno 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 definizione 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 è predefinito, 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ò significa che per garantirsi la portabilità dei programmi su piattaforme diverse bisognerebbe evitare di usare il tipo INTEGER predefinito. 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 definire un nuovo tipo di intero non si rispettano i limiti il compilatore reclamerà con un messaggio d’errore. 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 l’uso di differenti 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 l’istruzione di conversione seguente: pagine := NUMERO_PAGINE(i); La conversione di un FLOAT in un INTEGER comporta l’arrotondamento all’intero più vicino al FLOAT. Esempio 3.2.6 Consideriamo il seguente programma: ----Nome del file: ROUND.ADB Autore: Claudio Marsan Data dell’ultima 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 l’output:

=> 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 l’arrotondamento 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 definiscono nuovi tipi interi non si hanno a disposizione le routines di input e output del package Ada.Integer_Text_IO. Tuttavia in esso è definito 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 l’uso delle procedure Put e Get per delle variabili di un tipo intero non predefinito: -----Nome del file: PROVA_TIPI_INTERI.ADB Autore: Claudio Marsan Data dell’ultima modifica: 7 maggio 2002 Scopo: input e output per un tipo di dato intero definito dall’utente 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 definizione 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 definizione 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 definizione l’intervallo 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 predefinite per il tipo INTEGER, con la particolarità che il secondo operando dell’operatore 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 overflow o un underflow del risultato poiché tutta l’aritmetica viene effettutata modulo N. Inoltre è possibile applicare, come nel caso dell’aritmetica 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 è l’output del programma -----Nome del file: PROVA_TIPI_MODULO.ADB Autore: Claudio Marsan Data dell’ultima 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 dell’esercizio 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 definisce 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. Definire 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 (floating 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 fissa (fixed point), con precisione assoluta (due numeri reali a virgola fissa 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 definizione del tipo FLOAT, come tipo derivato dal tipo universal_float (nota: la precisione del tipo FLOAT è dipendente dalle varie implementazioni di Ada 95; l’uso 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 predefiniti nel package STANDARD (essi differiscono 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 definire nuovi tipi reali si userà la sintassi seguente: type NOME_TIPO is digits NUMERO_CIFRE;

111

dove NUMERO_CIFRE esprime l’accuratezza sotto forma di numero delle cifre significative (deve essere un’espressione intera statica). Il compilatore ha il compito di scegliere la rappresentazione binaria interna più adatta tra quelle disponibili per soddisfare la precisione richiesta dall’utente nella definizione dei suoi tipi di dati reali in virgola mobile. Esempio 3.3.1 Definizione di due tipi reali in virgola mobile (il primo con 4 cifre significative, il secondo con 15 cifre significative): 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 definendo (in matematica ciò corrisponderebbe alla definizione 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 Definizione 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 definizione data di PROB_ERRORE assicura che, quando un programma è in esecuzione, variabili di questo tipo non assumano valori che giacciono al di fuori dell’intervallo di definizione. Tutte le operazioni permesse con i FLOAT sono permesse anche per i reali definiti dall’utente; come nel caso degli interi non è tuttavia possibile mescolare l’uso 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 l’input e l’output di tipi di dati reali definiti dall’utente è necessario creare un nuovo package con l’istruzione 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 definito dal programmatore. Esempio 3.3.4 Il seguente programma mostra l’input e l’output di variabili di un tipo di dati reale definito dal programmatore: ------Nome del file: IO_REALI.ADB Autore: Claudio Marsan Data dell’ultima 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 ridefiniscono dei tipi predefiniti è necessario definire anche il package per l’input e l’output. Esempio 3.3.5 Nel programma seguente si ridefinisce il tipo predefinito FLOAT come un reale con 9 cifre significative. Da notare che bisogna definire il package Float_Text_IO per l’input e l’output (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 dell’ultima 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 l’arrotondamento 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: • T’FIRST: il più piccolo numero reale del tipo T che può essere memorizzato; • T’LAST: il più grande numero reale del tipo T che può essere memorizzato; • T’PREC(x): il numero macchina che precede x; • T’SUCC(x): il numero macchina che segue x; • T’MIN(x, y): restituisce il valore minore tra x e y; • T’MAX(x, y): restituisce il valore maggiore tra x e y; • T’ROUNDING(x): restituisce un numero di tipo T che rappresenta l’arrotondamento a numero intero di x; • T’TRUNCATION(x): restituisce un numero di tipo T che rappresenta la parte intera di x (troncamento di x); • T’FLOOR(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

• T’CEILING(x): restituisce un numero di tipo T che rappresenta il più piccolo intero non minore di x; • T’DIGITS: fornisce il numero di cifre significative del tipo T; • T’MANTISSA: fornisce la lunghezza della mantissa binaria di un numero di tipo T; • T’WIDTH: restituisce un numero intero indicante il numero di caratteri necessario per stampare sotto forma di stringa un qualsiasi valore di tipo T; • T’IMAGE(x): restituisce una stringa che contiene il valore di x; • T’VALUE(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 l’uso di questi attributi: -----Nome del file: ATTRIBUTI_FLOATING_POINT.ADB Autore: Claudio Marsan Data dell’ultima 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 => "FLOAT’FIRST: "); Ada.Float_Text_IO.Put(Item => FLOAT’FIRST, Fore => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "FLOAT’LAST: "); Ada.Float_Text_IO.Put(Item => FLOAT’LAST, Fore => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’DIGITS: "); Ada.Integer_Text_IO.Put(Item => FLOAT’DIGITS, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’MANTISSA: "); Ada.Integer_Text_IO.Put(Item => FLOAT’MANTISSA, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’WIDTH: "); Ada.Integer_Text_IO.Put(Item => FLOAT’WIDTH, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’IMAGE(x): "); Claudio Marsan Liceo cantonale di Mendrisio, 2002

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

115

Ada.Text_IO.Put("FLOAT’PRED(x): "); Ada.Float_Text_IO.Put(Item => FLOAT’PRED(x), Fore => 0, Aft => 10, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’SUCC(x): "); Ada.Float_Text_IO.Put(Item => FLOAT’SUCC(x), Fore => 0, Aft => 10, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’MIN(x, y): "); Ada.Float_Text_IO.Put(Item => FLOAT’MIN(x, y), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’MAX(x, y): "); Ada.Float_Text_IO.Put(Item => FLOAT’MAX(x, y), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’ROUNDING(x): "); Ada.Float_Text_IO.Put(Item => FLOAT’ROUNDING(x), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’TRUNCATION(x): "); Ada.Float_Text_IO.Put(Item => FLOAT’TRUNCATION(x), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’FLOOR(x): "); Ada.Float_Text_IO.Put(Item => FLOAT’FLOOR(x), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put("FLOAT’CEILING(x): "); Ada.Float_Text_IO.Put(Item => FLOAT’CEILING(x), Fore => 0, Exp => 0); Ada.Text_IO.New_Line; end Attributi_Floating_Point; L’output del programma (Gnat 3.13p su Windows 2000) è il seguente: FLOAT’FIRST: -3.40282E+38 FLOAT’LAST: 3.40282E+38 FLOAT’DIGITS: 6 FLOAT’MANTISSA: 21 FLOAT’WIDTH: 12 FLOAT’IMAGE(x): 2.71828E+00 y = FLOAT’VALUE(s): 3.14159 FLOAT’PRED(x): 2.7182798386 FLOAT’SUCC(x): 2.7182803154 FLOAT’MIN(x, y): 2.71828 FLOAT’MAX(x, y): 3.14159 Liceo cantonale di Mendrisio, 2002 Claudio Marsan

116 FLOAT’ROUNDING(x): 3.00000 FLOAT’TRUNCATION(x): 2.00000 FLOAT’FLOOR(x): 2.00000 FLOAT’CEILING(x): 3.00000

CAPITOLO 3. TIPI DI DATI IN ADA 95

3.3.3

Numeri reali a virgola fissa

In questo paragrafo parliamo brevemente dei tipi di dati reali a virgola fissa, che useremo solo raramente nel seguito del nostro corso. Un tipo di dati reale a virgola fissa è utile se è necessario lavorare con dei numeri reali per i quali la differenza 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 fissa 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 definendo; • ERRORE rappresenta l’errore 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 fissa: 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 fissa sono le stesse di quelle per i tipi di dati reali a virgola mobile. Sia REALE_FISSO un tipo di dati reale a virgola fissa. 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 l’input e l’output di variabili di tipo REALE_FISSO. Esiste un solo tipo di dato reale a virgola fissa predefinito: 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 l’uso di un vincolo di precisione uguale o minore a quello richiesto. Se si definisce un sottotipo di un tipo reale in virgola fissa 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 fissa: 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 fissa 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 l’uso dell’input e output e di qualche attributo per un tipo di dati reale a virgola fissa: -----Nome del file: REALI_FISSI.ADB Autore: Claudio Marsan Data dell’ultima 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 := UNITARIO’DELTA; UNITARIO_IO.Put(Item => y); Ada.Text_IO.New_Line; y := x + y; UNITARIO_IO.Put(Item => y); end Reali_Fissi; Ecco l’output del programma: Dare un numero reale tra 0 e 1: 0.23 0.001 0.231 Un altro esempio di output (notare l’arrotondamento 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 definiti 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 definizioni 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 definiti gli operatori relazionali (=, /=, <, <=, >, >=) e gli operatori in e not in e che il primo elemento definito 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: • T’FIRST: il primo elemento di T; • T’LAST: l’ultimo elemento di T; • T’WIDTH: restituisce un intero indicante il numero di caratteri necessario per stampare sotto forma di stringa un qualsiasi valore di tipo T; • T’POS(x): la posizione di x nella definizione di T; • T’VAL(n): il valore dell’elemento di ordine n in T; • T’SUCC(x): il successore di x in T; • T’PRED(x): il predecessore di x in T; • T’IMAGE(x): la rappresentazione di x sotto forma di stringa; • T’VALUE(x): il valore di cui x è la stringa immagine. Esempio 3.4.2 Il seguente programma mostra l’uso degli attributi per un tipo enumerativo definito dal programmatore: -----Nome del file: ATTRIBUTI_TIPI_DISCRETI.ADB Autore: Claudio Marsan Data dell’ultima 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 => "SEMI’FIRST: "); Semi_IO.Put(Item => SEMI’FIRST); Ada.Text_IO.New_Line;

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

3.5. SOTTOTIPI Ada.Text_IO.Put(Item => "SEMI’LAST: "); Semi_IO.Put(Item => SEMI’LAST); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMI’WIDTH: "); Ada.Integer_Text_IO.Put(Item => SEMI’WIDTH, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMI’POS(CUORI): "); Ada.Integer_Text_IO.Put(Item => SEMI’POS(CUORI), Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMI’VAL(2): "); Semi_IO.Put(Item => SEMI’VAL(2)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMI’SUCC(CUORI): "); Semi_IO.Put(Item => SEMI’SUCC(CUORI)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMI’PRED(CUORI): "); Semi_IO.Put(Item => SEMI’PRED(CUORI)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMI’IMAGE(CUORI): "); Ada.Text_IO.Put(Item => SEMI’IMAGE(CUORI)); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "SEMI’VALUE(""CUORI""): "); Semi_IO.Put(Item => SEMI’VALUE("CUORI")); Ada.Text_IO.New_Line; end Attributi_Tipi_Discreti; Ecco l’output del programma: SEMI’FIRST: QUADRI SEMI’LAST: PICCHE SEMI’WIDTH: 6 SEMI’POS(CUORI): 1 SEMI’VAL(2): FIORI SEMI’SUCC(CUORI): FIORI SEMI’PRED(CUORI): QUADRI SEMI’IMAGE(CUORI): CUORI SEMI’VALUE("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 dell’intervallo in cui si possono scegliere i valori di S. Esempio 3.5.1 Se è definito 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 l’istruzione 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 L’istruzione oggi := prossimo_lavorativo; è lecita. È anche possibile un’istruzione come prossimo_lavorativo := oggi; sebbene questa possa portare ad un errore di run–time se oggi assume un valore al di fuori dell’intervallo LUN..VEN. Ricordiamo che nel package Standard sono predefiniti i sottotipi: • • subtype NATURAL is INTEGER range 0..INTEGER’LAST; subtype POSITIVE is INTEGER range 1..INTEGER’LAST;

3.6

Array

Essenzialmente possiamo considerare gli array come l’implementazione del concetto matematico di vettore n–dimensionale, 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 definite in fase di definizione del tipo di dato; • array non vincolati (unconstrained array), dei quali non si predefinisce 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 Definizione 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 definito nel modo seguente: type COLORE is (ROSSO, GIALLO, BLU, VERDE, BIANCO); Allora il tipo NUMERO_COLORE potrebbe essere definito come type NUMERO_COLORE is array(COLORE range ROSSO..BIANCO) of INTEGER; oppure come type NUMERO_COLORE is array(COLORE) of INTEGER; Riassumendo: per definire 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 dall’indice 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);

L’aggregato di array è una lista nella quale ad ogni componente dell’array 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 l’alternativa others essa deve essere l’ultima. 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 nell’ultima riga di codice del frammento abbiamo usato una espressione qualificata 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 dell’aggregato a causa dell’uso di others). Claudio Marsan Liceo cantonale di Mendrisio, 2002

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

123

È possibile definire anche array di array di array di ... In matematica (in particolare nell’algebra 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 definire 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: • T’FIRST: restituisce il limite inferiore del primo indice; • T’FIRST(n): restituisce il limite inferiore dell’n–esimo indice; • T’LAST: restituisce il limite superiore del primo indice; • T’LAST(n): restituisce il limite superiore dell’n–esimo indice; • T’RANGE: restituisce l’intervallo del primo indice; • T’RANGE(n): restituisce l’intervallo dell’n–esimo indice; • T’LENGTH: restituisce il numero dei valori del primo indice; • T’LENGTH(n): restituisce il numero dei valori dell’n–esimo indice. Esempio 3.6.9 Il seguente programma mostra l’uso di alcuni attributi per un tipo di array vincolato: -----Nome del file: ATTRIBUTI_ARRAY.ADB Autore: Claudio Marsan Data dell’ultima 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 => "VETTORE’FIRST: "); Ada.Integer_Text_IO.Put(Item => VETTORE’FIRST, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTORE’LAST: "); Ada.Integer_Text_IO.Put(Item => VETTORE’LAST, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTORE’LENGTH: "); Ada.Integer_Text_IO.Put(Item => VETTORE’LENGTH, Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTORE’FIRST(2): "); Ada.Integer_Text_IO.Put(Item => VETTORE’FIRST(2), Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTORE’LAST(2): "); Ada.Integer_Text_IO.Put(Item => VETTORE’LAST(2), Width => 0); Ada.Text_IO.New_Line; Ada.Text_IO.Put(Item => "VETTORE’LENGTH(2): "); Ada.Integer_Text_IO.Put(Item => VETTORE’LENGTH(2), Width => 0); Ada.Text_IO.New_Line; end Attributi_Array; Ecco l’output del programma: VETTORE’FIRST: 3 VETTORE’LAST: 12 VETTORE’LENGTH: 10 VETTORE’FIRST(2): 2 VETTORE’LAST(2): 5 VETTORE’LENGTH(2): 4

3.6.2

Array non vincolati

L’esercizio 3.6.1 mette in evidenza il limite degli array vincolati: siccome il prodotto di matrici è definito solo tra matrici di tipo m × p e p × n (e dà come risultato una matrice m × n) nel caso volessimo definire una funzione o una procedura per il calcolo del prodotto di matrici sarebbe necessario definire ben tre tipi di matrici diverse! Con gli array non vincolati non è necessario dare le dimensioni dell’array; dichiarando un array non vincolato si specifica il tipo dell’indice 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 l’indice. 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; ... L’uso degli aggregati è permesso anche con gli array non vincolati. Nel package Standard è predefinito 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 l’assegnazione tra due variabili di tipo array bisogna prestare attenzione a quanto segue: • se l’array è vincolato e le variabili sono dello stesso tipo non ci sono problemi; • se l’array 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 definendo 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 definire 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 definiamo la variabile distanza : DISTANCE_TABLE; e ammettiamo che sia stato definito il package DISTANCE_IO che permette l’output 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 Definizione 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(a’RANGE(1), a’RANGE(2)); begin for i in a’RANGE(1) loop for j in a’RANGE(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 definire le matrici anche come array di array. Esempio 3.6.17 Definizione 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 definizioni date nell’ultimo 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 è sufficiente 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 un’automobile in un ipotetico registro di automobili. Un’auto può essere caratterizzata da molte cose, per esempio: • numero di registrazione; • marca; • anno di fabbricazione; • peso; • potenza. Avendo definito 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 l’informazione 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 specificare 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 all’esempio 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 definire 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 i–esima 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 all’esempio 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 all’esempio 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 Definendo 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 definito nell’esempio 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 un’auto useremo la sintassi seguente: Ada.Text_IO.Put(Item => mia_auto.Proprietario.Numero_AVS); Esercizio 3.7.1 Definire 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à definita 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 indefinite. Esercizio 3.7.2 Definire 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 Definire 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 definire dei tipi di dati che sono array di record. Vediamo due esempi nel seguito. Esempio 3.7.7 Consideriamo la classifica 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 definire 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 classifica serve un array di record del tipo T_CORRIDORE, che possiamo definire 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 fino 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 specificare 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 efficace gli array di record è necessario poter leggere e scrivere il contenuto dei campi su file, altrimenti quando si esce dal programma bisogna digitare nuovamente tutti i dati! Tratteremo i file 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 definire 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 nell’esempio visto), un valore di default. La parte della dichiarazione che segue la parola riservata record è detta parte fissa del record : in essa vengono dichiarate le componenti che sono comuni a tutte le varianti. Esempio 3.7.10 Riferendosi all’esempio 3.7.9 abbiamo: • nome del discriminante: tipo_di_veicolo; • tipo del discriminante: T_VEICOLO; • valore di default: SCONOSCIUTO; • parte fissa: 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 differenti varianti. Quando si dichiara una variabile di tipo record con parte variante viene messo a disposizione solo lo spazio sufficiente per memorizzare una sola variante del record: l’uso 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 l’esecuzione 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 affinché si possano definire 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 definire un valore di default per il record. Le componenti della parte fissa 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 è un’AUTO_PRIVATA. Un discriminante può sempre essere letto. Esempio 3.7.13 L’istruzione 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 all’intero 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_ditta’RANGE loop Print_Info(Item => veicolo_della_ditta(i)); end loop; ... Esercizio 3.7.4 Definire la procedura Print_Info(Item : in VEICOLO) usata nel precedente esempio. Suggerimento: utilizzare un’istruzione 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 finora hanno il seguente punto comune: esse risiedono tutte nella memoria principale del calcolatore. Ciò significa che la cancellazione, volontaria o meno, della memoria provoca la distruzione di queste strutture e del loro contenuto, come d’altra parte del programma che le utilizza. Potrebbe essere necessario conservare dei dati alla fine dell’applicazione che li ha creati, in previsione di un uso futuro come, per esempio, nell’uso 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 file.

4.1

Il concetto di file

Possiamo tradurre in italiano la parola file con archivio o con schedario (in francese si usa il termine fichier, in tedesco si usa il termine Datei ). Fuori dal mondo informatico ognuno di noi ha già manipolato degli archivi (per esempio: l’elenco del telefono, lo schedario di una biblioteca, . . . ). Per accedere ad un elemento particolare dell’archivio bisogna far passare tutti gli elementi dell’archivio o utilizzare una chiave d’accesso se l’archivio è stato ordinato secondo questa chiave (per esempio: ordine alfabetico, ordine di acquisto dei libri, o altro ancora). In informatica un file è una struttura composta di dati, il cui numero non è noto a priori e che risiede in un dispositivo fisico di memorizzazione (hard disk, floppy disk, . . . ). L’accesso a un elemento (a un dato) può essere fatto in vari modi: • sequenzialmente (sequential access): il file viene percorso elemento per elemento partendo dall’inizio finché si trova l’elemento desiderato; • direttamente (direct access): si dà la posizione dell’elemento; • secondo una chiave (access by key): ogni valore della chiave che designa un elemento permette di ottenere l’elemento desiderato. Siccome i files 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 files: • files di testo (text files) contenenti caratteri e leggibili da un essere umano (non solo leggibili ma anche editabili, stampabili, . . . ); 135

136

CAPITOLO 4. FILES IN ADA 95

• files binari (binary files) contenenti del codice binario rappresentante ogni elemento (files di questo tipo dovrebbero essere manipolati solo da programmi!). Esempio 4.1.1 Per illustrare la differenza fra file di testo e file binario consideriamo il caso semplice nel quale vogliamo memorizzare 75 in un file. Il valore 75 sarà memorizzato nel file binario sottoforma di sequenza di bits come 00000000 01001011 (ammesso che ci vogliano due bytes per memorizzare tale valore) mentre in un file di testo sarà rappresentato dai caratteri ’7’ e ’5’ accostati. In Ada 95, come in altri linguaggi di programmazione, si distinguono i termini file (file object) e file esterno (external file). Un file è una variabile di un programma che serve per designare un file esterno. Un file 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 file.

4.2

Files di testo

Se gli elementi di un file sono caratteri (ossia di tipo CHARACTER) il file è detto file di testo. I files di testo sono organizzati in righe, ognuna delle quali termina con un carattere di fine riga; dopo l’ultima riga il file di testo è terminato (fine del file). Possiamo distinguere due tipi di files di testo: 1. files di testo che corrispondono ai dispositivi di input e output del computer; 2. files di testo che sono memorizzati in file esterni. In Ada 95 entrambi i tipi di files 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 file di testo esiste fisicamente nel computer ed ha un nome speciale (si parla anche di file fisico e di nome fisico). Il nome fisico dipende dal sistema operativo in uso. Esempio 4.2.1 Il nome fisico per la stampante (file speciale) collegata alla porta parallela LPT1: nei sistemi MS–DOS è, solitamente, LPR; in un sistema Unix potrebbe essere /dev/lp0 o qualcosa di simile. Per ovviare alla diversità dei nomi fisici dovuti ai diversi sistemi operativi, un programma scritto in Ada 95 lavora con files logici. Per poter utilizzare i files logici di testo bisogna utilizzare il package Ada.Text_IO, nel quale è definito il tipo FILE_TYPE che serve appunto per dichiarare i files logici di testo (ossia per dichiarare variabili di tipo file di testo). Esempio 4.2.2 Nel seguente frammento di programma si dichiara la variabile infile di tipo file 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 significa questo) e quindi il programmatore non può sapere come in realtà appare una variabile come la infile dichiarata nell’esempio 4.2.2. L’unica 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 un’altra e neppure dichiarare costanti di tipo FILE_TYPE. Nel seguito useremo la seguente convenzione: • con file intenderemo file logico in un programma; • con file esterno intenderemo file fisico in un supporto di memorizzazione.

4.2.1

Creazione, apertura e chiusura di un file di testo

In Ada 95 un file di testo può essere letto, scritto o completato alla fine. Si dice che il file è utilizzato, rispettivamente, in modo lettura, modo scrittura o in modo aggiunta. La lettura (read ) di un file consiste nella consultazione del suo contenuto senza apportare alcuna modifica; la scrittura (write) di un file permette di creare un contenuto nuovo, cancellando il vecchio (se questo era presente); l’aggiunta (append ) permette di aggiungere alla fine di un file esistente dei nuovi dati, senza perdere il contenuto già esistente. L’apertura (open) di un file consiste, tra le altre cose, nell’associare una variabile file a un file esterno e a scegliere il modo d’uso (lettura, scrittura, aggiunta) del file. Ciò viene eseguito con la procedura Create se il file è nuovo oppure con la procedura Open se il file 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 file di testo creato oppure il file di testo che viene aperto se già esistente; • Mode è il modo d’uso (o di accesso) del file, per default in scrittura nel caso della creazione del file; • 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 file esterno designato tramite File; • Form è un parametro che permette di fissare le proprietà del file esterno (per esempio il diritto d’accesso). Il valore di default di Name della procedura Create permette di creare un file temporaneo che sarà automaticamente cancellato alla fine del programma. Il valore di default di Form permette di utilizzare le opzioni correnti dell’implementazione, 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 file fisico my_file.txt a cui è associato il file logico new_file. Adesso è possibile scrivere sul file new_file. Osservazione 4.2.1 Attenzione: se un file esistente viene aperto in scrittura (ossia con Mode => Out_File) il contenuto precedente verrà sovrascritto e distrutto. Se un file è associato ad un file esterno mediante una chiamata di Create o di Open, diremo che il file è aperto. Quando si tenta di aprire un file potrebbero verificarsi degli errori, per esempio il file potrebbe già essere aperto oppure non esiste alcun file esterno con il nome passato alla procedura. La funzione Is_Open può essere utilizzata per controllare se un file è aperto. L’intestazione di tale funzione, presente nel package Ada.Text_IO, è la seguente: function Is_Open(File : FILE_TYPE) return BOOLEAN; La chiusura (close) di un file consiste nella soppressione dell’associazione, realizzata all’apertura, tra un file e un file esterno. Essa si effettua tramite la procedura Close, la cui intestazione è: procedure Close(File : in out FILE_TYPE); dove File è il file che deve essere chiuso. È obbligatorio chiudere i files aperti in precedenza, altrimenti potrebbero esserci comportamenti anomali (dipende da sistema a sistema)! Da notare che se un file 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 l’unico posto in un programma dove possono essere trovati nomi di files esterni è nella chiamata della procedura Create oppure della procedura Open; altrimenti sono sempre usati nomi logici di files.

4.2.2

Accesso agli elementi di un file di testo

Per l’input e l’output con i files 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 file di testo è sufficiente aggiungere il nome della variabile di tipo file di testo come primo parametro. Esempio 4.2.4 Il programma seguente mostra come si crea un file di testo e come se ne legge poi il contenuto. -----Nome del file: IO_FILE_DI_TESTO.ADB Autore: Claudio Marsan Data dell’ultima 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 l’output di un programma direttamente alla stampante. Dovremo procedere come segue: stampante : Ada.Text_IO.FILE_TYPE; e poi associare il file 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 fine del file 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 fine della riga corrente nel file di testo che si sta considerando. Esempio 4.2.6 Il programma seguente permette di fare una copia del file di testo (giacente nel corrente direttorio) sorgente.txt nel file di testo copia.txt: -----Nome del file: COPIA_FILE.ADB Autore: Claudio Marsan Data dell’ultima 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 dell’esempio 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 fine di una riga, di passare alla prossima riga. I files 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 file 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 l’istruzione Ada.Float_Text_IO.Put(File => datafile, Item => valore); il numero reale valore viene scritto nel file 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 l’istruzione 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 sufficiente per scrivere il prossimo valore sulla riga corrente. Se non avessimo specificato una lunghezza massima per le righe, allora tutti i valori sarebbero scritti su una stessa riga, in teoria senza fine. Esempio 4.2.9 Nel frammento di programma che segue vogliamo leggere numeri reali da un file di testo contenente numeri reali, per esempio dal file datafile creato in precedenza. Il programma calcola e scrive la media aritmetica dei numeri letti dal file. 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 file 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, l’altra per il numero di telefono). Vogliamo costruire un programma che, letto dal terminale il nome di una persona, va a ricercare nell’elenco il numero di telefono. Useremo l’algoritmo più semplice: il programma parte dall’inizio della lista e legge un nome alla volta finché trova il nome richiesto oppure finché giunge alla fine del file. 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 dell’ultima 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 nell’elenco 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’ nell’elenco!"); end if; end Trova_Numero_di_Telefono; Abbiamo visto che è possibile o leggere un file di testo, scrivere su un file di testo e appendere dei dati alla fine di un file di testo, ma non è possibile alternare le operazioni di scrittura e lettura. In un file di testo la lettura e la scrittura avvengono in modo sequenziale, dall’inizio alla fine del file: non si può tornare indietro ad una determinata posizione nel mezzo del file; si può solo tornare indietro all’inizio del file mediante la procedura Reset (è un’operazione 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 specificare il nome del file, è possibile stabilire se è richiesta la lettura o la scrittura del file. Facendo il reset del file è così possibile cambiare anche il modo di accesso. 2. procedure Reset(File : in out FILE_TYPE); nella quale il file viene riportato all’inizio e il modo d’accesso 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 l’esempio 4.2.10 dell’elenco telefonico e modifichiamo il codice in modo tale da permettere il cambiamento di un numero di telefono dell’elenco. Siccome non è possibile scrivere e leggere contemporaneamente da uno stesso file, verrà creata una copia temporanea del file telefono.lst. Se durante il processo di copia la persona a cui bisogna cambiare il numero di telefono viene trovata nel file telefono.lst, nella copia verrà scritto il nuovo numero di telefono. Poi entrambi i files verrano resettati e il file temporaneo verrà copiato su telefono.lst. Ecco il codice: -----Nome del file: CAMBIA_NUMERO_DI_TELEFONO.ADB Autore: Claudio Marsan Data dell’ultima 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 all’inizio 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’ nell’elenco!"); 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 files di testo

Se usiamo le procedure e le funzioni presenti nel package Ada.Text_IO senza indicare il nome logico del file si intendono, per default, i dispositivi di input e output del terminale (tastiera e schermo). Il terminale è infatti considerato come un insieme di due files di testo, detti standard input e standard output. Questi files 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 file esterno associato a File; la funzione function Mode(File : FILE_TYPE) return FILE_MODE; restituisce il modo d’uso 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 dell’ultima modifica: 14 maggio 2002 Scopo: mostra l’uso 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 l’output 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 file esterno associato a File.

4.3

Files binari

I files binari comprendono tutti i files che non sono files di testo. Il loro contenuto può essere visto come una successione di bits che costituiscono gli elementi del file, sistemati uno dopo l’altro. Come per i files di testo la fine del file si situa dopo l’ultimo elemento. In Ada 95 i files 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 definiti tutti i tipi, sottotipi, procedure, funzioni, . . . che sono necessari alla manipolazione dei files binari. Un file binario si dichiara come una variabile di tipo FILE_TYPE la cui struttura è nascosta all’utente (come d’altronde succedeva per i files di testo). La manipolazione di un file binario richiede la conoscenza del tipo degli elementi del file. Esempio 4.3.1 Vogliamo costruire un file binario sequenziale di INTEGER. Per avere a disposizione le procedure e le funzioni per la manipolazione di simili files 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 file 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 files 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 ...; L’unica 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 un’altra e neppure dichiarare costanti di tipo FILE_TYPE.

4.3.1

Creazione, apertura e chiusura di un file binario

In Ada 95 un file binario può sempre essere letto oppure scritto. Inoltre può essere ancora completato alla fine se il file è sequenziale oppure letto e scritto contemporaneamente se il file è ad accesso diretto. Si dirà così che il file binario è aperto, rispettivamente, in modo lettura, in modo scrittura, in modo aggiunta oppure in modo lettura–scrittura. La lettura (read ) consiste nel consultare il contenuto senza modificarlo; la scrittura (write) permette di creare un nuovo contenuto (eliminando un eventuale vecchio contenuto); l’aggiunta (append ) lascia il contenuto attuale invariato e appende alla fine del file il nuovo contenuto; la lettura– scrittura (read-write) permette di leggere, modificare o aggiungere informazioni in qualsiasi parte del file. L’apertura (open) di un file binario consiste, tra l’altro, nell’associare una variabile di tipo file ad un file esterno e nello scegliere il modo d’uso (accesso sequenziale oppure diretto, lettura, scrittura, aggiunta, lettura–scrittura). Ciò si fa tramite la procedura Create se il file è nuovo oppure tramite la procedura Open se il file è già esistente. Ecco l’intestazione per la procedura Create: • nel caso di un file 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 file 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 l’intestazione della procedura Open: procedure Open(File : Mode Name Form dove: • File è il file che verrà creato, se nuovo, oppure aperto, se già esistente; • Mode è il modo d’uso del file; in fase di creazione il valore di default è Out_File se il file è sequenziale oppure InOut_File se il file è ad accesso diretto; • File_Mode è un tipo enumerativo che ammette i valori: – In_File (lettura), Out_File (scrittura) e Append_File (aggiunta) se il file è sequenziale; – In_File (lettura), Out_File (scrittura) e InOut_File (lettura–scrittura) se il file è ad accesso diretto. • Name è il nome del file esterno associato a File (per default viene creato un file temporaneo che verrà cancellato automaticamente alla fine del file); • Form è un parametro che permette di fissare le proprietà del file esterno. La chiusura (close) di un file binario, come per un file di testo, consiste nella soppressione dell’associazione, attivata all’apertura del file, tra un file e un file esterno. Essa avviene tramite la procedura Close, la cui intestazione è: procedure Close(File : in out FILE_TYPE); È al più tardi alla chiusura del file che il contenuto viene modificato in funzione delle operazioni svolte. in out FILE_TYPE; : in File_Mode; : in STRING := ""; : in STRING := "");

4.3.2

Accesso agli elementi di un file binario sequenziale

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

150 dove: • File è il file sequenziale; • Item è l’elemento 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 file binario sequenziale: -----Nome del file: ESEMPIO_FILE_BINARIO_SEQUENZIALE.ADB Autore: Claudio Marsan Data dell’ultima 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_GIORNO’IMAGE(Item.Giorno)); Ada.Text_IO.Put(Item => " "); Ada.Text_IO.Put(Item => T_MESE’IMAGE(Item.Mese)); Ada.Text_IO.Put_Line(Item => INTEGER’IMAGE(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 files binari sequenziali

La manipolazione di un file binario sequenziale consiste, essenzialmente, nella lettura, nell’uso del contenuto e nella scrittura di un (nuovo) file binario. Qui il file termina dopo l’ultimo elemento (fine del file). Una lettura corretta deve tener conto della fine del file, affinché si eviti di leggere oltre, provocando così un errore. Nel package Ada.Sequential_IO è definita la funzione End_Of_File che restituisce il valore TRUE se è stata raggiunta la fine del file, altrimenti restituisce FALSE. L’intestazione 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 file 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 dell’ultima 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 file binario ad accesso diretto

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

4.3. FILES BINARI

153

ste operazioni possono essere sequenziali ma anche effettuarsi in funzione del valore di un indice. Questo indice è un numero intero compreso tra 1 e un certo limite massimo, imposto dall’implementazione del compilatore Ada 95. Tale indice è di tipo Positive_Count, che è un sottotipo di Count; entrambi sono definiti nel package Ada.Direct_IO. Abbiamo così: type Count is range 0..limite_massimo; subtype Positive_Count is Count range 1..Count’LAST; 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 file ad accesso diretto; • Item è l’elemento letto o scritto; • Element_Type è il tipo di Item; • From è il valore dell’indice, ossia la posizione dell’elemento da leggere; • To è il valore dell’indice, ossia la posizione di scrittura dell’elemento. In ogni caso l’indice viene incrementato di 1 dopo ogni lettura o scrittura. Essendo arbitrario il valore dell’indice è possibile leggere o scrivere in una qualsiasi parte di un file binario ad accesso diretto. L’indice può essere inoltre modificato 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 file ad accesso diretto; • To è il nuovo valore dell’indice. Osservazione 4.3.1 Il nuovo valore dell’indice, assegnato tramite la procedura Set_Index, può oltrepassare la posizione dell’ultimo elemento del file! Per sapere qual è la posizione attuale dell’indice si può utilizzare la funzione Index, la cui intestazione è: function Index(File : in FILE_TYPE) return Positive_Count; Sia Set_Index che Index sono definite nel package Ada.Direct_IO.

4.3.5

Manipolazione di files binari ad accesso diretto

Nel package Ada.Direct_IO sono presenti anche le funzioni End_Of_File, che funziona come nel caso di files binari sequenziali, e la funzione Size che restituisce il numero di elementi di un file binario ad accesso diretto. L’intestazione di quest’ultima 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 l’indice dopo la fine del file 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 file non possono esserci “buchi” e quindi tra l’elemento che era l’ultimo del file e il nuovo elemento verranno creati, se necessario, degli elementi il cui contenuto non è definito. Un file 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 file binario ad accesso diretto: -----Nome del file: COPIA_FILE_BINARIO_AD_ACCESSO_DIRETTO.ADB Autore: Claudio Marsan Data dell’ultima 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_GIORNO’IMAGE(Item.Giorno)); Ada.Text_IO.Put(Item => " "); Ada.Text_IO.Put(Item => T_MESE’IMAGE(Item.Mese)); Ada.Text_IO.Put_Line(Item => INTEGER’IMAGE(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 file binario ad accesso diretto non possono essere array non vincolati e nemmeno record con parti varianti senza valori di default.

4.4

Altre osservazioni sull’uso dei files

Per ognuna delle tre categorie di files (files di testo, files binari sequenziali e files 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 file File e cancella il file esterno ad esso associato dal dispositivo di memorizzazione. L’intestazione 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 file da manipolare e Mode è il nuovo modo d’uso. Tale procedura posiziona il file in modo tale che la lettura ricominci dal primo elemento per i modi lettura e lettura–scrittura, o che la scrittura parta dall’inizio per i modi lettura–scrittura e scrittura, o che la scrittura riprenda dopo l’ultimo elemento per il modo aggiunta. La seconda forma dela procedura Reset permette di cambiare il modo del file, ciò potrebbe essere utile, per esempio, per rileggere gli elementi di un file che sono appena stati scritti. Inoltre per un file binario ad accesso diretto, l’uso della procedura Reset comporta l’assegnazione all’indice 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 file 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 (run–time 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 un’applicazione provoca la sua fine brutale (macchina in stallo, files corrotti, bombe, errori irreversibili di sistema, . . . ). Un programma scitto in Ada 95 può recuperare e trattare certi errori di esecuzione se chi concepisce l’applicazione 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 Un’eccezione 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 dall’intervallo permesso; • dare un input errato a un programma o a un sottoprogramma; • ... Quando si scrive un programma l’algoritmo deve essere il più possibile chiaro e facile da eseguire. Se però ad ogni passo dell’algoritmo si fanno tutti i controlli necessari per prevenire ogni possibile tipo di errore e ogni tipo di evento anormale, allora l’algoritmo diventa molto complesso e difficile da seguire. Per questo motivo in Ada 95 c’è un meccanismo per gestire gli eventi eccezionali all’esterno della parte di codice relativa alla programmazione dell’algoritmo.

5.2

Eccezioni predefinite

Quando capita un errore nell’esecuzione di un programma normalmente il programma termina e viene visualizzato un messaggio d’errore. 157

158

CAPITOLO 5. GESTIONE DELLE ECCEZIONI IN ADA 95

Esempio 5.2.1 Il seguente è il messaggio d’errore 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 file calcolatrice.adb è capitato un evento eccezionale di tipo (constraint error). In Ada 95 sono predefiniti 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 è l’eccezione che si riscontra più spesso; • PROGRAM_ERROR: capita quando sussiste la violazione di una struttura di controllo (per esempio il raggiungimento dell’end finale 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 d’arresto errata che provoca troppi livelli di ricorsione; • TASKING_ERROR: può capitare nella programmazione parallela (non verrà trattato). Le quattro eccezioni elencate sopra sono definite nel package STANDARD e quindi sono automaticamente definite 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 dell’ultima modifica: 6 giugno 2002 Scopo: solleva un’eccezione 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 dell’ultima modifica: 6 giugno 2002 Claudio Marsan Liceo cantonale di Mendrisio, 2002

5.2. ECCEZIONI PREDEFINITE -- Scopo: solleva un’eccezione 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 l’argomento 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 nell’esempio precedente, in fase di compilazione non viene segnalato alcun avvertimento di CONSTRAINT_ERROR. Quando capita un’eccezione (tecnicamente si dice che viene sollevata un’eccezione, in inglese: exception raising) l’esecuzione del programma termina subito. Se non si prendono delle precauzioni nel programma, esso terminerà in modo anormale con un messaggio d’errore che cita il tipo d’eccezione che ha provocato l’arresto del programma. Esempio 5.2.4 Il seguente programma stampa le potenze di un intero, scelto dall’utente, fino ad un certo esponente, pure scelto dall’utente: -----Nome del file: ESPONENTI_SENZA_ECCEZIONI.ADB Autore: Claudio Marsan Data dell’ultima 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 l’esponente 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 NATURAL’FIRST..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 l’utente dà un intero fuori dall’intervallo ammesso per gli INTEGER verrà sollevata l’eccezione 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 l’utente dà un esponente negativo allora verrà sollevata un’eccezione 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 l’esponente piu’ grande: -4

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

5.3. TRATTAMENTO DI UN’ECCEZIONE

161

raised CONSTRAINT_ERROR : esponenti_senza_eccezioni.adb:19 • se l’utente dà un esponente nullo allora verrà sollevata un’eccezione 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 l’esponente piu’ grande: 0

raised CONSTRAINT_ERROR : esponenti_senza_eccezioni.adb:27 • se l’esponente è abbastanza grande verrà sollevata un’eccezione alla linea commentata con (3) poiché il valore dell’espressione 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 l’esponente 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 un’eccezione

Non è mai piacevole vedere un’applicazione che termina brutalmente a causa di un messaggio d’errore (nemmeno quando il messaggio appare in una finestra colorata o con una bomba accesa e una musichetta!). Il trattamento di un’eccezione (exception handling) in Ada 95 consiste in: • intercettare l’eccezione per impedire che questa risalga fino al sistema operativo; • ristabilire una situazione normale sopprimendo o evitando la causa dell’errore. Osservazione 5.3.1 Attenzione! È sempre meglio vedere un messaggio d’errore piuttosto che ottenere dei risultati sbagliati nell’esecuzione di un’applicazione. Bisogna reagire e sopprimere l’eccezione solo se l’applicazione è capace di gestirla e di ritrovare alla fine uno stato coerente. A livello di codice il trattamento di un’eccezione consiste nel completare la fine del corpo di una funzione, di una procedura o di un blocco immediatamente prima dell’end finale con una parte, detta manipolatore di eccezioni (exception handler ). Il manipolatore di eccezioni ha la forma seguente, molto simile a quella dell’istruzione 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 l’ultima istruzione del corpo e prima dell’end finale; • scelta_1, scelta_2, . . . sono formati da uno o più valori d’eccezione separati da barre verticali; • trattamento_1, trattamento_2, . . . sono composti da uno o più istruzioni; • la parola riservata others rappresenta tutti gli altri casi d’eccezione; questo ramo è opzionale (se presente è, ovviamente, l’ultimo 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ù difficile. Quando un’eccezione è sollevata nelle istruzioni del corpo contenente un simile manipolatore di eccezioni, l’esecuzione del programma viene trasferita al ramo identificato dal nome dell’eccezione oppure, se presente, dall’ultimo ramo che inizia con when others. L’esecuzione del programma prosegue poi normalmente nel trattamento del ramo e l’eccezione viene eliminata. Se il nome dell’eccezione non fa parte delle possibili scelte e se non c’è un ramo che inizia con when others, allora l’eccezione si propaga come se il manipolatore di eccezioni non fosse presente. Esempio 5.3.1 Il programma seguente è la riscrittura del programma dell’esempio 5.2.4 con il trattamento delle eccezioni: -----Nome del file: ESPONENTI_CON_ECCEZIONI.ADB Autore: Claudio Marsan Data dell’ultima 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 l’esponente 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 UN’ECCEZIONE 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 NATURAL’FIRST..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 modifiche eseguite l’eccezione sollevata alla linea commentata con (1) provoca il trasferimento dell’esecuzione al manipolatore di eccezioni della funzione che provvederà a visualizzare sullo schermo un messaggio e a restituire il valore 1 se l’esponente 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 l’esponente 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 l’esponente 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 dell’esecuzione 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 l’esponente piu’ grande: 25 Ecco la lista delle potenze: 1 12 144 248832 2985984 35831808 Potenza troppo grande! Fine! 1728 429981696 20736

L’eccezione che viene sollevata dalla linea commentata con (2) non viene trattata dal manipolatore di eccezioni della procedura: infatti un’eccezione 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 l’esecuzione di un’istruzione all’interno del manipolatore di eccezioni solleva un’eccezione allora anche quest’ultima si propaga. Non è possibile tornare al punto del programma dove si è prodotta l’eccezione (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ì l’esecuzione del corpo. Esempio 5.3.2 Sostituendo la funzione Esponente_Massimo nel programma dell’esempio 5.3.1 con la seguente risolviamo il problema dell’input errato dell’esponente (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 l’esponente 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 UN’ECCEZIONE 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 l’uso del blocco (senza parte dichiarativa) non sarebbe stato possibile riportare l’esecuzione del programma all’inizio della funzione. Ecco un esempio di output: Calcolo delle potenze di un intero fino ad un esponente massimo. Dare un numero intero: 2 Dare l’esponente piu’ grande: qw Esponente errato! Ricominciare! Dare l’esponente piu’ grande: 123456789012 Esponente errato! Ricominciare! Dare l’esponente piu’ grande: -5 Esponente negativo! Ricominciare! Dare l’esponente 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 un’eccezione

Un’eccezione può essere dichiarata nella parte dichiarativa, non importa in quale posizione; la sua forma è: nome_eccezione : exception; dove: • nome_eccezione è un identificatore (nota: è possibile usare anche più identificatori, separati da virgole); • la parola riservata exception è qui usata per stabilire la natura dell’identificatore. Le regole di visibilità per un’eccezione sono le stesse che si hanno per variabili e costanti; per esempio il nome di un’eccezione non è conosciuto all’esterno del sottoprogramma in cui è definito (in ogni caso se viene sollevata un’eccezione i suoi effetti possono diffondersi all’esterno del sottoprogramma nel quale era stata definita). Esempio 5.4.1 L’eccezione Time_Up può essere dichiarata mediante l’istruzione Time_Up : exception; Questa è una dichiarazione e dunque va messa insieme alle altre dichiarazioni, di variabili, per esempio. In effetti la forma è simile alla dichiarazione di una variabile, con la sola differenza 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 Definendo 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 un’eccezione

La propagazione di un’eccezione avviene allo stesso modo, sia nel caso di un’eccezione predefinita che nel caso di un’eccezione definita dal programmatore. Il sollevare un’eccezione non predefinita non può avvenire automaticamente ma deve essere eseguito tramite una particolare istruzione, l’istruzione raise, la cui sintassi è: raise nome_eccezione; dove nome_eccezione designa l’eccezione da sollevare. Esempio 5.5.1 La seguente funzione divide due numeri di tipo RATIONAL (vedi esempio 5.4.3) e solleva l’eccezione 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 un’eccezione è identico per ogni tipo di eccezione; tuttavia quanto detto nel paragrafo 5.3 va completato. Dapprima esiste la forma semplificata raise;, senza menzione esplicita dell’eccezione. Questa forma può essere usata solo in un manipolatore d’eccezioni e serve per sollevare di nuovo l’eccezione 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 l’eccezione 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 l’uscita 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 l’eccezione 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 l’eccezione in corso grazie alle tre funzioni seguenti (tutte restituiscono un valore di tipo STRING): • Exception_Name restituisce il nome dell’eccezione corrente; • Exception_Message produce un messaggio di una riga correlato all’eccezione; • Exception_Information fornisce il nome dell’eccezione, il messaggio e altre informazioni ancora. Il messaggio e le altre informazioni dipendono dall’implementazione e devono servire a identificare la causa e il posto in cui è stata sollevata l’eccezione. Queste tre funzioni hanno bisogno di un parametro che permetta di accedere all’eccezione; tale parametro deve identificare 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 l’esempio 5.6.1: ... exception when CONSTRAINT_ERROR => ... -- prepara l’uscita 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 l’eccezione 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 dell’ultima 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 sull’eccezione:"); 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 sull’eccezione:"); 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

0

0

0

0

0

• Dare un intero: 0 F(0) = NaN Informazioni sull’eccezione: 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 sull’eccezione: 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 sull’eccezione: 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 files che contengono definizioni 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 difficoltà nella manutenzione e nell’aggiornamento del programma. In Ada 95, come d’altronde 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 ininfluenti 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 un’altra costruzione, oltre ai sottoprogrammi, che permette di raggruppare parti di programma in un’unità 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 specificata. I dettagli che non sono essenziali per l’utilizzatore del package possono essere nascosti nel package. Un package può essere sviluppato e compilato separatamente. Esempio 6.1.1 Quando un prodotto complicato come un’automobile deve essere costruito è necessario costruire le diverse parti separatamente e poi assemblarle in seguito; in caso contrario la fabbricazione diventerebbe troppo complicata. Affinché le varie parti costruite separatamente possano essere assemblate è necessaria una specificazione 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 un’automobile è 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 l’aiuto della specificazione del package si può stabilire come un package deve essere inserito in un’altra 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 grafica o altro ancora. Questi packages possono essere scritti da programmatori diversi oppure essere packages standard di un’implementazione di Ada 95. In Ada 95 ci sono anche dei packages detti child packages (traducibile con packages figli ): questi possono essere utilizzati per estendere dei packages esistenti senza ricompilazione oppure per dividere un sistema grande e complesso in sottosistemi.

6.2

Specificazione di un package

Ogni package ha una specificazione (package specification). Essa può essere vista come l’esposizione di ciò che è presente nel package e che viene offerto al potenziale utilizzatore. La specificazione del package specifica dunque l’interfaccia del package con altri parti del programma. La specificazione 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 specificazione 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 figure 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 figure. La specificazione 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. L’AMBIENTE DI PROGRAMMAZIONE DI ADA 95 function Circonferenza(raggio : T_LUNGHEZZA) return T_LUNGHEZZA; end Planimetria;

173

La specificazione del package Planimetria mostra così all’utente che sono stati definiti 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 specificazione di un package in un file con estensione .ads (ada specification). Esempio 6.2.2 Riferendoci all’esempio 6.2.1 il file che conterrà la specificazione del package Planimetria dovrà chiamarsi planimetria.ads.

6.3

L’ambiente 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 sull’ambiente 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 specificazione è 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à sufficiente scrivere all’inizio del programma la riga with Planimetria; Il compilatore andrà a cercare nella libreria Ada il package Planimetria e tutto ciò che è definito in esso sarà utilizzabile nel nostro programma: Per esempio: -----Nome del file: CALCOLA_AREA.ADB Autore: Claudio Marsan Data dell’ultima modifica: 8 giugno 2002 Scopo: calcola l’area 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 file 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 files calcola_area.o e calcola_area.ali: il primo è il codice oggetto del programma, il secondo è un file di testo che contiene le informazioni della libreria Ada (l’estensione .ali sta per Ada Library Information). A titolo di curiosità ne riportiamo il contenuto; per il significato di ogni singola riga si rimanda alla consultazione dello GNAT for Windows NT: User’s 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 specificazione di un package abbiamo definito un oggetto, per fare riferimento ad esso in un programma dobbiamo premettere al nome dell’oggetto il nome del package seguito da un punto. Esempio 6.4.2 Nell’esempio 6.4.1 abbiamo definito il tipo T_AREA nella specificazione del package Planimetria. Nel programma Calcola_Area abbiamo definito 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 è definito nel package Planimetria. Quanto descritto sopra è detto selezione. In generale se P è un package e N è un qualsiasi oggetto definito nella specificazione 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, definite nel package predefinito 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 all’inizio di una unità di compilazione allora la sua validità sarà estesa a tutta l’unità di compilazione; se invece essa è messa nella parte dichiarativa di un sottoprogramma la sua validità sarà limitata al sottoprogramma in cui è definita. 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 all’interno della procedura e dunque la riga Put_Line(Item => "Salve!"); nel programma principale genererebbe un errore. Osservazione 6.4.1 Lo svantaggio nell’uso 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 differenti: solo la selezione permette di stabilire con facilità la reale provenienza dell’oggetto. 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 all’utente. Dettagli che l’utente 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 specificazione è l’interfaccia del package; • il corpo è l’implementazione 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 dall’esterno 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 l’esecuzione. Esempio 6.5.1 Il codice seguente è il codice del corpo del package Planimetria (vedi esempio 6.2.1 per la specificazione del package) -----Nome del file: PLANIMETRIA.ADB Autore: Claudio Marsan Data dell’ultima 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 è definita all’interno del corpo del package Planimetria e dunque non è accessibile (e nemmeno visibile!) dall’esterno 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 l’ordine di compilazione possiamo dire quanto segue: • prima si compila la specificazione 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. D’altra parte se la specificazione 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 specificazione e non il corpo del package. Esempio 6.5.2 Il seguente package (o meglio: specificazione del package) contiene la definizione di costanti atomiche: ------Nome del file: COSTANTI_ATOMICHE.ADS Autore: Claudio Marsan Data dell’ultima 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 specificazione 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 significato è 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 specificazione del package. Esempio 6.5.3 Soluzione dell’esercizio 6.5.1. Dapprima vediamo la specificazione 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 dell’ultima 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 dell’ultima 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 all’inizio 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; Infine il programma di test: -----Nome del file: TEST_NUMERI_COMPLESSI.ADB Autore: Claudio Marsan Data dell’ultima 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 definire 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 definire il package Long_Integer_Math_Lib che conterrà tali funzioni. Ecco la specificazione del package: ------Nome del file: LONG_INTEGER_MATH_LIB.ADS Autore: Claudio Marsan Data dell’ultima 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 dell’ultima 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 l’algoritmo 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 l’input e l’output di un numero razionale anche da un file 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 specificazione: -----Nome del file: RATIONAL_NUMBERS.ADS Autore: Claudio Marsan Data dell’ultima 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 dell’ultima 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 := s’LENGTH; 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_INTEGER’VALUE(s(1..length_of_s)); q.den := 1; else q.num := LONG_INTEGER’VALUE(s(1..(slash_found - 1))); q.den := LONG_INTEGER’VALUE(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 specificazione del package Rational_Numbers_IO: -----Nome del file: RATIONAL_NUMBERS_IO.ADS Autore: Claudio Marsan Data dell’ultima modifica: 8 giugno 2002 Scopo: specificazione del package per l’input e l’output 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 dell’ultima modifica: 8 giugno 2002 Scopo: corpo del package per l’input e l’output 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_INTEGER’VALUE(a_as_string(1..length_of_a)); aa.den := 1; else aa.num := LONG_INTEGER’VALUE(a_as_string(1..(slash_found - 1))); aa.den := LONG_INTEGER’VALUE(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 specificazione 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 dell’ultima 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 un’operazione: "); Ada.Text_IO.Get(Item => operazione); Ada.Text_IO.Skip_Line; Ada.Text_IO.Put(Item => "Dare un’altra 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 è l’albero gerarchico dei packages disponibili nell’implementazione 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

L’albero 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 all’indirizzo web http://home.pacbell.net/nma123 Per avere ragguagli sui diversi packages e sul loro contenuto basta consultare la guida on–line 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 files separati che andranno compilati separatamente. Nel programma principale (ammettiamo che, per comodità, si chiami Main) bisogna indicare l’intestazione del sottoprogramma e farla seguire dalla parola riservata separate; in generale la sintassi è: function XYZ(...) return ... is separate; oppure procedure XYZ(...) is separate; Nel file che contiene la definizione del sottoprogramma bisogna scrivere, prima dell’intestazione del sottoprogramma, separate(Main). Il sottoprogramma sarà poi utilizzabile solo dall’unità a cui è collegato. Osservazione 6.8.1 Se vogliamo memorizzare in un file separato la routine XYZ e usarla poi nel programma principale Main i files dovranno avere, nell’implementazione Gnat del compilatore Ada 95, i nomi seguenti: MAIN.ADB per il file contenente il programma principale e MAIN-XYZ.ADB per il file contenente il codice della routine XYZ. Esempio 6.8.1 Riprendiamo l’esempio 2.4.3, scrivendo la procedura Swap in un file separato. Il file SCAMBIA_SEPARATI.ADB contiene il programma principale: -----Nome del file: SCAMBIA_SEPARATI.ADB Autore: Claudio Marsan Data dell’ultima 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 file SCAMBIA_SEPARATI-SWAP.ADB contiene la definizione della procedura Swap: -----Nome del file: SCAMBIA_SEPARATI-SWAP.ADB Autore: Claudio Marsan Data dell’ultima 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 file eseguibile con il compilatore GNAT 3.13p è sufficiente compilare il file 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 l’esempio 6.8.1. Il file SCAMBIA_LIBRERIA.ADB contiene il programma principale mentre il file SWAP.ADB contiene la definizione della procedura Swap. Ecco il contenuto di SCAMBIA_LIBRERIA.ADB -----Nome del file: SCAMBIA_LIBRERIA.ADB Autore: Claudio Marsan Data dell’ultima 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 file 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 dell’ultima 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 l’istruzione with Swap. L’istruzione 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, definita nell’esempio 2.3.7; modificarlo 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 all’utente i dettagli dell’implementazione (in pratica il corpo del package); • incapsulamento dei dati (data encapsulation), ossia il fornire all’utente 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, first in last out). Il package che costruiremo deve fornire all’utente: • una pila in uno stato noto (per esempio vuota); • aggiungere un elemento in cima alla pila (Push); • leggere e togliere l’elemento in cima alla pila (Pop); Claudio Marsan Liceo cantonale di Mendrisio, 2002

6.9. PARADIGMA MODULARE • leggere, senza togliere, l’elemento in cima alla pila (Peek ); • svuotare la pila (Clear ); • gestione degli errori con le eccezioni. Costruiremo una pila di CHARACTER. Questa è la specificazione del package: -----Nome del file: PILA.ADS Autore: Claudio Marsan Data dell’ultima 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 file del corpo del package (implementazione): -----Nome del file: PILA.ADB Autore: Claudio Marsan Data dell’ultima 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 dell’ultima 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, nell’ordine i file seguenti: 1. PILA.ADS; 2. PILA.ADB; 3. TEST_PILA.ADB;

213

e poi si linkano i files, in modo da ottenere il file eseguibile TEST_PILA.EXE. Notiamo nuovamente che i dettagli dell’implementazione sono nascosti all’utente. 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 definito dall’utente: con esso vengono specificati 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 dell’astrazione dei dati; con Ada 95 il supporto di tale paradigma è facilitato grazie alla possibilità di definire tipi di dati privati o riservati. Nella specificazione di un package possono essere utilizzati i cosiddetti tipi privati, la cui implementazione appare in un’apposita sezione della specificazione, invisibile all’esterno del package, detta parte privata. I tipi privati servono per impedire all’utente 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, l’utente può utilizzare il tipo privato T per definire oggetti con i quali compiere le operazioni specificate dal package stesso (mediante i sottoprogrammi). Quando si definisce un tipo privato T le uniche operazioni disponibili su oggetti di tipo T sono: • l’assegnazione ( := ); • l’uguaglianza ( = e /= ); • l’appartenenza ( in e not in ); Liceo cantonale di Mendrisio, 2002 Claudio Marsan

214 • il passaggio di parametri;

CAPITOLO 6. PACKAGES IN ADA 95

• le operazioni definite nella parte non privata del package. Osservazione 6.10.1 Se si definisce il tipo T come limited private allora le istruzioni di assegnazione e di uguaglianza non saranno disponibili! Non sarà così possibile definire 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 specifica per questo scopo). Esempio 6.10.1 Riprendiamo l’esempio dello stack visto nel paragrafo precedente. Il codice che avevamo scritto in precedenza ci permetteva di operare con un unico stack; definendo invece un nuovo tipo di dato privato PILA nel nuovo package Pile potremo costruire più stacks, occultando comunque sempre i dettagli dell’implementazione. Ecco l’interfaccia del nuovo package: ------Nome del file: PILE.ADS Autore: Claudio Marsan Data dell’ultima 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 dall’esterno (si dice che si trova nella parte visibile della specificazione), mentre quello che segue private non è accessibile dall’esterno del package; in particolare: • Push, Pop, Peek, pila_piena e pila_vuota sono accessibili dall’esterno del package Pile; • dim_pila, VETT_PILA, vp e cima non sono accessibili dall’esterno 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 l’uso di tipi array anonimi all’interno di un record. Ecco l’implementazione del nuovo package: -----Nome del file: PILE.ADB Autore: Claudio Marsan Data dell’ultima 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 dell’ultima 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 l’interfaccia seguente: -----Nome del file: NUMERI_COMPLESSI.ADS Autore: Claudio Marsan Data dell’ultima 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 un’espressione del tipo x.Im := x.Im + 1.0; -- "+" è un operatore per i FLOAT

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

Definendo 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 dell’ultima 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 l’implementazione di queste funzioni all’interno 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. Un’istruzione 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 definizione 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 specifica è 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 definito 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 specifici sottoprogrammi o specifici 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 effettuate 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 significa, evidentemente, che non possiamo effettuarne il passaggio, così come si fa con i parametri, al momento dell’esecuzione. 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 specificazione 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 specificazione sono indicati separatamente. La specificazione 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 un’ulteriore procedura effettiva 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 dell’unità generica. Esempio 7.1.2 Riprendiamo l’esempio 7.1.1. L’attualizzazione 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 quest’ultima 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 significati (tecnicamente si dice che essa è stata sovraccaricata (overloaded )) che si differenziano 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 dell’esempio 7.1.1. Ecco il file di specificazione: Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.1. DICHIARAZIONI E ATTUALIZZAZIONI -----Nome del file: EXCHANGE.ADS Autore: Claudio Marsan Data dell’ultima 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 file di implementazione: -----Nome del file: EXCHANGE.ADB Autore: Claudio Marsan Data dell’ultima modifica: 8 giugno 2002 Scopo: contiene l’implementazione 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 dell’ultima 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; L’output 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 l’istanziazione (ossia la concretizzazione) avviene passando i parametri generici attuali. Tali parametri non devono essere confusi con i parametri della procedura! Abbiamo visto come definire funzioni e procedure generiche; ora vogliamo fare la stessa cosa per l’insieme dei sottoprogrammi di un package, definendo così un package generico. Esempio 7.1.4 Riprendiamo l’esempio degli stacks (vedi 6.10.1) definendo ora un tipo PILA generico e parametrizzato nel numero degli elementi. Ecco il codice relativo alla specificazione: -- Nome del file: PILA_GENERICA.ADS -- Autore: Claudio Marsan Claudio Marsan Liceo cantonale di Mendrisio, 2002

7.1. DICHIARAZIONI E ATTUALIZZAZIONI -- Data dell’ultima 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 all’implementazione: -----Nome del file: PILA_GENERICA.ADB Autore: Claudio Marsan Data dell’ultima modifica: 8 giugno 2002 Scopo: contiene l’implementazione 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 dell’ultima 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 qualificata per evitare conflitti (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 fissati nella fase di istanziazione. Per esempio nel package Pila_Generica la dimensione dei vettori veniva fissata in fase di vincolo del tipo PILA; un’altra possibilità potrebbe essere quella di fissare la dimensione dell’array 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. L’istanziazione 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, all’interno di un sottoprogramma o di un package generico, si può dare per scontato unicamente che per il tipo T siano definite l’uguaglianza e l’assegnazione. Da questo momento nell’unità generica, il tipo T si comporta proprio come un tipo riservato esterno al package che lo definisce. Oltre a poter definire un parametro tipo generico della forma private è possibile definire anche altre forme. Ecco l’elenco: • 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 fissa; • 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 file di specificazione: ------Nome del file: NEXT.ADS Autore: Claudio Marsan Data dell’ultima 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 file di implementazione: ------Nome del file: NEXT.ADB Autore: Claudio Marsan Data dell’ultima modifica: 8 giugno 2002 Scopo: contiene l’implementazione 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 = DISCRETE’LAST then return DISCRETE’FIRST; else return DISCRETE’SUCC(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 dell’ultima 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 l’output 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 nell’integrazione di funzioni. In Ada 95 l’unica possibilità di passare un sottoprogramma come parametro ad un’altro sottoprogramma è quello di usare i sottoprogrammi come parametri generici. Per far ciò l’intestazione (ossia la specificazione) del sottoprogramma deve essere preceduta dalla parola riservata with (da non confondere con l’istruzione 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 nell’intervallo [0, 1] con 10 passi d’integrazione 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 all’utente 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 specificazione: ------Nome del file: INTEGRAZIONE.ADS Autore: Claudio Marsan Data dell’ultima 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 l’implementazione -----Nome del file: INTEGRAZIONE.ADB Autore: Claudio Marsan Data dell’ultima 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 infine il programma di test: ------Nome del file: TEST_INTEGRAZIONE.ADB Autore: Claudio Marsan Data dell’ultima 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 l’output 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à); definire 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 dell’esercizio precedente. Dapprima il file di specificazione: ------Nome del file: VETTORI3D.ADS Autore: Claudio Marsan Data dell’ultima 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 file di implementazione: ------Nome del file: VETTORI3D.ADB Autore: Claudio Marsan Data dell’ultima 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 l’ascissa 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 l’ordinata 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 infine un programma di test: ------Nome del file: TEST_VETTORI3D.ADB Autore: Claudio Marsan Data dell’ultima 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 definire, nella specificazione, il tipo generico ELEMENT come segue: generic type ELEMENT is private; Purtroppo ciò non funziona: basti pensare che per l’input e l’output 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 definiti dal programmatore: non è allora più evidente il significato di taluni operatori (per esempio quelli aritmetici). L’unica soluzione consiste dunque nell’implementare vari packages!

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 8 Strutture di dati dinamiche
I puntatori rappresentano un argomento tra i più difficili, soprattutto per i programmatori neofiti, 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 l’uomo. 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 dell’oggetto. Potremmo dire che il nome dell’oggetto è il nome dello spazio in memoria. Lo spazio in memoria viene creato quando l’oggetto viene dichiarato ed esiste fino alla fine dell’esecuzione dell’unità di programma nel quale appare la sua dichiarazione. Un simile oggetto è detto statico; esso non può né essere creato né essere distrutto durante l’esecuzione 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 l’esecuzione 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 (l’alternativa statica è un array sufficientemente grande, necessariamente sovradimensionato, con evidente spreco di memoria!). Altri esempi di strutture di dati dinamiche sono le code, gli alberi, i grafi, . . . Le strutture di dati statiche hanno i seguenti vantaggi: • realizzazione semplice; • accesso, in generale, efficace dal punto di vista del tempo–processore. Il loro principale inconveniente risiede nella loro natura statica: l’impossibilità di aumentare o di diminuire di grandezza durante l’esecuzione poiché questa è fissata durante la compilazione (si parla di allocazione statica della memoria, static memory allocation). 241

242

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

L’allocazione dinamica della memoria (dynamic memory allocation) permette di eliminare questa restrizione. L’idea è 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 l’allocazione 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 definite da un indirizzo e da una grandezza. Generalmente, nei linguaggi di programmazione la gestione degli indirizzi di memoria si effettua 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, l’allocazione di una parte di memoria sufficientemente 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 verifiche (di tipo) classiche in caso di un’assegnazione 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 definito; • 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 definisce 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 definisce 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. L’ALLOCATORE 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 definiscono 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 un’unica 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 dell’allocatore 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 dell’allocatore new.

8.3

L’allocatore 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 l’allocatore new è utilizzato come una funzione, quale risultato restituisce? Il risultato comprende l’indirizzo 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 un’assegnazione. Così facendo una variabile puntatore farà riferimento o punterà ad una variabile puntata. Esempio 8.3.2 Alcune assegnazioni del risultato dell’allocatore new (si usano i tipi di dati definiti 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 dell’allocatore 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

L’esperienza 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: l’inizio di una simile freccia indica dov’è registrato l’indirizzo mentre la fine designa la variabile puntata che possiede questo indirizzo; • il valore particolare null è rappresentato da una linea obliqua. Esempio 8.4.1 Nella figura 8.1 si può vedere la rappresentazione schematica delle dichiarazioni fatte nell’esempio 8.3.2 mentre nella figura 8.2 si può vedere la rappresentazione schematica delle assegnazioni fatte nell’esempio 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 figura 8.3 rappresenta schematicamente la seguente istruzione: Liceo cantonale di Mendrisio, 2002 Claudio Marsan

246 intP

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

5

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 figura 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

L’accesso ad una variabile puntata è possibile solo per mezzo di una variabile puntatore che funge da intermediaria o, indirettamente, per mezzo di un’altra 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). L’accesso alla totalità della variabile puntata si effettua aggiungendo il suffisso all alla variabile puntatore che la individua, mentre l’accesso ad una componente si fa usando la notazione abituale di accesso a questa componente, usando il nome della variabile puntatore come prefisso (seguita o non seguita da all). Ovviamente l’espressione derivante dall’accesso è 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 definiti 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 dall’allocatore 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 all’istruzione sopra! for i in vp.all’RANGE loop -- 6 Ada.Integer_Text_IO.Put(Item => vp(i)); end loop; ap.numero := 10; -- 7 ap.all.numero := 10; -- identica all’istruzione sopra! ap.tabella := vp.all; -- 8 for i in ap.tabella’RANGE 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 definita nell’esempio 8.4.3. Con essa possiamo accedere al nuovo oggetto che è stato creato (pPers punta all’oggetto) e modificare per esempio alcuni campi o fare altre operazioni utilizzando la notazione dell’esempio seguente: pPers.peso := 75.0; Ada.Text_IO.Put(Item => pPers.nome); Se è richiesto l’intero 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 all’oggetto verso il quale punta il puntatore. Esempio 8.5.3 Consideriamo la definizione 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 tp2’RANGE 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

L’assegnazione tra variabili di tipo puntatore si effettua come di consueto; la sola limitazione è che la variabile puntatore e l’espressione devono essere dello stesso tipo puntatore. Attenzione a non confondere l’assegnazione di puntatori con l’assegnazione di variabili puntate: l’assegnazione di un puntatore sostituisce l’indirizzo contenuto nella variabile assegnata mentre l’assegnazione di una variabile puntata cambia il valore di quest’ultima senza modificarne l’indirizzo. Per l’istruzione 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 dell’oggetto 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 l’eccezione Constraint_Error ... Claudio Marsan Liceo cantonale di Mendrisio, 2002

8.6. ASSEGNAZIONI

249

Nelle figure 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

1

Figura 8.9: Riga 6 L’istruzione in 7 non è valida poiché le due variabili puntatore sono di tipo diverso. L’istruzione in 8 potrebbe essere valida ma solleverebbe un’eccezione di tipo Constraint_Error poiché il puntatore fp possiede il valore null e quindi non designa alcuna variabile puntata. Osservazione 8.6.1 L’istruzione della riga 8 nell’esempio 8.6.1 rivela che l’assegnazione e, generalmente, ogni tentativo di modificare dei puntatori o delle variabili puntate cela delle trappole nelle quali cascano anche i programmatori più esperti. Queste trappole sono tra le più perfide poiché il compilatore non le scopre: esse si rivelano infatti solo in fase di esecuzione del codice. Tra le trappole classiche, le più diffuse sono: • tentativo d’accesso ad una variabile puntata quando questa non esiste (come nell’istruzione della riga 8 nell’esempio 8.6.1); • la confusione tra assegnazione di puntatori e di variabili puntate (come nelle istruzioni delle righe 2 e 6 nell’esempio 8.6.1 ); • la creazione di variabili puntate inaccessibili. Un oggetto dinamico cessa di esistere appena l’esecuzione abbandona la parte del programma nella quale il tipo accesso all’oggetto è 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 significa che non accadrà mai che un puntatore possa puntare ad una parte di memoria che è stata liberata (può invece capitare, durante l’esecuzione, che esistano degli oggetti dinamici verso i quali non punta alcun puntatore). Una variabile puntata diventa inaccessibile se non è più referenziata da un’altra 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 l’informazione contenuta nella variabile inaccessibile doveva ancora servire all’applicazione. 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 l’esecuzione 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 l’attributo ACCESS, per esempio: fp := x’ACCESS; Quando si usa l’attributo ACCESS il compilatore controlla che la variabile e il tipo accesso non siano dichiarati in modo tale che puntatori ad una variabile che cessa d’esistere possano continuare ad esistere (nel nostro esempio la variabile x cessa di esistere quando l’esecuzione 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 l’attributo UNCHECKED_ACCESS. Ovviamente un puntatore generale può puntare anche ad un oggetto che è stato allocato mediante l’allocatore 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 : PI’ACCESS; 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 l’uso di puntatori generali: -----Nome del file: PUNTATORI_GENERALI.ADB Autore: Claudio Marsan Data dell’ultima 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 := x’ACCESS; 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 := PI’ACCESS; -- errore! fpc := PI’ACCESS; 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 l’output 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 figura 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 figura). Notiamo che: • è relativamente facile aggiungere elementi nuovi nella lista, in qualunque posizione (spesso, nelle applicazioni, si aggiungono all’inizio oppure alla fine 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 definire un tipo di dati adatto all’esempio 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 indefinito quando il tipo LIST_ELEMENT viene dichiarato; d’altra parte se sistemiamo la dichiarazione di LINK prima di quella di LIST_ELEMENT, allora sarà LIST_ELEMENT a restare indefinito 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 l’elemento della lista viene creato. La situazione attuale è illustrata nella figura 8.13. LIST null 5

Figura 8.13: Costruzione della lista concatenata: primo passo Naturalmente era più semplice inizializzare l’elemento in fase di creazione con l’istruzione LIST := new LIST_ELEMENT’(null, 5); Ammettiamo ora di voler aggiungere, all’inizio 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 figura 8.14. LIST null 3 5

Figura 8.14: Costruzione della lista concatenata: secondo passo Era naturalmente possibile aggiungere un elemento alla lista con l’istruzione 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 all’inizio 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 fintanto che l’ultimo 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 fino alla fine della lista (p ha il valore null) oppure fintanto che la persona richiesta è stata trovata. Da notare che è importante l’uso dell’operatore booleano cortocircuitato and then nell’espressione booleana dell’istruzione 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 run–time poiché p non punta da nessuna parte. Come visto in precedenza è facile inserire un nuovo elemento all’inizio della lista. Se invece vogliamo inserire un nuovo elemento alla fine della lista dobbiamo attraversare la lista finché non raggiungiamo la sua fine. Esempio 8.7.5 La seguente procedura crea un nuovo elemento e lo sistema alla fine 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 all’ultimo 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 all’ultimo. 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 fine di una lista. Sfruttando la ricorsione si può usare il seguente algoritmo: se la lista è vuota allora il nuovo elemento è l’unico elemento della lista ed è così messo all’inizio della lista, altrimenti il nuovo elemento deve essere messo alla fine della coda. Esempio 8.7.7 La seguente è una procedura ricorsiva per aggiungere un elemento alla fine 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 l’albero sono collegati da rami. Esempio 8.8.1 Gli alberi genealogici, i diagrammi ad albero nel calcolo delle probabilità, l’organigramma di un’azienda, . . ., sono tipiche strutture ad albero.

8.8.1

Definizione

Un albero è un insieme finito 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 definizione di albero che abbiamo dato sopra è una definizione ricorsiva. Esempio 8.8.2 Consideriamo l’albero della figura 8.15. Allora A è la radice dell’albero, mentre E, F, G, I e J sono le foglie dell’albero.

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 figura 8.16 sono così da considerare diversi. Liceo cantonale di Mendrisio, 2002 Claudio Marsan

260

CAPITOLO 8. STRUTTURE DI DATI DINAMICHE

A

B

C

D

E

F

G

H

I

J

Figura 8.15: Albero

A

A

B

C

C

B

Figura 8.16: Alberi binari diversi Esempio 8.8.4 L’albero della figura 8.17 è un albero binario. L’attraversamento di un albero può essere definito 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

A

B

C

D

E

F

G

Figura 8.17: Albero binario • modo prefix (preordine): nodo, sottoalbero sinistro, sottoalbero destro; • modo infix (inordine): sottoalbero sinistro, nodo, sottoalbero destro; • modo postfix (postordine): sottoalbero sinistro, sottoalbero destro, nodo. Esercizio 8.8.1 Attraversare l’albero dell’esempio 8.8.4 nei tre modi. Osservazione 8.8.2 Le espressioni aritmetiche possono essere ridotte ad alberi binari da attraversare in modo infix; nelle calcolatrici Hewlett–Packard con notazione polacca inversa le stesse espressioni possono essere ridotte ad alberi binari da attraversare in modo postfix; nel linguaggio di programmazione Lisp le stesse espressioni possono essere ridotte ad alberi binari da attraversare in modo prefix.

8.8.3

Ordinamento con albero binario

Vediamo ora come ordinare, mediante un albero binario, una lista di numeri interi introdotti da tastiera dall’utente. Definiamo 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 definiti due puntatori in ogni record di tipo TREE_ELEMENT. La prima procedura che trattiamo serve per inserire un nuovo elemento nell’albero: se il nuovo elemento che vogliamo inserire è minore del primo elemento dell’albero esso verrà sistemato nel ramo sinistro dell’albero, 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 dell’albero 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 dell’albero 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 l’albero in modo infix, 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 dell’ultima 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 dell’albero 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 graficamente l’albero 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 paragrafi 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 l’elemento in cima allo stack; • Peek: leggere l’elemento 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 definita 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 dell’ordinamento di un vettore di numeri interi definiti 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

L’ordinamento 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 finché 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 l’ordinamento 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 l’ordinamento della lista. Esercizio 9.1.1 La lista di interi 10 29 23 30 72 9 6

è da ordinare usando l’ordinamento 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_input’LAST-1 loop p := i; for j in i+1..v_input’LAST 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 è definito come all’inizio del capitolo. Claudio Marsan Liceo cantonale di Mendrisio, 2002

9.2. ORDINAMENTO A BOLLE

267

9.2

Ordinamento a bolle

L’ordinamento 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 nell’ultima posizione della lista; 4. si ripete ora lo stesso procedimento per i primi n − 1 elementi della lista: alla fine 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 l’ordinamento 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 l’1; 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 è l’elemento 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 l’ordinamento 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_input’LAST 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 è definito come all’inizio del capitolo. L’ordinamento 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_input’LAST; 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 è definito come all’inizio del capitolo.

9.3

Ordinamento per inserzione

L’ordinamento 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 l’ordinamento 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 l’ordinamento 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_input’LAST 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 è definito come all’inizio del capitolo.

9.4

Quick sort

L’algoritmo 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_input’LAST); 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 l’algoritmo presentato sopra.

9.5

Efficienza

L’efficienza 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 l’efficienza 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 l’interfaccia del package: -----Nome del file: SORT.ADS Autore: Claudio Marsan Data dell’ultima 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 dell’ultima 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_input’LAST-1 loop p := i; for j in i+1..v_input’LAST 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_input’LAST 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_input’LAST; 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_input’LAST 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_input’LAST); 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à d’esecuzione di una procedura dobbiamo far partire un cronometro prima dell’esecuzione della procedura e arrestare lo stesso appena la procedura è terminata. Nel package Ada.Calendar esiste la funzione Clock che restituisce l’ora 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 dell’ultima 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 all’ordinamento della lista 1, 2, 3, . . . , n; • la seconda colonna si riferisce all’ordinamento della lista n − 1, n − 2, n − 3, . . . , 2, 1, 0; • la terza colonna si riferisce all’ordinamento della lista 1, 1, 1, . . . , 1, 1; • la quarta colonna si riferisce all’ordinamento della lista −1, 2, −3, 4, . . . , −(n − 1), n; • la quinta colonna si riferisce all’ordinamento 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 séquentiell 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 l’example. De Boeck & Larcier, Paris, 1999(2). [8] Feldmann Michael B., Koffman Elliot B. Ada 95 Problem Solving and Program Design. Addison–Wesley,Reading, 1997(2). [9] Frosini Graziano, Lazzerini Beatrice. Ada: un linguaggio per la programmazione avanzata. Addison–Wesley, Milano, 1990. [10] Guerid Abdelali, Breguet Pierre, Röthlisberger Henri. Algorithmes et structures de données 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 d’Ingènieurs de l’État 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. Addison–Wesley, Harlow, 1997(3). [16] Smith James F., Frank Thomas S. Introduction to Programming Concepts and Methods with Ada. McGraw–Hill, New York, 1994. [17] Zaffalon Luigi, Breguet Pierre. Programmation concurrente et temps réel avec Ada 95. Presse Polytechniques Romandes, Lausanne, 1999.

287