Schleifen

Einführung

Soll eine Block von Anweisungen mehrere Male ausgeführt werden, solange eine bestimmte Bedingung erfüllt ist, dann bedient man sich den sogenannten Schleifen. Ein Beispiel aus der Windowsprogrammierung wäre die sogenannte Nachrichtenschleife. Im Prinzip werden in dem Anweisungsblock solange Nachrichten abgeholt, übersetzt und weitergeleitet bis eine ganz bestimmte Nachricht abgeholt wird. Es existieren mehrere Arten eine Schleife zu programmieren.

Die while-Schleife

Die 'while'-Schleife ähnelt im Prinzip sehr stark der 'if'-Bedingung. Tatsächlich haben beide aber einen wesentlichen Unterschied. Im allgemeinen sieht eine 'while'-Schleife wie folgt aus:

while (<Ausdruck>)
{
	/* --- Anweisungen ---
	...
	*/
}

Der Unterschied ist im wesentlichen folgender: Bei beiden wird zwar der Anweisungsblock nur dann ausgeführt, wenn der Ausdruck positiv ist, nachdem der Anweisungsblock einmal ausgeführt wurde wird erneut überprüft, ob der Ausdruck immer noch positiv ist. Ist dies der Fall, dann wird der Anweisungs-Block erneut ausgeführt usw. Bei der 'if'-Bedingung wird der Anweisungsblock dann ausgeführt, falls der Ausdruck, bei der 'while'-Schleife hingegen solange der Ausdruck positiv ist. Dies verlangt natürlich, dass der Ausdruck irgendwann null wird, da sonst die Schleife unendlich lange ausgeführt wird und das Programm nicht mehr reagiert.

Angenommen es soll ein Programm geschrieben werden, welches die ersten 'n' Zahlen addiert, wobei 'n' vom Benutzer eingegeben wird. Der entsprechende Quellcode könnte dann wie folgt aussehen:

//Variable n
unsigned int n; cin >> n;

//Die Variable mit der errechneten Summe
unsigned int Sum = 0;

//Schleife
unsigned int i = 1;

while (i <= n)
{
	Sum += i;
	i++;
}

//Ausgabe
cout << "Die Summe der ersten " << n << " Zahlen ist: " << Sum << endl;

Die Schleife wird solange durchlaufen bis der Wert der Variablen 'i', welcher innerhalb des Anweisungsblockes inkrementiert wird, größer als der Wert der Variablen 'n' wird. Außerdem wird jeweils der Wert von 'i' zu der Variablen  'Sum' hinzuaddiert.

Die do-while-Schleife

Die allgemeine Darstellung dieser Schleife sieht in etwa so aus:

do
{
	/* --- Anweisungen ---
	...
	*/
}
while (<Ausdruck>);

Der wesentliche Unterschied zu der 'while'-Schleife besteht darin, dass erst nach der Ausführung der einzelnen Anweisungen überprüft wird, ob der Ausdruck positiv wird. Anders gesagt bedeutet dies, dass der Anweisungsblock mindesten ein Mal ausgeführt wird. Das obige Beispiel lässt sich natürlich auch mit der 'do-while'-Schleife durchführen:

//Variable n
unsigned int n; cin >> n;

//Die Variable mit der errechneten Summe
unsigned int Sum = 0;

//Schleife
unsigned int i = 0;

do
{
	Sum += i;
	i++;
}
while (i <= n);

//Ausgabe
cout << "Die Summe der ersten " << n << " Zahlen ist: " << Sum << endl;

Im Prinzip hat sich aber nicht viel verändert. Meist hängt es von der Aufgabenstellung ab, welche Schleife die geeignetere ist. 

Da kurz vor der Überprüfung der Bedingung die Variable 'i' inkrementiert wird, lässt sich die Schleife auch "eleganter" schreiben:

do
{
	Sum += i;
}
while (++i <= n);

Da dann der Anweisungsblock aus nur noch einer Anweisung besteht, lässt sich die Schleife auch ohne die geschweiften Klammer schreiben:

do Sum += i;
while (++i <= n);

Bei solchen Änderungen sollte natürlich darauf geachtet werden, dass die Leserlichkeit nicht zu kurz kommt.

Die for-Schleife 

Bei den letzten beiden Schleifen wurde jeweils überprüft, ob eine Variable 'i' eine bestimmte Bedingung erfüllt. Diese Variable wurde dann innerhalb des Anweisungsblockes verändert. Ist eine Veränderung einer solchen Variablen immer gleich und hängt nicht von dem Anweisungsblock aus, dann kann man die 'for'-Schleife verwenden. Ihre allgemeine Deklaration sieht wie folgt aus:

for (<Initialisierungen>; <Ausdruck>; <Anweisungen>)
{
	/* --- Anweisungen ---
	...
	*/
}

In den Initialisierung können die Variablen initialisiert werden, welche in der Schleife benötigt werden. (In dem obigen Beispiel wäre das 'i' und evtl. noch 'Sum').  Anweisungen lassen sich sowohl in dem Block vornehmen als auch im "Kopf" der 'for'-Schleife. Im Falle des letzteren werden mehrere Anweisungen durch Kommata abgetrennt. Normalerweise sollten in dem Kopf aber nur die Anweisungen durchgeführt werden, welche für den Ausdruck von Bedeutung sind. In Bezug auf unser Beispiel könnte 'for'-Schleife so implementiert werden: 

//Variable n
unsigned int n; cin >> n;

//Die Variable mit der errechneten Summe
unsigned int Sum = 0;

//Schleife (Bsp 1)
for (unsigned int i = 1; i <= n; i++)
{
	Sum += i;
}
//Ausgabe
cout << "Die Summe der ersten " << n << " Zahlen ist: " << Sum << endl;

Für die Schleife wären allerdings auch folgende Varianten denkbar:

//Schleife (Bsp 2)
for (unsigned int i = 1, Sum = 0 ; i <= n; Sum += i, i++);

oder

//Schleife (Bsp 3)
unsigned int i = 1;
for (; i <= n;)
{
	Sum += i;
	i++;
}

oder

//Schleife (Bsp 4)
unsigned int i = 0;
for (; ++i <= n;)
{
	uiSum += i;
}

Die obere (Bsp 1) erscheint aber in jedem Fall als am lesbarsten. (Im Prinzip "für 'i' initialisiert mit 1 soll, solange 'i <= n',  'i' inkrementiert werden"). Wie im vorletzten Fall zu sehen, lässt sich die 'for'-Schleife auch genauso wie eine 'while'-Schleife verwenden.

Das Schlüsselwort break

Mit dem Schlüsselwort 'break' kann eine Schleife vorzeitig beendet werden. Dazu folgendes Beispiel: Angenommen man Schreibt ein Programm, welches überprüft ob eine vom Benutzer eingegebene Zahl 'n' eine Primzahl ist. Der entsprechende Quellcode könnte dann so aussehen:

//Variable n
unsigned int n; cin >> n;

//Variable die angibt ob n eine Primzahl ist oder nicht
bool bPrime = true;

//Schleife
for (unsigned int i = 2; i < n; i++)
{
	if (n % i == 0)		//Division ergibt kein Rest -> n ist durch i teilbar
	{
		bPrime = false;
		break;
	}
}

//Sonderfall n = 1
if (n == 1) bPrime = false;
//Ausgabe
if (bPrime) cout << n << " ist eine Primzahl" << endl;
else cout << n << " ist keine Primzahl" << endl;

Da nur überprüft werden soll, ob 'n' eine Primzahl ist oder nicht (und nicht wie viele Teiler 'n' z.B. besitzt), braucht nachdem feststeht, dass 'n' durch irgend eine Zahl teilbar ist nicht mehr festgestellt werden, ob 'n' noch durch eine andere Zahl teilbar ist. Ist 'n' durch irgendeine Zahl zwischen '2' und 'n-1' teilbar, dann kann 'n' definitionsgemäß keine Primzahl mehr sein.

Nur zur Optimierung: Es reicht vollkommen die Schleife nur von '2' bis zur Wurzel aus 'n' zu durchlaufen, da, wenn es eine Zahl 'm' gibt, die größer als die Wurzel aus 'n' ist durch die 'n' teilbar ist, dann muss der Quotient aus 'n' und 'm' eine Zahl sein, welche kleiner als die Wurzel aus 'n' ist. Durch diese Zahl ist 'n' dann wohl auch teilbar und deswegen reicht es die Schleife von '2' bis zur Wurzel aus 'n' durchlaufen zu lassen. Um die Wurzel nicht berechnen zu müssen sollte man dann überprüfen ob das Quadrat der jeweiligen Zahl 'i' kleiner als 'n' ist, denn wenn 'i' kleiner ist als die Wurzel aus 'n', dann ist 'i*i' kleiner als 'n'. Lange Rede kurzer Sinn; die Schleife ließe sich wie folgt optimieren:

//Schleife
for (unsigned int i = 2; i*i <= n; i++)
{
	if (n % i == 0)		//Division ergibt kein Rest -> n ist durch i teilbar
	{
		bPrime = false;
		break;
	}
}

Probieren Sie doch mal beide Varianten an einer möglichst großen Primzahl aus (z.B. '232792561'). Sie werden den Unterschied merken. Bei mir hat es bei der optimierten Version sechs, bei der nicht optimierten 24287 Millisekunden gedauert.

Übrigens es muss 'i*i <= n' sein, damit Zahlen dessen Wurzel eine Primzahl ist (z:B. 4) auch berücksichtigt werden.

Das Schlüsselwort continue

Verwendet man das Schlüsselwort 'continue' innerhalb eines Anweisungs-Blocks, dann werden alle weiteren Anweisungen übersprungen und die Schleife wird noch einmal von Anfang an durchlaufen. Im Falle der 'for'-Schleife werden dann allerdings noch die Anweisungen ausgeführt, welche im "Kopf" stehen. Beispielsweise ließe sich die obige Schleife auch so schreiben:

//Schleife
for (unsigned int i = 2; i*i <= n; i++)
{
	if (n % i != 0) continue;

	bPrime = false;
	break;
}

Für die nächsten Lektionen

In den nächsten Lektionen werden Schleifen immer mal wieder auftauchen. Ihre Funktionalität und ihre Anwendung sollte also in etwa bekannt sein   .

Zurück Nach oben Weiter