Sei sulla pagina 1di 293

Claudio Marsan

Programmazione in Ada 95

Lavoro di maturit 2002 Liceo cantonale di Mendrisio

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

ultima revisione: 11 marzo 2003

A Questo testo stato scritto dallautore con L TEX2.

Claudio Marsan Liceo cantonale di Mendrisio Via Agostino Maspoli CH6850 Mendrisio

e-mail: claudio.marsan@liceomendrisio.ch

INDICE

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

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

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

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

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

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

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

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

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

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

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

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

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

iv 1.9

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

vi

INDICE

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 1 Introduzione al linguaggio Ada 95


1.1 Introduzione

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

1.1.1

Genesi

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

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

1.1.2

Dove viene usato Ada?

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

1.1.3

Specicazioni di Ada

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

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

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

1.2

Alcune caratteristiche di Ada

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

4 la gestione delle eccezioni;

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

1.3
1.3.1

Primi passi con Ada 95


Il pi semplice programma in Ada 95

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

1.3. PRIMI PASSI CON ADA 95

1.3.2

Hello, World!

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

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

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

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

1.3.3

Come creare un programma scritto in Ada 95?

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

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

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

1.3.4

Input e output

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

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

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

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

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

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

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

1.3.5

Un programma un po pi complicato

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

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

Liceo cantonale di Mendrisio, 2002

10

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

1.3.6

Identicatori

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

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

11

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

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

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

until use when while with xor

limited loop mod

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

12

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

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

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

1.3. PRIMI PASSI CON ADA 95

13

1.3.7

Variabili e costanti

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

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

14

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

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

1.3.8

Tipi di dati

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

1.3.9

Il tipo INTEGER

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

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

15

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

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

16

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

1.3. PRIMI PASSI CON ADA 95

17

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

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

18

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

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

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

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

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

19

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

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

=> un => =>

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

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

20

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

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

Dai un numero di tipo INTEGER: 23

Hai Hai Hai Hai Hai Hai Hai

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

23 23 23

Un altro esempio di output:

Dai un numero di tipo INTEGER: -1999

Hai Hai Hai Hai Hai Hai Hai

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

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

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

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

1.3. PRIMI PASSI CON ADA 95

21

Hai Hai Hai Hai Hai Hai Hai

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

1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890

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

01 02

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

22

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

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

Dare un numero intero di 3 cifre: 329

La somma dei cubi delle cifre di 329 vale: 764

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

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

1.3. PRIMI PASSI CON ADA 95

23

raised CONSTRAINT_ERROR : a-tiinio.ads:61

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

1.3.10

Il tipo FLOAT

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

24

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

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

=> 5, Exp

=> 0);

01 02 03

=> 3); => 4);

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

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

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

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

25

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

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

26 ------

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

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

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

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

27

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

28

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

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

-- sbagliato! -- corretto

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

1.3. PRIMI PASSI CON ADA 95

29

1.3.11

I tipi enumerativi

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

30

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

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

1.3. PRIMI PASSI CON ADA 95

31

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

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

32

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

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

1.3.12

Alcuni attributi utili

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

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

33

30 gennaio 2002 variabile di tipo enumerativo e IMAGE Windows 2000

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

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

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

34

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

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

1.4. ISTRUZIONI CONDIZIONALI

35

1.3.13

Flusso di controllo

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

1.4

Istruzioni condizionali

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

1.4.1

Listruzione if ...

then ...

end if;

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

36

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

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

1.4.2

Listruzione if ...

then ...

else ...

end if;

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

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

1.4. ISTRUZIONI CONDIZIONALI

37

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

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

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

1.4.3

Operatori relazionali
Gli

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

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

38

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

1.4.4

Operatori logici

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

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

1.4.5

Listruzione if ...

then ...

elsif ...

end if;

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

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

39

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

nota <= 5.0 then Claudio Marsan

Liceo cantonale di Mendrisio, 2002

40

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

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

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

1.4.6

Selezione multipla

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

1.4. ISTRUZIONI CONDIZIONALI

41

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

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

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

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

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

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

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

42

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

1.5

Istruzioni di ripetizione

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

1.5. ISTRUZIONI DI RIPETIZIONE

43

1.5.1

Cicli semplici

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

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

44

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

1.5.2

Il ciclo for

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

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

45

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

1.5.3

Il ciclo while

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

46

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

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

1.5. ISTRUZIONI DI RIPETIZIONE

47

1.5.4

Listruzione exit

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

48

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

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

49

Valore di k (for): -53

Valore di k (while): -53

Valore di k (loop): -53

1.5.5

Cicli nidicati

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

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

50

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

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

51

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

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

1.6

Listruzione null

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

52

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

1.7

Listruzione goto

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

1.8. BLOCCHI DI ISTRUZIONI

53

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

1.8

Blocchi di istruzioni

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

54

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

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

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

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

55

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

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

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

56

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

1.9

Qualche cenno sugli array

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

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

57

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

58

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

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

1.9. QUALCHE CENNO SUGLI ARRAY

59

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

:= := := :=

3.1417; 3.1298; 3.1287; 3.1423;

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

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

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

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

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

VETTORE VETTORE VETTORE VETTORE

is := := :=

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

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

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

61

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

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

62

CAPITOLO 1. INTRODUZIONE AL LINGUAGGIO ADA 95

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 2 Procedure e funzioni in Ada 95


2.1 Introduzione

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

64

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

2.2

Funzioni matematiche predenite

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

FLOAT; FLOAT; FLOAT; FLOAT;

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

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

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

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

65

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

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

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

66

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

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

2.3

Funzioni

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

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

67

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

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

68

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

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

69

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

70

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

2.4

Procedure

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

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

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

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

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

71

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

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

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

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

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

73

10 0

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

74

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

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

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

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

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

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

2.5. SOVRACCARICARE FUNZIONI E PROCEDURE

75

2.5

Sovraccaricare funzioni e procedure

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

76

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

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

2.6

Parametri di default

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

-- (1) -- (2)

: : : :

in in in in

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

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

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

77

2.7

Sottoprogrammi ricorsivi

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

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

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

78 2 * Factorial(1);

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

n = 2, 3, . . .

2.8

Esempi di algoritmi

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

2.8. ESEMPI DI ALGORITMI

79

2.8.1

Calcolo del massimo comune divisore

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

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

80

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

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

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

2.8. ESEMPI DI ALGORITMI

81

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

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

82

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

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

2.8.2

Divisione per tentativi

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

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

83

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

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

= 150;

= 75;

= 25;

= 5;

= 1;

84

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

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

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

85

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

2.8.3

Il crivello di Eratostene

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

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

86

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

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

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

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

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

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

87

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

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

88

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

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

2.8.4

Lalgoritmo di Sundaram

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

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

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

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

89

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

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

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

90

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

with Ada.Text_IO, Ada.Integer_Text_IO;

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

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

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

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

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

2.8. ESEMPI DI ALGORITMI

91

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

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

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

92

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

2.8.5

Il metodo della fattorizzazione di Fermat

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

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

93

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

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

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

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

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

94

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

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

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

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

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

95

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

2.8.6

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

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

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

(x0 + x1 );

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

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

96

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

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

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

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

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

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

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

97

EPSILON ok a, b a1, a2 zero

: : : : :

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

-- errore tollerato

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

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

98

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

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

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

2.8.7

Il metodo di Newton

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

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

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

2.8. ESEMPI DI ALGORITMI end F;

99

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

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

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

EPSILON ok x_iniziale zero

: : : :

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

-- errore tollerato

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

100 end if; end loop;

CAPITOLO 2. PROCEDURE E FUNZIONI IN ADA 95

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

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

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

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 3 Tipi di dati in Ada 95


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

3.1

Astrazione dei dati

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

102

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

3.2. ANCORA SUI TIPI INTERI

103

3.2

Ancora sui tipi interi

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

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

104

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

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

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

Claudio Marsan

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

105

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

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

=> n, Width => 0);

=> n, Width => 0);

=> n, Width => 0);

=> n, Width => 0);

=> n, Width => 0);

=> n, Width => 0);

2 1 2 -2 -1 -2

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

106

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

3.2.1

Input e output di interi

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

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

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

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

107

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

3.2.2

Tipi interi non segnati

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

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

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

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

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

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

109

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

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

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

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

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

3.3

Ancora sui tipi reali

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

3.3.1

Numeri reali a virgola mobile

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

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

111

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

112

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

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

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

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

113

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

3.3.2

Attributi per i numeri reali in virgola mobile

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

114

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

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

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

115

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

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

CAPITOLO 3. TIPI DI DATI IN ADA 95

3.3.3

Numeri reali a virgola ssa

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

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

117

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

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

3.4

Tipi discreti

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

118

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

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

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

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

119

3.5

Sottotipi

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

120 subtype S is T range A..B;

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

3.6

Array

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

3.6. ARRAY

121

3.6.1

Array vincolati

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

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

CAPITOLO 3. TIPI DI DATI IN ADA 95

is array(1..3) of FLOAT;

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

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

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

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

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

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

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

123

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

with Ada.Text_IO, Ada.Integer_Text_IO;

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

124 procedure Attributi_Array is

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

-- vincolato

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

3.6.2

Array non vincolati

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

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

125

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

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

(*) (**)

126

CAPITOLO 3. TIPI DI DATI IN ADA 95

3.6.3

Array multidimensionali

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

Berlin London Madrid Paris Roma

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

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

127

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

3.6.4

Array di array

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

128

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

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

3.7

Record

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

: : : : :

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

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

3.7. RECORD mia_auto : T_AUTO;

129

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

130

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

: : : : : :

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

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

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

131

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

3.7.1

Array di record

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

-- in minuti

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

132

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

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

3.7.2

Record con parte variante

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

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

133

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

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

134

CAPITOLO 3. TIPI DI DATI IN ADA 95

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

Claudio Marsan

Liceo cantonale di Mendrisio, 2002

CAPITOLO 4 Files in Ada 95


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

4.1

Il concetto di le

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

136

CAPITOLO 4. FILES IN ADA 95

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

4.2

Files di testo

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

4.2. FILES DI TESTO

137

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

4.2.1

Creazione, apertura e chiusura di un le di testo

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

in in in in

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

138 Esempio 4.2.3 Mediante

CAPITOLO 4. FILES IN ADA 95

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

4.2.2

Accesso agli elementi di un le di testo

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

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

4.2. FILES DI TESTO

139

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

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

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

140 Ada.Text_IO.New_Line;

CAPITOLO 4. FILES IN ADA 95

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

Claudio Marsan

4.2. FILES DI TESTO

141

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

142

CAPITOLO 4. FILES IN ADA 95

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

4.2. FILES DI TESTO

143

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

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

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

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

CAPITOLO 4. FILES IN ADA 95

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

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

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

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

145

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

Liceo cantonale di Mendrisio, 2002

Claudio Marsan

146

CAPITOLO 4. FILES IN ADA 95

4.2.3

Altre manipolazioni possibili con i les di testo

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

with Ada.Text_IO;

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

4.3. FILES BINARI

147

Ada.Text_IO.Put(Item => "File standard di output: "); Ada.Text_IO.Put_Line