2.10 Programmschleifen
»Stumpfsinn, Stumpfsinn ein Vergnügen! Stumpfsinn, Stumpfsinn eine Lust!
Ohne Stumpfsinn gibt's kein Vergnügen, ohne Stumpfsinn gibt's keine Lust.«
(Kinderlied)
Wenn Sie nicht gerade ein Verfechter dieser Philosophie sind, sollten Sie sich mit Schleifen beschäftigen. Denn sie vermeiden es, Code wieder und wieder zu schreiben zu müssen, der fast gleich ist. Stellen Sie sich vor, Sie möchten die Summe der ersten 100 positiven ganzen Zahlen ermitteln. Mit den bisherigen Kenntnissen würde das bedeuten, 100 Summanden zu tippen. Mit Schleifen zählen Sie einen Zähler hoch und verwenden ihn in einer einzelnen Zeile zur Summierung.
Jede Schleife besteht aus zwei Teilen:
- dem Kontrollteil: Bedingungen und gegebenenfalls eine Schleifenvariable steuern, wie oft die Schleife ausgeführt wird.
- dem Rumpf: die in jedem Durchlauf ausgeführten Anweisungen
Man kann alle Schleifen in zwei Gruppen einteilen:
- Cowboyschleife: erst schießen und dann fragen
- Sheriffschleife: erst fragen und dann schießen
Langweiliger ausgedrückt (optionale Teile stehen in eckigen Klammern, Alternativen sind durch einen senkrechten Strich getrennt, kursive Teile müssen Sie Ihren Bedürfnissen anpassen):
- Fußgesteuerte Schleifen: Erst werden die Anweisungen ausgeführt, dann wird geprüft, ob die Schleife noch einmal ausgeführt werden soll:
Do ... Loop (While | Until) <Bedingung> For Variable [As Typ] = Wert To Wert [Step Wert] ... Next [<Variable>] For Each Variable [As Typ] In Liste ... Next [<Variable>]
- Kopfgesteuerte Schleifen: Erst wird geprüft, ob die Schleife noch einmal ausgeführt werden soll, dann werden die Anweisungen ausgeführt:
While <Bedingung> ... End While Do (While | Until) <Bedingung> ... Loop
Im Prinzip lässt sich jedes Schleifenproblem mit jeder dieser Schleifen lösen. Jedoch ist der eine oder andere Schleifentyp dem Problem besser gewachsen. Ansonsten ist es eine Geschmackssache, für welche Schleife man sich im konkreten Fall entscheidet.
Zur Generierung zufälliger Werte verwenden wir die Funktion Rnd(), die spezifisch für Visual Basic ist. Die sprachunabhängige .NET-Klasse Random würde die Generierung eines Objekts erfordern, was erst im nächsten Kapitel erläutert wird.
Eine weitere Einteilung der Schleifen bestimmt die Aufteilung der nächsten Abschnitte:
- Unbestimmte Schleifen (Do+While): Die Anzahl der Durchläufe ist nicht im Voraus bekannt.
- Bestimmte Schleifen (For): Die Anzahl der Durchläufe wird vorgegeben.
2.10.1 Do und While
Diese Schleifen eignen sich besonders dann, wenn die Anzahl der Schleifendurchläufe nicht als Zahl bekannt ist, sondern von einer Bedingung abhängt. Sie sind selbst dafür verantwortlich, dass irgendwann während der Ausführung der Schleife Bedingungen eintreten, die das Verlassen der Schleife ermöglichen. Ansonsten tritt eine »ewig« laufende Endlosschleife auf.
Hinweis |
Eine vergessene Bedingung einer While-Schleife und eine vergessene Bedingung hinter Do oder Loop einer Do-Schleife wird durch True ersetzt. Eine solche Schleife ist also eine Endlosschleife. |
Do… Loop While
Sollen die Anweisungen im Schleifenrumpf mindestens einmal ausgeführt werden, bietet sich eine fußgesteuerte Schleife an. Eine erste Form der Do-Schleife führt die Anweisungen vor der ersten Prüfung aus und hat folgende Syntax:
Do ... Loop While <Bedingung> |
Als Beispiel verwenden wir eine Prozedur, in der ein Fahrer einer Spedition so lange Touren zu Kunden macht, bis sein Tank auf Reserve ist. Die erste Tour wird in jedem Fall durchgeführt, da erst der Schleifenrumpf ausgeführt wird und erst dann geprüft wird, ob eine weitere Tour möglich ist. Dies ist selbst dann der Fall, wenn der Tank zu Beginn, simuliert durch 50 * Rnd(), bereits auf Reserve ist.
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On Namespace Sprachsyntax Module Schleifen ... Function Kundenanruf() As Double Return Math.Round(1 + Rnd() * 10) 'zufällig End Function Sub DoLoopWhileSchleife() Randomize() 'Initialisierung Zufallszahlengenerator Const reserve As Double = 10 Dim tour As Double, tankstand As Double = 50 * Rnd() Do tour = Kundenanruf() Console.WriteLine("Tour mit {0} km", tour) tankstand -= tour Loop While tankstand > reserve Console.ReadLine() End Sub End Module End Namespace
Do… Loop Until
Wenn Sie die Sichtweise bevorzugen, bis wann (statt wie lange) etwas passieren soll, werden Sie die Until-Form der Do-Schleife verwenden. Der Unterschied zur vorigen Schleife besteht nur in einer »negierten« Prüfung. Um das gleiche Ergebnis wie im vorigen Beispiel zu erhalten, wird lediglich die Zeile
Loop While tankstand > reserve
gegen diese ausgetauscht:
Loop Until tankstand <= reserve
Do While
»Der Mensch will manchmal höchlich sauer mit dem Kopf durch eine Mauer. Doch der Kluge fragt mit Verstand: aus welchem Stoff ist denn die Wand?«
(Karl-Heinz Söhler)
Manchmal lohnt es sich also, erst nachzudenken. Wollen Sie nur dann in eine Schleife eintreten, wenn eine bestimmte Bedingung erfüllt ist, sollten Sie eine kopfgesteuerte Variante wählen. Hierzu gibt es eine Do- und eine For-Variante, wobei die mit Do-While formulierte Variante folgende Syntax hat:
Do While <Bedingung> ... Loop |
Im folgenden Beispiel ist die Lenkzeit eines Fahrers beschränkt und wird vor Beginn der nächsten Tour geprüft, also vor Eintritt in den Schleifenrumpf. Die erste Tour wird nur dann gefahren, wenn die erlaubte Lenkzeit nicht bereits durch vorige Touren, simuliert durch lenkzeit = 6 * Math.Round(Rnd(), 2), überschritten wurde. Durch die Anpassung der Lenkzeit im Schleifenrumpf wird die Prüfung nach einer Tour durchgeführt, und die letzte Tour überschreitet die erlaubte Lenkzeit.
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On Namespace Sprachsyntax Module Schleifen ... Function Fahrzeit() As Double Return Math.Round(Rnd(), 2) 'zufaellig End Function Sub DoWhileSchleife() Randomize() 'Initialisierung Zufallszahlengenerator Dim lenkzeit, strecke As Double Const limit As Double = 4 lenkzeit = 6 * Math.Round(Rnd(), 2) 'bereits gefahren Do While lenkzeit < limit strecke = Fahrzeit() Console.WriteLine("Strecke mit {0} Stunden", strecke) lenkzeit += strecke Loop Console.WriteLine("Lenkzeit: {0} Stunden", lenkzeit) Console.ReadLine() End Sub End Module End Namespace
While
Diese Schleife ist inhaltlich identisch zur vorigen und hat folgende Syntax:
While <Bedingung> ... End While |
Ersetzt man im vorigen Beispiel die Zeilen
Do While lenkzeit < limit Loop
durch
While lenkzeit < limit End While
verhält es sich identisch. Welche Variante Sie wählen, ist Geschmackssache.
Do Until
Der Unterschied zur Do While-Schleife besteht nur in einer »negierten« Prüfung. Um das gleiche Ergebnis wie im Beispiel zu erhalten, wird lediglich die Zeile
Do While lenkzeit < limit
gegen diese ausgetauscht:
Do Until lenkzeit >= limit
2.10.2 For
Ist die Anzahl der Schleifendurchläufe vor Eintritt in die Schleife bekannt, bietet sich die For-Schleife mit folgender Syntax an (optionale Teile stehen in eckigen Klammern, kursive Teile müssen Sie hren Bedürfnissen anpassen):
For <Variable> [As <Typ>] = <Wert> To <Wert> [Step <Wert>] ... Next [<Variable>] |
Diese Schleife ist immer kopfgesteuert. Stößt der Programmablauf erstmals auf den Schleifenkopf, wird die Zählervariable mit dem Startwert initialisiert. Die Zählervariable wird nach jedem Durchlauf um eins erhöht bzw. um den positiven oder negativen Betrag, der im optionalen Step angegeben ist. Die Schleife wird beendet, und mit den Anweisungen nach der Schleife wird fortgefahren, wenn der Wert hinter To über- bzw. unterschritten wird.
Hinweis |
Die Variable hinter Next erhöht die Lesbarkeit eines Programms erheblich und sollte immer angegeben werden. |
Grundform
Im folgenden Beispiel wird eine zufällige Anzahl an Paketen ausgeliefert. Sie legt die obere Grenze der Schleifenvariablen (auch Zähler genannt) fest.
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On Namespace Sprachsyntax Module Schleifen ... Sub ForSchleife() Randomize() 'Initialisierung Zufallszahlengenerator Dim no, pakete As Integer pakete = CType(Rnd() * 10, Integer) For no = 1 To pakete Console.WriteLine("Paket {0} geliefert.", no) Next no Console.ReadLine() End Sub End Module End Namespace
Schrittweite
Man kann in einer Schleife auch herunterzählen. Das nächste Beispiel modifiziert das vorige dadurch, dass die noch ausstehenden Pakete im Schleifenzähler festgehalten werden. Der Befehl Next ist unabhängig von der Schrittweite und -richtung.
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On
Namespace Sprachsyntax
Module Schleifen
...
Sub ForStepSchleife()
Randomize() 'Initialisierung Zufallszahlengenerator
Dim no, pakete As Integer
pakete = CType(Rnd() * 10, Integer)
'Schrittweite 2: in jede Hand ein Paket
For no = pakete To 1 Step –2
Console.WriteLine("Noch {0} Pakete.", no)
Next no
Console.ReadLine()
End Sub
End Module
End Namespace
Geschachtelte Schleifen
Eine Schachtelung von Schleifen ist nichts Besonderes. Die innere Schleife wird wie jede andere Anweisung behandelt. Für das Next gibt es eine besondere Notation, die es erlaubt, mehrere Schleifen gleichzeitig hochzuzählen, wie das folgende Codefragment zeigt:
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On Namespace Sprachsyntax Module Schleifen ... Sub GeschachtelteForSchleifen() Randomize() 'Initialisierung Zufallszahlengenerator Dim tour, paket, touren, pakete As Integer touren = CType(Rnd() * 5, Integer) Console.WriteLine("{0} Touren:", touren) For tour = 1 To touren pakete = CType(Rnd() * 5, Integer) Console.WriteLine("{0} Pakete:", pakete) For paket = 1 To pakete Console.Write("Paket {0} in Tour {1} ", paket, tour) If paket = pakete Then Console.WriteLine() Next paket, tour Console.ReadLine() End Sub End Module End Namespace
For Each
Will man alle Elemente einer Wertemenge durchlaufen, ist es einfacher, eine hierfür spezialisierte Schleife zu verwenden. Das nächste Codefragment gibt die einzelnen Buchstaben einer Zeichenkette zusammen mit ihrem Zahlencode aus. Die Wertemenge besteht hier also aus den Buchstaben, die den String bilden. Den Datentyp von dig kann der Compiler automatisch typsicher ermitteln.
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On Namespace Sprachsyntax Module Schleifen ... Sub ForEachSchleife() For Each dig In "0123" Console.WriteLine("{0} hat Code {1}", dig, Asc(dig)) Next dig Console.ReadLine() End Sub End Module End Namespace
Hinweis |
Wenn nötig, werden automatisch explizite Typumwandlungen angewendet, um die Schleifenvariable mit Werten aus der Sammlung zu belegen. |
Hinweis |
Ressourcen der Sammlung werden automatisch freigegeben, wenn sie die Schnittstelle IDisposable implementiert. |
Fließkommazahlen als Zähler
Das folgende Codefragment testet einen nicht ganzzahligen Schleifenzähler:
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On Namespace Sprachsyntax Module Schleifen ... Sub ForFliesskommaSchleife() Dim I As Single For i = 0.5 To 1.0 Step 0.1 Console.WriteLine(i) Next i Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt, dass ein Durchlauf zu wenig erfolgt ist:
0.5 0.6 0.7 0.8000001 0.9000001
Dies liegt daran, dass die Zahl 0.1 keine exakte Darstellung vom im Computer benutzten Binärsystem hat. Die am nächsten liegende darstellbare Zahl ist ein bisschen größer, sodass die Schleife etwas über die 1 hinausschießt.
Hinweis |
Für Schleifen sollten immer ganzzahlige Zähler und Schrittweiten verwendet werden. |
Andere Zählertypen
Alle Datentypen, die die für den Schleifendurchlauf benötigten Operatoren <=, >=, + und – definieren, können als Zähler verwendet werden, wenn die (optionale) Schrittweite zum Zähler passt. Das folgende Codefragment durchläuft die Aufzählung ConsoleColor und gibt die Farben farbig aus:
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On Namespace Sprachsyntax Module Schleifen ... Sub ForSchleifeMitAnderemZaehlertyp() Dim no As ConsoleColor For no = ConsoleColor.Gray To ConsoleColor.White Console.ForegroundColor = no Console.Write(no.ToString() & " ") Next no Console.ReadLine() End Sub End Module End Namespace
Variablendeklaration im Schleifenkopf
Die Schleifenvariablen können auch direkt im Schleifenkopf einer For-Schleife vereinbart werden. Das folgende Codefragment gibt die ersten 9 Quadrate natürlicher Zahlen aus. Der Typ der Schleifenvariablen wird explizit in einer As-Klausel vereinbart.
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On
Namespace Sprachsyntax
Module Schleifen
...
Sub ForSchleifeMitDeklaration()
For i As Integer = 1 To 9
Console.WriteLine("{0,3}", i ^ 2)
Next i
Console.ReadLine()
End Sub
End Module
End Namespace
Auswertung des Schleifenkopfs
Schleifen sind in einem Punkt »intelligent«: Der Schleifenkopf einer For-Schleife wird nur einmal während der gesamten Schleifenausführung ausgewertet. Das folgende Codefragment simuliert eine Paketausgabe und gibt außer der Paketlieferung nur eine einzige Zeile aus. Da in diesem Beispiel alle Grenzen und die Schrittweite bei jeder Auswertung eine Ausgabe erzeugen, beweist das die nur einmalige Auswertung des Schleifenkopfs.
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On Namespace Sprachsyntax Module Schleifen ... Function Von() As Integer Von = 12 + CType(Rnd() * 9, Integer) Console.Write("Von {0} ", Von) End Function Function Bis() As Integer Bis = CType(Rnd() * 9, Integer) Console.Write("bis {0} ", Bis) End Function Function Schritt() As Integer Schritt = –2 Console.WriteLine("mit Schrittweite {0}", Schritt) End Function Sub ForKopf() Randomize() 'Initialisierung Zufallszahlengenerator Dim no As Integer For no = Von() To Bis() Step Schritt() Console.WriteLine("Noch {0} Pakete.", no) Next no Console.ReadLine() End Sub End Module End Namespace
Hinweis |
Da der Schleifenkopf nur einmal ausgewertet wird, sind Änderungen der Grenzen oder der Schrittweite sowie der Schrittrichtung im Schleifenrumpf nicht möglich. |
2.10.3 Gültigkeitsbereich einer Variablendeklaration
Variablen, die in einer Schleife deklariert werden, sind nur innerhalb der Schleife bekannt. Jeder Zugriff außerhalb der Schleife erzeugt einen Compilerfehler. Auch ist eine Deklaration derselben Variablen in der gleichen Prozedur untersagt. Die Fehler in den folgenden Zeilen treten analog in einer For-Each-Schleife und in allen Varianten einer Do-Schleife auf.
For i As Integer = 0 To 10 Dim j, k As Integer ' Anweisungen Next i = 34 ' Compilerfehler!! j = 12 ' Compilerfehler!! Dim k As Integer ' Compilerfehler!!
2.10.4 Exit und Continue
Es gibt Situationen, in denen man während der Ausführung des Schleifenrumpfs feststellt, dass eine Fortsetzung der aktuellen Iteration nicht angebracht ist. Ein Ansatz könnte sein, den Rest des Schleifenrumpfs in einer If-Bedingung zu kapseln. Eine sehr viel einfachere Möglichkeit bietet Continue. Im nächsten Beispiel gibt ein Fahrer längere Touren an seinen Kollegen weiter.
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On
Namespace Sprachsyntax
Module Schleifen
...
Sub ContinueSchleife()
Randomize() 'Initialisierung Zufallszahlengenerator
Const reserve As Double = 10
Dim tour As Double, tankstand As Double = 200
Do
tour = Kundenanruf()
If tour > 5 Then Continue Do
Console.WriteLine("Tour mit {0} km", tour)
tankstand -= tour
Loop While tankstand > reserve
Console.ReadLine()
End Sub
End Module
End Namespace
Alle Schleifen haben die Möglichkeit, vorzeitig aus ihnen auszusteigen. Im folgenden Beispiel wird die Möglichkeit einer Panne simuliert, die eine Weiterführung der Tour nicht erlaubt:
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On
Namespace Sprachsyntax
Module Schleifen
...
Sub ExitSchleife()
Randomize() 'Initialisierung Zufallszahlengenerator
Dim no, pakete As Integer
pakete = CType(Rnd() * 10, Integer)
Console.WriteLine("{0} Pakete zu liefern.", pakete)
For no = 1 To pakete
Dim panne As Boolean = Rnd() > 0.5
If panne Then Exit For
Console.WriteLine("Paket {0} geliefert.", no)
Next no
Console.ReadLine()
End Sub
End Module
End Namespace
Hinweis |
Continue und Exit sind in allen Schleifentypen erlaubt. |
2.10.5 GoTo
Während der Verwendung von geschachtelten Schleifen kann es vorkommen, dass man, abhängig von einer Bedingung, mit einer äußeren Schleife fortfahren will, ohne mühsam Kontrollstrukturen wie If zu bemühen. Dies ist einer der ganz wenigen Fälle, wo ein GoTo sinnvoll eingesetzt werden kann. Im folgenden Codefragment weigert sich ein Fahrer, bei mehr als 3 Paketen weiterzufahren, und das Programm fährt mit dem nächsten LKW fort. Dies ist ein Sprung zum Ende der übernächsten Schleife, was mit Exit nur recht mühsam möglich wäre. Das Sprungziel kann eine Zahl oder ein Bezeichner mit nachfolgendem Doppelpunkt sein. Es muss in der ersten Spalte stehen.
' ...\Sprachsyntax\Schleifen\Schleifen.vb |
Option Strict On Namespace Sprachsyntax Module Schleifen ... Function Anzahl() As Integer Anzahl = CType(Rnd() * 5, Integer) Console.WriteLine(Anzahl) End Function Sub SchleifenUndGoto() Randomize() 'Initialisierung Zufallszahlengenerator Dim lkw, tour, paket As Integer Console.Write("Laster: ") For lkw = 1 To Anzahl() Console.Write("Touren: ") For tour = 1 To Anzahl() Console.Write("Pakete: ") For paket = 1 To Anzahl() If paket > 3 Then Console.WriteLine("Genug!") GoTo lkw End If Console.Write("{{{0},{1},{2}}} ", paket, tour, lkw) Next paket Console.WriteLine() Next tour lkw: Next lkw Console.ReadLine() End Sub End Module End Namespace
Hinweis |
Bis auf diese Ausnahme (Sprung zum Kontrollteil) sollte GoTo tabu sein, da es das Verfolgen der Programmlogik extrem erschwert. |
2.10.6 Zusammenfassung
- Fußgesteuerte Schleifen entscheiden für oder gegen einen weiteren Durchlauf nach Ausführung des Schleifenrumpfs. Mindestens ein Durchlauf ist garantiert.
- Kopfgesteuerte Schleifen prüfen erst und führen dann die Anweisungen aus.
- Unbestimmte Schleifen haben keine im Voraus bestimmte Anzahl an Durchläufen (Do und While).
- Bestimmte Schleifen geben die Anzahl der Durchläufe vor (For).
- Die Einsatzgebiete der verschiedenen Schleifentypen sind fließend.
- Die For-Next-Schleife wird von einem Start- bis zu einem einschließlichen Endwert durchlaufen. Soll der Zähler nicht um +1 erhöht werden, kann das durch die Angabe von Step geändert werden.
- Der Eintritt bzw. der Austritt aus einer Do-Schleife wird durch eine Bedingung gesteuert, die einen booleschen Wert repräsentiert.
- Until-Schleifen werden abgeschlossen, sobald die Bedingung True ist, While-Schleifen genau dann, wenn die Bedingung False wird.
- Werden innerhalb einer unbestimmten Schleife Werte nicht geändert, die in den Bedingungen geprüft werden, liegt eine Endlosschleife vor.
- While ... End While zeigt dasselbe Verhalten wie eine kopfgesteuerte Do-While-Schleife.
- Alle Schleifen können mit Exit verlassen werden.
- In allen Schleifen kann mit Continue zum nächsten Durchlauf gesprungen werden.
- Variablen, die in einer Schleife deklariert werden, sind nur dort gültig. Eine weitere Variable in derselben Prozedur oder Funktion mit gleichem Namen ist verboten.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.