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
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.
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.
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.
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:
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
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.
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
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.
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
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.
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
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
' 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.
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
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:
-
Schleifen (
EXIT DO
,EXIT WHILE
,EXIT FOR
) -
SELECT
-Blöcke (EXIT SELECT
) -
Unterprogramme (
EXIT SUB
,EXIT FUNCTION
) -
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.
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:
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.
' 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)
WICHTIG: Verwenden Sie in Schleifen mit langer Ausführungsdauer immer zumindest SLEEP 1 , um die Rechnerperformance aufrechtzuerhalten! |
11.7 Fragen zum Kapitel
-
Was versteht man unter einer kopf- bzw. fußgesteuerten Schleife?
-
Wann kann eine
DO
-Schleife eingesetzt werden, wann eineFOR
-Schleife? -
Was ist eine Endlosschleife?
-
Welche Datentypen eignen sich für die Zählvariable der
FOR
-Schleife? -
Welche Problemfälle sind bei einer
FOR
-Schleife zu beachten? -
Welche Möglichkeiten gibt es, in den normalen Ablauf einer Schleife einzugreifen?
-
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.