Virtual OS/2 International Consumer Education
VOICE Homepage: http://de.os2voice.org
September 2003

[Inhaltsverzeichnis]
[Vorherige Seite] [Nächste Seite]
[Artikelverzeichnis]

editor@os2voice.org


DrDialog, oder wie ich lernte REXX zu lieben - Teil 10

Von Thomas Klein © September 2003

In der vorherigen Folge haben wir bereits die beiden ersten Arten der drei bekannten Programmstrukturen kennen gelernt: Sequenz (Batch) und Verzweigungen. Heute schließen wir dieses Thema ab, indem wir den dritten Typ besprechen - Iterationen (oder "Schleifen" wenn Sie so wollen).
Schleifen werden im Prinzip verwendet, um eine bestimmte Verarbeitung solange zu wiederholen, bis eine bestimmte Bedingung erfüllt ist wie zum Beispiel das Lesen aller Zeilen (bzw. Datensätze)  einer Datei bis das Dateiende erreicht ist. Schleifen bzw. deren Bestandteile werden Sie in jeder Programmiersprache finden und immer bestehen Sie schematisch gesehen aus drei Teilen: Kopf, Rumpf und Fuß.
Als Rumpf bezeichnen wir die Abfolge der Befehle, die bei jedem Durchlauf ausgeführt wird. Schleifenkopf und -Fuß steuern den eigentlichen "Wiederholvorgang", der solange stattfindet, bis eine bestimmte Bedingung erfüllt ist, die für gewöhnlich im Schleifenkopf definiert wird.

Hierbei können wir Menschen sprachlich vier Typen von Abbruchsituationen unterscheiden: Eine Schleife wird jeweils solange wiederholt,...

Das ist zwar sehr detailliert und exakt, aber es verkompliziert sowohl das Lernen als auch das Lehren des Themas. Aus diesem Grund verwenden wir hier lediglich den etwas allgemeineren Begriff der "Abbruchbedingung": Ist die Bedingung erfüllt, wird die Schleife beendet.

Und noch eine Bemerkung: Die Begriffe "Kopf" und "Fuß" entsprechen nicht unbedingt bestimmten Befehlen der verwendeten Programmiersprache. Vielmehr leiten sie sich aus der schematischen Darstellung einer Schleife ab um zu verdeutlichen, wie eine Schleife funktioniert. Denn wir unterscheiden grundsätzlich zwischen zwei Arten von Schleifen (bzw. Funktionsweisen von Schleifen).

Kopfgesteuerte Schleifen
'Kopfgesteuert' bedeutet hierbei, daß VOR jeder Ausführung des Rumpfes geprüft wird, ob die Abbruchbedingung zutrifft.

Fußgesteuerte Schleifen
...sind entsprechend anders, da hierbei die Abbruchbedingung erst NACH jedem Durchlauf des Rumpfes geprüft wird.

Der Unterschied wird deutlich, wenn wir beispielsweise eine Schleife haben, bei der die Abbruchbedingung von vornherein erfüllt ist: Eine kopfgesteuerte Schleife prüft die Bedingung vor der Ausführung des Rumpfes und wird diesen somit gar nicht ausführen, während eine fußgesteuerte Schleife den Rumpf bereits einmal ausgeführt hat, bevor die Bedingung geprüft wird. In Rexx sieht eine kopfgesteuerte Schleife so aus:
DO WHILE <Bedingung>
  <Befehl-1>
   ...
  <Befehl-n>
END
Eine fußgesteuerte Schleife (bei der die Abbruchbedingung nach Ausführung des Rumpfs geprüft wird) sieht so aus:
DO UNTIL <Bedingung>
  <Befehl-1>
   ...
  <Befehl-n>
END
Ja, das ist ziemlich gemein: In beiden Fällen wird die Bedingung zwar syntaktisch im "Kopf" der Schleife definiert, dennoch arbeiten beide Typen vollkommen verschieden. Um das wie oben bereits genannt einmal zu verdeutlichen, nehmen wir ein Beispiel:
Wir haben eine Schleife, deren Rumpf daraus besteht, den Wert der Variablen XYZ auszugeben und ihn danach um 1 zu erhöhen. Die Schleife soll solange laufen, bis der Wert von XYZ größer als 4 ist. "Dummerweise" sorgen wir aber zusätzlich dafür, daß der Wert von XYZ bereits VOR Ausführung der Schleife 7 ist.
Eigentlich dürfte also nichts passieren... hier die kopfgesteuerte Schleife:
XYZ = 7
DO WHILE XYZ \> 4
  SAY XYZ
  XYZ = XYZ + 1
END
Unsere Bedingung lautet hier "solange XYZ nicht größer ist als 4".
(Mit dem backslash ("\") wird die Bedingung "größer als" (">") verneint)
Wenn Sie diesen code nun laufen lassen, werden Sie sehen daß schlichtweg nichts passiert, da die Bedingung VOR Ausführung des Rumpfes geprüft wird.
Jetzt nehmen wir die fußgesteuerte Variante der selben Schleife:
XYZ = 7
DO UNTIL XYZ > 4
  SAY XYZ
  XYZ = XYZ + 1
END
Hier lautet die Bedingung sinngemäß "bis XYZ größer als 4 ist". Sprachlich also im Prinzip das selbe wie im vorherigen Fall ("solange nicht größer als 4"). Da der Wert von XYZ bereits zu Beginn 7 ist, dürfte also ebenfalls nichts passieren. Tja - Pech! Denn wenn man das laufen lässt, wird einmal "7" ausgegeben, denn: Bei einer fußgesteuerten Schleife wird die Bedingung NACH Ausführung des Rumpfes geprüft.
Ganz schön verwirrend, nicht wahr? ;)

Damit hätten wir die Grundlagen zum Thema "Bedingungsschleifen" abgehakt.
Eine andere Art von Schleifen ist diejenige, die nur eine bestimmte Anzahl an Wiederholungen beinhaltet. In der REXX.INF Datei werden diese zwar als "Wiederholungsschleifen" bezeichnet, sind aber im Prinzip nichts anderes, als eine andere Art von Bedingungsschleifen, bei denen die Bedingung quasi "fest eingebaut" wurde.
Während Bedingungsschleifen so lange wiederholt werden, bis eine Bedingung erfüllt ist (oder nicht mehr), verwenden die Wiederholungsschleifen eine vorgegebene Anzahl, die im Kopf der Schleife explizit angegeben wird. Dabei gibt es wiederum zwei Arten. Zunächst die ganz einfache Variante:
DO <anzahl>
  <Befehl-1>
   ...
  <Befehl-n>
END

Wobei <anzahl> schlicht eine Zahl ist... Beispiel:
DO 3
  say "Hallo"
END

Die andere Art der Wiederholungsschleife (die vielseitiger und auch am geläufigsten ist) enthält eine Laufvariable (oder "Zählervariable" wenn Sie so wollen), deren Wert bei jedem Durchlauf des Schleifenrumpfes automatisch um einen bestimmten Faktor erhöht wird. Die vollständige Syntax lautet:
DO <variable> = <startwert> TO <endwert> [ BY <erhöhung> ]
  <Befehl-1>
   ...
  <Befehl-n>
END
Beispiel:
DO durchlauf = 1 TO 10 BY 1
  say "Das ist Durchlauf Nummer:" durchlauf
END
Wie Sie vielleicht noch aus unzähligen vorherigen Syntaxdiagrammen wissen, kennzeichnen die eckigen Klammern optionale Bestandteile. Somit handelt es sich bei der Angabe des Erhöhungswerts also um einen wahlfreien Bestandteil, der auch weggelassen werden kann. In diesem Fall wird intern der Erhöhungswert 1 verwendet - der Inhalt der Laufvariable wird also bei jedem Durchlauf (um genau zu sein: nach Ausführung des Rumpfes) um 1 erhöht. Das gleiche Ergebnis wie im obigen Beispiel kann man also auch erreichen durch...:
DO durchlauf = 1 TO 10
  say "Das ist Durchlauf Nummer:" durchlauf
END
Beachten Sie, daß wenn Sie eine "rückwärts" laufende Schleife haben wollen, Sie auch einen negativen Erhöhungswert angeben müssen. Falls nicht, wird Ihre Schleife ewig laufen, denn der Endwert (die Abbruchbedingung sozusagen) kann dann nicht erreicht werden. Wenn Sie also so etwas wie eine "Countdown"-Schleife basteln wollen, könnten Sie das beispielsweise so erreichen:
DO durchlauf = 10 TO 0 BY -1
  say durchlauf
END
Die Angaben für Start-, End- und Erhöhungswert der Laufvariablen sind selbstverständlich beliebig wählbar. In diesem Zusammenhang können Sie übrigens beruhigt an die Sache gehen: Der Endwert muss nicht genau "getroffen" werden - es genügt, wenn die Laufvariable dem Endwert entspricht oder "jenseits" davon liegt, wird die Schleife beendet. Auch hierzu ein Beispiel:
DO zähler = 7 TO 94 BY 17
 say "wert von 'zähler' ist jetzt:" zähler
END
Das bewirkt, daß die Laufvariable "zähler" vor dem ersten Durchlauf auf den Wert 7 gesetzt wird und in den folgenden Durchläufen um jeweils 17 erhöht wird: 24, 41, 58, 75, 92 und schließlich 109. Da aber 109 bereits größer als der angegebene Endwert 94 ist, wird die Schleife davor beendet.
Wichtiger Hinweis: Wenn wir testweise noch ein SAY hinter das Ende der Schleife setzen, können Sie erkennen, daß zähler tatsächlich den Wert 109 enthält, jedoch lediglich die Schleife vor dem normalerweise folgenden Durchlauf beendet wurde:
DO zähler = 7 TO 94 BY 17
 say "wert von 'zähler' ist jetzt:" zähler
END
SAY "nach schleifenende:" zähler
...dieser code würde folgende Ausgabe erzeugen:
wert von 'zähler' ist jetzt: 7
wert von 'zähler' ist jetzt: 24
wert von 'zähler' ist jetzt: 41
wert von 'zähler' ist jetzt: 58
wert von 'zähler' ist jetzt: 75
wert von 'zähler' ist jetzt: 92
nach schleifenende: 109
Eine Wiederholungsschleife mit Laufvariable verhält sich also genau wie eine kopfgesteuerte Bedingungsschleife: Die Abbruchbedingung wird VOR Ausführung des Rumpfes geprüft. Es ist wichtig, dieses Verhalten im Hinterkopf zu haben, wenn man beispielsweise nach Beendigung der Schleife den Inhalt der Laufvariablen verwenden möchte, was wir übrigens in einem der folgenden Beispiele tun werden...
Die gerade besprochenen Regeln gelten natürlich ebenfalls für eine "rückwärts" laufende Schleife: Der Endwert muss nicht genau getroffen werden, auch ein Wert "jenseits" davon beendet die Schleife:
DO zähler = 99 to 1 BY -15
 say "wert von 'zähler' ist jetzt:" zähler
END
say "nach schleifenende:" zähler
Die Ausgabe dieser Schleife sieht so aus:
wert von 'zähler' ist jetzt: 99
wert von 'zähler' ist jetzt: 84
wert von 'zähler' ist jetzt: 69
wert von 'zähler' ist jetzt: 54
wert von 'zähler' ist jetzt: 39
wert von 'zähler' ist jetzt: 24
wert von 'zähler' ist jetzt: 9
nach schleifenende: -6
Und das wär's zum Thema Schleifengrundlagen... ich wünsche fröhliches "Schleifen"! ;)

Erweiterte Schleifensteuerung

Je nach Anwendungszweck ist es manchmal erforderlich innerhalb einer Schleife auf bestimmte Bedingungen zu reagieren. Die beiden häufigsten Fälle sind:

Fall 1: Sofortiges Verlassen der Schleife

Nehmen wir direkt ein Beispiel: Sie haben eine "Tabelle" aus jeweils 8 Namen und 8 Telefonnummern besteht. Eine Stammvariable namens "NAME" speichert die 8 Namen und eine weitere Stammvariable namens "TELNR" speichert die jeweils zugehörigen 8 Telefonnummern. Die Zuordnung von Namen zu Nummern und umgekehrt erfolgt durch die Eintragsnummer, d.h. NAME.1 gehört zu TELNR.1 und umgekehrt, und so weiter...Das könnte also beispielsweise so aussehen:

name.1 = "Peter"
name.2 = "Paul"
name.3 = "Karin"
name.4 = "Uwe"
name.5 = "Hugo"
name.6 = "Martin"
name.7 = "Mutti"
name.8 = "Vati"

telnr.1 = "010-99887"
telnr.2 = "020-88776"
telnr.3 = "030-77665"
telnr.4 = "040-66554"
telnr.5 = "050-55443"
telnr.6 = "060-44332"
telnr.7 = "070-33221"
telnr.8 = "080-22110"
Wir möchten jetzt, daß das Programm zunächst "Bitte Namen eingeben" anzeigt, dann den eingegebenen Namen in der Namenstabelle sucht, falls er gefunden wird die zugehörige Telefonnummer ausgibt und dann endet. Wir gehen auch davon aus, daß die Namen eindeutig sind (es kann also z.B. nur einen "Peter" geben).
Gut, eigentlich sollte man das Programm selbst als eine große Schleife anlegen, die so lange läuft bis statt eines Namens "EXIT" oder so was eingegeben wurde, aber lassen Sie uns das Ding so einfach wie möglich halten.

Nach den obigen Anweisungen (dem Initialisieren unserer Stammvariablen) kommt also jetzt:
SAY "Bitte Namen eingeben..."
PARSE PULL eingabe
DO eintrag = 1 to 8
    IF name.eintrag = eingabe then
         say name.eintrag ":" telnr.eintrag
END
Und das wäre schon mal die erste Fassung.
Das Programm gibt den Text aus und verarbeitet dann die Benutzereingabe. Alle Einträge werden mit der Eingabe verglichen und falls die Namen übereinstimmen, wird die zugehörige Nummer ausgegeben.
Aber: Nach der Ausgabe einer Telefonnummer werden die restlichen Einträge auch noch verglichen, was ja eigentlich nicht notwendig wäre, da es ja keine weiteren Übereinstimmungen geben kann (da die Namen ja eindeutig sind). Was wir also hier brauchen könnten, wäre ein Weg, die Schleife nach Ausgabe einer gefundenen Übereinstimmung direkt zu verlassen. Stellen Sie sich vor, die Tabelle bestünde nicht aus 8 sondern aus 500 oder mehr Paaren von Namen und Telefonnummern. Wenn nun der erste Eintrag bereits der gesuchte wäre, würden dennoch 499 Einträge durchsucht... würden Sie das beim Suchen im Telefonbuch auch machen?
Und das ist genau die Stelle, an der die REXX-Anweisung LEAVE ins Spiel kommt:
SAY "Bitte Namen eingeben..."
PARSE PULL eingabe
DO eintrag = 1 to 8
    IF name.eintrag = eingabe then
         DO
            say name.eintrag ":" telnr.eintrag
            LEAVE
         END
END
Da wir nun innerhalb unseres IF mehr als eine Anweisung verwenden, müssen wir die in eine DO...END -Struktur einbetten (erinnern Sie sich an die letzte Ausgabe?).
Jetzt wird die Schleife sofort beendet, nachdem eine gefundene Übereinstimmung ausgegeben wurde - alle restlichen Einträge werden "übersprungen".

Hmm... aber das gefällt mir noch nicht. Wenn es keine Übereinstimmung gibt, wird das Programm einfach stillschweigend beendet ohne entsprechende Mitteilung. Nö, das müssen wir noch ändern. Warum wenden wir nicht einfach unsere bisherigen Kenntnisse zum verhalten von Schleifen an? Schauen Sie mal hier:
SAY "Bitte Namen eingeben..."
PARSE PULL eingabe
DO eintrag = 1 to 8
    IF name.eintrag = eingabe then LEAVE
END

if eintrag > 8 then
   say "Keine Übereinstimmung gefunden für <"eingabe">"
else
   say name.eintrag ":" telnr.eintrag
Jau, das sieht schon viel besser aus... aber was läuft da ab?
Zuerst sollten Sie wissen, daß  LEAVE die Schleife zwar direkt verlässt, den aktuellen Wert der Laufvariablen jedoch beibehält. Zweitens befindet sich der Wert der Laufvariablen (wie Sie bereits wissen) nach einer vollständigen Schleifenbeendigung "jenseits" des angegebenen Endwerts. Diese beiden Tatsachen haben wir im obigen code für unsere Zwecke benutzt:
Während das Programm einen Namenseintrag nach dem anderen mit der Eingabe vergleicht, wird die Laufvariable erhöht und dient als Index - also jeweils als Eintragsnummer. Wenn eine Übereinstimmung gefunden wurde, wird die Schleife mittels LEAVE sofort verlassen. Wenn keine Übereinstimmung gefunden wurde, ist die Schleife ebenfalls beendet da alle Einträge geprüft wurden (der Endwert der Laufvariable ist überschritten).
Der "clou" kommt aber erst jetzt: Nachdem die Schleife beendet wurde (auf eine der beiden Arten) wird geprüft WIE sie beendet wurde: Wenn die Laufvariable nach dem Ende der Schleife einen Wert größer als den Endwert (im Beispiel also größer als 8) enthält, bedeutet das, daß alle Einträge geprüft wurden und keine Übereinstimmung gefunden wurde. Ansonsten muss zwangsläufig eine Übereinstimmung vorgelegen haben - und in diesem Fall enthält die Laufvariable die Nummer des Eintrags, die wir dann zur Ausgabe der Telefonnummer verwenden.

Noch eine letzte Anmerkung: Das Programm ist in seiner obigen Form nicht besonders tolerant, was Groß- und Kleinschreibung angeht. Um es etwas freundlicher zu gestalten, sollte man alle Namen in Großbuchstaben eingeben (beim Initialisieren der Stammvariablen). Dann kann man durch das Weglassen der Instruktion PARSE erreichen, daß REXX die Benutzereingabe automatisch in Großbuchstaben umwandelt und Sie haben perfekte Übereinstimmungen, egal ob der Anwender beispielsweise "Peter", "PeTeR" oder "peter" eingibt.
Außerdem könnte man noch evtl. führende oder folgende Leerzeichen aus der Benutzereingabe entfernen... hier ist der vollständige Programmcode, damit Sie ein wenig damit herum experimentieren können:

/* REXX Schleifenbeispiel 1 */

name.1 = "PETER"
name.2 = "PAUL"
name.3 = "KARIN"
name.4 = "UWE"
name.5 = "HUGO"
name.6 = "MARTIN"
name.7 = "MUTTI"
name.8 = "VATI"

telnr.1 = "010-99887"
telnr.2 = "020-88776"
telnr.3 = "030-77665"
telnr.4 = "040-66554"
telnr.5 = "050-55443"
telnr.6 = "060-44332"
telnr.7 = "070-33221"
telnr.8 = "080-22110"

SAY "Bitte Namen eingeben..."
PULL eingabe
eingabe = strip(eingabe)   /* führende und folgende Leerstellen aus eingabe entfernen */
DO eintrag = 1 to 8
    IF name.eintrag = eingabe then LEAVE
END

if eintrag > 8 then
   say "Keine Übereinstimmung gefunden für <"eingabe">"
else
   say name.eintrag ":" telnr.eintrag

/* Beispielcode Ende */


Fall 2: "Eine Runde aussetzen"

Mit Hilfe des REXX-Befehls ITERATE kann die Ausführung des Schleifenrumpfes direkt abgebrochen werden um mit dem nächsten Durchlauf zu beginnen - sofern dann natürlich nicht schon die Abbruchbedingung "zuschlägt". Die Vorteile von ITERATE steigen mit der Komplexität der dadurch "übersprungenen" Verarbeitung. Das heißt, wenn der Rumpf Ihrer Schleife nur einen einzelnen Befehl enthält, gewinnen Sie durch ITERATE nicht besonders viel. Im Fall einer sehr komplexen und zeitaufwendigen Verarbeitung die damit übersprungen wird, kann ITERATE den Ablauf um einiges beschleunigen.

Egal - um ein Beispiel zu nehmen lassen Sie uns einmal überlegen was die Leute immer über die 70er Jahre sagen: "Wer sich an die 70er erinnern kann, war nicht dabei.".
Nun gut - tun wir diesen Leuten doch einen Gefallen und streichen wir die 70er aus der Liste der Jahrzehnte des letzten Jahrhunderts (beginnend mit 1940)...:
do jahrzehnt = 40 to 90 by 10
      wenn jahrzehnt = 70 then ITERATE
      say "Ich erinnere mich immer noch an die" jahrzehnt"'er Jahre"
end

Verstanden, wie's funktioniert? ;)
Alle Anweisungen, die innerhalb des Schleifenrumpfes auf ITERATE folgen, werden damit übersprungen.
In diesem Beispiel wird der Schleifenrumpf (Ausgabe des Text) übersprungen, wenn die Laufvariable den Wert 70 enthält.

Natürlich kann man dasselbe auch ohne ITERATE durch folgende Codierung erreichen:
do jahrzehnt = 40 to 90 by 10
      if jahrzehnt \= 70 then say "Ich erinnere mich immer noch an die" jahrzehnt"'er Jahre"
end
Diese Variante gibt den Text nur aus, wenn die Laufvariable NICHT 70 ist.

ITERATE ist also eigentlich ein expliziter Befehl, um klar zu machen, daß eine bestimmte Anweisungsfolge innerhalb einer Schleife übersprungen werden soll - ein sehr gut selbstdokumentierender Befehl zur besseren Lesbarkeit, wenn Sie so wollen.
Wie dem auch sei, Sie wissen nun, daß es etwas namens ITERATE gibt und was dahinter steckt. Damit sind Sie etwas flexibler, wenn Sie Ihre Programm- bzw. Schleifenabläufe konstruieren (müssen).

Und damit wären wir einmal mehr am Ende - zumindest für diese Ausgabe. Wenn mein Plan mich nicht im Stich lässt, dann werden wir in der nächsten Ausgabe in die Fülle von REXX's Stringfunktionen (also zur Zeichenkettenverarbeitung) einsteigen. Wenn genügend Zeit bleibt, schauen wir uns direkt noch die Funktionen an, die in der REXXUTIL Funktionsbibliothek enthalten sind. Das wird bestimmt interessant ...und spaßig. Dann kommen wir aber auf jeden Fall wieder zurück zum eigentlichen Arbeiten mit DrDialog und werden testen, was wir mit unseren REXX-Kenntnissen darin bewerkstelligen können... und werden ein paar Tricks kennen lernen.

Bis nächsten Monat  - selbe Zeit, selbe Stelle! ;)

Daten und Quellen:

GuiObjectREXX Yahoo! group: http://groups.yahoo.com/group/GuiObjectREXX/
News group for GUI programming with REXX: news://news.consultron.ca/jakesplace.warp.visualrexx
Download from Hobbes: http://hobbes.nmsu.edu/cgi-bin/h-search?key=drdialog&pushbutton=Search
IBM Redbook - "OS/2 REXX: Bark to Byte" http://publib-b.boulder.ibm.com/Redbooks.nsf/9445fa5b416f6e32852569ae006bb65f/50d91ba723b12cfa8525659d002a5793?OpenDocument&Highlight=0,rexx


[Artikelverzeichnis]
editor@os2voice.org
[Vorherige Seite] [Inhaltsverzeichnis] [Nächste Seite]
VOICE Homepage: http://de.os2voice.org