Fortran: Fortran 95: Zeiger

Aus Wikibooks
Zur Navigation springen Zur Suche springen
<<< zur Fortran-Startseite
<< Fortran 95 Fortran 2003 >>
< Ein- und Ausgabe Vektoren- und Matrizenrechnung >


Was sind Zeiger?[Bearbeiten]

Wikipedia hat einen Artikel zum Thema:

Ein Zeiger (Pointer) ist eine Variable, die Adresse und (Daten-)Typ enthält – ähnlich wie bei einem Array, das rank und shape enthält. Der Zeiger (Pointer) charakterisiert Position, Länge und Organisation des Speicherabschnitts, an dem die Variable (Target) gehalten wird.

Zeiger in Fortran 95[Bearbeiten]

In Fortran 95 werden Zeiger durch Zufügen des Attributes pointer bei der Deklaration von Variablen erzeugt.

datentyp, pointer :: variable

Ein so deklarierter Zeiger kann auf andere Zeiger oder auf mittels target gekennzeichnete Variablen verweisen.

datentyp, target :: variable

Die Zeigerzuordnung erfolgt durch das Symbol

=>

Beispiel:

Fortran 90/95-Code (free source form)
program bsp
  implicit none
 
  real, pointer :: ptr
  real, target :: trg
 
  trg = 5.5
  ptr => trg
  write(*,*) ptr
  ! Ausgabe: 5.50000
  ! Zeiger werden bei Bedarf automatisch dereferenziert
end program bsp


Fortran95zeiger.png

Assoziationsstatus[Bearbeiten]

Ein Zeiger kann einen der folgenden Assoziationzustände annehmen:

  • undefiniert (dangling)
  • nicht zugeordnet (disassociated, null)
  • zugeordnet (associated)

Der Zuordnungsstatus eines Zeigers ist unmittelbar nach der Deklaration undefiniert. Mittels zeiger => null() oder nullify(zeiger) kann ein Zeiger auf einen nicht zugeordneten Status gesetzt werden. Verweist ein Zeiger auf einen anderen zugeordneten Zeiger oder ein Target, so ist sein Zustand zugeordnet.

Der Assoziationsstatus eines Zeigers lässt sich über die Funktion

associated (zeiger [, ziel])

abfragen. Sinnvoll ist eine derartige Abfrage nur dann, wenn der Zuordnungsstatus nicht undefiniert ist.

Beispiel:

Fortran 90/95-Code (free source form)
program bsp
  implicit none
 
  integer, pointer       :: ptr1 => null()
  character(20), pointer :: ptr2
  character(20), target  :: str
 
  str = "Hallo, Welt!"
  ptr2 => str
 
  write(*,*) associated(ptr1)
  ! Ausgabe: F
 
  write(*,*) associated(ptr2)
  ! Ausgabe: T
 
  write(*,*) associated(ptr2, ptr1)
  ! Ausgabe: F
 
  write(*,*) associated(ptr2, str)
  ! Ausgabe: T
end program bsp

Speicherplatz dynamisch reservieren und freigeben[Bearbeiten]

Für normale Variablen läuft die Speicherplatzverwaltung automatisch ab. Bisher wurden Zeiger immer solchen normalen (Target)Variablen, für die bereits Speicherplatz reserviert war, zugeordnet. Aber auch für Zeiger selbst kann Speicherplatz reserviert werden. Bei der Zeigerdeklaration ist der Zeigerstatus undefiniert oder nicht zugeordnet. Eine Wertzuweisung an eine solche Zeigervariable würde zur Laufzeit einen Speicherzugriffsfehler ergeben. Die Funktion

allocate (zeiger1, [zeiger2, ...] [,stat=integervar])

reserviert in Abhängigkeit des Zeiger-Datentyps Speicherplatz für die einzelnen Zeiger. Die Funktion

deallocate (zeiger1, [zeiger2, ...] [,stat=integervar])

gibt diesen Speicherplatz wieder frei.

Beispiel:

Fortran 90/95-Code (free source form)
program bsp
  implicit none
 
  integer, pointer :: ptr1 => null(), ptr2 => null()
  integer :: status
  	 
  allocate(ptr1, stat = status)
  ptr1 = 2222	 
  
  write (*,*) "Status = ", status
  ! Wenn status = 0, dann wurde erfolgreich Speicherplatz reserviert
  
  write (*,*) ptr1
  ! Ausgabe: 2222
  
  ptr2 => ptr1
  ptr1 = 5555	 
  
  write (*,*) ptr1, ptr2
  ! Ausgabe: 5555   5555
 
  deallocate(ptr1)
end program bsp

Zeiger und Felder[Bearbeiten]

Beispiel:

Fortran 90/95-Code (free source form)
program bsp
  implicit none
 
  integer, dimension(:), pointer   :: ptr => null()
  integer, dimension(5:10), target :: arr = (/55, 66, 77, 88, 99, 111/)
  	 
  ptr => arr
  write(*,*) ptr 
  ! Ausgabe:  55    66    77    88    99    111
  
  ptr => arr(7:)
  write(*,*) ptr 
  ! Ausgabe:  77    88    99    111
end program bsp


Beispiel: "ragged array"

Ragged array.svg

Fortran 90/95-Code (free source form)
program bsp
  implicit none 
  
  type element
    integer, dimension(:), pointer :: ptr
  end type element

  integer, dimension(5), target :: a = (/ 1, 2, 3, 4, 5 /)
  integer, dimension(2), target :: b = (/ 99, 55 /)
  integer, dimension(4), target :: c = (/ -11, -12, -13, -14 /)
  integer                       :: i
  type( element ), dimension(3) :: rarr
  
  rarr(1)%ptr => a
  rarr(2)%ptr => b
  rarr(3)%ptr => c

  do i = 1,3 
    write (*,*) "rarr(", i, "): ", rarr(i)%ptr
  end do
  
! Ausgabe
!   rarr( 1 ):  1 2 3 4 5
!   rarr( 2 ):  99 55
!   rarr( 3 ):  -11 -12 -13 -14
end program bsp

Verkettete Listen[Bearbeiten]

In der Regel ist die Größe einer Liste vor ihrer Erstellung (zum Bsp. durch Einlesen einer Datei) nicht bekannt, was die Verwendung von Array erschwert. Dies lässt sich durch Pointer vereinfachen. Man unterscheidet dabei viele verschiedene Listentypen:

  • linear und einfach verkettet: Diese Liste lässt sich nur in eine Richtung durchlaufen und das Ende hat keinen Bezug zum Anfang.
  • zyklisch und einfachverkettet: Nur eine Durchlaufrichtung, aber das Ende ist mit dem Anfang verpointert. Nach dem vollständigen Durchlaufen beginnt man wieder am Anfang.
  • linear und doppelt verkettet: Lineare Liste, in der jedes Element auf das nächste UND das vorherige zeigt. Der Durchlauf der Liste ist so in zwei Richtungen möglich. Der Anfang ist dabei nicht mit den Ende verpointert.
  • zyklisch und doppelt verkettet: Zyklische Liste innerhalb derer zwei Durchlaufrichtungen zur Verfügung stehen.

Die Wahl des Listentyps richtet sich nach dem Verwendungszweck. Einfach verkettete Listen sind auch einfach in der Implementierung. Für den Zugriff entgegen der Durchlaufrichtung muss die Liste jedoch einmal vollständig durchlaufen werden, was den Rechenaufwand und vor allem die Rechenzeit erhöht. Sie eignen sich daher gut für einfache Aufgaben mit gerichteter Abarbeitung. Doppelt verkettete Listen lassen mehr Freiraum hinsichtlich ihr Durchlaufrichtung. Sie sind jedoch komplexer in Ihrer Implementierung und erhöhen den Speicheraufwand.

Anfügen von Listenelementen[Bearbeiten]

Beispiel: Lineare, einfach verkettete Liste (LIFO)

Fortran 90/95-Code (free source form)
module m1
  implicit none
 
  type node
    integer :: id
    character(5) :: value
    type(node), pointer :: next => null()
  end type
 
  type(node), pointer :: first => null()
  
  private ! Auf alle nachfolgenden Anweisungen kann von aussen nicht zugegriffen werden

  public :: add_node, write_all, free_all ! Auf die Subroutinen add_node, write_all und free_all 
                                ! kann von aussen explizit zugegriffen werden, 
                                ! jedoch nicht auf innerhalb der Subroutinen
                                ! deklarierte Datentypen

  contains
    subroutine add_node(id, str)
      implicit none
 		 
      integer, intent(in) :: id
      character(5), intent(in) :: str
 		 
      type(node), pointer :: new, tmp
		 
      ! Speicher reservieren
      allocate(new)
 		 
      ! Werte setzen
      new%id = id
      new%value = str
 		 
      ! Am Beginn der Liste einfügen
      if (.not. associated(first)) then
        first => new
      else
        tmp => first
        first => new
        first%next => tmp
      end if
  
    end subroutine add_node
 	 
 	 
    subroutine write_all()
      implicit none
 		 
      type(node), pointer :: tmp
 		 
      tmp => first
 		 
      do 
        if (associated(tmp) .eqv. .FALSE.) exit
 		 
        write(*,*)tmp%id, tmp%value
        tmp => tmp%next
      end do 			 
    end subroutine write_all


    subroutine free_all()
      implicit none
                 
      type(node), pointer :: tmp
         
      do        
      	tmp => first
         
        if (associated(tmp) .eqv. .FALSE.) exit
          
        first => first%next
        deallocate(tmp)
      end do                     
    end subroutine free_all

end module m1
 
 
program bsp
  use m1
  implicit none
 
  call add_node (1, "AAAAA")
  call add_node (2, "BBBBB")
  ! ...
  call add_node (150, "ZZZZZ")
 
  call write_all   
  ! Ausgabe:
  !   150 ZZZZZ
  !   2 BBBBB
  !   1 AAAAA

  call free_all ! Die verkettete Liste wird wieder freigegeben

end program bsp


Fortran95vliste1.png


Beispiel: Zyklische einfach verkettete Liste:

Fortran 90/95-Code (free source form)

module m1
  implicit none
  private
  public: ... 

  type node
    integer :: id
    character(5) :: value
    type(node), pointer :: next => null()
  end type
 
  type(node), pointer :: last => null()  !externer Zeiger auf die zyklische Liste


...  ! Vergleich oben, subroutine add_node(id, str) 

 subroutine add_node(id, str)
      implicit none
 		 
      integer, intent(in) :: id
      character(5), intent(in) :: str
 		 
      type(node), pointer :: new, tmp
		 
      ! Speicher reservieren
      allocate(new)
 		 
      ! Werte setzen
      new%id = id
      new%value = str
 		 
      ! Listen Elemente werden in Reihenfolge des Einlesen verpointert
      if (.not. associated(last)) then
        last => new
        new%next => new                     ! das erste Element muss auf sich selbst zeigen für den zkylus 
      else
        tmp => last
        last => new
        new%next => tmp%next
        tmp%next => new
      end if
  
    end subroutine
...

Löschen von Listenelementen[Bearbeiten]

Für das Löschen eines Elementes aus einer Liste lässt sich eine kurze Subroutine schreiben, welche die Zeiger neu setzt und das Element löscht. Man kann dabei in einfach verketteten Listen nicht ohne größeren Aufwand das Element löschen, welches soeben in der Liste betrachtet wird, sondern nur das nachfolgende Element.

Fortran 90/95-Code (free source form)

 SUBROUTINE del_next(pntr)
  TYPE(liste),POINTER :: pntr                    !Subroutine bekommt einen externen Pointer übergeben
  TYPE(liste),POINTER :: tmp => NULL()

  tmp => pntr               ! pntr wird in tmp zwischengespeichert
  pntr => pntr%next         ! pntr wird um zwei Postionen weitergesetzt  
  pntr => pntr%next                                                               
  DEALLOCATE(tmp%next)      ! tmp%next wird gelöscht
  tmp%next => pntr          ! Verbindung wird neu gesetzt 
 END SUBROUTINE


<<< zur Fortran-Startseite
<< Fortran 95 Fortran 2003 >>
< Ein- und Ausgabe Vektoren- und Matrizenrechnung >