Fortran: Fortran 95: Unterprogramme

<<< zur Fortran-Startseite
<< Fortran 95 Fortran 2003 >>
< Standardfunktionen Ein- und Ausgabe >

Werden Programme umfangreicher und komplexer oder werden einzelne Programmabschnitte öfter verwendet, dann ist es sinnvoll Unterprogramme (Prozeduren) zu verwenden. Fortran 95 kennt zwei Typen von Unterprogrammen:

  • Funktionen (function)
  • Subroutinen (subroutine)

Darüber hinaus existiert in Fortran die Möglichkeit Unterprogramme und Daten mittels Modulen in das Programm einzubeziehen.

Das function Unterprogramm

Bearbeiten

Eine Funktion ist ein Unterprogramm, das genau einen Wert zurück gibt. Welcher Art von Datentyp der Wert ist, wird durch eine Zuweisung an den Funktionsnamen erreicht ( z. B. real).

Die allgemeine Funktionsdeklaration lautet:

datentyp function funktionsname ( [formale parameter] )
  Vereinbarungsteil
  Anweisungsteil
  funktionsname = wert
  [return]
end function funktionsname

Wird der Datentyp bei der Funktionsdeklaration nicht angegeben, so muss der Datentyp der Funktion im Vereinbarungsteil festgelegt werden:

function funktionsname ( [formale parameter] )
  Vereinbarungsteil
  datentyp :: funktionsname
  Anweisungsteil
  funktionsname = wert
  [return]
end function funktionsname

Mittels return kann zur aufrufenden Programmeinheit zurückgekehrt werden. Ein return unmittelbar vor der end-Anweisung ist nicht unbedingt erforderlich, da das Programm beim Erreichen der end-Anweisung zur aufrufenden Programmeinheit zurück springt. Deswegen sollte bei allen neueren Programmen darauf verzichtet werden. Wichtig ist die return-Anweisung für alternative Rücksprünge zur aufrufenden Programmeinheit.

Eine Funktion wird meist mittels folgender allgemeiner Anweisung von der ausführenden Programmeinheit aufgerufen:

variable = funktionsname( aktuelle parameter )

Dabei orientiert sich diese Unterprogrammtechnik direkt an mathematischen Funktionen. Im folgenden Beispiel ruft das Programm test die Funktion funk auf, führt das Unterprogramm aus und gibt den entsprechenden Rückgabewert an das Programm test zurück:

Datei bsp.f95:

Fortran 90/95-Code (free source form)
program test
  !
  implicit none

  real :: funk
  real :: x, y

  write(*,*) 'x -> '
  read(*,*) x

  y = funk( x )

  write(*,*) 'funk -> ', y

end program test


real function funk( a )
  !
  implicit none

  real :: a

  funk = a**2

end function funk

Das Unterprogramm funk kann sich direkt unter der aufrufenden Programmeinheit in der selben Textdatei oder in einer separaten Datei befinden. Befindet sich das Unterprogramm in einer separaten Datei, so muss diese und die aufrufende Programmeinheit zusammen kompiliert werden:

Übersetzung mit gfortran im vorliegenden Fall:

gfortran -o bsp bsp.f95 

Übersetzung mit gfortran bei separaten Dateien (bspw.):

gfortran -o bsp bsp.f95 funk.f95

Es ist aber auch möglich eine Funktion nur durch ihren Namen aufzurufen. Der Rückgabewert der Funktion wird dann direkt ausgegeben:

Datei bsp.f95:

Fortran 90/95-Code (free source form)
program test
  !
  implicit none

  real :: funk
  real :: x

  write(*,*) 'x -> '
  read(*,*) x

  write(*,*) 'funk -> ', funk( x )

end program test


real function funk( a )
  !
  implicit none

  real :: a

  funk = a**2

end function funk

Das subroutine Unterprogramm

Bearbeiten

Eine subroutine ist ein Unterprogramm, das mehr als einen Wert zurückgeben kann. Eine Subroutine besitzt im Gegensatz zu einer Funktion keinen Datentyp und Rückgabewert.

Die allgemeine Deklaration einer Subroutine lautet:

subroutine subroutinenname ([formale parameter])
  Vereinbarungsteil
  Anweisungsteil
  [return]
end subroutine subroutinenname

Mittels return kann zur aufrufenden Programmeinheit zurückgekehrt werden. Ein return unmittelbar vor der end-Anweisung ist nicht unbedingt erforderlich, da das Programm beim Erreichen der end zur aufrufendenen Programmeinheit zurück springt. Deswegen sollte bei allen neueren Programmen darauf verzichtet werden. Wichtig ist die return Anweisung für alternative Rücksprünge zur aufrufenden Programmeinheit.

Aufgerufen wird eine Subroutine aus der aufrufenden Programmeinheit mittels der call-Anweisung:

call subroutinenname [([aktuelle parameter])]


Im folgenden Beispiel wird die Subroutine sub aufgerufen. Diese gibt dann zwei Werte zurück:

Datei bsp.f95

Fortran 90/95-Code (free source form)
program bsp
  !
  implicit none

  real :: x, y, z

  write( *, * ) 'x -> '
  read( *, * ) x

  call sub( x, y, z )

  write( *, * ) 'y -> ', y, 'z -> ', z

end 

subroutine sub( a, b, c )
  !
  implicit none

  real :: a, b, c

  b = a**2  
  c = a**2 + b

end subroutine sub

Weitere Anweisungen für die Unterprogrammtechnik

Bearbeiten

Das intent-Attribut

Bearbeiten

Im Vereinbarungsteil der Funktion wird der Parameterdeklaration das Schlüsselwort intent mitgegeben.

datentyp, intent( in ) :: var

Mit intent( in ) wird angezeigt, dass der Parameter var in der Funktion nicht geändert wird und kein Rückfluss der Information in die aufrufende Programmeinheit stattfindet. Ein intent( out ) oder intent( inout ) wie bei Subroutinen wäre meist auch möglich, widerspricht aber dem Grundgedanken des Fortran-Funktionsbegriffs und der strukurierten Programmierung. Bei einer Funktion soll der Informationsrückfluss über den Rückgabewert stattfinden und nicht über Parameter.


Bei der Parameterübergabe bietet eine Subroutine folgende intent-Möglichkeiten:

  • datentyp, intent(in) :: var ... Informationfluss von der aufrufenden Programmeinheit in die Funktion
  • datentyp, intent(out) :: var ... Informationfluss von der Subroutine zur aufrufenden Programmeinheit
  • datentyp, intent(inout) :: var ... beidseitiger Informationsfluss

Beispiel:

Fortran 90/95-Code (free source form)
program bsp
  implicit none
  real :: a, b, c
 
  a = 1.0
  b = 2.0
  c = 3.0
  
  call sub(a, b, c)
 
  write (*,*) 'Hauptprogramm: ', a, b, c
  ! Ausgabe: 1.000000       22.20000       33.30000
end  

subroutine sub(x, y, z)
  implicit none
  real, intent(in)    :: x
  real, intent(out)   :: y
  real, intent(inout) :: z
 
  write (*,*) 'Unterprogramm: ', x, y, z
  ! Ausgabe: 1.000000       2.000000       3.000000
 
  y = 22.2
  z = 33.3
 
end subroutine sub

Die aktuellen und formalen Parameter müssen hinsichtlich Datentyp, Anzahl, Reihenfolge übereinstimmen.

Die pure Anweisung

Bearbeiten

Ein Unterprogramm welches keine Seiteneffekte hat ist ein bloßes bzw. reines (pure) Unterprogramm. Ein Unterprogramm erzeugt dann keine Seiteneffekte, wenn es weder seine Eingabedaten, noch die Daten verändert, die außerhalb des Unterprogrammes liegen, es sei denn, es wären seine Ausgabedaten. In einem reinen Unterprogramm haben die lokalen Variablen keine save Attribute, noch werden die lokalen Variablen in der Datendeklaration initialisiert.

Reine Unterprogramme sind für das forall-Konstrukt notwendig: das forall-Konstrukt wurde für das parallele Rechnen konzipiert, weshalb hier der Computer entscheidet, wie das Konstrukt abgearbeitet werden soll. Dazu ist es aber notwendig, das es egal ist in welcher Reihenfolge das Konstrukt abgearbeitet wird. Gilt dies nicht - hat das Unterprogramm also Seiteneffekte - so kann das forall-Konstrukt nicht verwendet werden.

Jedes Ein- und Ausgabeargument in einem reinen Unterprogramm muss mittels des intent-Attributs deklariert werden. Darüberhinaus muss jedes Unterprogramm, das von einem reinen Unterprogramm aufgerufen werden soll, ebenfalls ein reines Unterprogramm sein. Sonst ist das aufrufende Unterprogramm kein reines Unterprogramm mehr.

Die elemental Anweisung

Bearbeiten

Ein Unterprogramm ist elementar, wenn es als Eingabewerte sowohl Skalare als auch Felder akzeptiert. Ist der Eingabewert ein Skalar, so liefert ein elementares Unterprogramm einen Skalar als Ausgabewert. Ist der Eingabewert ein Feld, so ist der Ausgabewert ebenfalls ein Feld.

Die return Anweisung

Bearbeiten

Eine return-Anweisung darf nur im Gültigkeitsbereich von Unterprogrammen verwendet werden. Sie bewirkt einen Abbruch der Unterprogrammausführung und eine Rückkehr zum Aufrufpunkt, wo mit der nächsten Anweisung fortgesetzt wird.

Beispiel:

Fortran 90/95-Code (free source form)
program main
  implicit none
  
  call xyz( -2 )  
  write( *, * ) "UP-Ende"  
  stop
  write( *, * ) "Programmende"


  contains
    subroutine xyz( n )
      integer, intent( in ) :: n
      integer               :: k
      
      do k = n, n+20
        write( *, * ) k
	if( k == 5 ) return
      end do

      write( *, * ) "k_max =", k
    end subroutine xyz
    
! Ausgabe:
!          -2
!          -1
!           0
!           1
!           2
!           3
!           4
!           5
!   UP-Ende
end program main

Einige Compiler erlauben zwecks Programmabbruch auch ein return anstelle des stop im Hauptprogramm. Das ist aber nicht standardkonform. Andere Compiler würden solchen Code mit einer Fehlermeldung abweisen, das Programm wäre somit nicht mehr uneingeschränkt portabel.

Ein exit in der Schleife anstelle der return-Anweisung würde nur den Schleifendurchlauf abbrechen und die Unterprogrammausführung würde nach der Schleife fortgesetzt.

Felder als Parameter

Bearbeiten

Beispiel: Übergabe eines ganzen Feldes

Datei bsp.f95:

Fortran 90/95-Code (free source form)
program bsp
  implicit none 
 
  integer, dimension(3,3) :: feld
  integer :: cnt, i, j
   
  cnt = 1
 
  do i = 1, 3
    do j = 1, 3
      feld(j,i) = cnt
      cnt = 1 + cnt
    end do
  end do
  
  !  Unterprogrammaufruf
  call sub(feld)
  !  Ausgabe: 1   2   3   4   5   6   7   8   9
end program bsp


Datei sub.f95

Fortran 90/95-Code (free source form)
subroutine sub(arr)
  implicit none
  integer, dimension(3,3), intent(in) :: arr
 
  write(*,*) arr
     
end subroutine sub


Beispiel: Übergabe einer Feld-Teilkette

Datei bsp.f95:

Fortran 90/95-Code (free source form)
program bsp
  implicit none 
 
  integer,dimension(3,3) :: feld
  integer :: cnt, i, j
   
  cnt = 1
 
  do i = 1, 3
    do j = 1, 3
      feld(j,i) = cnt
      cnt = 1 + cnt
    end do
  end do
  
  ! Unterprogrammaufruf
  call sub(feld(1:2,2:3))
  !  Ausgabe: 4    5    7     8
end program bsp


Datei sub.f95

Fortran 90/95-Code (free source form)
subroutine sub(arr)
  implicit none
  integer, dimension(0:1, 0:1), intent(in) :: arr
  
  write(*,*) arr
    
end subroutine sub


Beispiel: Übergabe eines Feld-Einzelelements

Datei bsp.f95:

Fortran 90/95-Code (free source form)
program bsp
  implicit none
 
  integer, dimension(3,3) :: feld
  integer cnt, i, j
   
  cnt = 1
 
  do i = 1, 3
    do j = 1, 3
      feld(j,i) = cnt
      cnt = 1 + cnt
    end do
  end do
  
  ! Unterprogrammaufruf
  call sub(feld(1,2))
  ! Ausgabe: 4
end program bsp


Datei sub.f95

Fortran 90/95-Code (free source form)
subroutine sub(arr)
  implicit none
  integer, intent(in) :: arr
 
  write(*,*) arr

end subroutine sub

Prozeduren als Parameter

Bearbeiten

Auch Prozeduren können als Parameter übergeben werden.

Standardfunktionen (intrinsic functions) werden dazu folgendermaßen im Vereinbarungsteil gekennzeichnet:

intrinsic namensliste

oder

datentyp, intrinsic :: namensliste


Eigene Unterprogramme oder Unterprogramme aus Bibliotheken mit:

external namensliste

oder

datentyp, external :: namensliste


Beispiel:

Fortran 90/95-Code (free source form)
program bsp
  real, parameter :: PI=3.1415927
 
! intrinsic functions
  intrinsic sin, cos
 
! Unterprogrammaufrufe
  call sub(sin, PI)
! Ausgabe: 0.000000
  call sub(cos, PI)
! Ausgabe: -1.000000
end program bsp
 
subroutine sub(funk, x)
  implicit none
  real :: funk, x

  write(*,*) nint(funk(x)*1000)/1000.0

end subroutine sub

Optionale Parameter

Bearbeiten

Ab Fortran 90 sind auch optionale Parameter für Unterprogramme erlaubt. Solche Parameter sind durch das Attribut optional zu kennzeichnen. Auch der aufrufenden Programmeinheit muss diese Parameter-Optionalität bekannt gegeben werden, z.B. über ein Interface. Das aktuelle Vorhandensein eines als optional markierten Parameters beim Unterprogrammaufruf kann im Unterprogramm selbst durch die Funktion present geprüft werden.


Beispiel:

Fortran 90/95-Code (free source form)
program bsp
  implicit none 
  
  interface 
    subroutine ab(a, b)
      integer, intent( in )           :: a
      integer, intent( in ), optional :: b
    end subroutine ab
  end interface
  
  call ab( 1 )
  call ab( 8, 12 )  

! Ausgabe:
!   Nur a gegeben  1
!   Beide Parameter gegeben  8 12
end program bsp

subroutine ab(a, b)
  integer, intent( in )           :: a
  integer, intent( in ), optional :: b
   
  if( present( b ) ) then
    write (*,*) "Beide Parameter gegeben",  a, b
  else
    write (*,*) "Nur a gegeben", a
  end if   
 
end subroutine ab

Module ersetzen in Fortran 95 die FORTRAN 77-COMMON-Blöcke. In einem Modul können Variablen, Konstanten und auch Unterprogramme abgelegt werden. Diese können dann von verschiedenen Programmeinheiten angesprochen werden.

module modulname
  [implicit none]
  [save]
 
  Deklaration von Variablen, Konstanten
 
  [contains 
     Unterprogramme
  ]
end module modulname


Das Einbinden eines Moduls in eine Programmeinheit geschieht mittels der Anweisung

use modulname [, only liste]

only signalisiert, dass nur die in liste angegebenen Variablen oder Konstanten verwendet werden sollen.


Beispiel:

Fortran 90/95-Code (free source form)
module m1
  implicit none
  save
   
  real, parameter :: PI=3.1415
  real :: a
end module m1
 
program bsp
  use m1
  implicit none 
 
  a = 1.5
  write(*,*) 'Hauptprogramm 1: ', PI, a
  ! Ausgabe: Hauptprogramm 1:    3.141500       1.500000
  call sub
  write(*,*) 'Hauptprogramm 2: ', PI, a
  ! Ausgabe: Hauptprogramm 2:    3.141500       2.500000

end program bsp
 
subroutine sub
  use m1
  implicit none
   
  write(*,*)  'Unterprogramm: ', PI, a
  ! Ausgabe: Unterprogramm:    3.141500       1.500000
  a = 2.5

end subroutine sub

Das save-Statement in Modulen stellt sicher, dass die aktuellen Werte von Modulvariablen beim Wechsel zwischen den verschiedenen Programmeinheiten sicher erhalten bleiben.

Rekursiver Unterprogrammaufruf

Bearbeiten

In Fortran 95 können Unterprogramme (Funktionen, Subroutinen) auch rekursiv aufgerufen werden. Rekursion bedeutet, dass ein Unterprogramm sich selbst wieder aufruft (lat. recurrere oder en. recur ... wiederkehren, zurückkommen).

Beispiel: Berechnung von n! (vereinfacht)

Fortran 90/95-Code (free source form)
program bsp
  implicit none 
 
  integer :: zahl, ergebnis, fac
 
  write(*,*) "Gib eine Ganzzahl ein: "
  read(*,*) zahl
  
  ergebnis = fac(zahl)
 
  write(*,*) "Ergebnis ist: ", ergebnis 
end program bsp
 
 
recursive function fac(n) result(zerg)
  implicit none
  integer, intent(in) :: n
  integer :: zerg
    
  ! Vereinfacht: Keine Überprüfung ob Überlauf, negative Zahl, etc.
   
  zerg = n
 
  if (n > 1) then
    zerg = n * fac(n-1)
  end if
  
  return
end function fac


Der Funktionskopf ist bei diesem Beispiel etwas anders gestaltet als üblich. Während der Intel-Fortran-Compiler auch ein

recursive integer function fac(n)

oder ein

integer recursive function fac(n)

problemlos akzeptiert, wirft der gfortran-Compiler bei diesen Varianten einen Syntaxfehler.


<<< zur Fortran-Startseite
<< Fortran 95 Fortran 2003 >>
< Standardfunktionen Ein- und Ausgabe >