Concetti Fondamentali di FORTRAN 90

Transcript

Concetti Fondamentali di FORTRAN 90
 APPUNTI DEL CORSO DI LABORATORIO DI CALCOLO AVANZATO Concetti fondamentali di FORTRAN 90 MARCO LIMONGI
Istituto Nazionale di Astrofisica – Osservatorio Astronomico di Roma
1. CONCETTI FONDAMENTALI DI FORTRAN 90 Sviluppare un programma in fortran implica i seguenti tre passi: • Scrivere il sorgente. Cioe’, attraverso un editor di testo, scrivere un file ascii che contenga tutte le istruzioni da eseguire • Compilare. Cioe’ trasformare le istruzioni in linguaggio macchina • Eseguire il Programma. La sequenza di comandi da digitare in ambiente bash e’: $emacs programma.f90 & (editing del programma) $ifort [eventuali opzioni] programma.f90 (compilazione del programma) $./a.out (esecuzione del programma) oppure $ifort [eventuali opzioni] programma.f90 –o eseguibile
$./eseguibile
1.1.
Struttura di un programma in Fortran Un programma Fortran ha generalmente la seguente struttura: • Sezione dichiarativa: che include il nome del programma (o della routine, o della funzione), il tipo e il nome delle variabili utilizzate. • Sezione esecutiva: che include le istruzioni che descrivono le azioni che il programma deve compiere. • Sezione conclusiva: che e’ la dichiarazione di fine del programma (o della routine, o della funzione) 1 Program nomeprogramma
! SEZIONE DICHIARATIVA
Implicit none
....dichiarazioni di variabili
:
! SEZONE ESECUTIVA
....istruzioni
! SEZIONE CONCLUSIVA
End Program
1.2.
Sezione dichiarativa La sezione dichiarativa e’ la prima parte del programma e consiste in una serie di dichiarazioni (o definizioni) preliminari. 1.2.1. Tipi semplici intrinseci. Il fortran 90 mette a disposizione 5 tipi di dati intrinseci: - INTEGER Æ tipo numerico per gli interi - REAL Æ tipo numerico per i reali - COMPLEX Æ tipo numerico per i numeri complessi (non trattato in questo corso) - LOGICAL Æ tipo per la gestione dei valori logici - CHARACTER Æ tipo per la rappresentazione di stringhe di caratteri 1.2.2. Variabili. Le variabili nella sezione dichiarativa vengono definite attraverso le istruzioni di dichiarazione. La dichiarazione associa ad una determinata variabile un determinato tipo, che ne definisce le caratteristiche. Per ogni tipo si avra’ in generale una dichiarazione differente. 2 Tipo INTEGER. Questo tipo puo’ utilizzare 1, 2, 4 o 8 bytes. L’intervallo di definizione dipendera’ dal numeri di bytes utilizzati. Percio’ la sintassi per la dichiarazione di una variabile intera sara’: -
INTEGER(KIND=1)
INTEGER(KIND=2)
INTEGER(KIND=4)
INTEGER(KIND=8)
::
::
::
::
variabile
variabile
variabile
variabile
(1 byte, valori da ‐27 a 27‐1) (2 bytes, valori da ‐215 a 215‐1) (4 bytes, valori da ‐231 a 231‐1) (8 bytes, valori da ‐263 a 263‐1) Tipo REAL. Questo tipo puo’ utilizzare 4, 8, o 16 bytes. L’intervallo di definizione e la precisione dipendera’ dal numeri di bytes utilizzati. Percio’ la sintassi per la dichiarazione di una variabile reale sara’: - REAL(KIND=4) :: variabile
(4 bytes, valori da 10‐38 a 1038 circa in modulo, precisione 8 cifre decimali) - REAL(KIND=8) :: variabile
(8 bytes, valori da 10‐323 a 10308 circa in modulo, precisione 16 cifre decimali) - REAL(KIND=16) :: variabile
(16 bytes, valori da 10‐4966 a 104932 circa in modulo, precisione 32 cifre decimali) Tipo LOGICAL. Questo tipo e’ utilizzato per rappresentare i valori logici VERO o FALSO. In generale questo tipo puo’ utilizzare, come gli interi, 1, 2, 4 o 8 bytes. La sintassi per la dichiarazione di una variabile di tipo logico e’: -
LOGICAL(KIND=1)
LOGICAL(KIND=2)
LOGICAL(KIND=4)
LOGICAL(KIND=8)
::
::
::
::
variabile
variabile
variabile
variabile
(1 byte) (2 bytes) (4 bytes) (8 bytes) Tipicamente si utilizza la dichiarazione con 1 byte. Tipo CHARACTER. Questo tipo e’ utilizzato per rappresentare sequenze di caratteri (stringhe) e la sintassi generale per la dichiarazione di variabili stringhe e’: - CHARACTER(LEN=n) :: variabile
3 (n = numero di caratteri) 1.2.3. Costanti Le costanti possono essere utilizzate sia nella sezione dichiarativa che in quella esecutiva. Se utilizzate nella sezione dichiarativa la sintassi generale sara’ - TIPO(KIND=n),PARAMETER :: costante=valore dove, al solito, il tipo e’ uno di quelli definito sopra. Se, invece, le costanti sono utilizzate nella sezione esecutiva, allora il loro tipo sara’ definito implicitamente. Ad esempio: 0, +28, -8540 0.E0, -3.3E2,1.0E-2
0.D0, -3.3D2,1.0D-2
.TRUE., .FALSE. ’ciao’, “ciao” Costanti di tipo INTEGER Costanti di tipo REAL(KIND=4) Costanti di tipo REAL(KIND=8) Costanti di tipo LOGICAL Costanti di tipo CHARACTER 1.2.4. Vettori. Un vettore (a array) e’ una sequenza di variabili tutte dello stesso tipo. Queste variabili vengono memorizzate in posizioni contigue di memoria. Si puo’ accedere ad una di queste variabili direttamente tramite un indice (o piu’ in generale una n‐upla di indici). La dichiarazione di un vettore avviene aggiungendo al nome tipo la sua dimensione. - TIPO(KIND=n,LEN=n),DIMENSION(m) :: nome dove m deve essere un intero costante o una costante definita come parameter. Ad esempio: REAL(KIND=8),DIMENSION(3) :: a
dichiara un vettore di 3 reali, accessibili singolarmente come a(1), a(2), a(3)
Gli array possono avere piu’ indici (dimensioni). Il rango di un array e’ il numero di dimensioni dell’array stesso. Ad esempio REAL(KIND=8),DIMENSION(4,6) :: b
dichiara una matrice 4x6 i cui elementi sono accessibili singolarmente come a(i,j).
4 Gli indici degli array possono avere diversi valori di minimo e massimo. Percio’ la dichiarazione piu’ generale di un array e’: - TIPO(KIND=n,LEN=n),DIMENSION([estremo_inferiore:]estr
emo_superiore) :: nome Ad esempio. INTEGER(KIND=4),DIMENSION(4:10) :: b
definisce un vettore b le cui compenenti vanno da b(4) a b(10). Come le altre variabili un array puo’ essere inizializzato nella sezione dichiarativa. Ad esempio: REAL(KIND=8),DIMENSION(3) :: forza=(/3.2d0,-1.7d0,0.5d0/) N.B. Il numero di elementi fra i delimitatori / deve coincidere con quello degli elementi dell’array. E’ possibile definire array la cui dimensione e’ allocata dinamicamente, cioe’ durante l’esecuzione. Lo spazio viene allocato solo quando viene incontrata l’istruzione ALLOCATE. La funzione ALLOCATED informa se l’array e’ stato allocato o meno. La funzione DEALLOCATE libera lo spazio allocato per l’array. Per utilizzare queste funzioni, l’array deve essere definito com ALLOCATABLE, cioe’: - TIPO(KIND=n,LEN=n),ALLOCATABLE,DIMENSION(:) :: nome o anche piu’ semplicemente - TIPO(KIND=n,LEN=n),ALLOCATABLE :: nome(:) Percio’ le istruzioni: REAL(KIND=4),ALLOCATABLE :: b(:)
REAL(KIND=4),ALLOCATABLE :: a(:,:) dichiarano rispettivamente un vettore b e una matrice a di dimensioni indefinite. Per l’allocazione e la deallocazione dinamica della memoria degli array rimandiamo al paragrafo sulla sezione dichiarativa. 5 1.2.5. Blocchi di memoria etichettati. I blocchi di memoria etichettati, cioe’ provvisti di nomi, vengono definiti da una istruzione di specificazione nella sezione dichiarativa del programma che assume la forma generica - COMMON /nome/nlista dove nome e’ il nome globale del blocco COMMON e nlista e’ la lista dei nomi delle variabili e dei nomi dei vettori che sono nell’area di memoria etichettata nome separati dalla virgola. A questa area di memoria puo’ accedere qualsiasi unita’ o sottoprogramma (vedi dopo) del programma principale semplicemente inserendo la stessa istruzione COMMON e preservando l’ordine delle variabili. Un disallineamento delle variabili provochera’ un errore in compilazione. Ovviamente una variabile puo’ comparire solo in un COMMON all’interno dello stesso programma, sottoprogramma o unita’. Se in un COMMON compare un vettore, la sua dimensione non puo’ essere allocata dinamicamente ma deve essere specificata. 1.2.6. Definizione dei valori iniziali. L’istruzione DATA assegna un valore iniziale ad una variabile prima che il programma entri nella parte esecutiva. Questa istruzione, quindi, si trova nella sezione dichiarativa del programma. La sintassi dell’istruzione DATA e’ la seguente: - DATA nlist1/clist1/,nlist2/clist2/.... dove nlist e’ una lista di nomi di variabili e clist e’ un elenco di costanti. L’effetto di questa istruzione, quindi, e’ quello di assicurare che ogni variabile della lista abbia un assegnato valore iniziale specificato dalla corrispondente costante. Ad esempio l’istruzione: DATA A/0.1d0/,B/0.2d0/,N/3/ dara’ alle variabili A e B (reali) i valori iniziali 0.1 e 0.2, e alla variabile N (intera) il valore iniziale 3. 6 1.3.
Sezione esecutiva. La sezione esecutiva e’ costituita da una serie di istruzioni. 2.3.1. Espressioni Le espressioni numeriche sono in generale costituite da operandi numerici ed operatori numerici. Il risultato di una espressione numerica e’ generalmente un singolo valore numerico. Gli operatori numerici sono i seguenti: Operatore ** *
/
+
‐
Funzione
Elevamento a potenza
Moltiplicazione
Divisione
Somma
Sottrazione
Le operazioni tra numeri interi hanno come risultato numeri interi. L’operazione eseguita sul risultato e’ un troncamento. Cioe’: 3/4=0, 4/4=1, 20/4=5, 21/4=5
Le operazioni tra numeri reali hanno come risultato numeri reali. Attenzione, pero’, numero reale non vuol dire appartenente ad un continuo. In generale il risultato di una operazione e’ una approssimazione. Cioe’ 3.E0/4.E0=0.75E0, 4.E0/4.E0=1.E0, 1.E0+1.E-16=1.E0
In operazioni miste tra reali ed interi i numeri interi vengono convertiti in reali. Attenzione pero’ che la conversione avviene in ogni singolo sottocalcolo. 1.E0+1/4=1.E0, 1.E0+1.E0/4=1.25E0
Le espressioni relazionali consistono in due (o piu’) espressioni i cui valori vengono confrontati tra loro per valutare se e’ verificata o meno la relazione stabilita dall’operatore relazionale. Gli operatori relazioni sono: 7 Operatore < <=
==
/=
> >=
Relazione
Minore di
Minore di o uguale a Uguale a
Diverso da
Maggiore di
Maggiore di o uguale a
Il risultato dell’espressione relazionale sara’ .TRUE. o .FALSE. se la relazione specificata dall’operatore relazionale sara’ soddisfatta o meno. Le espressioni logiche consistono in uno o piu’ operatori logici e operandi logici, numerici o relazionali. Gli operatori logici sono: Operatore
.AND.
Esempio
A .AND. B
.OR.
A .OR. B
.EQV.
A .EQV. B
.NEQV.
A .NEQV. B
.NOT.
.NOT. A
8 Relazione
L’espressione e’ vera se A e B sono vere L’espressione e’ vera se A o B, o entrambe, sono vere L’espressione e’ vera se A e B sono entrambe vere o entrambe false L’espressione e’ vera se A o B sono vere, ma l’espressione e’ falsa se entrambe A e B sono vere L’espressione e’ vera se A e’ falsa, e falsa se A e’ vera. 2.3.2. Istruzioni di assegnazione Il simbolo = non e’ il simbolo di uguaglianza, ma di assegnazione. In generale, quindi, in una istruzione di assegnazione avremo a sinistra del simbolo = il nome di una variabile, a destra del simbolo = una qualsiasi espressione valida. A=100
Assegna alla variabile A il valore 100 I=I+1
Aggiunge 1 alla variabile I e assegna il risultato alla variabile I 2.3.3. Allocazione dinamica Come gia’ detto in precedenza e’ possibile definire degli array la cui dimensione non e’ fissata a priori, ma viene stabilita dinamicamente durante l’esecuzione del programma, a seconda delle necessita’. Per fare questo, oltre a definire l’array di tipo ALLOCATABLE, e’ necessario scrivere l’istruzione - ALLOCATE(nomevettore(n))
dove n e’ la dimensione del vettore. Per definire un’intervallo degli indici l’istruzione sara’ - ALLOCATE(nomevettore(n1:n2))
dove n1 e n2 sono l’indice inferiore e superiore del vettore, rispettivamente. Se ad essere allocata e’ una matrice, allora l’istruzione sara’: - ALLOCATE(nomematrice(n1:n2,m1:m2))
La generalizzazione di questo comando a matrici con piu’ indici e’ evidente. Ovviamente i valori degli indici non sono in generale noti al momento della compilazione ma possono essere definiti durante l’esecuzione del programma. Dopo l’utilizzo e’ opportuno deallocare la memoria con il comando - DEALLOCATE(nomevettore(n))
- DEALLOCATE(nomevettore(n1:n2))
- DEALLOCATE(nomematrice(n1:n2,m1:m2))
9 Se si vuole verifica l’allocazione di un vettore (o una matrice) si puo’ utilizzare il comando: - ALLOCATED(nomevettore(n))
che restituisce un valore logico .TRUE. se il vettore e’ stato allocato o .FALSE. veceversa. L’allocazione dinamica della memoria e’ utile allorquando non si conoscano le taglie degli array al momento della compilazione. In questo modo, quindi, e’ possibile gestire la memoria con maggiore efficenza, consentendo di ottimizare la quantita’ di spazio richiesto per il programma e permettendo cosi’ di poter trattare problemi altrimenti non trattabili a causa della scarsita’ della memoria. 2.3.4. Costrutto IF‐THEN‐ELSE Per implementare algoritmi complessi abbiamo bisogno di istruzioni piu’ complesse. La prima di queste permette di effetuare delle decisioni all’interno del programma. E’ possibile questo attraverso il costrutto IF-THEN-ELSE. La forma piu’ semplice per questo costrutto e’: IF(espressione_logica) istruzione
Questo quando l’istruzione da effettuare e’ una sola. Se invece si ha un blocco di istruzioni e’ possibile utilizzare il comando: IF(espressione_logica) THEN
istruzioni
...
...
ENDIF
Notate come sia opportuno (anche se non necessario) che le istruzioni siano indentate per aumentare la leggibilita’ del codice. Nel caso ci siano due possiblita’ da decidere il costrutto si puo’ complicare nella forma: 10 IF(espressione_logica) THEN
istruzioni
...
...
ELSE
istruzioni
...
...
ENDIF
Nel caso le decisioni da prendere siano piu’ di due allora la sintassi sara’: IF(espressione_logica) THEN
istruzioni
...
...
ELSEIF(espressione_logica2) THEN
istruzioni
...
...
ELSE
istruzioni
...
...
ENDIF
11 Quindi nella forma piu’ generale il costrutto sara: IF(espressione_logica_1) THEN
sequenza_di_istruzioni_1
ELSEIF(espressione_logica_2) THEN
sequenza_di_istruzioni_2
ELSE
sequenza_di_istruzioni_3
ENDIF
E’ possibile infine anche associare un nome al costrutto secondo la sintassi [nome:] IF(espressione_logica_1) THEN
sequenza_di_istruzioni_1
ELSEIF(espressione_logica_2) THEN [nome]
sequenza_di_istruzioni_2
ELSE [nome]
sequenza_di_istruzioni_3
ENDIF [nome]
dove nome deve essere una stringa lunga massimo 31 caratteri e deve iniziare con una lettere. Nelle clausole ELSE IF e ELSE il nome e’ opzionale, mentre in ENDIF e’ obbligatorio. 12 2.3.5. Costrutto SELECT CASE Un’altra istruzione di diramazione e’ il costrutto SELECT CASE la cui sintassi piu’ generale e’: [nome:] SELECT CASE(espressione_di_case) THEN
CASE(selettore_1) [nome]
sequenza_di_istruzioni_1
CASE(selettore_2) [nome]
sequenza_di_istruzioni_2
CASE DEFAULT [nome]
sequenza_di_istruzioni_3
END SELECT [nome]
In questo caso espressione_di_case viene confrontata con i selettori. Un selettore specifica una lista di range: se espressione_di_case e’ nel range di un selettore, allora il blocco di istruzioni corrispondenti verra’ eseguito, altrimento si passa al selettore successivo e cosi’ via fino eventualmente ad eseguire il blocco di istruzion di CASE DEFAULT. Attenzione perche’ espressione_di_case deve essere di tipo INTEGER, CHARACTER o LOGICAL ma non di tipo REAL. Un range puo’ essere specificato come segue: -
valore esegue il blocco se espressione_di_case==valore valore: esegue il blocco se espressione_di_case>=valore :valore esegue il blocco se espressione_di_case<=valore val1:val2 esegue il blocco se val1<=espressione_di_case<=val2 Un esempio dell’uso di questo costrutto e’ il seguente: 13 INTEGER :: valore
SELECT CASE (valore)
CASE (1,2,3,5,7,11,13)
stampa a terminale “Numero primo” CASE (4,6,8:10,12,14:15)
stampa a terminale “Numero non primo” CASE (16:)
stampa a terminale “Numero troppo grande” CASE DEFAULT
stampa a terminale “Errore” END SELECT
Questo costrutto permette una maggiore chiarezza rispetto al costrutto IF-THENELSE. 2.3.6. Istruzioni di ciclo DO Le istruzioni di ciclo si utilizzano per eseguire compiti ripetitivi fino a che una determinata condizione si verifica. I cicli possono essere determinati, se si conosce esattamente il numero di iterazioni da effettuare, o indeterminati, se il numero di iterazioni e’ ignoto a priori ma finito. I primi vengono detti cicli iterativi mentre i secondi cicli while. Nei cicli iterativi il numero di iterazioni e’ noto nel momenti un cui si inizia il ciclo. E’ quindi necessaria una variabile (detta “contatore”) che funge da inidice del ciclo. Questa variabile viene inizializzata automaticamente all’inizio del ciclo, viene aggiornata automaticamente ad ogni iterazione e non deve essere mai cambiata dal programmatore. E’ necessario, inoltre stabilire gli estremi (iniziale e finale) del ciclo, che possono essere decisi durante l’esecuzione e una volta stabiliti non possono essere piu’ cambiati, ed eventualmente un incremento che anch’esso puo’ essere definito durante l’esecuzione del programma e una volta stabilito non puo’ essere piu’ cambiato. La sintassi generale di un ciclo iterativo e’: 14 DO indice=inizio,fine[,incremento]
sequenza di istruzioni
END DO
Nel caso piu’ semplice in cui l’incremento e’ uguale a 1 avvengono implicitamente le seguenti cose: 1. Immediatamente prima del DO viene posto indice=inizio (cio’ avviene una sola volta. 2. Se indice<=fine allora viene eseguita la sequenza di
istruzioni. Immediatamente prima di END DO viene posto indice=indice+1. 3. Altrimenti si esce dal ciclo In questo caso il numero di iterazioni totali sara’ fine-inizio+1. L’estensione al caso in cui l’incremento sia arbitrario e’ 1. Immediatamente prima del DO viene posto indice=inizio (cio’ avviene una sola volta. 2. Se indice×incremento<=fine×incremento allora viene eseguita la sequenza di istruzioni. Immediatamente prima di END DO viene posto indice=indice+incremento. 3. Altrimenti si esce dal ciclo In questo caso il numero di iterazioni inizio+incremento)/incremento. totali sara’ (fine-
Nei cicli iterativi, quindi, il numero di iterazioni da eseguire e’ noto al momento in cui si entra nel ciclo. La limitazione di questi cicli, pero’, sta nel fatto che esistono problemi che non si possono risolvere con un numero di iterazioni definito a priori. Ad esempio la lettura da tastiera di una sequenza di interi terminata da uno 0, il calcolo della lunghezza della sequenza (senza lo 0) e la stampa a terminale di quest’ultima. Questa limitazione puo’ essere superata con i cicli while. Nei cicli while il numero di iterazioni non e’ necessariamente noto al momento di iniziare le iterazioni. La sintassi generale di un ciclo while e’ la seguente: 15 DO
sequenza_di_istruzioni_1
IF(espressione_logica) EXIT
sequenza_di_istruzioni_2
END DO
Sia la prima che la seconda sequenza di istruzioni possono essere vuote (ovviamente non contemporaneamente). L’espressione_logica e’ la sentinella del ciclo e rappresenta un evento particolare (ad esempio un valore particolare di una variabile) per cui il ciclo deve essere interrotto. L’istruzione EXIT, percio’, determina l’uscita dal ciclo. Nei cicli annidati questa istruzione ha effetto sempre sul ciclo piu’ interno. Percio’ in questa sequenza DO
sequenza_di_istruzioni_1
DO
sequenza_di_istruzioni_2
IF(espressione_logica) EXIT
sequenza_di_istruzioni_3
END DO
sequenza_di_istruzioni_4
END DO
l’istruzione EXIT porta alla sequenza_di_istruzioni_4. Nota che l’itruzione EXIT puo’ essere utilizzata in qualsiasi tipo di ciclo, anche in quelli iterativi. Un’altra possibilita’ di implementazione dei cicli while e’ la seguente: DO WHILE (condizione)
sequenza_di_istruzioni_1
END DO
In questo caso il ciclo viene eseguito fino a che la condizione risulta verificata. 16 Una istruzione spesso utilizzata all’interno dei cicli (sia iterativi che while) e’ il CYCLE, che fa partire la prossima iterazione del ciclo. Se questa istruzione viene utilizzata nei cicli while, l’esecuzione riparte dalla prima sequenza di istruzione del ciclo dopo il DO, se invece e’ utilizzata in un ciclo iterativo, allora il contatore viene incrementato e l’esecuzione riparte dalla prima sequenza di istruzioni subito dopo il DO. DO
sequenza_di_istruzioni_1
IF(espressione_logica) CYCLE
sequenza_di_istruzioni_2
END DO
DO indice=inizio,fine[,incremento]
sequenza_di_istruzioni_1
IF(espressione_logica) CYCLE
sequenza di istruzioni_2
END DO
Come per l’istruzione EXIT, anche l’istruzione CYCLE, nei cicli annidati, ha effetto sempre sul ciclo piu’ interno. Come per i costrutti IF-THEN-ELSE e SELECT CASE, anche i cicli posso essere identificati con un nome. Percio’ in generale la sintassi completa sara’: [nome:] DO indice=inizio,fine[,incremento]
sequenza di istruzioni
END DO [nome]
17 [nome:] DO
sequenza_di_istruzioni_1
IF(espressione_logica) EXIT [nome]
sequenza_di_istruzioni_2
END DO [nome]
[nome:] DO WHILE (condizione)
sequenza_di_istruzioni_1
END DO [nome]
L’utilizzo del nome e’ utile spesso per gestire i comandi EXIT e CYCLE nei cicli annidati. Ad esempio in questa sequenza di istruzioni: esterno: DO
sequenza_di_istruzioni_1
interno: DO
sequenza_di_istruzioni_2
IF(espressione_logica) EXIT esterno
sequenza_di_istruzioni_3
END DO interno
sequenza_di_istruzioni_4
END DO esterno
sequenza_di_istruzioni_5
il comando EXIT non porta all’esecuzione della sequenza_di_istruzioni_4
come farebbe se non ci fosse indicato il nome, ma porta all’uscita del ciclo piu’ esterno e percio’ all’esecuzione della sequenza_di_istruzioni_5.
18 2.3.7. Operazioni di Input/Output Un programma FORTRAN puo’ leggere e scrivere su file, cioe’ una sequenza di elementi (caratteri e/o numeri) memorizzati in memoria secondaria (es. hard disk). La disciplina di accesso ad un file puo’ essere sequenziale, cioe’ dal primo elemento ai successivi in sequenza, o casuale, cioe’ ad un elemento qualsiasi in qualsiasi momento. E’ da notare che questi concetti sono generali ed indipendenti dal linguaggio di programmazione. Le operazioni principali su file sono: -
Apertura di un file Lettura di dati Scrittura di dati Chiusura del file Aprire di un file signifca creare una corrispondenza tra il nome fisico del file ed un nome logico da utilizzare all’interno del programma e si effettua attraverso l’istruzione: - OPEN (lista_open) La lista_open e’ una lista di parametri separati da una virgola. I parametri sono: - UNIT: che e’ il nome logico da utilizzare per accedere al file all’interno del programma. Es. UNIT=10 - FILE: che e’ il nome fisico del file. Es. FILE=’pippo.txt’ - STATUS: che puo’ assumere i seguenti valori: - ’OLD’: se il file esiste gia’ e deve essere solo aperto; se il file non esiste viene segnalato un errore. - ’NEW’: per creare un nuovo file se questo non esiste; se il file invece e’ gia’ esistente viene segnalato un errore - ’REPLACE’: se il file esiste viene cancellato e ricreato - ’SCRATCH’: per creare un file temporaneo che non viene salvato - ’ACTION’: che puo essere ’READ’ o ’WRITE’ ƒ ’READ’: il file puo’ essere solo letto, se viene scritto viene segnalato un messaggio di errore ƒ ’WRITE’: il file puo’ essere solo scritto, se viene letto viene segnalato un messaggio di error 19 ƒ ’READWRITE’: il file puo’ essere sia letto che scritto - ’IOSTAT’: variabile di tipo INTEGER che memorizza eventi particolari e che permette di controllare lo stato delle operazioni. La sintassi e’ IOSTAT=nome_variabile. Se ƒ nome_variabile==0 Æ tutto e’ andato a buon fine ƒ nome_variaible/=0 Æ c’e’ stato un errore nell’operazione (ad esempio, un file in lettura non esiste, si e’ raggiunto la fine di un file in lettura o la fine di una riga, si e’ sbagliato il formato di lettura, etc.) In generale, se si vuole aprire un file in scrittura e’ sufficiente l’istruzione - OPEN (UNIT=10,FILE=’pippo.txt’,IOSTAT=ierr) cioe’ si e’ assegnato al file nuovo pippo.txt il nome logico 10 e la variabile intera ierr contiene il risultato delle operazioni su questo file. Attenzione perche’ se il file esiste questo viene cancellato e ricreato. La chiusura di un file significa cancellare l’accoppiamento tra il nome fisico del file e l’unita’ logica creata all’interno del programma. La sintassi del comando e’: - CLOSE (UNIT=unita_logica) Se un file aperto non viene chiuso, la chiusura avviene in modo implicito al termine del programma. La lettura e la scrittura su file avvengono attraverso i comandi - READ (UNIT=unita_logica,formato,IOSTAT=variabile) - WRITE (UNIT=unita_logica,formato) dove appunto l’unita_logica e’ il nome logico assegnato al file nel comando OPEN. La specifica di formato verra’ descritto nel paragrafo successivo. Altre operazioni che possiamo fare sui files sono: - REWIND che ci riposiziona all’inizio del file - BACKSPACE che ci riposiziona alla linea precedente Se al posto dell’unita’ logica si scrive un asterisco, allora questo indica una lettura o scrittura dall’input o l’ouptut standard, tipicamente il monitor. 20 2.3.8. Formattazione dell’Input e Output Abbiamo visto nel paragrafo precedente che alla lettura e alla scrittura su file o su monitor puo’ essere associata una specifica di formato. La specifica di formato puo’ essere assegnata attraverso: - un intero che specifica l’etichetta di una istruzione FORMAT esplicita. Ad es. write(*,100)a
100 FORMAT(decrittori_di_formato)
dove 100 e’ l’etichetta di formato; - una stringa costante con nome formato=’(descrittori_di_formato)’
write(*,formato)a
- una stringa costante senza nome write(*,FMT=’(descrittori_di_formato)’)a
- una stringa variabile (uso sofisticato che non tratteremo in questo corso) In tutti i casi i descrittori_di_formato specificano completamente il formato. In particolare: •
•
•
•
Controllano la posizione verticale della riga Controllano la posizione orizzontale Specificano come stampare una determinato valore Specificano quante volte deve essere ripetuto un descrittore I descrittori di formato utilizzano i seguenti simboli: -
c: numero di colonna d: numero di cifre a destra del punto decimale m: numero minimo di cifre da visualizzare n: numero di spazi da saltare r: fattore di ripetizione w: quanti caratteri utilizzare per un dato valore Se al posto della specifica di formato c’e’ un asterisco, allora si intende un formato libero, cioe’ senza formattazione. 21 Numeri Interi: Il descrittore di formato generale per i numeri interi ha la forma - rIw.m:
- r indica quanto volte applicare il descrittore
- w indica quanti caratteri usare per il numero
- m indica quante cifre mostrare
Ecco alcuni esempi:
Numeri Reali: Il descrittore di formato generale per i numeri reali ha la forma - rFw.d (formato non esponenziale o virgola fissa):
- r indica quanto volte applicare il descrittore
- w indica quanti caratteri usare per il numero
- d indica quante cifre decimali usare dopo il punto
- rEw.d (formato esponenziale o virgola mobile):
- r indica quanto volte applicare il descrittore
- w indica quanti caratteri usare per il numero
- d indica quante cifre decimali usare dopo il punto
Nota bene, se il descrittore di formato e’ preceduto da 1P allora il primo carattere del numero e’ la prima cifra significativa. 22 Alcuni esempi: WRITE(*,FMT=’(E12.4)’)0.153
Æ
0.1530E+00
XXXXXXXXXXXX
WRITE(*,FMT=’(1P,E12.4)’)0.153
Æ
1.5300E-01
XXXXXXXXXXXX
WRITE(*,FMT=’(F8.4)’)0.153
Æ
0.1530
XXXXXXXX Stringhe: Il descrittore di formato generale per le stringhe ha la forma: - rAw:
- r indica quanto volte applicare il descrittore
- w (opzionale) specifica la larghezza del campo
Alcuni esempi: WRITE(*,FMT=’(A6)’)’pippo’
Æ
pippo
XXXXXX
WRITE(*,FMT=’(A8)’)’pippo’
Æ
pippo
XXXXXXXXX
1.4.
Sezione Conclusiva La sezione conclusiva e’ composta dal comando: - END [PROGRAM [nome_programma]] 23 1.5.
Modularizzazione (Subroutine, Function, Module) La modularizzazione e’ un’esigenza generale della programmazione. Essa e’ utile a chi utilizza moduli realizzati da altri ed e’ utile a chi programma moduli per farli usare ad altri. Un modulo e’ una scatola nera. Per utilizzarlo: • basta conoscere cosa fa • non serve conoscere come lo fa In questo corso vedremo come realizzare semplici moduli software chiamati sottoprogrammi. E’ ragionevole scrivere un sottoprogramma in almeno due casi: • uno stesso compito deve essere ripetuto piu’ volte, in contesti diversi • uno stesso compito e’ parametrico In FORTRAN90 esistono die tipi di unita’ di programma: • SUBROUTINE
• FUNCTION
Le unita’ di programma in FORTRAN90 possono essere contenute in apposite librerie denominate moduli (MODULE). FUNCTION, SUBROUTINE e MODULE possono essere all’interno dello stesso file che contiene il programma principale oppure possono essere file a se’ stanti (con estensione .f90). In questo secondo caso devono essere compilate separatamente e successivamente collegate al programma principale. Supponiamo di avere i seguenti file sorgenti: •
•
•
•
princ.f90 (contiene il PROGRAM main o programma principale) miomod.f90 (contiene un MODULE modulo) miasub.f90 (contiene la SUBROUTINE stampe) miafunc.f90 (contiene la FUNCTION areacerchio) La sequenza di comandi per creare ed utilizzare il file eseguibile e’ la seguente: • Compilazione separata dei singoli files: ƒ $ifort –c princ.f90 Æ risultato: file princ.o ƒ $ifort –c miomod.f90 Æ risultato: file miomod.o e
modulo.mod 24 ƒ $ifort –c miasub.f90 Æ risultato: file miasub.o ƒ $ifort –c miafunc.f90 Æ risultato: file miafunc.o • Collegamento (link) dei singoli files e creazione eseguibile: ƒ $ifort –o prova princ.o miomod.o miasub.o miafun.o Æ
risultato file eseguibile prova • Esecuzione file eseguibile: ƒ $./prova Percio’ in generale lo sviluppo di programmi prevede la situazione tipica in cui si ha: • Un file sorgente (.f90) contente il PROGRAM (o programma principale) • vari files sorgente (.f90) ogniuno contente un MODULE; ogni MODULE puo’ contenere varie SUBROUTINE e/o FUNCTION; • vari files sorgente (.f90) contenenti una o piu SUBROUTINE e una o piu’ FUNCTION Ogni unita’ di programma sara’ cotituita, come il programma principale, da una sezione dichiarativa, una sezione esecutiva ed una sezione conclusiva e per queste varranno le stesse regole discusse per il programma principale. Andiamo a vedere piu’ in dettaglio le varie unita’ di programma. 2.5.1. SUBROUTINE Una subroutine e’ una unita’ di programma indipendente che • puo’ essere utilizzata all’interno di altri programmi • accetta un certo numero di parametri in input che puo’ elaborare • restituisce un cetro numero parametri in output La sintassi per la dichiarazione di una subroutine e’ la seguente: 25 SUBROUTINE nome_subroutine(lista_argomenti)
sezione dichiarativa
sezione esecutiva
RETURN
END SUBROUTINE nome_subroutine Nella dichiarazione della subroutine, la lista_argomenti e’ una lista di parametri che possono essere in input e/o output. Per invocare una subroutine e’ necessario utilizzare l’istruzione CALL nome_subroutine(lista_argomenti)
L’istruzione RETURN a fine procedura e’ opzionale, tuttavia alcuni compilatori potrebbero richiederla obbligatoriamente.
2.5.2. MODULE Il Module e’ un’unita’ di programma che permette di includere subroutine e funzioni al suo interno. La sintassi per la dichiarazione di un modulo e’: MODULE nome_modulo
CONTAINS
sezione dichiarativa
dichiarazioni_di_unita’_di_sottoprogrammi
END MODULE nome_modulo
Per invocare i sottoprogrammi o utilizzare le definizioni di tipo contenuti nel modulo e’ necessario importare il modulo attraverso l’istruzione USE. Ad es. PROGRAM nome_programma
USE nome_modulo
IMPLICIT NONE
.....
26 2.5.3. FUNCTION Le funzioni definite dall’utente possono essere utilizzate come qualsiasi funzione implicita FORTRAN90. La sintassi per definire una funzione e’: FUNCTION nome_function(lista argomenti)
!
sezione dichiarativa
TIPO:: nome_function
!
sezione esecutiva
...
nome_function=espressione
RETURN
END FUNCTION [nome_function]
O in maniera equivalente: TIPO FUNCTION nome_function(lista argomenti)
Come nel caso delle subroutine anche in questo caso l’istruzione RETURN e’ opzionale. Le differenze principali tra subrotuine e function sono le seguenti. •
•
•
•
Le function restituiscono un valore Il tipo di tale valore va opportunamente dichiarato Le function vengono chiamate all’interno delle espressioni Nell’invocazione di una function non va usata l’istruzione CALL 2.5.4. Regole di visibilita’ Le costanti/variabili dichiarate in una unita’ di programma si chiamano LOCALI e non sono visibili all’esterno dell’unita’ di sottoprogramma stessa. Analogamente, le costanti/variabili definite nel programma principale non sono visibili all’interno delle varie unita’ di programma. Di conseguenza possiamo dichiarare costanti e/o variabili con lo stesso nome in vari sottoprogrammi senza paura che ci sia interferenza tra queste. 27 2.5.5. Passaggio dei parametri nei sottoprogrammi I parametri costituiscono l’interfaccia di una unita’ di programma, ovvero i dati che occorrono per poterla utilizzare. La lista dei parametri, presente tra le parentesi tonde a fianco al nome dell’unita’ di programma, e’ costituita in generale da variabili e/o array. I parametri dei sottoprogrammi FORTRAN90 sono detti argomenti fittizi, cioe’ sono dei “segnaposto” per i valori veri e propri che saranno utilizzati durante l’esecuzione del programma (argomenti reali). Esistono tre modalita’ distinte per ogni parametro passato che si definiscono attraverso l’attributo INTENT nei seguenti modi: • INTENT(IN): ingresso. Il parametro non puo’ essere modificato dentro la subroutine o funzione • INTENT(OUT): uscita. Al termine della subroutine o funzione il valore del parametro potra’ essere stato modifica, il valore in ingresso non sara’ utilizzato • INTENT(INOUT): ingresso e uscita. Il parametro ha informazioni in ingresso ma potra’ essere modificato (DEFAULT). Quando si invoca un sottoprogramma e’ necessario istanziare tutti i parametri, sostituendo cioe’ ai parametri fittizi i valori effettivi (espressioni, variabili, array, costanti). Lo schema di passaggio dei valori utilizzato dal FORTRAN90 e’ quello per riferimento, non vengono passati i valori veri e propri ma viene passo il riferimento alla locazione di memoria che contiene il valore. 28 2.6.
Diagrammi di flusso Per sviluppare programmi complessi e’ utile avvalersi dei cosi’ detti diagrammi di flusso che sono una rappresentazione grafica delle operazioni che il programma dovra’ eseguire per effettuare il compito che noi desideriamo. La notazione grafica dei diagrammi di flusso e’ riportata nella seguente figura: Un esempio di diagrammi di flusso di un programma e’ il seguente: 29 2.6.1. Diagramma di flusso del costrutto IF‐THEN‐ELSE Sulla base delle definizioni precedenti possiamo indicare il diagramma di flusso di un costrutto IF-THEN-ELSE che sara’: Ad esempio se volessimo scrivere un programma che, acquisiti in ingresso due numeri a e b determini la soluzione dell’equazione ax+b=0 e la stampi a terminale, avremmo il seguente diagramma di flusso: 30 2.6.2. Diagramma di flusso del costrutto SELECT CASE Analogamente il diagramma di flusso di un costrutto SELECT CASE sara’: 31 2.6.2. Diagramma di flusso del ciclo DO Il diagramma di flusso di un ciclo iterativo e’: Un esempio di implementazione di un ciclo iterativo in un programma piu’ complesso e’: 32 Il diagramma di flusso per un ciclo while, invece, ha la forma: 33 34