Schleifen
Java Insel: Schleifen
Auf Schleifen greift man dann zurück, wenn Programmteile wiederholt ausgeführt werden sollen. Der zu wiederholende Programmteil wird dazu in einen Anweisungsblock eingeschlossen. Dieser Anweisungsblock wird auch Schleifenkörper genannt. Durch die Auswertung einer Bedingung wird entschieden, ob der Schleifenkörper ausgeführt wird (Wert der Bedingung ist true) oder nicht ausgeführt wird (Wert der Bedingung ist false).
In den meisten Fällen ist es erwünscht, dass die Schleife wieder verlassen werden kann (die Bedingung nimmt den Wert false an). Daher ist es üblicherweise notwendig, dass im Schleifenkörper mindestens eine der in der Bedingung verwendeten Variablen verändert wird. In manchen Fällen sind Bedingungen so beschaffen, dass sie Daten auswerten, die nicht durch das Programm modifiziert werden, wie z. B. Sensordaten (Position, Umgebungslicht, Systemzeit, usw.). Derartige Bedingungen spielen in dieser Lehrveranstaltung keine Rolle.
In Java gibt es - wie in den meisten Programmiersprachen - drei wesentliche Schleifentypen:
- kopfgesteuerte Schleifen
- fußgesteuerte Schleifen
- Zählschleifen
while-Schleife
while-Schleifen sind kopfgesteuerte Schleifen, weil die Bedingung, die über den Eintritt in den Schleifenkörper entscheidet, am Anfang der Schleife, am Kopf, notiert wird. Der Schleifenkörper wird ausgeführt so lange die Bedingung den Wert true hat.
- Allgemeine Form
while (Bedingung) { Anweisungsblock // Variable aus der Bedingung sollte verändert werden }
- Beispiele
Berechnung von 2n
// Exponent int n = scan.nextInt(); // Schleifenzähler int i = 1; // (Zwischen-)speicher für das Ergebnis int y = 1; // Falls eine Zahl < 1 für n eingegeben wird, wird die Schleife nie betreten. // Das kann durchaus erwünschtes Verhalten des Programms sein. while (i <= n) { y = y * 2; i++; }
Aufsummieren von Zahlen, die über die Tastatur eingegeben werden.
// Benutzereingabe int zahl; // Zwischenergebnis int summe = 0; System.out.println("Zahl eingeben: "); zahl = scan.nextInt(); while (zahl != 0) { summe = summe + zahl; System.out.println("Nächste Zahl eingeben: "); zahl = scan.nextInt(); } System.out.println("Summe: " + summe);
Im obigen Beispiel finden sich zwei Muster:
- die Zeile int summe = 0; initialisiert das Ergebnis der Berechnung, das während der folgenden Schleifendurchläufe immer aktualisiert wird und nach Verlassen der Schleife das Endergebnis enthält.
- Die Zeile zahl = scan.nextInt(); ist zweimal notiert. Die Struktur der Schleife erfordert diese Konstruktion. Beim ersten Mal wird die Zahl einmalig eingelesen, damit die vor dem Schleifenkörper stehende Bedingung geprüft werden kann. Beim zweiten Mal, und das ist das eigentliche Muster, wird die Zahl eingelesen, um den nächsten Schleifendurchlauf vorzubereiten.
do-while-Schleife
do-while-Schleifen sind fußgesteuerte Schleifen, da die Bedingung, die über den (erneuten) Eintritt in den Schleifenkörper am Ende der Schleife, am Fuß, notiert wird. Der Schleifenkörper wird ausgeführt so lange die Bedingung den Wert true hat.
Der Schleifenkörper einer do-while-Schleife wird mindestens einmal ausgeführt, da die Bedingung erst am Ende erreicht und geprüft wird.
- Allgemeine Form
do { Anweisungsblock // Variable aus der Bedingung sollte verändert werden } while (Bedingung);
- Beispiel
Es sollen solange Benutzereingaben erfolgen, bis eine gültige Monatsnummer (1..12) eingegeben wurde.
int monat; do { System.out.print("Monatsnummer: "); monat = scan.nextInt(); } while (monat < 1 || monat > 12);
Bei diesem Beispiel ist zu beachten, dass die Bedingung in "umgekehrter" Logik formuliert werden muss: Die Eingabe soll wiederholt werden, so lange die Monatszahl nicht gültig ist. Es muss also eine Bedingung konstruiert werden, die bei fachlich falscher Eingabe den Wert true annimmt.
for-Schleife
for-Schleifen sind Zählschleifen: Es ist von Anfang an bekannt, wie oft die Schleife durchlaufen werden soll. Die Anzahl der Schleifendurchläufe muss dabei keine Konstante sein, sondern kann - und wird es in den meisten Fällen auch - ein berechneter Wert sein.
Im Kopf der for-Schleife werden die folgenden drei Parameter notiert:
- Initialisierung
- Bedingung / Zielwert
- Inkrement
- Allgemeine Form
for (Initialisierung; Bedingung / Zielwert; Inkrement) { Anweisungsblock }
- Beispiele
Ausgabe der Quadratzahlen \( f(x) = x^2 | x \in [1, 100] \). Die Ausgabe soll formatiert so erfolgen, dass x und f(x) rechtsbündig in zwei Spalten nebeneinander stehen.
for (int i = 1; i <= 100; i++) { // formatierte Ausgabe, das Ergebnis wird direkt in der Ausgabe berechnet System.out.printf("%3d%7d", i, i * i); // Zeilenumbruch, könnte noch gespart werden, indem der Formatstring von printf um "\n" erweitert würde. System.out.println(); }
Weitere Beispiele
Grundsätzlich lassen sich die drei Schleifentypen bei identischer Funktionalität in alle anderen Schleifentypen überführen. Betrachten Sie die Berechnung der Fakultät:
Berechnung der Fakultät
Berechnung der Fakultät \( f(n) = n! = \prod_{i=1}^{n} i \) mit den drei Schleifentypen:
int n = scan.nextInt(); // Zwischenspeicher für das Ergebnis; Produkte werden mit 1 initialisiert. int f = 1; for (int i = 1; i <= n; i++) { f = f * i; } System.out.println(n + "! = " + f);
int n = scan.nextInt(); // Zwischenspeicher für das Ergebnis; Produkte werden mit 1 initialisiert. int f = 1; // Schleifenzähler int i = 1; while (i <= n) { f = f * i; i++; } System.out.println(n + "! = " + f);
int n = scan.nextInt(); // Zwischenspeicher für das Ergebnis; Produkte werden mit 1 initialisiert. int f = 1; // Schleifenzähler int i = 1; do { f = f * i; i++; } while (i <= n); System.out.println(n + "! = " + f);
Größter gemeinsamer Teiler
Bestimmen des ggT zweier natürlicher Zahlen x und y mit drei verschiedenen Verfahren.
Beispiel 1: Alle Teiler beginnend beim größtmöglichen Teiler - der kleineren der beiden Zahlen - durchprobieren
// Zahl 1 int x = scan.nextInt(); // Zahl 2 int y = scan.nextInt(); // Teiler initialisieren int teiler = x; // kleinere Zahl bestimmen if (y < x) { teiler = y; } // ggT ermitteln while (x % teiler != 0 || y % teiler != 0) { teiler--; } System.out.println("ggT(" + x + ", " + y + ") = " + teiler);
Dieser Algorithmus ist extrem langsam, was sich insbesondere bei sehr kleinem ggT bemerkbar macht, z. B. x = 1000 und y = 1002.
Beispiel 2: Algorithmus von Euklid
// Zahl 1 int x = scan.nextInt(); // Zahl 2 int y = scan.nextInt(); // so lange die kleinere von der größeren Zahl abziehen, bis die beiden Zahlen gleich sind while (x != y) { if (x > y) { x = x - y; } else { y = y - x; } } // sowohl x als auch y enthalten jetzt den ggT System.out.println("ggT(" + x + ", " + y + ") = " + x);
Beispiel 3: Beschleunigter Algorithmus von Euklid (Turbo-Euklid)
// Zahl 1 int x = scan.nextInt(); // Zahl 2 int y = scan.nextInt(); while (y != 0) { int temp = x % y; x = y; y = temp; } // x enthält jetzt den ggT System.out.println("ggT(" + x + ", " + y + ") = " + x);
Vermeiden von break und continue
Mittels break lässt sich eine Schleife beenden, ohne dass die Bedingung erneut geprüft wird. Dies wird auch als Schleifenausbruch bezeichnet:
// Summe int s = 0; for (int i = 1; i <= 10; i++) { summe = summe + i; if (summe >= 25) { // beendet die Schleife sofort break; } } // summe hat den Wert 28 // break wurde nach sieben Schleifendurchläufen aufgerufen System.out.println(summe);
Derartige Konstruktionen sind zu vermeiden, da sie die Lesbarkeit, Verständlichkeit und Wartbarkeit des Codes massiv verschlechtern. Der Befehl break kann großen Einfluss auf den Programmablauf nehmen, lässt sich jedoch sehr leicht übersehen. Falls ein vorzeitiger Schleifenabbruch erfolgen soll, sollte die Bedingung entsprechend angepasst werden:
// Summe int s = 0; int i = 1; boolean exit = false; while(i <= 10 && !exit) { summe = summe + i; i++; if (summe >= 25) { // erzwingt das Schleifenende bei der nächsten Prüfung der Bedingung exit = true; } } // summe hat den Wert 28 System.out.println(summe);
Eine ähnliche Bedeutung hat continue, das jedoch nicht den Schleifenaustritt, sondern den Abbruch des aktuellen Durchlaufs und die erneute Prüfung der Bedingung veranlasst. Die Verwendung von continue ist aus den oben genannten Gründen ebenfalls zu vermeiden.