-std=c99
, also gcc -std=c99 for.c
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.
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 <stdio.h> 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.
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 <stdio.h> 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-Compiler1) 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:
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.
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 <stdio.h> 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.
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 <stdio.h> 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; }
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 „Wächter“ oder - damit man ein schöneres Buzzword hat - auch „Precondition“.
Schauen wir uns das zunächst einfach im Code an:
#include <stdio.h> 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 2).
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.
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 abgebrochen3) 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 <stdio.h> 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.
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 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“ 4). 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.
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“ 5) 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 nächsten Lektion Funktionen an.
-std=c99
, also gcc -std=c99 for.c