Kapitel 10: Bedingungen

Inhaltsverzeichnis

Kapitel 12: Prozeduren und Funktionen

11. Schleifen und Kontrollanweisungen

Oft soll eine Reihe von Anweisungen nicht nur einmal, sondern mehrmals hintereinander ausgeführt werden. Dazu gibt es das Konstrukt der Schleife.

Schleifen besitzen eine Laufbedingung und den Schleifenrumpf, auch Schleifenkörper genannt. Der Rumpf ist ein Anweisungsblock, der wiederholt ausgeführt werden soll. Die Bedingung steuert, unter welchen Voraussetzungen der Rumpf wiederholt werden soll; es handelt sich um eine Bedingungsstruktur, wie sie in Kapitel 10.2 behandelt wurde. Wie alle Kontrollstrukturen können Schleifen beliebig verschachtelt werden.

Schleifen sind bereits das erste Element der strukturierten Programmierung. Bevor sich das Programmierparadigma der strukturierten Programmierung durchsetzte, konnten Unterbrechungen im linearen Ablauf des Programms meist nur durch Sprunganweisungen durchgeführt werden. Die in den frühen BASIC-Jahren beliebte Sprunganweisung GOTO kann (und soll!) heute problemlos durch bessere Konstrukte ersetzt werden, nichtsdestotrotz ist sie in FreeBASIC weiterhin vorhanden und einsetzbar.

11.1 Sprunganweisungen

Eine Sprunganweisung im weiteren Sinn ist jede Art von Anweisung, die das Programm nicht in der nächsten Zeile, sondern an einer beliebigen Stelle fortsetzen lässt. Im engeren Sinn sind speziell die Funktionen GOTO und GOSUB gemeint, wovon letzterer jedoch von FreeBASIC nicht mehr unterstützt wird.

GOTO benötigt die Angabe einer Sprungmarke, auch Label genannt, und selbstverständlich muss die Position dieser Sprungmarke ebenfalls im Programm vermerkt sein. Der Name einer Sprungmarke folgt denselben Regeln wie Variablennamen. Die Sprungmarke wird mit einem Doppelpunkt : abgeschlossen.

PRINT "Hallo ";
GOTO welt
PRINT " du da!"
ende:
PRINT "Und ade!"
SLEEP
END
welt:
PRINT "Welt!"
GOTO ende
Ausgabe:
Hallo Welt!
Und ade!

Das Programm springt von Zeile 2 zum Label welt in Zeile 8, fährt dann mit Zeile 9 und 10 fort, wo es wiederum zum Label ende in Zeile 4 springt und mit den Zeilen 5 und 6 fortsetzt. Zeile 3 wird in diesem Beispiel gar nicht ausgeführt.

Der Code ist nicht wirklich sinnvoll; aufgrund seines munteren Springens ist er sogar einfach nur schlecht (aber er sollte ja auch lediglich das Prinzip veranschaulichen). Mit GOTO lassen sich aber auch sinnvollere Dinge wie z. B. Schleifen umsetzen:

DIM AS STRING eingabe, passwort="schwertfisch8"
schleifenanfang:
INPUT "Gib das Passwort ein: ", eingabe
IF eingabe <> passwort THEN GOTO schleifenanfang
SLEEP

GOTO ist sehr mächtig, weil man damit sehr frei durch das Programm springen kann, aber darin liegt auch sein größter Nachteil. Es ist auf diesem Weg nämlich sehr einfach, den Programmcode unüberschaubar zu machen — und damit ist nicht nur die schwere Lesbarkeit für den menschlichen Programmierer, sondern auch maschinelle Auswertbarkeit des Codes gemeint. In den frühen BASIC-Jahren entstand dadurch eine Menge an nahezu nicht mehr wartbarem „Spaghetti-Code“. Der Einsatz von GOTO ist daher in nahezu allen höheren Programmiersprachen verpönt.1 Einige Sprachen haben diese Art von Sprunganweisung sogar komplett aus dem Sprachschatz verbannt.

Wie oben bereits erwähnt lassen sich Programmierprobleme auch ohne GOTO lösen. Die oben verwendete GOTO-Schleife zur Passwortabfrage werden wir in Quelltext 11.1 durch eine DO-Schleife ersetzen.

11.2 DO … LOOP

Die DO-Schleife ist gewissermaßen der Allrounder der Schleifen. Sie beginnt mit DO und endet mit LOOP — alles dazwischen gehört zum Schleifenrumpf.

DO
  ' Anweisungen im Schleifenrumpf
LOOP

Die oben aufgeführte Schleife enthält keine Laufbedingung; sie wird immer wieder ohne Ende durchgeführt. In diesem Fall spricht man von einer Endlosschleife. Natürlich ist es in der Regel nicht erwünscht, dass das Programm endlos in der Schleife hängen bleibt. Deshalb gibt es zwei Möglichkeiten, eine Laufbedingung einzufügen. Man unterscheidet die kopfgesteuerte und die fußgesteuerte Schleife.

' kopfgesteuerte Schleife
DO { UNTIL | WHILE } Bedingung
  ' Anweisungen im Schleifenrumpf
LOOP

' fussgesteuerte Schleife
DO
  ' Anweisungen im Schleifenrumpf
LOOP { UNTIL | WHILE } Bedingung

Bei einer kopfgesteuerten Schleife wird die Bedingung überprüft, bevor die Schleife das erste Mal durchlaufen wird. Ist die Bedingung zu Beginn nicht erfüllt, dann wird der Schleifenrumpf überhaupt nicht ausgeführt, und das Programm fährt direkt am Ende der Schleife fort. Eine fußgesteuerte Schleife dagegen wird mindestens einmal durchlaufen, da die Bedingung erst nach dem ersten Durchlauf überprüft wird.

Die Schreibweise DO { UNTIL | WHILE } Bedingung ist eine Kurzschreibweise — die geschweiften Klammern bedeuten, dass genau eine der darin aufgeführten Möglichkeiten verwendet werden muss. Es lautet also entweder DO UNTIL Bedingung oder DO WHILE Bedingung. Diese Art der Schreibweise finden Sie auch sehr häufig in der Befehlsreferenz.2 Die beiden Versionen besitzen einen kleinen, aber feinen Unterschied:

  • DO UNTIL Bedingung führt die Schleife aus, bis die Bedingung erfüllt ist. Man spricht hier auch von einer Abbruchbedingung — ist sie erfüllt, wird die Schleife abgebrochen.

  • DO WHILE Bedingung führt die Schleife aus, solange die Bedingung erfüllt ist. Die Schleife wird also verlassen, sobald die Bedingung das erste Mal nicht erfüllt ist. Dies ist der eigentliche Fall einer Laufbedingung — die Bedingung muss erfüllt sein, um weiterhin die Schleife zu durchlaufen.

Das gilt analog auch für fußgesteuerte Schleifen.

Grundsätzlich kann immer frei gewählt werden, ob eine Laufbedingung oder eine Abbruchbedingung verwendet werden soll; man muss lediglich die Bedingung passend formulieren. Die Schleife „Iss etwas, bis du satt bist“ (UNTIL-Version) lässt sich auch formulieren als „Iss etwas, solange du hungrig bist“ (WHILE-Version). Welche der beiden Möglichkeiten verwendet wird, ist zum Teil Geschmacksache, manchmal sieht auch eine Version eleganter aus als die andere.

Ob jedoch eine kopf- oder fußgesteuerte Schleife verwendet wird, ist durch die Aufgabenstellung bereits vorgegeben. Manchmal muss der Schleifenrumpf erst einmal ausgeführt werden, bevor überhaupt eine Abbruchbedingung feststeht. Quelltext 11.1 fragt so lange nach dem Passwort, bis die Eingabe richtig ist (natürlich öffnet diese Vorgehensweise Brute-Force Tor und Tür …); eine Überprüfung macht jedoch erst nach der ersten Eingabe Sinn. Eine kopfgesteuerte Schleife wird dagegen z. B. benötigt, wenn Sie eine Datei bis zum Ende auslesen wollen — sollte die Datei leer sein (d. h. das Dateiende ist bereits zu Beginn erreicht), soll auch nichts ausgelesen werden.

Quelltext 11.1: Passwortabfrage in einer Schleife
DIM AS STRING eingabe, passwort="schwertfisch8"
DO
  INPUT "Gib das Passwort ein: ", eingabe
LOOP UNTIL eingabe = passwort

Mit den Schleifen lässt sich auch die Stärke der Arrays hervorragend ausspielen. Mit unserem Wissen aus Kapitel 8 können wir jetzt eine Schleife programmieren, die den Benutzer so lange Namen eingeben lässt, bis er mit einer Leereingabe beendet. Da im Vorfeld nicht feststeht, wie viele Eingaben erfolgen werden, benötigen wir ein dynamisches Array, das während der Eingabe ständig erweitert wird.

Quelltext 11.2: Wiederholte Namenseingabe
DIM AS STRING nachname(), eingabe  ' dynamisches Array deklarieren
DIM AS INTEGER i = 0               ' Zaehlvariable fuer die Array-Laenge
PRINT "Geben Sie die Namen ein - Leereingabe beendet das Programm"
DO
  PRINT "Name"; i+1; ": ";
  INPUT "", eingabe
  IF eingabe <> "" THEN
    REDIM PRESERVE nachname(i)
    nachname(i) = eingabe
    i += 1
  END IF
LOOP UNTIL eingabe = ""
PRINT "Sie haben"; UBOUND(nachname)+1; " Namen eingegeben."
SLEEP

Die Zählweise „von 0 bis (Anzahl-1)“ statt „von 1 bis Anzahl“ mag vielleicht etwas gewöhnungsbedürftig sein, ist jedoch durchaus üblich. Sie können stattdessen selbstverständlich auch 1 als untere Grenze wählen; dazu muss das Programm ein einigen wenigen Stellen angepasst werden.

11.3 WHILE … WEND

Ein Sonderfall ist die WHILE-Schleife:

WHILE bedingung
  ' Anweisungen im Schleifenrumpf
WEND

Es handelt sich dabei lediglich um einen Spezialfall einer kopfgesteuerten Schleife und kann durch eine DO-Schleife ersetzt werden:

DO WHILE bedingung
  ' Anweisungen im Schleifenrumpf
LOOP

11.4 FOR … NEXT

Während die DO-Schleife eine sehr frei definierbare Abbruchbedingung besitzt, führt die FOR-Schleife eine eigene Zählvariable mit. Diese wird bei jedem Schleifendurchlauf um einen festen Wert verändert. Überschreitet sie einen zu Beginn festgelegten Wert, dann wird die Schleife verlassen.

FOR zaehlvariable = startwert TO endwert [STEP schrittweite]
  ' Anweisungen im Schleifenrumpf
NEXT

zaehlvariable wird zu Beginn auf den Wert startwert gesetzt und dann nach jedem Schleifendurchlauf um den Wert schrittweite erhöht. Wenn sie dadurch den Wert endwert überschreitet, wird die Schleife verlassen. Die FOR-Schleife bietet sich also vor allem dann an, wenn eine Schleife eine genau festgelegte Anzahl von Durchläufen haben soll. Praktischerweise lässt sich die Zählvariable aber auch innerhalb der Schleife jederzeit abfragen.

Die eckigen Klammern bei [STEP schrittweite] bedeuten, dass diese Angabe optional ist, d. h. es ist nicht notwendig, eine Schrittweite anzugeben. Ohne anderweitige Angabe wird die Schrittweite 1 verwendet.

Note Hinweis:
Hinter dem NEXT kann noch einmal der Name der Zählvariablen angegeben werden. Dies war vor allem in älteren BASIC-Dialekten notwendig. In FreeBASIC ist diese Angabe nicht nötig; sie kann bei verschachtelten Schleifen aber der Übersichtlichkeit dienen.

11.4.1 Einfache FOR-Schleife

Das Prinzip lässt sich am einfachsten anhand von Beispielen verstehen. Quelltext 11.3 zählt einfach nur von 10 bis 20 nach oben:

Quelltext 11.3: Einfache FOR-Schleife
DIM AS INTEGER i
PRINT "Variablenwert vor der Schleife:"; i
FOR i = 10 TO 20
  PRINT i
NEXT
PRINT "Variablenwert nach der Schleife:"; i
SLEEP
Ausgabe:
Variablenwert vor der Schleife: 0
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
Variablenwert nach der Schleife: 21

Zunächst einmal wurde eine Zählvariable i angelegt. i mag ein sehr kurzer und nicht sehr aussagekräftiger Name sein, aber er hat sich für Zählvariablen eingebürgert, womit er bereits wieder einiges an Aussagekraft besitzt. Da der Variablen zu Beginn kein anderer Wert zugewiesen wurde, wird sie automatisch mit dem Wert 0 belegt.

Nun wird die Schleife betreten. i erhält den Startwert 10, und die Schleife wird bis zum NEXT durchlaufen. Danach wird i auf 11 erhöht und mit dem Endwert (hier 20) verglichen. Da i noch zu klein ist, springt das Programm wieder zurück zu Zeile 4, also in die Zeile nach dem FOR.

Dies geht so lange, bis i den Wert 20 erreicht hat. Nun wird die Schleife noch ein letztes Mal durchlaufen. Auch jetzt wird beim Erreichen des NEXT der Wert um 1 erhöht, weshalb i nun den Wert 21 besitzt. i wird wiederum mit der Laufbedingung verglichen. Da der Wert jetzt größer ist als 20, wird die Schleife verlassen. Es mag auf den ersten Blick ungewöhnlich erscheinen, dass die Laufvariable am Ende größer ist als der Endwert, aber so arbeitet die FOR-Schleife nun einmal. Außerdem werden wir in Kapitel 11.5 sehen, wie dieses Verhalten sinnvoll genutzt werden kann.

11.4.2 FOR-Schleife mit angegebener Schrittweite

Mithilfe von STEP kann das Programm veranlasst werden, bei jedem Schleifendurchgang nicht um 1, sondern um einen beliebigen anderen Wert weiterzuzählen. Dieser Wert kann sogar negativ sein. Tatsächlich wird die Angabe STEP -1 recht häufig verwendet, eben um im Einerschritt abwärts zu zählen. Bei negativer Schrittweite muss der Endwert dann auch kleiner sein als der Startwert, um einen sinnvollen Durchlauf starten zu können.

In Quelltext 11.4 wird ein kleiner Countdown programmiert, der schrittweise von 10 herunterzählt, immer mit einer Wartezeit von einer Sekunde. Mit SLEEP kann ein Parameter angegeben werden, der das Programm veranlasst, so viele Millisekunden zu warten. SLEEP 1000 etwa wartet eine Sekunde (1000 Millisekunden). Allerdings kann die Wartezeit auch durch einen Tastendruck übersprungen werden. Wenn der Countdown genau zehn Sekunden dauern soll und nicht durch Tastendruck verkürzt werden darf, kann man SLEEP noch einen zweiten Parameter übergeben. Als zweiter Parameter ist nur 0 (Wartezeit kann übersprungen werden; Standard) oder 1 (Wartezeit kann nicht übersprungen werden) sinnvoll.

Quelltext 11.4: Countdown
DIM AS INTEGER i
PRINT "Der Countdown laeuft!"
FOR i = 10 TO 1 STEP -1
  PRINT i; " ..."
  SLEEP 1000, 1         ' Warte 1 Sekunde; nicht unterbrechbar
NEXT
PRINT "START!"
SLEEP
Ausgabe:
Der Countdown laeuft!
 10 ...
 9 ...
 8 ...
 7 ...
 6 ...
 5 ...
 4 ...
 3 ...
 2 ...
 1 ...
START!

Sie können auch testweise den zweiten Parameter von SLEEP entfernen (also SLEEP 1000 statt SLEEP 1000, 1), um zu testen, wie sich Tastendrücke auf den Programmablauf auswirken.

11.4.3 FOR i AS datentyp

Da die Zählvariable oft tatsächlich nur zum Zählen verwendet wird und außerhalb der Schleife keine Bedeutung hat, wird sie häufig nur für die Dauer der Schleife angelegt. Dazu schreibt man, ähnlich wie bei der Variablen-Deklaration mit DIM, hinter der Zählvariablen ein AS gefolgt vom gewünschten Datentyp. Das kann z. B. folgendermaßen aussehen:

' ausserhalb der Schleife - hier ist i nicht bekannt
FOR i AS INTEGER = 1 TO 10
  ' innerhalb der Schleife - hier ist i bekannt
  PRINT i
NEXT
' wieder ausserhalb - hier ist i nicht mehr bekannt
SLEEP

Sobald die Schleife verlassen wird, „vergisst“ das Programm die Variable — sie kann jetzt nicht mehr angesprochen werden (ein Versuch würde zum Compilier-Fehler Variable not declared führen). Sie belegt jetzt auch keinen Speicherplatz mehr.

Praktisch ist auch, dass sich der Programmierer keine Gedanken darüber machen muss, ob bereits eine Variable mit demselben Namen existiert und ob ihr Wert später noch gebraucht wird. Das Programm überschreibt einfach kurzfristig die alte Belegung und stellt sie nach Beendigung der Schleife wieder her. Man spricht hier vom Sichtbarkeitsbereich bzw. vom Scope der Variablen: die (neue) Laufzeitvariable ist nur innerhalb der Schleife sichtbar.

Quelltext 11.5: Sichtbarkeit der Zählvariablen
DIM AS INTEGER i = 10, k = 20
PRINT "ausserhalb:", i, k
FOR i AS INTEGER = 1 TO 3
  PRINT "innerhalb:", i, k
NEXT
PRINT "ausserhalb:", i, k
SLEEP
Ausgabe:
ausserhalb:    10            20
innerhalb:     1             20
innerhalb:     2             20
innerhalb:     3             20
ausserhalb:    10            20

Normalerweise können Sie innerhalb der Schleife auf eine Variable, die außerhalb deklariert wurde, zugreifen (wie hier anhand von k demonstriert). Da aber für die Schleife die Zählvariable i neu deklariert wird, ist die alte Belegung von i kurzzeitig unsichtbar. Auf sie kann erst nach Beendigung der Schleife wieder zugegriffen werden.

Das Thema „Gültigkeitsbereich von Variablen“ wird noch ausführlich in [KapGueltigkeit] besprochen.

11.4.4 Übersprungene Schleifen

Um es noch einmal zu präzisieren: Bei einer positiven Schrittweite wird die Schleife verlassen, sobald die Zählvariable größer ist als der Endwert. Bei negativer Schrittweite muss sie sinnvollerweise kleiner werden als der Endwert. Diese Bedingung wird bereits vor dem ersten Durchlauf geprüft. Das bedeutet: Wenn die Schrittweite positiv ist und der Startwert größer ist als der Endwert, dann wird die Schleife überhaupt nicht durchlaufen. Dies kann sinnvoll sein, wenn Start- und/oder Endwert variabel sind (z. B. abhängig von Benutzereingaben). An einem konkreten Beispiel: Wenn Sie aufwärts von 8 bis 3 zählen wollen, gibt es natürlich überhaupt nichts zu zählen. Analog dazu wird die Schleife auch übersprungen, wenn die Schrittweite negativ und der Startwert kleiner als der Endwert ist.

11.4.5 Fallstricke

11.4.5.1 Gleitkomma-Schrittweite

Es gibt zwei Fallstricke, die Sie beim Verwenden der FOR-Schleife im Kopf behalten sollten. Die erste betrifft die Verwendung einer Schrittweite kleiner oder gleich 0.5, wenn zugleich eine Ganzzahl-Laufvariable angegeben wird. Die Schrittweite wird dann zuerst auf eine Ganzzahl gerundet, in diesem Fall also auf 0 abgerundet. Es findet damit effektiv keine Erhöhung der Laufvariablen statt und die Schleife läuft endlos.

Warum überhaupt „Schrittweite kleiner oder gleich 0.5“? Wird bei 0.5 denn nicht aufgerundet?
Nein, wird es nicht. FreeBASIC rundet, wie auch sehr viele andere Programmiersprachen, nicht kaufmännisch, sondern mathematisch. Das bedeutet: bei x.5 wird in Richtung der nächsten geraden Zahl gerundet. Mit Rundungen werden wir uns in Kapitel 14.1.4 etwas näher beschftigen.

Das Problem mit der Rundung kann leicht vermieden werden, indem Sie immer, wenn Sie eine Gleitkommazahl als Schrittweite verwendet wollen, auch eine Gleitkommazahl als Zählvariable verwenden. Meist ist es sogar besser, auf Gleitkomma-Schrittweiten komplett zu verzichten — denken Sie hier auch an die bereits in Kapitel 6.2.2 erwähnten Probleme mit der Genauigkeit — und bei Bedarf den benötigten Gleitkommawert im Schleifenrumpf zu berechnen.

Quelltext 11.6: FOR: Probleme der Gleitkomma-Schrittweite
PRINT "mit Gleitkomma-Schrittweite"
FOR i AS DOUBLE = -1 TO 0 STEP .2
  PRINT i
NEXT
PRINT

PRINT "mit Integer-Schrittweite"
FOR i AS INTEGER = -5 TO 0
  PRINT i/5
NEXT
SLEEP
Ausgabe:
mit Gleitkomma-Schrittweite
-1
-0.8
-0.6000000000000001
-0.4000000000000001
-0.2000000000000001
-5.551115123125783e-17

mit Integer-Schrittweite
-1
-0.8
-0.6
-0.4
-0.2
 0
11.4.5.2 Überschreitung des Wertebereichs

Bei Zählvariablen, die einen ganzzahligen Datentyp besitzen, ist es zudem nicht möglich, bis zum letzten Wert des Wertebereichs zu zählen, da sonst eine Endlosschleife entsteht. Ein Beispiel:3

Quelltext 11.7: FOR: Probleme mit dem Wertebereich
' Achtung: erzeugt eine Endlosschleife!
FOR i AS UBYTE = 0 TO 255
  PRINT i
NEXT
SLEEP

Was ist passiert? Nach dem „letzten“ Durchlauf mit i=255 wird die Zählvariable, wie gewohnt, noch einmal um 1 erhöht. Der neue Wert 256 liegt aber bereits außerhalb des Wertebereichs eines UBYTEs. In der Variablen i wird also stattdessen der Wert 0 gespeichert — und die Schleife fährt munter fort.

Oft gibt es gar keinen triftigen Grund, als Zählvariable einen platzsparenden Datentyp zu verwenden, zumal ein INTEGER deutlich schneller verarbeitet werden kann. Bei der Verwendung eines INTEGERs werden Sie andererseits deutlich schwerer an die Grenzen des Wertebereichs stoßen. Sie sehen also, dass man auch diesen Fallstrick in den Griff bekommen kann.

11.5 Kontrollanweisungen

Der reguläre Ablauf einer Schleife kann durch Kontrollanweisungen verändert werden. Zwei Kontrollanweisungen stehen zur Verfügung: mit der ersten springt das Programm vorzeitig an das Ende der Schleife (und wiederholt sie gegebenenfalls), mit der zweiten wird die Schleife sofort verlassen.

11.5.1 Fortfahren mit CONTINUE

Die Anweisung CONTINUE DO bzw. CONTINUE WHILE oder CONTINUE FOR springt an das Ende der aktuellen DO-, WHILE- bzw. FOR-Schleife. Es wird dann ganz regulär überprüft, ob die Schleife weiter ausgeführt werden muss oder nicht. Ist die Abbruchbedingung erfüllt, wird die Schleife verlassen, sonst fährt sie mit dem nächsten Durchlauf fort. Selbstverständlich kann CONTINUE nur innerhalb einer entsprechenden Schleife eingesetzt werden.

In Quelltext 11.8 werden die Zahlen von 1 bis 5 ausgegeben, allerdings soll die 3 und die 5 übersprungen werden. Natürlich macht das Überspringen der 5 programmiertechnisch wenig Sinn (man hätte ja auch gleich nur bis 4 zählen können), aber die Abfrage der Laufvariablen nach Ende der Schleife zeigt sehr schön, dass auch hier ordentlich zum Schleifenende gesprungen und die Variable noch einmal wie gewohnt um 1 erhöht wird.

Quelltext 11.8: CONTINUE FOR
DIM AS INTEGER i
FOR i = 1 TO 5
  ' Dieser Bereich wird noch fuer jedes i ausgefuehrt
  IF i = 3 OR i = 5 THEN CONTINUE FOR
  ' Dieser Bereich wird fuer i=3 und fuer i=5 uebersprungen
  PRINT i
NEXT
PRINT "Nach der Schleife: i ="; i
SLEEP
Ausgabe:
 1
 2
 4
Nach der Schleife: i = 6

11.5.2 Vorzeitiges Verlassen mit EXIT

Um eine Befehlsstruktur vorzeitig zu verlassen, dient die Kontrollanweisung EXIT, also z. B. EXIT DO zum Verlassen einer DO-Schleife. Im Gegensatz zu CONTINUE gibt es hier jedoch deutlich mehr Strukturen, die diese Anweisung einsetzen können:

  1. Schleifen (EXIT DO, EXIT WHILE, EXIT FOR)

  2. SELECT-Blöcke (EXIT SELECT)

  3. Unterprogramme (EXIT SUB, EXIT FUNCTION)

  4. Blockstrukturen im Zusammenhang mit UDTs (EXIT CONSTRUCTOR, EXIT DESTRUCTOR, EXIT OPERATOR, EXIT PROPERTY)

Unterprogramme werden noch in Kapitel 12 zur Sprache kommen.

In Quelltext 11.4 wurde ein Countdown vorgestellt, der bis zum bitteren Ende nach unten zählt. Der Benutzer kann das Programm nicht unterbrechen (außer durch das Schließen der Konsole). In der Regel werden über einen längeren Zeitraum laufende Sequenzen, die ohne erkennbaren Grund nicht unterbrochen werden können, von Benutzerseite nicht gern gesehen. Deswegen erlauben wir dem Benutzer jetzt, den Countdown durch einen Tastendruck abzubrechen.

Quelltext 11.9: Countdown mit Abbruchbedingung
DIM AS INTEGER i
' Tastenpuffer leeren
DO UNTIL INKEY = ""
LOOP

PRINT "Der Countdown laeuft!"
FOR i = 10 TO 1 STEP -1
  PRINT i; " ..."
  SLEEP 1000
  IF INKEY <> "" THEN EXIT FOR     ' Abbruchmoeglichkeit
NEXT

' War der Start erfolgreich?"
IF i > 0 THEN
  PRINT "*** START ABGEBROCHEN! ***"
ELSE
  PRINT "START!"
END IF
SLEEP

Die Funktion INKEY() wurde bereits in Kapitel 5.2.2 kurz vorgestellt. Sie ruft, falls vorhanden, eine Taste aus dem Tastenpuffer ab. Um sicherzustellen, dass sich beim Start der Schleife nicht noch Informationen im Puffer befinden, wird dieser in den Zeilen 3 und 4 geleert — er wird einfach so lange abgefragt, bis er leer ist.

Dem SLEEP in Zeile 9 wurde sein zweiter Parameter genommen. Dadurch muss sich der Benutzer nach einem Tastendruck nicht noch bis zum Ablauf der Wartesekunde gedulden. Allerdings leert SLEEP den Tastaturpuffer nicht, d. h. in Zeile 10 wird die gedrückte Taste dann registriert und die Schleife verlassen.

Interessant ist auch die Abfrage am Ende, in der festgestellt wird, ob der Countdown ordentlich heruntergezählt wurde. Nach dem letzten kompletten Schleifendurchlauf wird die Laufvariable ja noch einmal um 1 reduziert und enthält damit den Wert 0. Wird die Schleife aber im letzten Durchlauf abgebrochen, besitzt die Variable den Wert 1 (bei einem früheren Abbruch natürlich auch einen höheren Wert).

Eine Ausgabe zu Quelltext 11.9 könnte z. B. folgendermaßen aussehen:

Ausgabe:
Der Countdown laeuft!
 10 ...
 9 ...
 8 ...
 7 ...
*** START ABGEBROCHEN! ***

Das Prüfen der Laufvariablen nach Beendigung der Schleife kann z. B. auch hilfreich sein, wenn eine Reihe von Daten durchlaufen werden soll, bis der erste passende Wert gefunden wurde. Außerhalb der Schleife kann dann überprüft werden, ob ein Wert gefunden wurde, und wenn ja, welcher. Diese Möglichkeit einer Abfrage der Laufvariablen außerhalb der Schleife funktioniert selbstverständlich nicht mit einem Konstrukt wie FOR i AS INTEGER, da ja in einer solchen Version die Laufvariable nur innerhalb der Schleife existiert und anschließend zerstört wird!

11.5.3 Kontrollstrukturen in verschachtelten Blöcken

Wenn mehrere Schleifen ineinander verschachtelt sind, ist es manchmal erforderlich, aus dem gesamten Schleifenkonstrukt herauszuspringen, also nicht nur die aktuelle Schleife, sondern eine der umgebenden Schleifen zu verlassen. EXIT FOR (als Beispiel) verlässt die innerste FOR-Schleife, die gefunden werden kann. Wenn sich nun z. B. innerhalb der FOR-Schleife eine DO-Schleife befindet und Sie darin ein EXIT FOR aufrufen, werden natürlich beide Blöcke gleichzeitig verlassen.

' Codeschnipsel 1
FOR i as INTEGER = 1 TO 10
  FOR k as INTEGER = 1 TO 10
    EXIT FOR
  NEXT
  ' Hier geht es nach dem "EXIT FOR" weiter
NEXT

' Codeschnipsel 2
FOR i as INTEGER = 1 TO 10
  DO
    EXIT FOR
  LOOP
NEXT
' Hier geht es nach dem "EXIT FOR" weiter

Wenn zwei oder mehr gleichartige Blöcke gleichzeitig verlassen werden sollen, kann man den Blocknamen mehrmals durch Komma getrennt angeben. Das sieht dann folgendermaßen aus:

' Codeschnipsel 3
FOR i as INTEGER = 1 TO 10
  FOR k as INTEGER = 1 TO 10
    EXIT FOR, FOR
  NEXT
NEXT
' Hier geht es nach dem "EXIT FOR, FOR" weiter

' Codeschnipsel 4
FOR i as INTEGER = 1 TO 10
  FOR k as INTEGER = 1 TO 10
    DO
      FOR l as INTEGER = 1 TO 10
        EXIT FOR, FOR
      NEXT
    LOOP
  NEXT
  ' Hier geht es nach dem "EXIT FOR, FOR" weiter
NEXT

Analog dazu können z. B. drei DO-Schleifen gleichzeitig durch EXIT DO, DO, DO verlassen werden. Die Mehrfach-Angabe ist natürlich auch für alle anderen Strukturen möglich, bei denen Kontrollanweisungen erlaubt sind (SELECT-Blöcke, Unterprogramme usw.), und sie kann auch bei CONTINUE angewendet werden.

11.6 Systemauslastung in Dauerschleifen

Bisher haben alle in diesem Kapitel verwendeten Schleifen eine sehr überschaubare Laufzeit gehabt, dabei ist ein wichtiger Punkt bisher nicht zur Sprache gekommen. Moderne Betriebssysteme sind Multitasking-Systeme, in denen sich viele Prozesse die zur Verfügung stehenden Ressourcen teilen müssen (selbst wenn Ihr Programm aktuell „das einzige“ aktuell laufende Programm ist, laufen doch auf jeden Fall auch die Steuerungsprozesse des Betriebssystem, aber wahrscheinlich auch eine Menge an anderen Programmen im Hintergrund). Das System regelt das, in dem es der Reihe nach den einzelnen Prozessen Ressourcen zuteilt und diese Prozesse dann immer wieder ihre Ausführung kurz unterbrechen, um die Kontrolle an das System zurückzugeben, damit andere Prozesse nicht zu kurz kommen. Solche Unterbrechungen finden in der Regel im Millisekundenbereich statt.

Sobald Sie einen Befehl wie INPUT oder SLEEP aufrufen, der auf eine Benutzeraktion wartet, kümmert sich Ihr Programm selbständig um die Unterbrechung der Ausführung. Ansonsten aber werden die Befehle ohne Unterbrechung nacheinander abgearbeitet (was aber von verschiedenen Betriebssystemen unterschiedlich gelöst wird). Wenn Ihr Programm nun sehr kurz ist, spielt das keine Rolle. Wenn die Programmausführung aber länger dauert (und mit „länger“ ist schon der Sekundenbereich gemeint), sollte man sich Gedanken über die Unterbrechung des Programmablaufs machen, um die Systemauslastung zu reduzieren.

Die einfachste Möglichkeit dazu ist ein simples SLEEP 1. Die Unterbrechung ist so kurz, dass sie im Programm nur selten eine Rolle spielt4 , gibt aber dem Betriebssystem die Möglichkeit, auch andere Prozesse zum Zug kommen zu lassen. Besonders in Dauerschleifen ist das mehr als angebracht.

Quelltext 11.10: Dauerschleife mit SLEEP 1
' Die Schleife arbeitet bis zum Abbruch ueber die Escape-Taste
DIM AS STRING taste
PRINT "Brechen Sie das Programm mit ESC ab!"
DO
  ' hier koennen verschiedene Anweisungen stehen, die wiederholt werden sollen
  ' anschliessend: Abfrage fuer den Schleifenabbruch
  taste = INKEY
  ' Unterbrechung zur Reduzierung der Systemauslastung
  SLEEP 1
LOOP UNTIL taste = CHR(27)
Warning WICHTIG:
Verwenden Sie in Schleifen mit langer Ausführungsdauer immer zumindest SLEEP 1, um die Rechnerperformance aufrechtzuerhalten!

11.7 Fragen zum Kapitel

  1. Was versteht man unter einer kopf- bzw. fußgesteuerten Schleife?

  2. Wann kann eine DO-Schleife eingesetzt werden, wann eine FOR-Schleife?

  3. Was ist eine Endlosschleife?

  4. Welche Datentypen eignen sich für die Zählvariable der FOR-Schleife?

  5. Welche Problemfälle sind bei einer FOR-Schleife zu beachten?

  6. Welche Möglichkeiten gibt es, in den normalen Ablauf einer Schleife einzugreifen?

  7. In Quelltext 11.2 wurde der Benutzer aufgefordert, eine (beliebig lange) Reihe an Namen einzugeben. Erweitern Sie das Programm: Es soll nach abgeschlossener Eingabe alle eingegebenen Namen wieder ausgeben, und zwar in umgekehrter Reihenfolge.


Fußnoten:
1) Und das seit schon über 50 Jahren — denken Sie über diesen Sachverhalt nach, bevor Sie ernsthaft in Erwägung ziehen, GOTO einzusetzen.

2) Die deutschsprachige Befehlsreferenz finden Sie unter http://www.freebasic-portal.de/befehlsreferenz

3) Die Ausgabe kann aus Platzgründen nicht im Buch abgedruckt werden.

4) Bei aufwendigen Berechnungen werden Sie vermutlich durchaus einen messbaren Unterschied wahrnehmen.


Kapitel 10: Bedingungen

Inhaltsverzeichnis

Kapitel 12: Prozeduren und Funktionen