====== Wiederholungen ====== * Kopfgesteuerte Schleifen * Fußgesteuerte Schleifen * Unterbrechen von Wiederholungen * Unterbrechen verschachtelter Schleifen * Endlosschleifen ===== Kopfgesteuerte Schleifen ===== Unsere bisherigen Programme hatten einen großen Nachteil: Sie erfüllten eine Aufgabe und nichts veränderte sich im Ablauf. Bisher ging es darum Ausdrücke und Variablen kennen zu lernen und zu sehen, wie man sie verwendet. Nun geht es einen Schritt weiter: Wir werden lernen, wie man Dinge mehrfach macht. Dafür sind Computer schließlich da: Viel gleichartige Arbeit zu leisten. Dabei spielt es keine Rolle, ob man tausende Adressen ausdruckt oder viele 3D-Grafiken rendert, um ein 3D Spiel flüssig zu animieren oder einfach nur darauf gewartet wird, bis jemand das Schließsymbol in der Titelleiste drückt. Wenn der Benutzer daneben klickt, muss man nochmal warten. Kurz: Wenn man ein Programm schreibt, müssen viele Anweisungen wiederholt werden bis... ja, bis man fertig ist. So banal das klingt, so banal ist es auch. Wir können ja bereits Entscheidungen treffen, also müssen wir ein Kriterium formulieren lassen, mit dem wir entscheiden, dass wir eine Schleife nicht länger durchlaufen wollen. ==== Die While-Schleife ==== Wenn wir von 1 bis 5 zählen wollen, so ist das Kriterium 'wenn wir noch nicht bei 5 angekommen sind, dann müssen wir noch weiter zählen'. Wichtig ist: Wir müssen das Kriterium so formulieren, dass man ''true'' erhält, wenn man weiterhin in der Schleife verbleiben möchte. Das Schlüsselwort für eine Schleife lautet ''while'' und es funktioniert im Prinzip wie ein ''if''. Statt bei if "Falls die Bedingung wahr ist, dann..." wird daraus bei einem ''while'' "Solange die Bedingung wahr ist, dann...". Ist der "Then-Part" abgeschlossen, geht es nicht mit der Anweisung hinter der geschweiften Klammer weiter, sondern es wird zum ''while'' zurückgesprungen und es wird erneut gefragt, ob die Bedingung wahr ist. Wenn ja, wird der Schleifenkörper erneut durchlaufen. Schauen wir uns das Zählen von 1 bis 5 an: #include int main(void) { int wert = 1; while( wert <= 5 ) { printf( "Ich bin bei '%d'\n", wert ); wert = wert + 1; } return 0; } Das Programm liefert folgende Ausgabe: Ich bin bei '1' Ich bin bei '2' Ich bin bei '3' Ich bin bei '4' Ich bin bei '5' Wie bei ''if'' ist der Ausdruck in den runden Klammern der while-Bedingung frei zusammenstellbar. Wir können aufwendige Ausdrücke verwenden, Vergleiche, logische Konjunktionen (&&, ||) und alles, woraus sich ein Ausdruck zusammenstellen lässt. Innerhalb der geschweiften Klammern können wir nun nach Belieben eigenen Code einfügen. Allerdings muss man aufpassen, dass man das Abbruchkriterium einhält, sonst findet man sich sehr schnell in einer Endlosschleife wieder. Wir könnten das Abbruchkriterium auch wie folgt formulieren: while( wert != 5 ) Solange der Wert nicht 5 ist, zähle weiter. Das funktioniert mit dem oben stehenden Programm wunderbar. Ändern wir aber die Schleife so, dass ''wert'' nicht mehr 5 wird, sondern wir einfach an 5 vorbeiziehen, bekommen wir Probleme: int wert = 1; while( wert != 5 ) { printf( "Ich bin bei '%d'\n", wert ); wert = wert + 3; } Hier wird die Schleife für den ''wert'' 1, 4, 7, 10... aufgerufen. Das lässt sich mit ''while( wert <= 5 )'' vermeiden. Man muss also sehr genau aufpassen, dass man genau das programmiert, was man ausdrücken möchte. **Bis (einschließlich)** 5 heißt, dass die erlaubten Zahlen kleiner oder gleich 5 sind, also ''wert <= 5''. Benutzt man den ''!='' Operator stattdessen, so ist die Bedingung so lange wahr, solange ''wert'' ungleich 5 ist. Das ist eine andere Bedeutung und wer einen Computer programmiert muss sich klar und deutlich ausdrücken, denn ein Computer hinterfragt nicht, ob das Programm einen Sinn ergibt oder nicht. Die while-Schleife eignet sich besonders für Aufgaben, bei denen das Ende sich im Programmverlauf ergibt, beispielsweise wenn eine Datei eingelesen wird, dann weiß man bei der Programmierung nicht, wie lang die Datei sein wird, die sich der Benutzer ausgesucht hat. Irgendwann stimmt die Bedingung nicht mehr und die Schleife endet. ==== Die For-Schleife ==== Für abzählbare Probleme wie dieses eignet sich eine weitere Schleifenform, die nichts anders macht als die while-Schleife, aber eine geringfügig andere Schreibweise hat. Wir haben eine Variable ''wert'', auf der wir die einzelnen Werte durchzählen, innerhalb der runden Klammern die Bedingung und wir haben die Beschreibung, wie sich die Zählvariable verändert. Diese drei Elemente sind typisch für eine Schleife, die etwas abzählt. Darum gibt es die for-Schleife, die diese drei Elemente in eine Zeile zusammenpackt, damit man sie einfacher lesen kann: #include int main(void) { for( int wert = 1; wert <= 5; wert = wert + 1 ) { printf( "Ich bin bei '%d'\n", wert ); } return 0; } Das Programm ist identisch mit der while-Version. Die Zählvariable wird angemeldet, überprüft und es wird weitergezählt. Zu beachten ist, dass nach der Prüfung der Bedingung erst der Schleifenkörper (also die printf()-Anweisung) ausgeführt wird. Erst dann wird weitergezählt. Ergibt die Bedingung ''false'', so wird der Schleifenkörper nicht ausgeführt und es wird auch nicht mehr weitergezählt. Das Programm ist damit absolut identisch zu der ''while''-Version, man erkennt hier lediglich alle für das Durchzählen erforderlichen Informationen direkt in einer Zeile. Die Variable ''wert'' wird hier innerhalb der Schleife definiert, das bedeutet, dass sie nach der Schleife auch nicht mehr existiert. Alte C-Compiler((Der GCC-Compiler kompiliert nach altem Standard. Das stört normalerweise nicht - hier schon: Um den C99-Standard von 1999 zu nutzen, rufst man den Compiler zusätzlich mit dem Flag ''-std=c99'', also gcc -std=c99 for.c)) akzeptieren diese Syntax leider noch nicht. In dem Fall muss man die Variable vorher definieren. int wert; for( wert = 1; wert <= 5; wert = wert + 1 ) { ... } Das Kapitel heißt 'Kopfgesteuerte Schleife'. Das bedeutet, dass die Steuerung - also die Bedingung - am oberen Teil der Schleife steht. **Zuerst** muss die Bedingung erfüllt werden, **dann** wird der Schleifenkörper ausgeführt. Es geht aber auch andersherum: ===== Fußgesteuerte Schleifen ===== Eine fußgesteuerte Schleife wird - wie der Name schon sagt - am unteren Ende kontrolliert. Das bedeutet, dass der Schleifenkörper entsprechend oben steht. Im Quelltext formuliert man Anweisung für Anweisung und er wird von oben gelesen. In diesem Fluss wird also erst der Schleifenkörper gelesen und ausgeführt und dann erst entschieden, ob er wiederholt wird. Eine fußgesteuerte Schleife verwendet man also immer dann, wenn der Inhalt des Schleifenkörpers mindestens einmal ausgeführt werden soll und erst am Ende des Schleifenkörpers entschieden wird, ob der Schleifenkörper erneut ausgeführt werden soll. ==== Die do...while-Schleife ==== Eingeleitet wird sie mit dem Schlüsselwort ''do'', gefolgt vom Schleifenkörper und beendet mit dem Schlüsselwort ''while'' mit der Bedingung: "Tue {irgendwas} und wiederhole solange( die Bedingung wahr ist )" Schauen wir uns unser Zählbeispiel von vorhin an. Wenn wir dort die Variable ''wert'' auf 10 stellen und dort solange zählen, wie ''wert <= 5'' ist, passiert nichts. Nun schauen wir uns die fußgesteuerte Schleife an: #include int main(void) { int wert = 10; do { printf( "Ich bin bei '%d'\n", wert ); wert = wert + 1; } while( wert <= 5 ); return 0; } Hier ist die Ausgabe Ich bin bei '10' dann erst wird entschieden, dass der Schleifenkörper nicht wiederholt wird, weil die Bedingung ''wert <= 5'' nicht wahr ist. ===== Unterbrechen von Wiederholungen ===== Wir haben bisher zwei Möglichkeiten kennengelernt, wie man eine Schleife steuern kann, aber wer aufwendige Programme schreibt, der schreibt eventuell auch aufwendige Schleifen. Da kann es passieren, dass ein Teil grundsätzlich ausgeführt werden muss, also eine fußbasierte Schleife erforderlich ist, aber in der gleichen Schleife ein Teil eventuell übersprungen werden soll. Lösen wir folgendes Problem: Wir haben wieder eine Schleife, die von 1 bis 10 zählt. Wir wollen eine Handlung, die für alle Zahlen gilt und wir wollen angeben, ob die Zahl durch zwei teilbar ist. Wie in der Grundschule können wir hier den Rest einer Division verwenden. Dieser Operator heißt "modulo" und wird durch ein Prozentzeichen ('%') ausgedrückt. 14 % 5 ist entsprechend 4, weil 5 zweimal in 14 hineinpasst und als Rest 4 überbleibt. 2 * 5 + 4 ist 14. Das lässt sich wie folgt formulieren: #include int main(void) { int wert; for( wert = 1; wert <= 10; wert = wert + 1 ) { printf( "Ich bin bei '%d'\n", wert ); if( (wert % 2) == 0 ) // Rest einer Division durch 2 ist 0? { printf( " Dieser Wert ist durch zwei teilbar.\n" ); } } return 0; } ==== continue ==== Es gibt in C zwei Möglichkeiten auf Bedingungen einzugehen. Die hier Gezeigte fragt die Bedingung ab und verschachtelt dann im Then-Part. Wenn's passt, dann... Es gibt aber auch eine andere Vorgehensweise: Wenn's nicht passt, dann höre auf. Man stellt sich einen Türsteher vor den "Then-Part", der - wenn die Bedingung nicht erfüllt ist - die Weiterverarbeitung unterbindet. Damit das if in den "Then-Part" verzweigt muss die Bedingung so geschrieben werden, dass sie wahr wird, wenn sie nicht erfüllt wird (Sie wird negiert, also im Wahrheitswert umgekehrt). Beispiel: Der Wert soll 2 sein. Dann ist die Bedingung **nicht** erfüllt, wenn der Wert **nicht** 2 ist. In C: ''!( wert == 2 )'' oder in Kurz: ''wert != 2''. Wir können so dann reagieren, wenn etwas **nicht** wie gewünscht ist. Gewünscht war ''wert == 2'', wir reagieren, wenn ''wert != 2''. Wenn Wunschbedingung **nicht** erfüllt, dann... Einen solchen Türsteher nennt man "[[glossary:guard|Wächter]]" oder - damit man ein schöneres [[glossary:Buzzword]] hat - auch "[[glossary:Precondition]]". Schauen wir uns das zunächst einfach im Code an: #include int main(void) { int wert; for( wert = 1; wert <= 10; wert = wert + 1 ) { printf( "Ich bin bei '%d'\n", wert ); if(wert % 2) continue; printf( " Dieser Wert ist durch zwei teilbar.\n" ); } return 0; } Wir haben nun einen Wächter, der - wenn die Zahl nicht durch zwei teilbar ist - den Befehl ''continue'' aufruft. continue unterbricht die Abarbeitung der Schleife, das nachfolgende ''printf( "Dieser Wert ist durch zwei teilbar" )'' wird also nicht mehr erreicht. Stattdessen wird die Schleife direkt am Kopf fortgesetzt ((engl. to continue: fortsetzen)). Continue springt an den Anfang der Schleife, durchläuft bei einer for-Schleife den dritten Codepart, um die Variable aufzuzählen (hier ''wert = wert + 1'') und prüft anschließend die Bedingung erneut ab, um herauszufinden, ob der Schleifenkörper erneut durchlaufen wird. Bei einer while-Schleife muss das Durchzählen der Variablen selbst kontrolliert werden - aber es kann ja auch erwünscht sein, dass sich an der Zählvariablen beim nächsten Durchlauf nichts ändert. Das Schöne an diesen Wächtern ist, dass man sie einfach hintereinander setzen kann, ohne dass man laufend Klammern öffnen muss und sich der Quelltext weiter nach rechts verschiebt. ==== break ==== Schaffen wir eine zusätzliche Bedingung, die erfüllt sein muss, damit ''"Dieser Wert ist durch zwei teilbar"'' geschrieben werden soll: Der Wert soll nicht gleich 8 sein - wenn der Wert 8 ist, soll die Schleife nicht fortgesetzt, also abgebrochen((engl. to break: abbrechen)) werden. Hier haben wir also wieder eine Vorabbedingung, eine Pre-Condition und können einen weiteren Wächter in die Reihe stellen. Der Befehl, um die aktuelle Schleife abzubrechen heißt ''break''. #include int main(void) { int wert; for( wert = 1; wert <= 10; wert = wert + 1 ) { printf( "Ich bin bei '%d'\n", wert ); if(wert % 2) continue; if(wert == 8) break; printf( " Dieser Wert ist durch zwei teilbar.\n" ); } return 0; } Wir sehen, dass sich die Wächter der Reihe hintereinander schalten lassen - nur wenn ein ''wert'' alle Vorabbedingungen erfüllt, wird er bis zum zweiten printf() durchgelassen. ''break'' springt an die erste Anweisung hinter dem Ende der aktuellen Schleife. Das kann auch eine schließende Klammer einer anderen Schleife sein, welche dann fortgesetzt wird. ===== Unterbrechen verschachtelter Schleifen ===== Manchmal muss man zwei Schleifen miteinander verschachteln. Das Ärgerliche in C ist, dass man mit ''break'' und ''continue'' leider immer nur aus der aktuellen Schleife springen kann. Doch auch um das Problem werden wir uns noch in den fortgeschrittenen Themen kümmern. Bis auf Weiteres sei gesagt, dass man mit einer zusätzlichen Hilfsvariable, die man beim Verlassen der inneren Schleife setzt, die äußere Schleife darüber informieren kann, dass auch sie abgebrochen werden muss: int helper = 1; // wenn helper != 0, läuft die Schleife int i, j; for( i = 0; i <= 10 && helper; i++ ) { for( j = 0; j <= 10; j++ ) { if( i + j == 12 ) { helper = 0; // äußere Schleife nicht wiederholen break; // innere Schleife abbrechen } } } ===== Endlosschleifen ===== Endlosschleifen sind Schleifen, die - wie der Name sagt - keine Abbruchbedingung haben. Sie werden entweder durch ein ''break'' verlassen oder laufen bis der Computer ausgeschaltet wird (bzw. sie vom Betriebssystem abgeschossen werden). Dafür gibt es drei übliche Möglichkeiten, sie absichtlich zu formulieren, wobei do...while(1) sehr selten ist. while( 1 ) { ... } for( ;; ) { ... } do { ... } while( 1 ); Die Bedingung darf bei for(;;) weggelassen werden, bei while muss etwas stehen, was nicht 0 ist. 1 ist nicht 0, deswegen ist die Bedingung immer wahr. Endlosschleifen können gewünscht sein um zum Beispiel bei "Embedded Systems" ((kleine Computer, die zum Beispiel auf den Druck einer Taste in Deiner Tastatur warten, um das Ereignis per USB an den PC zu senden)). Diese Programme enden erst, wenn sie abgeschaltet werden, z.B. USB keinen Strom mehr liefert. Häufig entstehen sie aber versehentlich aus ungeschickt gewählten Bedingungen, bzw. weil vergessen wurde, die Zählvariable zu verändern. Wenn Dein Programm also nicht mehr reagiert, ist die Chance hoch, dass Du Dich in einer Endlosschleife verlaufen hast. ====== Ziel dieser Lektion ====== Nach dieser Lektion solltest Du kopf- und fußgesteuerte Schleifen unterscheiden und anwenden können. Nimm Dir Zeit, kleine Testprogramme zu schreiben und mit den Schleifen zu spielen und Dir auszugeben, was passiert. Du kannst nun den Ablauf einer Schleife in der Form unterbrechen, dass Du den Rest des Schleifenkörpers in diesem Durchlauf ignorierst oder die Schleife vollständig verlässt. Du solltest mit ''continue'' und ''break'' spielen und ausprobieren, bis Du es soweit sicher verstanden hast. Du kennst nun eine weitere "rhetorische" ((Rhetorik ist die Kunst, Sprache geschickt einzusetzen)) Möglichkeit, eine if-Bedingung als Wächter zu formulieren, um für Dein Programm Vorabbedingungen abzuprüfen und die Fortsetzung des fraglichen Programmteils gegebenenfalls aufzuhalten. Dir ist bewusst, dass Endlosschleifen Dein Programm reaktionsunfähig machen können. Programmiere eine Endlosschleife, die einen Text ausgibt und führe es in der Konsole aus. Stoppe das Programm in dem Du gleichzeitig Steuerung (Strg auf Deiner Tastatur oder Ctrl für Control) und C drückst. Anschließend schauen wir uns in der [[c:tutorial:functions|nächsten Lektion Funktionen]] an.