2.6 Unterprogramme

Einem Programm eine übersichtliche Struktur zu geben ist eine wesentliche Voraussetzung, um verzwickte und schwer zu findende Fehler zu vermeiden.

Unterprogramme (Sub-Routinen) sind in sich geschlossene Programmabschnitte, quasi Programme innerhalb eines Programms. Die Vorteile bei der Verwendung von Unterprogrammen sind:

  • Strukturierung: Das Programm wird wesentlich besser lesbar, da es in kleine, voneinander unabhängige Einheiten (Unterprogramme) zerlegt wird.

  • Stabilität: Kleine Unterprogramme sind wesentlich einfacher fehlerfrei zu halten als ein komplexes Riesenprogramm.

  • Kapselung: Die Verwendung lokaler Variablen garantiert, dass sich die Programmteile nicht unerwünscht gegenseitig beeinflussen.

  • Mehrfache Verwendbarkeit: Unterprogramme können so oft gerufen werden, wie es nötig ist. Unterprogramme können andere Unterprogramme aufrufen, sie können sich sogar selbst aufrufen.

R-BASIC unterstützt folgende Arten von Unterprogrammen:

  • Unterprogramme ohne Rückgabe von Funktionswerten (SUB)
    SUB's werden über ihren Namen aufgerufen.
NameDerSub [ < ParameterListe> ]

  • Unterprogramme mit Rückgabe von Funktionswerten (FUNCTION)
    Funktionen werden ebenfalls über ihren Namen aufgerufen. In den meisten Fällen stehen Funktionen auf der rechten Seite einer Zuweisung.
<variable> = NameDerFunction ( [ <Paramterliste> ] )

  • Action-Handler für Objekte
    Actionhandler werden automatisch von ihren Objekten aufgerufen. Sie können nicht von anderen Teilen des Programms aus gerufen werden.

  • Aus Kompatibilitätsgründen wird die Kombination GOSUB / RETURN unterstützt. Sie sollten GOSUB in eigenen Programmen nicht verwenden.

^

2.6.1 Konzept: Lokale Variablen

Ein Unterprogramm hat vollen Zugriff auf alle global definierten Variablen, Konstanten, Strukturen usw. und kann mit diesen arbeiten. Globale Variablen werden üblicherweise im DIM & DATA Fenster vereinbart. Wenn aber alle Unterprogramme ausschließlich mit globalen Variablen arbeiten, wird das schnell unübersichtlich und es kann zu einer unerwünschten gegenseitigen Beeinflussung der Programmteile führen. Deswegen kann man Variablen "lokal", das heißt nur für dieses eine Unterprogramm definieren. Dazu schreibt man die entsprechende DIM-Anweisung innerhalb des Unterprogramms. Diese Variablen sind dem Compiler nur innerhalb des Unterprogramms bekannt und können auch nur innerhalb dieses Unterprogramms benutzt werden.

Stößt der Compiler innerhalb eines Unterprogramms auf eine Variable (z.B. A$), sucht er zuerst, ob diese lokal definiert ist. Ist das der Fall, wird die lokale Variable verwendet. Nur wenn sie nicht lokal definiert ist, wird die entsprechende global definierte Variable verwendet. Diese Technik nennt man Kapselung. Das hat drei sehr praktische Folgen:

  • Man kann die lokalen Variablen unabhängig von den global definierten Variablen, Konstanten usw. benennen. Bei Namensgleichheit wird auf jeden Fall die im Unterprogramm definierte Variable verwendet.

  • Verschiedene Unterprogramme brauchen ebenfalls keine Rücksicht aufeinander nehmen, auch dann nicht, wenn sie sich gegenseitig aufrufen. Damit kann man zum Beispiel ein Unterprogramm aus einen anderen Programm herüber kopieren und braucht sich keine Sorgen um die Benennung der lokalen Variablen machen.

  • Ein Unterprogramm hat nur Zugriff auf seine eigenen lokalen Variablen und auf die globalen Variablen. Ein Unterprogramm hat keinen Zugriff auf die lokalen Variablen der Routine, von der es aufgerufen wurde.
Ein Beispiel finden Sie bei der Erklärung, was Parameter sind.

Analog wird bei Labels und Konstanten (Anweisung CONST) verfahren. Nur Struktur-Definitionen (STRUCT-Anweisung) sind immer global.

^

2.6.2 Konzept: Parameter

In vielen Fällen muss man Werte an ein Unterprogramm übergeben, mit denen es dann arbeitet. Zum Beispiel benötigt ein Unterprogramm, das einen Namen in einer Liste suchen soll, den Namen, nach dem es suchen soll. Man könnte diesen Namen in eine globale Variablen schreiben und ihn so an das Unterprogramm übergeben. Das ist besonders für Anfänger leicht zu handhaben, letztlich jedoch ein schlechter und fehleranfälliger Programmierstil.

Die bessere Lösung für dieses Problem ist, den Namen direkt an das Unterprogramm zu übergeben. Werte, die man einem Unterprogramm direkt übergeben kann, werden als "Parameter" bezeichnet.

Ein einfaches Beispiel. Wir vereinbaren eine SUB, die einen Namen mehrfach ausgeben soll:

SUB Namensschleife ( name$ as String, x as real)
DIM N
  For N = 1 to X
    Print name$
  NEXT N
End SUB
Name$ und X sind die Parameter. N ist eine lokale Variable. Die For-Schleife gibt den Namen so oft aus, wie X vorgibt. Diese Sub können wir nun beliebig oft aufrufen.

DIM N, T$
T$ = "Willi"
N = 7
Namensschleife "Paul", 5  ' 5x Paul
Namensschleife T$, N+3    ' 10x Willi. N ist immer noch 7
N und T$ seien globale Variablen. Wie oben beschrieben unterscheidet der Compiler zwischen dem globalen N und dem lokalen N in der SUB Namensschleife. Wie Sie sehen ist es völlig egal ob Sie als Parameter einen festen Wert, eine Variable oder eine Berechnung übergeben. R-BASIC wertet den Ausdruck zur Laufzeit aus und kopiert den Wert dann in die Parameter des Unterprogramms (in unserem Fall name$ und X).

Intern behandelt R-BASIC die Parameter wie lokale Variablen. Der einzige Unterschied ist, dass sie beim Aufruf des Unterprogramms mit den übergebenen Werten belegt werden. Alle anderen lokalen Variablen werden beim Aufruf des Unterprogramms gelöscht (d.h. mit Nullen belegt). Das hat wieder zwei sehr praktische Folgen:

  • Sie haben die gleichen Freiheiten bei der Namensvergabe von Parametern wie bei den lokalen Variablen.

  • Sie dürfen einen Parameter innerhalb eines Unterprogramms verändern, ohne dass dies auf das Hauptprogramm zurückwirkt. Ändern Sie zum Beispiel unsere Sub von oben wie folgt:
SUB Namensschleife (name$ as String, x as real)
DIM N
name$ = "Der Name ist " + name$
  For N = 1 to X
    Print name$
  NEXT N
End SUB
und übergeben ihr dann die globale Variable T$
Namensschleife T$, N+3
so wird die globale Variabel T$ dadurch NICHT geändert.

^

2.6.3 SUB, END SUB

Die Anweisung SUB (für Subroutine = Unterprogramm) vereinbart ein Unterprogramm. Es können Parameter an das Unterprogramm übergeben werden und innerhalb des Unterprogramms können lokale Variablen, Konstanten und Labels definiert werden, die nur innerhalb des Unterprogramms gültig (dem Compiler bekannt) sind.

Mit SUB vereinbarte Unterprogramme müssen mit END SUB (Ende der Subroutine, Leerzeichen nicht vergessen) abgeschlossen werden.

Ein vorzeitiges Verlassen des Unterprogramms mit RETURN ist möglich.


Vereinbarung:
SUB <Name> ( [ <Parameterliste> ] )
<Lokale Vereinbarungen>
<ProgrammCode>
END SUB
<Name> Bezeichner, unter dem die SUB aufgerufen werden kann.
<Parameterliste> Liste Parametern,
die beim Aufruf an die SUB übergeben werden sollen. Die Werte werden beim Aufruf der SUB in die Parameter kopiert.

Die Parameterliste darf leer sein. Die Klammern sind erforderlich.



Aufruf: Name <Parameter>
Der Aufruf erfolgt über die Angabe des Namen des Unterprogramms, gefolgt von der Parameterliste. Eine Klammer um die Parameterliste ist zulässig, aber nicht erforderlich. Die einzelnen Parameter sind durch Komma zu trennen. Existiert keine Parameterliste, wird nur der Name angegeben.

Beispiel 1:

SUB Demo ()
Print "Ich bin ein Sub-Programm"
END SUB
Aufruf:
Print "Im Hauptprogramm"
Demo
Print "Zurück im Hauptprogramm"
Ausgabe:
Im Hauptprogramm
Ich bin ein Sub-Programm
Zurück im Hauptprogramm

Beispiel 2:
SUB PrintTableLine (x as real)  ' x ist der Parameter
DIM y, z as real                ' y und z sind lokale Variablen
y = x * x + 2 * x - 12
z = 12 * sin(x)
Print x, y, z
END  SUB
Aufruf:
For N = 1 To 14
  PrintTableLine (N)
Next N
Ausgabe:
1     -9      10.098
2     -4      10.912
3      3      1.6934
4      12    -9.0816
usw.

Beispiel 3:
SUB CheckValue (a, b as real)  ' a, b: Parameter
If  a = b Then  Return         ' Rückkehr wenn gleich
Print "Warnung! Werte sind nicht gleich!"
END  SUB
Aufruf:
CheckValue  X, 17' Warnt, wenn X nicht 17 ist

^

2.6.4 RETURN

Die Anweisung RETURN beendet ein Unterprogramm und kehrt zu der Routine zurück, die das Unterprogramm aufgerufen hat. RETURN in einem Actionhandler beendet die Abarbeitung des Actionhandlers und R-BASIC kehrt in den Wartezustand zurück. RETURN kann an beliebiger Stelle im Programm stehen. Insbesondere ist es erlaubt RETURN innerhalb von Schleifen und Verzweigungen zu verwenden.
Syntax: RETURN
Return kehrt vorzeitig aus einer SUB oder einem Actionhandler zurück.

Syntax: RETURN <Rückgabewert>
Return mit Rückgabewert kehrt aus einer Function zurück und übergibt den Rückgabewert an die aufrufende Routine.



^

2.6.5 FUNCTION - END FUNCTION

Die Anweisung FUNCTION (für Funktion = Formel, die einen Wert berechnet) vereinbart ein Unterprogramm, das einen Wert zurück liefert. Es können Parameter an die Function übergeben werden und innerhalb der Function können lokale Variablen, Konstanten und Labels definiert werden, die nur innerhalb des Unterprogramms gültig (dem Compiler bekannt) sind.

Die Vereinbarung einer FUNCTION endet mit der Anweisung END FUNCTION (Ende der Funktion, Leerzeichen nicht vergessen).

Um eine Function zu verlassen, muss die Anweisung

RETURN <Rückgabewert>
ausgeführt werden. Üblicher Weise steht diese Anweisung direkt vor der END FUNCTION Anweisung. Sie kann aber auch an beliebiger Stelle innerhalb der Function stehen. Der Rückgabewert muss dabei den in der Vereinbarung der Function angegebenen Typ haben.
Vereinbarung: FUNCTION <Name> ( <Parameterliste> ) AS <Typ>
<Lokale Vereinbarungen>
<ProgrammCode>
RETURN <Rückgabwert>
END FUNCTION

<Name> Bezeichner, unter dem die Function aufgerufen werden kann.

<Parameterliste> Liste mit Parametern,
die beim Aufruf an die Function übergeben werden sollen. Die Werte werden beim Aufruf der Function in die Parameter kopiert.

Die Parameterliste darf leer sein. Die Klammern sind erforderlich.

<Typ> bezeichnet den Datentyp der Funktion.
Es sind alle Standard-BASIC-Typen sowie selbst definierte Strukturen erlaubt. Der Rückgabewert in der RETURN-Anweisung muss diesen Typ haben.


Aufruf: <var> = Name ( <Parameter> )

<var> ist eine Variable vom Typ, den die Funktion hat.

<Parameter> sind die Parameter - falls vorhanden - mit Komma getrennt.
Die Klammern sind erforderlich, auch wenn keine Parameter existieren.

Aufruf: Name ( <Parameter> )
Es ist zulässig eine Function aufzurufen, ohne den Rückgabewert zu verwenden.


Beispiel 1:

Diese einfach Funktion berechnet die Anzahl der Pixel auf dem Bildschirm. MaxX und MaxY sind globale Variablen, die die maximale x- und y-Koordinate enthalten. Da die Koordinaten bei Null beginnen müssen wir jeweils 1 addieren.

FUNCTION PixelsOnScreen () AS Real
  Return (MaxX+1) * (MaxY+1)
END FUNCTION
Aufruf:
DIM anz as Real
  anz = PixelsOnScreen()
oder
Print PixelsOnScreen()

Beispiel 2:

Diese Funktion berechnet den Funktionswert einer linearen Funktion.

FUNCTION LinFunc (x as real) AS Real  ' x: Parameter
  DIM y as real                       ' y: lokale Variable
  y = 2 * x + 1
  Return y
END FUNCTION
Aufruf:
For N = -2 To 2
  Print N, LinFunc(N)
Next N
Ausgabe:
-2      -3
-1      -1
 0       1
 1       3
 2       5

Beispiel 3:

Komplexes Beispiel: Diese Funktion manipuliert eine String.

FUNCTION StringFunc( A$ as String, b as Real) AS String
  IF b = 0 THEN Return ""  ' Leeren String
  IF b > 0 THEN
    ReturnLeft$(A$, b)     ' die linken Buchstaben
  ELSE
    b = - b                ' Aus Minus mach Plus
    Return Right$(A$, b)   ' die rechten Buchstaben
  END IF
END FUNCTION
Aufruf:

Beachten Sie, dass der Compiler den Parameter A$ von der im folgenden Beispiel vereinbarten Variablen A$ unterscheidet.

DIM A$, B$
A$ = StringFunc("Hallo Welt", 3)
B$ = StringFunc("Hallo Welt", -3)
Print A$ + B$
Ausgabe:
Halelt

Funktionen können nur einen einzigen Wert zurückgeben. Wenn Sie mehr als einen Wert zurückgeben wollen, sollten Anfänger auf globale Variablen zurückgreifen. Fortgeschrittene Programmierer sollten eine Struktur definieren, die alle gewünschten Werte enthält und diese zurückgeben. Beispiel:
STRUCT Worker
  name$ As String(20)
  job$ AS String(20)
  tel AS DWORD
END Struct


Function InitWorker() as Worker
DIM w as Worker
  w.name$ = "Pink Panther"
  w.job$ = "Spaßbolzen"
  w.tel = 47320800
  Return w
End Function

^

2.6.6 Actionhandler

ACTION-Handler sind Unterprogramme, die von einem Objekt direkt aufgerufen werden. Ein R-BASIC Programm besteht eigentlich aus einer Sammlung von Actionhandlern, die zu gegebener Zeit aktiviert werden. Sie können selbst wieder andere Unterprogramme (Sub, Function) aufrufen.

Innerhalb eines Actionhandlers können wie bei jedem anderen Unterprogramm lokale Variablen, Konstanten und Labels definiert werden, die nur innerhalb des Handlers gültig (dem Compiler bekannt) sind. Actionhandler müssen mit END ACTION (Ende der Aktion, Leerzeichen nicht vergessen) abgeschlossen werden.

Ein vorzeitiges Verlassen des Handlers mit RETURN ist möglich. R-BASIC geht dann wieder in den Wartezustand über.

Der Typ des Handlers beschreibt, von welchen Objekten der Handler aufgerufen werden kann.

Alle Actionhandler haben den Parameter "sender" (enthält das Objekt, dass den Handler aktiviert hat) sowie weitere, vom Typ des Handlers abhängige Parameter. Bei der Vereinbarung eines Handlers werden die Parameter NICHT explizit angegeben. Tipp: Verwenden Sie den Menüpunkt "Extras"-"Code Bausteine"-"Action-Handler". Damit erhalten Sie neben dem Handler-Rumpf einen Kommentarblock mit allen Parametern des Handlers.


Vereinbarung: <HANDLERTYP> <Name>
<Lokale Vereinbarungen>
<ProgrammCode> END ACTION

<Handlertyp> Beschreibt, in welcher Situation und von welcher Objektklasse
der Handler aufgerufen wird. Die Handlertypen sind bei den Objekten beschrieben, die sie aufrufen.

<Name> Bezeichner, der den Handler identifiziert


Beispiel
ButtonAction DemoAction
  MsgBox "Button gedrückt"
END ACTION
Weitere Informationen zu Actionhandlern finden Sie im Objekt-Handbuch, Kapitel 1.5 (Vereinbarung von Action-Handlern) sowie bei der Beschreibung der einzelnen Objekte.

^

2.6.7 Vorab-Vereinbarung mit DECL

Sie können Unterprogramme (SUB, FUNCTION) erst dann verwenden, wenn diese zuvor dem R-BASIC-Compiler mit Namen und Parametern bekannt sind.

Damit Sie die Unterprogramme nicht in der Reihenfolge ihrer Verwendung im Quelltext anordnen müssen gibt es die DECL-Anweisung.

Die DECL-Anweisung (Declare = mache bekannt) informiert den Compiler über Namen und Parameterliste von Unterprogrammen, die erst weiter hinten im Quelltext vereinbart werden. Damit kann man

  • die Übersichtlichkeit von Programmen erhöhen

  • ermöglichen, dass sich Unterprogramme gegenseitig aufrufen können (A ruft B und B ruft A), was sonst nicht möglich wäre

  • Libraries schreiben: Die DECL-Anweisungen gehören dann in das EXPORT-Fenster.

Syntax: DECL SUB <Name> ( <ParameterListe> )
DECL FUNCTION <Name> ( <ParameterListe> ) AS <Type>
DECL <HandlerType> <Name>



Abarbeitung von Unterprogrammen

Die folgenden technischen Details beschrieben, wie der Interpreter intern den Aufruf von Subs und Functions organisiert. Die Kenntnis dieser Details ist für die Verwendung von Unterprogrammen nicht unbedingt erforderlich.
  • Stößt der Interpreter auf einen Unterprogrammaufruf, merkt er sich die Adresse des darauf folgenden Befehls (Rücksprungadresse) und verzweigt zum Unterprogramm.

  • Die Rücksprungadressen werden auf einem sogenannten Stapelspeicher (Stack) abgelegt, R-BASIC kann sich also sehr viele Adressen merken.

  • Die Anweisungen RETURN bzw. END SUB beenden die Abarbeitung des Unterprogramms.

  • Die zuletzt auf dem Stack abgelegte Returnadresse wird vom Stack geholt und das Programm wird an dieser Stelle fortgesetzt. Dadurch kann man Unterprogramme verschachteln, d.h. innerhalb von Unterprogrammen wieder andere Unterprogramme aufrufen. Die Verwendung eines Stacks stellt sicher, dass R-BASIC dabei immer an die korrekte Stelle zurückspringt.
Anmerkungen
  • Die Anweisung End Function sollte niemals erreicht werden, weil Functions immer mit RETURN beendet werden müssen. Wird End Function erreicht, handelt es sich um einen Programmierfehler und es kommt zu einem Laufzeitfehler.

  • Beim Aufruf eines Actionhandlers durch ein Objekt wird ebenfalls eine (spezielle) Rücksprungadresse auf dem Stack abgelegt. Die Anweisung End Action beendet die Ausführung eines Actionhandlers. R-BASIC erkennt die spezielle Rücksprungadresse, holt sie vom Stack und kehrt in den Ruhezustand zurück.

Abwärtskompatibilität

GOSUB

R-BASIC unterstützt auch die in vielen BASIC-Interpretern verwendete Kombination GOSUB-RETURN Das kann die Übertragung fremder BASIC-Programme vereinfachen.

Die Anweisung GOSUB (Gehe zu Sub-Routine) setzt den Programmablauf an der angegebenen Stelle fort und kehrt nach Beendigung des Unterprogramms (Anweisung RETURN) wieder zurück.

Diese einfache Form der Unterprogrammtechnik hat nicht die Vorteile einer SUB oder FUNCTION (Parameterübergabe, lokale Variablen und Labels) und sollte daher in eigenen Programmen nicht verwendet werden.


Syntax: GOSUB <sprungZiel>

<sprungZiel> muss eine im Programm mit der Anweisung LABEL
vereinbarte Marke oder eine im Programm explizit vergebene Zeilennummer sein.

Ein mit GOSUB aufgerufenes Unterprogramm muss mit RETURN beendet werden.


^

Weiter...