PureBasic: Prozeduren

Prozeduren sind ein Mittel, um immer wieder benötigten Code einfach zur Verfügung zustellen. Anstatt den gleichen Code immer und immer wieder in die Quelldatei zu schreiben, definiert man eine Prozedur. Prozeduren sind im Grunde nichts anderes, als die schon vorhandenen Befehle von PureBasic, PrintN() ist z.B. eine.

; Listing 25: Prozeduren

Procedure.l square(x.l)
  ProcedureReturn x*x
EndProcedure

OpenConsole()

PrintN(Str(square(2)))

Delay(2000)
Ausgabe:

4

Dieses einfache Beispiel verdeutlicht, wozu Prozeduren in der Lage sind: Anstatt immer wieder x*x einzutippen, schreibt man square(x), wodurch der Quellcode insgesamt lesbarer wird, da immer sofort erkennbar ist, was gemeint wurde. Natürlich kann man auch weitaus komplexeren Code in eine Prozedur schreiben.

Hinter dem Schlüsselwort Procedure wurde ein Typ übergeben (Long). Dieser ist wichtig, da er definiert, was für einen Typ der Rückgabewert hat. Der Rückgabewert ist das, was hinter ProcedureReturn steht. Nach der Auswertung der Prozedur erhält Str() diesen Wert und kann ihn weiterverarbeiten, genauso wie Str() einen String als Rückgabewert hat, der von PrintN() weiter verarbeitet werden kann. Nachdem ProcedureReturn aufgerufen wurde, springt die Programmausführung aus der Prozedur wieder zu der Stelle, an der die Prozedur aufgerufen wurde.

In den Klammern des Prozedurenbezeichners ist ein sogenanntes Argument. Dieses erhält einen Namen und einen Typ. Beim Aufruf der Prozedur muss immer ein Long als Argument übergeben werden, genauso wie man Str() einen Long übergeben muss, damit dieser in einen String umgewandelt werden kann. Eine Prozedur kann auch mehrere Argumente erhalten. Es ist zu beachten, dass nicht die Variable an sich übergeben wird, sondern der Wert der Variable in eine interne Prozedurvariable mit dem Argumentennamen als Bezeichner kopiert wird. Der Rückgabetyp, der Bezeichner und die Argumente ergeben zusammen den sogenannten Prozedurkopf.

In anderen Programmiersprachen wird das, was in PureBasic Prozeduren sind, normalerweise Funktionen genannt. Eigentlich haben Prozeduren keinen Rückgabewert, Funktionen hingegen immer, das Konzept der Funktionen wurde jedoch erst nach den Prozeduren entwickelt.

SichtbarkeitBearbeiten

Sichtbarkeit bedeutet, dass eine Variable, die außerhalb einer Prozedur definiert wurde, unter Umständen nicht in der Prozedur verfügbar ist.

; Listing 26: Sichtbarkeit

Procedure scope()
  x = 5
  PrintN(Str(x))
EndProcedure

OpenConsole()

x = 2
scope()
PrintN(Str(x))
 
Delay(2000)
Ausgabe:

5
2

Erst wird 5 ausgegeben und dann die 2, obwohl in der Prozedur x = 5 steht. Man könnte also annehmen, dass zweimal eine 5 augegeben werden müsste. Dies liegt daran, dass x in der Prozedur ein anderes ist, als das außerhalb. Es gibt Schlüsselwörter, wie Global, Protected und Shared, die dazu da sind, diesen Umstand zu umgehen, die Benutzung dieser ist jedoch in der modernen Programmierung verpönnt. Diese Schlüsselwörter stammen noch aus einer Zeit, als es noch keine Rückgabewerte und Argumente gab. Man arbeitet heutzutage über Zeiger, auf die in einem anderen Kapitel eingegangen wird.

StaticBearbeiten

Das Schlüsselwort Static wird dazu benutzt, um eine Variable in einer Funktion einmal zu definieren und zu initialiseren, jedoch kein weiteres mal.

; Listing 27: Static

Procedure static_beispiel()
  Static  a = 1
  a+1
  Debug a
EndProcedure
 
OpenConsole()

static_beispiel()
static_beispiel()
static_beispiel()

Delay(2000)
Ausgabe:

2
3
4

Die Ausgabe ergibt hintereinander 2, 3 und 4, obwohl in der Prozedur a = 1 steht. Dies liegt am Schlüsselwort Static, dass eine neue Definition und Initialisierung verhindert.

Arrays als ArgumentBearbeiten

Es ist auch möglich Arrays als Argument zu übergeben.

; Listing 28: Array als Argument
 
Procedure array_argument(Array a(1))
  For x = 2 To 0 Step -1
    a(x) = 2-x
  Next
EndProcedure
 
OpenConsole()

Dim a(2)
For x = 0 To 2
  a(x) = x
Next

array_argument(a())

For x = 0 To 2
  PrintN(Str(a(x)))
  Debug a(x)
Next

Delay(2000)
Ausgabe:

2
1
0

Wenn man ein Array als Argument übergibt, muss man in den Klammern des Arraybezeichners die Anzahl der Arraydimensionen angeben und vor den Bezeichner das Schlüsselwort Array schreiben. Wichtig ist, dass Arrays nicht wie normale Variablen kopiert werden, sondern als sogenannte Referenz übergeben werden, d.h. das Array in der Prozedur ist dasselbe Array wie außerhalb der Prozedur. Alle Änderungen die in der Prozedur am Array vorgenommen werden, sind auch außerhalb der Prozedur sichtbar, deswegen werden die Zahlen rückwärts ausgegen, weil sie in der Prozedur in dasselbe Array geschrieben werden. Deshalb muss man Arrays nicht als Rückgabewert zurückgeben, genauer: es geht garnicht.

Die gleichen Regeln gelten auch für Maps und Listen, man benutzt dann das Schlüsselwort Map bzw. List.

DeclareBearbeiten

Es gibt Fälle, in denen man eine Prozedur aufrufen möchte, diese jedoch noch nicht definiert ist. Hier kommen Prozedurprototypen ins Spiel.

; Listing 29: Declare

OpenConsole()

Declare.l square(x.l)

PrintN(Str(square(2)))

Procedure.l square(x.l)
  ProcedureReturn x*x
EndProcedure

Delay(2000)
Ausgabe:

4

In diesem Fall wird die Prozedur square() aufgerufen, bevor sie definiert wurde. Deshalb deklariert man sie vorher mit dem Schlüsselwort Declare, wodurch dieses Verhalten erst möglich wird. Der Prozedurkopf muss dabei genau der gleiche sein, wie der bei der Definition der Prozedur. Dieses Verhalten ist vorallem dann hilfreich, wenn man eine Prozedur in einer anderen aufrufen möchte, obwohl sie erst unter dieser definiert wird.

Rekursion und IterationBearbeiten

Es ist möglich eine Prozedur innerhalb einer anderen aufzurufen. Wenn eine Prozedur sich selber aufruft, spricht man von Rekursion. So ist es z.B. möglich die Fakultät einer Zahl zu berechnen.

; Listing 30: Rekursion

OpenConsole()

Procedure.l fak(n.l)
  If n <= 1
    ProcedureReturn 1
  Else
    ProcedureReturn n * fak(n-1)
  EndIf
EndProcedure

Print("Bitte geben Sie eine natürliche Zahl ein: ")
n = Val(Input())
PrintN("Die Fakultät lautet: "+Str(fak(n)))

Delay(2000)
Ausgabe:

Bitte geben Sie eine natürliche Zahl ein: 4
Die Fakultät lautet: 24

Es wird eine Prozedur fak() definiert, die die Fakultät rekursiv berechnen soll. Die Prozedur hat eine sogenannte Abbruchbedingung, d.h. in diesem Fall ruft sie sich nicht mehr selber auf. Gebe es diese Bedingung nicht, würde sich die Funktion immer wieder selber aufrufen, was zum Absturz des Computers führen würde, da der Arbeitsspeicher überfüllt wird. In allen anderen Fällen multipliziert sie die eingegebene Zahl mit allen Zahlen die niedriger als die Eingebene sind, bis zur 1, denn das wäre die Abbruchbedingung. Dies geschieht über Rekursion, die Prozedur ruft sich selber auf. Um genau zu verstehen was geschieht, sollte man einmal den Rückgabewert ausschreiben: 4 * fak(3). Am Anfang steht die eingegebene 4 und danach wird fak(3) aufgerufen. Nun ist n 3, es wird also aufgerufen 3 * fak(2). fak(3) steht also für 3 * fak(2), man erhält 4 * 3 * fak(2). fak(2) gibt wiederum 2 * fak(1) zurück, man hat bisher als Rückgabewert also 4 * 3 * 2 * fak(1). fak(1) erfüllt die Abbruchbedingung und gibt einfach 1 zurück. Der entgültige Rückgabewert lautet also 4 * 3 * 2 * 1, was die Fakultät von 4 (24) ist.

Ein weiteres Verfahren wäre die Iteration, ein Verfahren, das in PureBasic über Schleifen genutzt wird.

; Listing 31: Iteration

OpenConsole()

Procedure.l fak(n.l)
  fak = 1
  For k = 1 To n
    fak * n
  Next
  ProcedureReturn fak
EndProcedure

Print("Bitte geben Sie eine natürliche Zahl ein: ")
n = Val(Input())
PrintN("Die Fakultät lautet: "+Str(fak(n)))

Delay(2000)
Ausgabe:

Bitte geben Sie eine natürliche Zahl ein: 4
Die Fakultät lautet: 24

Das Beispiel bedarf keiner weiteren Erläuerung, wenn man Schleifen verstanden hat. Iterative Verfahren sind meistens weitaus schneller als rekursive und können außerdem den Arbeitsspeicher nicht zum Absturz bringen. Nach Möglichkeit sollte man also immer nach iterativen Verfahren suchen.

AufgabenBearbeiten

  1. Es soll ein Programm geschrieben werden, in dem einer Prozedur ein Array von Zahlen übergeben wird und die alle Zahlen ausgibt, die größer als der Durchschnitt sind. Der Durchschnitt berechnet sich über die Gesamtsumme der Zahlen geteilt durch die Anzahl der Zahlen. Der Inhalt des Arrays kann fest einprogrammiert werden und muss nicht unbedingt vom Benutzer eingegeben werden.
  2. Es soll ein Programm geschrieben werden, das die Fibonacci-Folge bis zu einer eingebenen Stelle berechnet. Diese soll dann augegeben werden. Eine Stelle der Fibonacci-Folge berechnet sich als die Summe der beiden vorherigen. Die nullte Stelle ist 0 und die erste 1.