forts. p˚a f¨oreg˚aende f¨orel¨asning:
Metoden att g¨omma data, som visades i den nyss beskrivna modulen g¨or programmeringen s¨akrare, och kan ocks˚a t.ex. anv¨andas f¨or att begr¨ansa tillg¨angligheten av t.ex. en databas. N¨ar man skriver en modul, kan man kontrollera variablernas tillg¨anglighet genom att anv¨anda attributen PRIVATE och PUBLIC i deklarationerna, t.ex.
REAL, PRIVATE :: a, x TYPE, PRIVATE :: special
REAL :: p, q, r END TYPE special
Variabler av en h¨arledd typ som ¨ar privat, ¨ar inte tillg¨angliga utanf¨or modulen, d¨ar de definierats, till
˚atskillnad fr˚an v˚art f¨orra exempel, d¨ar komponenterna inte var ˚atkomliga, men d¨aremot typen i sin helhet.
Observera ocks˚a, att ett dubbelkolon (::) m˚aste uts¨attas i en TYPE sats som inneh˚aller attributet PRIVATE eller PUBLIC.
Man kan ocks˚a ange tillg¨angligheten med en s¨arskild PUBLIC- eller PRIVATE-sats:
PUBLIC :: (allm¨ant tillg¨angliga variabler)
PRIVATE :: (ej allm¨ant tillg¨angliga variabler)
Exemplet med vektorer visar hur man kan skriva moduler f¨or att konstruera nya datatyper och anv¨anda dem i procedurer. ¨Annu mera anv¨andningsbar skulle en dylik programmeringsomgivning bli om man kunde konstruera operatorer som kan till¨ampas p˚a h¨arledda typer.
Vi har t.ex. skrivit en funktionsrutin skal_prod som r¨aknar ut skal¨arprodukten av tv˚a vektorer. Den kan anv¨andas i tillordningssatser av typen a_punkt_b = skal_prod(a,b). Det skulle dock vara naturligare att generalisera multiplikationsoperatorn s˚a att man kunde skriva a_punkt_b = a*b. Detta ¨ar m¨ojligt i Fortran 90 p˚a grund av att man kan definiera nya operatorer (eller utvidga definitionen av de inbyggda operatorerna, vilket ocks˚a kallas f¨or ¨overlagring). Detta g˚ar till p˚a f¨oljande s¨att:
INTERFACE OPERATOR (operatorsymbol) (rutin, som definierar operatorn) END INTERFACE
I denna programkonstruktion kan operatorsymbolen antingen vara en av de inbyggda operatorerna, eller ocks˚a en intill 32 tecken l˚ang teckenstr¨ang, omgiven av punkter. Rutinen som anv¨ands f¨or att definiera operatorn, kan ha antingen ett eller tv˚a argument, men eftersom funktionen normalt definieras i samma modul som inneh˚aller operatordefinitionen, s˚a kan den ers¨attas med satsen MODULE PROCEDURE, som definierar en modulprocedur.
Skal¨arprodukten av tv˚a vektorer kan d˚a definieras med en s¨arskild symbol, t.ex. .punkt.:
INTERFACE OPERATOR(.punkt.) MODULE PROCEDURE skal_prod END INTERFACE
eller hellre genom en utvidgning av multiplikationsoperatorns betydelse:
INTERFACE OPERATOR(*)
MODULE PROCEDURE skal_prod END INTERFACE
Ett exempel p˚a anv¨andningen av ¨overlagrade operatorer ¨ar ett program, d¨ar man anv¨ander mycket logiska variabler. Man tycker kanske att det ¨ar besv¨arligt att alltid skriva .AND., .OR. och .NOT.. Med hj¨alp av ¨overlagring kan dessa operatorer utbytas mot t.ex. ”*”, ”+” och ”-”. Ett exempel p˚a en modul som
˚astadkommer detta ser vi nedan:
MODULE logik
INTERFACE OPERATOR(*) MODULE PROCEDURE AND END INTERFACE
INTERFACE OPERATOR(+) MODULE PROCEDURE OR END INTERFACE
INTERFACE OPERATOR(-) MODULE PROCEDURE NOT END INTERFACE
CONTAINS
LOGICAL FUNCTION and(logisk1,logisk2) LOGICAL, INTENT(in) :: logisk1,logisk2 and = logisk1.and.logisk2
END FUNCTION and
LOGICAL FUNCTION or(logisk1,logisk2) LOGICAL, INTENT(in) :: logisk1,logisk2 or = logisk1.or.logisk2
END FUNCTION or
LOGICAL FUNCTION not(logisk) LOGICAL, INTENT(in) :: logisk not = .not.logisk
END FUNCTION not END MODULE logik
Modulen kan testas t.ex. med f¨oljande program, som ocks˚a visar hur de nya operatorerna kan anv¨andas:
PROGRAM testlog USE logik
IMPLICIT NONE
LOGICAL :: sant=.true.,falskt=.false.
PRINT *, " .true. * .false. =", sant * falskt PRINT *, " .true. + .false. =", sant + falskt PRINT *, "-.true. =", - sant
PRINT *, "Direkt proceduranrop:"
PRINT *, "AND(.true.,.false.)=", AND(sant,falskt) PRINT *, "OR (.true.,.false.)=", OR(sant,falskt) PRINT *, "NOT(.true.) =", NOT(sant)
END PROGRAM testlog
Om man utvidgar betydelsen av en inbyggd operator p˚a detta s¨att, b¨or man minnas, att det ¨ar inte till˚atet att ¨andra betydelsen av den inbyggda operatorn, utan endast att utvidga den. Antalet funktionsargument m˚aste ocks˚a ¨overensst¨amma med vad som g¨aller f¨or den inbyggda operatorn.
Om man utvidgar betydelsen av n˚agon av relationsoperatorerna < , <= , > , >= , == , /= s˚a kom- mer motsvarande utvidgning av betydelsen ocks˚a att g¨alla de motsvarande alternativa operatorerna .LT.
, .LE., .GT., .GE., .EQ., .NE..
I Fortran 90 har vi ytterligare m¨ojlighet att definiera om tillordningsoperatorn =. Detta beh¨ovs inte om man anv¨ander inbyggda typer, men f¨or h¨arledda typer kan det vara nyttigt, om man vill anv¨anda tillordnings- operatorn f¨or att konvertera mellan olika typer. Tillordningsoperatorn kan definieras analogt med andra operatorer:
INTERFACE ASSIGNMENT(=) (procedur_1)
(procedur_2) END INTERFACE
I modulen som definierar en vektortyp, skulle man kunna anv¨anda rutinerna alstra vektor och vektor racka f¨or att definiera om tillordningen mellan talr¨ackor och vektorer:
INTERFACE ASSIGNMENT(=)
MODULE PROCEDURE alstra_vektor MODULE PROCEDURE vektor_racka END INTERFACE
Med dessa definitioner ¨ar det m¨ojligt att skriva x = a
b = y
om x, y ¨ar av typ vektor, samt a,b ¨ar talr¨ackor. Observera dock, att vi inte kan variera vektorernas dimensioner i detta fall!
N¨ar man definierar operatorsymboler och tillordningssymboler, l¨onar det sig i allm¨anhet att ”g¨omma” nam- nen p˚a rutinerna som definierar operatorerna, och ist¨allet g¨ora symbolerna allm¨ant tillg¨angliga:
PRIVATE
PUBLIC :: ASSIGNMENT(=)
I FORTRAN 77 hade procedurerna ett ”implicit gr¨anssnitt”, dvs programmeraren b¨or se till att de aktuella argumenten vid anropet av en subrutin svarar mot argumentdeklarationerna i subrutinen. Konstruktionen INTERFACE OPERATOR ... ¨ar ett exempel p˚a ett s.k. ”explicit gr¨anssnitt” (eng. explicit interface), som beh¨ovs f¨or de nya konstruktionerna i Fortran 90. Genom att placera en procedur in i en modul, som kan anv¨andas i en annan programenhet via en USE-sats, ˚astadkommer man ocks˚a ett explicit gr¨anssnitt f¨or ifr˚agavarande procedur.
Man kan ocks˚a anv¨anda dylika gr¨anssnitt-konstruktioner f¨or att skriva generiska procedurer. Generiska procedurer har vi hittills st¨ott p˚a endast i samband med de inbyggda matematiska funktionerna, s˚asom SIN, COS, ABS, etc, men i Fortran 90 ¨ar det ocks˚a m¨ojligt att definiera egna generiska procedurer, genom att kombinera procedurdefinitioner i ett gr¨anssnitt:
INTERFACE (generiskt_namn) (procedurdefinition_1) ...
END INTERFACE
Vanligen antas procedurerna, som ˚asyftas, ing˚a i en modul och det ¨ar d¨arf¨or tillr¨ackligt att ange namnen p˚a dessa modulprocedurer i gr¨anssnittet, som anges i formen MODULE PROCEDURE namn.
Som ett exempel skall vi studera anv¨andningen av en generisk procedur, som kastar om tv˚a tal, som kan vara antingen heltal eller reella:
MODULE omkast IMPLICIT NONE INTERFACE byt
MODULE PROCEDURE byt_reella MODULE PROCEDURE byt_heltal END INTERFACE
CONTAINS
SUBROUTINE byt_reella(x,y) IMPLICIT NONE
REAL, INTENT(IN OUT) :: x,y REAL :: tmp
tmp = x x = y y = tmp RETURN
END SUBROUTINE byt_reella SUBROUTINE byt_heltal(x,y)
IMPLICIT NONE
INTEGER, INTENT(IN OUT) :: x,y INTEGER :: tmp
tmp = x x = y y = tmp RETURN
END SUBROUTINE byt_heltal END MODULE omkast
PROGRAM test_omkast
! Program f¨or att testa modulen omkast USE omkast
IMPLICIT NONE INTEGER :: a, b REAL :: c, d
PRINT *, "Skriv tv˚a heltal:"
READ *, a, b CALL byt(a,b) PRINT *, a,b
PRINT *, "Skriv tv˚a decimaltal:"
READ *, c,d CALL byt(c,d) PRINT *, c,d STOP
END PROGRAM test_omkast
Som vi ser, kan man anv¨anda byt som ett generiskt namn p˚a en rutin, som kastar om tv˚a tal.
F¨or att reservera globalt minne i Fortran 90 kan man ocks˚a utnyttja moduler, s˚asom t.ex.
module mymod implicit none real :: x
integer :: k, nmax parameter (nmax=1000) real :: y(nmax)
character (len=8) :: namn(nmax) end module mymod
Det reserverade minnesutrymmet kan som synes enkelt ¨andras genom att f¨or¨andra v¨ardet av parametern nmax.
I Fortran 90 ¨ar det ocks˚a m¨ojligt att allokera dynamiskt minne. Detta inneb¨ar, att de definerade talr¨ackorna skall ha ett visst antal dimensioner, men att storleken av dem vid kompileringen inte ¨annu ¨ar k¨and. F¨orst d˚a programmet k¨ors best¨ams antalet element i varje r¨acka.
I detta fall kan modulen ovan uttryckas module mymod
implicit none real :: x
integer :: k
real, dimension (:), allocatable :: y
character (len=8), dimension (:), allocatable :: namn end module mymod
Minnesallokeringen sker d˚a vid exekveringen genom anv¨andning av allocate-satsen:
use mymod
allocate (y (nmax)) allocate (namn (nmax))
Satsen allocate har i allm¨anhet formen allocate(lista,STAT=ier), d¨ar lista best˚ar av de talr¨ackor f¨or vilka minne skall reserveras, ˚atskilda av kommatecken, och STAT ¨ar en specifikation, som anger om allok- eringen lyckats (ier=0) eller misslyckats (ier>0). F¨or att frist¨alla minnet anv¨ands satsen deallocate.
Genom att Fortran 90 till˚ater dynamisk minnesallokering, ¨ar det ocks˚a m¨ojligt att definiera rekursiva funktioner, liksom i Pascal. Det klassiska exemplet ¨ar ber¨akningen av fakultetsfunktionen ur rekursions- formeln n! = n · (n − 1)! Denna metod anv¨andes redan i exemplet i avsnitt 2.7, men utan att anropa funktionen rekursivt. Med den rekursiva metoden kan funktionsrutinen skrivas
RECURSIVE INTEGER FUNCTION rfac(n) RESULT (fact) IMPLICIT NONE
INTEGER, INTENT(in) :: n INTEGER :: factorial IF (n<=0) THEN
fact = 1 ELSE
fact = n*rfac(n-1) END IF
END FUNCTION rfac
Som vi ser, kommer funktionen att anropa sig sj¨alv, ¨anda tills argumentet blir 0, resultatet blir 1 och multiplikationskedjan nystas upp. Observera, att resultatet ges namnet fact, f¨or att inte f¨orv¨axling med funktionsnamnet skall ske (detta ¨ar speciellt viktigt ifall resultatet ¨ar en indicerad talr¨acka).
Vi skall se p˚a ett annat exempel som visar att rekursion inte alltid l¨onar sig. Fibonacci-talen definieras vanligen med hj¨alp av likheterna
f (1) = 1 f (2) = 1
f (n) = f (n − 1) + f (n − 2); n > 2 som leder till en rekursiv funktionsrutin:
recursive function fibonacci (n) result (fibo) implicit none
integer, intent(in) :: n integer :: fibo
if (n <= 2) then fibo = 1
else
fibo = fibonacci (n-1) + fibonacci (n-2) end if
return
end function fibonacci
Ett program, som testar denna funktion, kan se ut s˚a h¨ar:
PROGRAM fibo_test IMPLICIT NONE INTEGER :: n DO
WRITE (*, ’(A)’, ADVANCE=’no’) ’Ange n: ’ READ (*,*) n
WRITE (*,*) ’n= ’, n IF (n<0) EXIT
WRITE (*,*) ’Fibonacci-talet ¨ar =’, fibonacci(n) END DO
CONTAINS
recursive function fibonacci (n) result (fibo) ...
end function fibonacci END PROGRAM fibo_test
H¨ar st˚ar funktionsrutinen efter CONTAINS (en ”inre” procedur). Observera ADVANCE=’no’, som anv¨ants i WRITE-satsen. Om n ges ett negativt v¨arde, avslutas programmet.F¨or stora v¨arden av n blir programmet mycket l˚angsamt. H¨ar ges ett exempel p˚a utskriften:
Ange n: 3
n= 3
Fibonacci-talet ¨ar = 2
Ange n: 15
n= 15
Fibonacci-talet ¨ar = 610 Ange n: 30
n= 30
Fibonacci-talet ¨ar = 832040 Ange n: 45
n= 45
Fibonacci-talet ¨ar = 1134903170 Ange n: -1
n= -1
Denna rekursiva procedur ¨ar inte s¨arskilt effektiv, eftersom m˚anga v¨arden ber¨aknas flera g˚anger. Om man t.ex anv¨ander den f¨or att r¨akna ut f (7), leder detta till ber¨akning av f (6) och f (5). Men ber¨akningen av f (6) leder till ber¨akning av b˚ade f (5) och f (4), s˚a att m˚anga av Fibonacci talen kommer att ber¨aknas p˚a nytt. En effektivare metod f˚as genom att ber¨akna funktionsv¨ardena i tur och och ordning och lagra dem i en dynamiskt allokerad vektor:
function fibonacci (n) result (fibo) implicit none
integer, intent (in) :: n integer :: fibo, i
integer, dimension (:), allocatable :: f if (n <= 2) then
fibo = 1 else
allocate (f (n)) f (1) = 1
f (2) = 1 do i = 3,n
f (i) = f (i-1) + f (i-2) end do
fibo = f (n) end if
return
end function fibonacci
Denna metod ¨ar snabb, men sl¨osar med minne, eftersom den reserverar en vektor med n element, fast¨an endast de tv˚a senast ber¨aknade talen beh¨ovs. F¨oljande version av programmet, som korrigerar detta problem,
¨ar snabbare ¨an de b˚ada ¨ovriga:
function fibonacci (n) result (fibo) implicit none
integer, intent (in) :: n integer :: fibo
integer :: fi, fi1, i if (n <= 2) then
fibo = 1 else
fi1 = 1 fi = 1 do i = 3,n
fi = fi + fi1 fi1 = fi - fi1 end do
fibo = fi end if
return
end function fibonacci
De variabler, som hittills behandlats, har alla inneh˚allit n˚agon form av data. Det finns dessutom en annan typ av variabler, som inte inneh˚aller data, men ist¨allet pekar ut den eller de variabler d¨ar data lagras. S˚adana variabler kallas pekare, och de anv¨ands framf¨orallt i s˚adana fall d˚a data alstras och f¨orintas dynamiskt.
Pekare anv¨ands ocks˚a f¨or att underl¨atta manipulationer med stora m¨angder data, eftersom det ¨ar mycket l¨attare att flytta pekare ¨an data.
I Fortran deklareras en variabel som en pekare med hj¨alp av attributet POINTER, t.ex. satsen REAL, POINTER :: a betyder att a ¨ar en pekare som pekar p˚a objekt av reell typ (n¨armare best¨amt endast objekt av reell typ). Pekare kan ocks˚a definieras s˚a att de pekar p˚a objekt av h¨arledda typer, t.ex. TYPE (medlem), POINTER :: q f¨orklarar q vara en pekare p˚a ett objekt av typ medlem. Varje pekare har dessutom en status, som anger om den f¨or tillf¨allet pekar p˚a n˚agonting. Denna status ¨ar ursprungligen odefinierad.
Emedan anv¨andningen av pekare l¨att kan p˚averka negativt programeffektiviteten, kr¨aver man dessutom att en pekare inte kan peka p˚a vilken som helst variabel av en given typ, utan att objekten som den pekar p˚a m˚aste ha ytterligare ett attribut, som kallas TARGET (”m˚alet”). Detta kan vi belysa med ett exempel:
INTEGER, POINTER :: q REAL :: a
REAL, TARGET :: b REAL, POINTER :: c
H¨ar ¨ar c en pekare, som kan peka p˚a b men inte p˚a a, eftersom den saknar m˚alattribut. Pekaren c kan ˚a andra sidan inte heller peka p˚a q, eftersom den har fel typ.
En pekare associeras med m˚alet via en pekartillordningssats, som har formen pekare=>m˚al. Observera, att tillordningsoperatorn ¨ar en sammansatt symbol, som best˚ar av ett likhetstecken och ett st¨orre ¨an-tecken (utan mellanrum). N¨ar pekaren pekar p˚a ett m˚al, s¨ages den vara associerad. Som ett exempel kan vi se p˚a f¨oljande program, som illustrerar dessa begrepp:
Program pektest implicit none
integer, pointer :: r, s integer, target :: t integer :: q
t = 1 r => t t = 3 s => t q = r*s
print *, r, s, t, q stop
End program pektest
(testa g¨arna programmet, och studera utskriften).
M˚alet f¨or en pekare kan givetvis ocks˚a vara en vektor, eller en matris. S˚alunda betyder t.ex. satsen REAL, DIMENSION(:), POINTER :: p v att p v pekar p˚a vektorer. En av de viktigaste anv¨andningarna av en vektorpekare ¨ar m¨ojligheten att dynamiskt alstra minne n¨ar det beh¨ovs, och frist¨alla det n¨ar det inte l¨angre beh¨ovs. Detta utf¨ors med ALLOCATE-satsen som vi redan tidigare st¨ott p˚a.
Den har f¨oljande form: ALLOCATE (pekare (dimension), STAT=status) d¨ar pekaren nu ¨ar en vektorstorhet, som har b˚ade dimensions- och pekarattribut, och status ¨ar en heltalsvariabel, som f˚ar v¨ardet 0 om allt lyckas.
En vanlig anv¨andning av pekare ¨ar i en s.k. l¨ankad struktur (f¨orekommer t.ex. i simuleringsprogram), d¨ar varje objekt pekar ut n¨asta objekt (typen kallas nod):
TYPE nod
INTEGER :: tal
TYPE (nod), POINTER :: nasta END TYPE nod
Denna typdeklaration definierar en enkel l¨ankad lista med tv˚a noder d¨ar varje nod inneh˚aller ett heltal och en l¨ank till n¨asta nod. Den sista noden pekar p˚a ett tomt m˚al, vilket man alstrar med satsen NULLIFY.
Som vi ser, kommer TYPE(nod) :: p
TYPE(nod), TARGET :: q p = q
att vara detsamma som p%tal = q%tal
p%nasta => q%nasta
En l¨ankad lista med noder av ovanst˚aende typ som avslutas med ett tomt m˚al skulle kunna konstrueras p˚a f¨oljande s¨att:
PROGRAM lankadlista IMPLICIT NONE TYPE nod
INTEGER :: tal ! data
TYPE (nod), POINTER :: nasta ! pekaren END TYPE nod
TYPE (nod), POINTER :: lista, aktuell INTEGER :: n, stat
NULLIFY(lista) !nollst¨all listan
DO
READ *, n ! l¨as v¨ardet av n
IF (n==0) EXIT ! sluta d˚a n = 0
ALLOCATE(aktuell, STAT=stat) ! alstra en ny nod IF (stat > 0) STOP ’ingen ny nod allokerades’
aktuell%tal=n ! ins¨att n som v¨arde i aktuell nod aktuell%nasta => lista ! peka p˚a den tidigare noden
lista => aktuell ! uppdatera b¨orjan av listan END DO
PRINT *, ’G˚a igenom listan och skriv ut v¨arden’
aktuell => lista ! g¨or den aktuella noden till listans alias DO
IF (.NOT. ASSOCIATED(aktuell)) EXIT ! hoppa ut om pekaren visar p˚a noll PRINT *, aktuell%tal ! Skriv ut v¨ardet
aktuell => aktuell%nasta ! g¨or den aktuella noden till n¨asta END DO
END PROGRAM lankadlista
I b¨orjan ¨ar listan tom, och lista pekar d¨arf¨or b˚ade p˚a b¨orjan och slutet. Detta ˚astadkommes genom att nollst¨alla listan med NULLIFY, som tar bort pekarens association. D¨arp˚a l¨aser man in ett heltalsv¨arde (n), och listan initialiseras med en nod, som inneh˚aller detta v¨arde. Detta sker genom att f¨orst reservera dynamiskt minne f¨or den aktuella noden med ALLOCATE. Denna f˚ar d˚a talv¨ardet n. Till slut f˚ar lista peka p˚a b¨orjan av listan som ¨ar den aktuella noden som just blivit allokerad. Processen upprepas ¨anda tills v¨ardet 0 blivit inl¨ast. Om talen 1,2,3 blivit inl¨asta (i denna ordning) blir listan →(3/nasta)→(2/nasta)→(1/noll).
Till slut g˚ar man igenom listan och skriver ut alla v¨arden. Till att b¨orja med l˚ater man den aktuella noden pe- ka p˚a den f¨orsta noden, som skrivs ut. D¨arp˚a f˚ar den aktuella noden peka p˚a n¨asta nod, och man g˚ar till b¨or- jan av DO–slingan. Om den aktuella noden ¨ar associerad (testas med funktionen ASSOCIATED(aktuell)), s˚a skrivs den ut, om inte, s˚a har vi kommit fram till listans slut.
Fortran 90 ger, som vi har sett, en m¨ojlighet att definiera nya datatyper, och s¨atta in dem i moduler, konstruera nya operatorer och definiera nya metoder att tillordna variabler v¨arden, konstruera pekare, och att anv¨anda attribut s˚asom PUBLIC och PRIVATE f¨or att kontrollera tillg¨angligheten av olika definitioner.
Dessa nya metoder kallas i allm¨anhet data-abstraktion, som ¨ar betydelsefull i all modern programmering.
Aven om Fortran 90 inte fullt underst¨¨ oder s.k. objekt-orienterad programmering, som nu ¨ar p˚a modet, s˚a inneh˚aller spr˚aket dock f¨oruts¨attningar d¨arf¨or, och nya Fortran-standarder utvecklas mer och mer i denna riktning.