Fortran: tipi derivati
Oltre ai tipi primitivi, il Fortran, come la maggior parte
dei linguaggi moderni di alto livello, permette la definizione da parte
del programmatore di nuovi tipi dati per aggregazione di quelli
primitivi. Ad esempio, un tipo dati in grado di rappresentare un
elemento chimico potrebbe essere definito come:
TYPE :: elemento
CHARACTER(LEN=2) :: simbolo
INTEGER :: numero_atomico
REAL :: massa_atomica
END TYPE elemento
I nomi simbolo, numero_atomico e massa_atomica rappresentano i nomi dei
tre campi dati di cui e' costituito il tipo dati "elemento".
Una volta definito il nuovo tipo dati, si possono dichiarare variabili
del nuovo tipo mediante dichiarazioni come:
TYPE(elemento) :: atomo1, atomo2
TYPE(elemento),DIMENSION(92) :: tavola_periodica
Il nuovo tipo dati può essere utilizzato come tipo di un campo di un ulteriore tipo dati derivato:
type :: atom
type(elemento) :: elemento
real, dimension(3):: pos
end type atomo
Da
notare che le uniche operazioni predefinite su entità del nuovo tipo dati sono:
- l' assegnazione :
atomo1 = atomo2
assegna tutti
i campi della variabile atomo2 ai rispettivi campi di atomo1)
- l' input/output (es.
print*, atomo1
read*, atomo2
rispettivamente scrivono o leggono tutti i campi delle
corrispondenti variabili, nell' ordine con cui sono dichiarate
nel tipo dati.
- si puo' "costruire" una variabile del nuovo tipo dati a partire
dai valori dei corrispondenti campi mediante una istruzione
"costruttore" con lo stesso nome del tipo:
atomo1 = element("H",1,1.0079)
I costruttori possono essere utilizzati anche nelle assegnazioni fatte
contestualmente alla dichiarazione di una variabile. Va tuttavia notato che i costruttori predefiniti nel linguaggio pongono delle limitazioni al programmatore: vanno modificati nel caso di ridefinizione del tipo dati e cessano di funzionare in presenza di campi private nella definizione del tipo dati. Per tale motivo si preferisce spesso sostituirli con function "costruttrici" scritte dal programmatore.
Se un tipo derivato contiene una o più componenti
allocabili queste vanno allocate esplicitamente. Tuttavia se una
variabile del tipo derivato è a sinistra del segno di
uguale in un' assegnazione dove a destra figura un' altra espressione
dello stesso tipo derivato, l' allocazione ( o riallocazione) avviene
automaticamente in modo che le componenti allocabili a sinistra e
destra del segno di uguale abbiano lo stesso numero di componenti.
Qualsiasi altra operazione deve essere definita mediante sottoprogrammi
appositamente scritti dal
programmatore.
I singoli campi di una variabile di un tipo dati derivato sono accessibili mediante il nome della variabile seguito dal selettore (%) e dal nome del campo (come risulta dalla definizione del tipo dati).
P.es.
atomo1%simbolo = 'Be'
atomo1%numero_atomico = 4
atomo1%massa_atomica = 9.01218
atom2 = atom1
print*, atomo2%massa_atomica
E' buona pratica di programmazione inserire in un unico modulo la
definizione di ciascun tipo dati indipendente insieme a tutti i
sottoprogrammi necessari a manipolarlo.
E' anche possibie definire nuovi operatori binari (due operandi,
uno a sinistra e uno a destra del simbolo del nuovo operatore) o unari
(un solo operando a destra del simbolo del nuovo operaore). O
anche, mediante interfacce esplicite (da inserire nella sezione
dati di un modulo) e' possibile aggiungere (sovraccaricare,
overloading) nuove funzionalita' ad operatori
preesistenti o alla assegnazione o a sottoprogrammi di libreria.
Esempi:
Estensione della funzione intrinseca SIN(x) ad argomenti
interi:
module newtyp
implicit none
interface sin ! aggiunge funzionalta' alla funzione di libreria sin(x)
module procedure sin_i
end interface sin
contains
function sin_i(i) result(s)
real :: s
integer,intent(in) :: i
s=sin(real(i))
end function sin_i
end module newtyp
program p
use newtyp
implicit none
print*,sin(1.0),sin(1)
end program p
Definizione di un operatore binario .X. per il prodotto
vettoriale tra vettori a tre componenti:
module tiponuovo
implicit none
private ! rende non accessibili tutte le entita' del modulo che
! non siano esplicitamente dichiarate public
interface operator (.X.)
module procedure v_prod
end interface
public :: operator(.X.) ! in tal modo i programmi che utiizzano il modulo
! possono usare solo l' operatore .X. ma non
! la corrispondente procedura v_prod
contains
function v_prod(a,b) result (p)
real,dimension(3),intent(in) :: a,b
real,dimension(3) :: p
p(1)=a(2)*b(3)-a(3)*b(2)
p(2)=a(3)*b(1)-a(1)*b(3)
p(3)=a(1)*b(2)-a(2)*b(1)
end function v_prod
end module tiponuovo
program pvec
use tiponuovo
implicit none
real,dimension(3) :: a,b
read*,a
read*,b
print*,a.X.b
end program pvec
Ridefinizione (overloading) dell' operatore
somma per un nuovo tipo dati (Punto2D che rappresenta punti del
piano mediante due campi reali):
module pv
implicit none
private
type, public :: punto2D
real ::x
real ::y
end type punto2D
interface operator(+)
module procedure add_p2D
end interface
public :: operator(+)
contains
function add_p2D(p,q) result(r)
type(punto2D),intent (in) :: p,q
type(punto2D) :: r
r%x = p%x + q%x
r%y = p%y + q%y
end function add_p2D
end module pv
program usa_pv
use pv
implicit none
real :: x
type(punto2D) :: p,q
p=punto2D(12.0,-3.0)
q=punto2D(2.0,5.0)
print*," p = (12,-3) q = (2,5) "
print*,"p+q = ",p+q
end program usa_pv
Procedure legate al tipo dati (Type-bound procedures)
Soprattutto nella programmazione che utilizza il paradigma ad oggetti, si preferisce legare in modo stretto le procedure che agiscono su variabili del nuovo tipo dati alla variabile (istanza dell' oggetto) stessa.
Il Fortran permette di legare più procedure (metodi) ad un tipo dati nel modo illustrato dal seguente esempio:
module class_Circle
implicit none
private
real :: pi = acos(-1.0)
type, public :: Circle
real :: radius
contains
procedure :: area => circle_area
end type Circle
contains
function circle_area(this) result(area)
class(Circle), intent(in) :: this
real :: area
area = pi * this%radius**2
end function circle_area
end module class_Circle
program circle_test
use class_Circle
implicit none
type(Circle) :: c ! Declare a variable of type Circle.
c = Circle(1.5) ! Use the implicit constructor, radius = 1.5.
print*,c%area() ! Call the type-bound subroutine
end program circle_test
Da notare:
-
Nella function circle_area l' argomento di tipo dati Circle è dichiarato come class(Circle) e non type(Circle). A questo livello la si può considerare come una richiesta della sintassi;
-
la function circle_area ha un argomento (this), il primo se ce ne è più di uno, che fa riferimento alla variabile del tipo Circle a cui il metodo area è associato;
-
in questo caso in cui l' unico argomento è this, nella chiamata non viene indicato nessun argomento. In generale il primo viene omesso (è implicito).