Schleife (Programmierung)
Eine Schleife (auch „Wiederholung“ oder englisch loop) ist eine Kontrollstruktur in Programmiersprachen. Sie wiederholt einen Anweisungs-Block – den sogenannten Schleifenrumpf oder Schleifenkörper –, solange die Schleifenbedingung als Laufbedingung[Anm 1] gültig bleibt bzw. als Abbruchbedingung nicht eintritt. Schleifen, deren Schleifenbedingung immer zur Fortsetzung führt oder die keine Schleifenbedingung haben, sind Endlosschleifen.
Schleifen können beliebig verschachtelt werden: Innerhalb des Schleifenkörpers der äußeren Schleife befindet sich wiederum eine Schleife, sie liegt innen, oder unter der äußeren Schleife. Jede Schleife kann in eine rekursive oder sogar endrekursive Form umgewandelt werden. Zur Beschleunigung des Programmablaufs werden Schleifen oft durch den Compiler entrollt.
Eine Schleife wird schrittweise wiederholt (iterativ) verarbeitet:
- Üblicherweise wird (außer bei einer fußgesteuerten Schleife) erst geprüft, ob die Schleifenbedingung gültig ist; falls nicht, wird die Schleife beendet.
- Der gesamte Schleifenrumpf wird ausgeführt. Anschließend wird mit 1. fortgesetzt.
Arten
Prinzipiell werden folgende Typen von Schleifen unterschieden:
- Die vorprüfende oder kopfgesteuerte Schleife.
Bei dieser Schleife wird vor der (eventuellen) Ausführung des Schleifenrumpfs eine Bedingung geprüft, ob der Schleifenrumpf (Schleifeninhalt) anschließend (erstmals/erneut) ausgeführt wird (meist mit WHILE = solange eingeleitet). - Die nachprüfende oder fußgesteuerte Schleife.
Bei dieser Schleife wird nach dem Durchlauf des Schleifenrumpfes (Schleifeninhalts) eine Bedingung überprüft, ob der Schleifenrumpf erneut ausgeführt wird (meist als Konstrukt DO…WHILE = „ausführen … solange“ oder REPEAT…UNTIL = „wiederholen … bis“). - Die Zählschleife, eine Sonderform der vorprüfenden Schleife (meist als FOR = für-Schleife implementiert).
- Die Mengenschleife, eine Sonderform der Zählschleife (meist als FOREACH = „für jedes Element der Menge“ implementiert; die Reihenfolge der Elemente ist beliebig).
Ferner können sich Schleifen bzgl. ihrer Bedingungsprüfung unterscheiden:
- Schleife mit Laufbedingung: Die Schleife wird solange durchlaufen, wie die Schleifenbedingung zu „wahr“ ausgewertet wird.
- Schleife mit Abbruchbedingung: Wertet die Bedingung zu „wahr“ aus, so wird die Schleife abgebrochen.
Schleifenabbruch im Sonderfall
In Fällen, die schwierig als Schleifenbedingung zu fassen sind, kann eine Schleife (aus dem Schleifenkörper heraus) meist abgebrochen werden.
Iterationsabbruch
Es wird lediglich die aktuelle Iteration abgebrochen: Der Rest des aktuellen Schleifenrumpfs wird übersprungen. Die Schleife jedoch wird mit der nächsten Iteration fortgesetzt. Mitunter kann in verschachtelten Schleifen auch auf eine weiter außen liegende Schleife Bezug genommen werden – der Abbruch der aktuellen Iteration gilt dann bezüglich jener angegebenen „weiter äußeren“ Schleife; die „weiter innere“, in deren Schleifenrumpf die Abbruchanweisung steht, wird dann komplett abgebrochen.
Schleifenabbruch
Meist gibt es auch einen Befehl zum Gesamt-Abbruch der Schleife, das Programm wird dann mit der ersten Anweisung nach der Schleife fortgesetzt. Oft ist in verschachtelten Schleifen auch eine Bezugnahme auf eine weiter außen liegende Schleife möglich, was die inneren dann ebenfalls abbricht.
Beispiele
Die nachfolgenden Beispiele in Pseudocode finden sich in den jeweiligen Programmiersprachen meist sehr ähnlich.
Zählschleife
FOR Iterator:=Anfangszahl TO Endezahl STEP Schrittweite DO Schleifenrumpf.
Bei einer For-Schleife zählt der Computer von einer Anfangszahl bis zu einer Endzahl und wiederholt dabei jedes Mal den Codeblock („Schleifenrumpf“). Die aktuelle Zahl wird in eine Variable („Iterator“) gesetzt, damit sie bei Bedarf in dem Codeblock Verwendung finden kann. Häufig ist die Zählschleife auf Ganzzahlen beschränkt. Das Ändern der Iterator-Variablen im Schleifenkörper ist bei vielen Programmiersprachen verboten und gilt als schlechter Programmierstil, da es oft zu schwer verständlichem Code führt – es läuft der Denkweise zuwider, direkt am Schleifenkopf die Anzahl der Durchläufe ablesen zu können.
Kopfgesteuerte Schleife
WHILE Logischer Ausdruck DO Schleifenrumpf.
Bei einer kopfgesteuerten Schleife erfolgt die Abfrage der Bedingung, bevor der Schleifenrumpf ausgeführt wird, also am Kopf des Konstruktes. Eine logische Operation kann beispielsweise sein: (x > 4)
Solange diese Bedingung wahr ist, werden die Anweisungen innerhalb der Schleife ausgeführt. Wird der Inhalt der logischen Operation nicht im Schleifenrumpf verändert, ist diese Kontrollstruktur meist nicht die richtige, weil diese Schleife sonst kein einziges Mal durchlaufen wird oder unendlich lang läuft.
Fußgesteuerte Schleife
DO Schleifenrumpf WHILE Logischer Ausdruck
bzw.
REPEAT Schleifenrumpf UNTIL Logischer Ausdruck
Bei einer fußgesteuerten Schleife erfolgt die Abfrage der Bedingung, nachdem der Schleifenrumpf ausgeführt wurde, also am Fuß des Konstruktes. Auf WHILE
(dt.: solange) folgt eine Laufbedingung, auf UNTIL
(dt.: bis) eine Abbruchbedingung. Wie für die kopfgesteuerte Schleife gilt: Wird der Inhalt der logischen Operation nicht im Schleifenrumpf verändert, ist diese Kontrollstruktur meist nicht die richtige, weil diese Schleife sonst genau einmal durchlaufen wird oder unendlich lang läuft.
Mengenschleife
FOREACH Element OF Menge DO Schleifenrumpf
Eine Mengenschleife führt den Schleifenrumpf für jedes Element einer Menge (z. B. Array oder Liste) aus.
Sie kann ersetzt werden durch eine Zählschleife mit dem Schleifenkörper
FOR Iterator2 := 1 TO Mächtigkeit(Menge) DO BEGIN Element:= Iterator2-tes Element von Menge; Schleifenrumpf END
Da die Reihenfolge der Abarbeitung bei der Mengenschleife beliebig ist, bleibt dem Compiler überlassen, wie er vorgeht. Aufgrund des sicher gegebenen Umstands, dass eine Iteration nicht von der „vorhergehenden“ abhängig sein kann, kann ein Compiler Mengenschleifen am einfachsten automatisch parallelisieren.
Iterationsabbruch
CONTINUE
bzw.
CONTINUE Schleifenbezeichner
Die erste Variante bricht die aktuelle Iteration ab, es geht weiter mit der Prüfung der Laufbedingung für die nächste Iteration. Die zweite Variante beendet in verschachtelten Schleifen alle inneren und wirkt wie die erste Variante für diejenige äußere Schleife, welche durch Schleifenbezeichner angesprochen wird. Oft kommt hier entweder ein Name ähnlich einer Sprungmarke zum Einsatz; in Zählschleifen erfolgt die Identifizierung mitunter über den Namen des Iterators.
Schleifenabbruch
BREAK
bzw.
BREAK Schleifenbezeichner
Die erste Variante bricht die aktuelle Schleife ab, es geht weiter mit der ersten Anweisung nach der Schleife. Die zweite Variante beendet in verschachtelten Schleifen alle inneren komplett sowie diejenige äußere Schleife, welche durch Schleifenbezeichner angesprochen wird. Oft kommt hier entweder ein Name ähnlich einer Sprungmarke zum Einsatz; in Zählschleifen erfolgt die Identifizierung mitunter über den Namen des Iterators.
Implementierung mit Sprungbefehlen
Früher wurden auch in Hochsprachen-Programmen häufig unbedingte Sprünge (Goto-Befehle) verwendet. Sprachen, die Sprunganweisungen verwenden, ermöglichen das Einfügen einer Marke (englisch Label). Eine solche Marke kann dann als Ziel einer Goto-Anweisung dienen.
Nach heutigen Programmierparadigmen, namentlich der strukturierten Programmierung, wird von der Verwendung von Goto-Sprüngen abgeraten, da durch diese der berüchtigte „Spaghetticode“ entsteht. Prinzipiell lässt sich jedoch jede Schleifenform mit Sprungbefehlen abbilden.
For-Schleife
Es gibt mehrere Alternativen, For-Schleifen mit Hilfe einfacherer Befehle zu implementieren. Im Normalfall verhalten sich diese Alternativen gleich, aber in einigen speziellen Situationen gibt es Unterschiede. Beispiele für spezielle Situationen sind:
- Die Schrittweite ist 0 (nicht in allen Varianten der For-Schleife möglich).
- Der Startwert oder der Endwert sind die kleinst- oder größtmögliche darstellbare Zahl.
Eine ausführlichere Darstellung der Varianten befindet sich im Artikel For-Schleife.
While-Do-Schleife
WHILE Logischer Ausdruck DO Befehlssequenz.
entspricht:
Marke1: IF NOT Logischer Ausdruck GOTO Marke2 (bedingter Vorwärtssprung zum Verlassen der Schleife) Befehlssequenz GOTO Marke1 (Rückwärtssprung für die nächste Iteration) Marke2:
Die Befehlssequenz wird keinmal durchlaufen, wenn der logische Ausdruck schon zu Beginn falsch ist.
Do-While-Schleife
DO Befehlssequenz WHILE Logischer Ausdruck
entspricht:
Marke1: Befehlssequenz IF Logischer Ausdruck GOTO Marke1 (bedingter Rückwärtssprung für die nächste Iteration, wenn Bedingung erfüllt ist)
Hier wird die Schleife mindestens einmal durchlaufen. Die Do-While-Schleife ist damit nachprüfend.
Repeat-Until-Schleife
REPEAT Befehlssequenz UNTIL Logischer Ausdruck
entspricht:
Marke1: Befehlssequenz IF NOT Logischer Ausdruck GOTO Marke1 (bedingter Rückwärtssprung für die nächste Iteration, wenn Bedingung nicht erfüllt ist)
Sie ist also lediglich eine Do-While-Schleife mit Abbruchbedingung statt Laufbedingung.
Befehle in Assemblersprache
Assemblercode bietet normalerweise nicht die aus höheren Programmiersprachen bekannten for/while/repeat Konstrukte. Da aber auch hier Schleifen eingesetzt werden müssen (Verzögerung durch aktives Warten (s. u.), serielles adressiertes Verarbeiten von Daten), stehen einfache Sprungbefehle für unbedingte und bedingte Sprünge zur Verfügung.
Letztere entscheiden anhand eines Statusflags der CPU (z. B. Zero-Flag), ob gesprungen werden muss. Trifft die Voraussetzung nicht zu, so wird der Programmcounter (PC) einfach um eins erhöht. Es wird dann also als Nächstes der Befehl nach dem bedingten Sprungbefehl ausgeführt.
Beispiel für Code für einen AVR-Mikrocontroller, der unter Verwendung einer Schleife um insgesamt 5000 Takte durch aktives Warten verzögert:
; delaying 4998 clocks in 2 loops
ldi R0, 7 ; initialisiere äußeren Schleifenzähler mit 7
Marke1:
; innere Schleife
ldi R1, 237 ; initialisiere inneren Schleifenzähler mit 237 (dezimal)
Marke2:
dec R1 ; Vermindert Inhalt in R1 um 1
brne Marke2 ; Sprung nur, wenn R1 nun nicht 0 ist. („BRanch if Not Equal to zero“)
dec R0 ; äußeren Schleifenzähler um 1 verringern
brne Marke1
; delaying additional 2 clocks
nop
nop
Häufig gibt es angepasste Assemblerbefehle, die mehrere Aktionen kombinieren („Prüfe Iterator auf Ist-Gleich-…, wenn ungleich springe nach …“, „Zähle Iterator 1 herunter, wenn er noch immer größer 0 ist, springe nach …“). Oft setzen diese voraus, dass ein Zähl-Iterator sich z. B. in einem bestimmten Register befindet.
LOOP-Befehl bei der x86-Architektur
Für die x86-Architektur gibt es einen speziellen LOOP
-Assemblerbefehl. Dieser ist aber nur auf i8086- und i80286-Prozessoren schneller als eine gleichwertige Kombination aus einem Assemblerbefehl wie bspw. DEC
oder SUB
und einer bedingten Sprunganweisung wie bspw. JNZ
. Er sollte daher in Programmcode für CPUs ab dem Intel 80386 nicht mehr verwendet werden.[1][2] Neben dem Geschwindigkeitsvorteil auf CPUs vor dem i386 benötigt er weniger Speicherplatz und führt zu lesbarerem Code.[3]
Literatur
- Thomas Rauber, Gudula Rünger: Parallele Programmierung. 3. Auflage. Springer Verlag, Berlin 2012, ISBN 978-3-642-13603-0.
Weblinks
- Programmierung mit C# – Grundkurs. (PDF) abgerufen am 1. Oktober 2015
- Programmieren mit Python – erste Schritte. (PDF) abgerufen am 29. Juni 2018
- Einführung in die Programmierung. ( vom 2. Oktober 2015 im Internet Archive) (PDF) abgerufen am 1. Oktober 2015, via archive.org
Anmerkungen
- ↑ http://archive.gamedev.net/archive/reference/articles/article369.html Abrash, Michael., Dr. Dobb's Journal March 1991 v16
- ↑ https://jacobfilipp.com/DrDobbs/articles/DDJ/1991/9103/9103a/9103a.htm Abrash, Michael., Dr. Dobb's Journal March 1991 v16 - Mit Grafiken.
- ↑ Assembler Referenz S. 82 Autor Oliver Müller Franzis Verlag ISBN 3-7723-7505-7