Steuerstrukturen in C++

Die neueste CRAY bewältigt eine Endlosschleife in sechs Sekunden.
– Computer-Folklore

Anweisungen und Anweisungsfolgen

Eine Anweisung ist ein durch Semikolon abgeschlossener Ausdruck. Neben Anweisungen, die tatsächlich Daten verarbeiten, enthält ein Programm Definitionen und Deklarationen (Ankündigungen) für Variablen, Datentypen und Funktionen. Eine Anweisungsfolge wird von geschweiften Klammern als Block umschlossen:

//> cpp_basics/anweisungsblock.cpp
import std;
 
int main()
{                                  
	int a = 2, b = 4;
	std::println("{} {}", a, b);
	{
		int temp = a;
		a = b;
		b = temp;
	}
	std::println("{} {}", a, b);
}

Wiederholungen

Heutige Computer können viele Millionen von Operationen je Sekunde (MOPS-Geschwindigkeit!), sogar Milliarden, ausführen. Dennoch gelingt es, sie auf Jahre mit Arbeit auszulasten. Das Geheimnis liegt darin, nicht jeden Befehl für jede Operation einzeln zu schreiben, sondern den Computer dazu zu bringen, dieselben Operationen mit anderen Werten wiederholt auszuführen. Weil Wiederholungsanweisungen so bedeutsam sind, besitzt C++ dafür mehrere Ausdrucksformen:

Syntax

do Anweisung while (Bedingung);
while (Bedingung) Anweisung
for (Initialisierung; Bedingung; Inkrement) Anweisung
for (Element : Wertefolge) Anweisung
kopfgesteuertfußgesteuertn+1/2-Schleife

Eine Bedingung wird geprüft. Ist sie erfüllt, wird die Anweisung oder Anweisungsfolge ausgeführt und danach wiederum geprüft, ob die Bedingung erfüllt ist. Bei nicht erfüllter Bedingung wird die Schleife abgebrochen. Formulierungen wie „Wiederhole bis …“ werden als gegenteilige Bedingung „Wiederhole, solange nicht …“ formuliert.

Der Rumpf einer while-Anweisung wird nur ausgeführt, nachdem die Bedingung als wahr ausgewertet wurde (kopfgesteuerte Schleife). Der Rumpf einer do ... while (...);-Anweisung wird dagegen mindestens einmal ausgeführt. Die Prüfung, ob wiederholt werden muss, erfolgt nach der ersten Ausführung (fußgesteuerte Schleife).

Die for-Anweisung bündelt die Festlegung von Startwerten, Bedingung und Aktualisierung an ihrem Anfang und ist äquivalent zu einer while-Anweisung. Die Lebensdauer in der Initialisierung definierter Variablen endet beim Verlassen der Schleife:

//> cpp_basics/zaehlschleifen.cpp
import std;
 
int main()
{
	for (int i = 0; i < 10; ++i)
	{
		std::print("{} ", i);
	}
	std::println();
	// äquivalent zu
	{
		int i = 0;
		while (i < 10)
		{
			std::print("{} ", i);
			++i;
		}
	}
	std::println();
}

Solche Wiederholungsanweisungen werden als Zählschleifen bezeichnet. Im Steuerteil der for-Anweisung definierte Variablen existieren nur bis zum Verlassen der Schleife. In einer Zählschleife müssen Bedingung und Inkrement tatsächlich den Schleifenzähler betreffen. Nachlässig kopierte verschachtelte Schleifenköpfe, die Schleifenzähler i und j verwechseln, werden gern übersehen. Compiler übergehen solche Anfängerfehler ebenfalls, denn for- und while-Anweisungen sind nicht auf das Zählen beschränkt.

Bleibt die Bedingung einer for-Anweisung leer, wird diese als true gewertet: for(;;) ... kann als „für immer“ gelesen werden und als while (true) ... formuliert werden. Eine solche „Endlosschleife“ wird durch eine bedingte return-Anweisung, oder break; im Schleifenrumpf verlassen („n+1/2-Schleife“).1

Ansammlungen gegebener Werte verarbeitet man bequem mit einer „range-based for loop“, ohne sich um Schleifenzähler kümmern zu müssen:

//> cpp_basics/for_range.cpp
import std;
 
int main()
{
	for (auto element : { 2, 3, 5, 7 })
	{
		std::println("{}", element);
	}
}

Man kann hier noch einen Startwert für einen Zähler voranstellen:

//> cpp_basics/for_range_counted.cpp
import std;
 
int main()
{
	for (auto n = 1; auto element : { 2, 3, 5, 7 })
	{
		std::println("{}. Primzahl = {}", n++, element);
	}
}

Aufgaben

  1. Schreibe kleine Programme, die unter Nutzung von Schleifen Folgendes ausgeben:
  • 20 mal "I will not waste chalk." oder einen beliebigen anderen Tafelspruch von Bart Simpson.
  • die einstelligen natürlichen Zahlen einschließlich der Null in aufsteigender bzw. absteigender Folge,
  • deren 2-, 3-, bzw. Vielfache,
  • ein Rechteck aus Sternchen "*" mit vorgegebener Breite und Höhe,
  • ein Dreieck bzw. halbes Quadrat links/rechts oberhalb/unterhalb der Diagonale.
  1. Formuliere die Schleifen jeweils als while- und als for-Anweisungen. Vergleiche.
  2. Schreibe ein Programm, welches mmittels zweier verschachtelter Schleifen das kleine Einmaleins ausgibt, tabellarisch formatiert mit "\t".
  3. Finde einen Anwendungsfall für eine n+1/2-Schleife.

Entscheidungen

Eine Entscheidungsanweisung hat diese allgemeine Form:

Syntax

if (Bedingung) dann_Anweisung else sonst_Anweisung
if (Bedingung) dann_Anweisung

Der Sonst-Zweig muss nicht angegeben werden. Beide Zweige als geschweifte Klammerblöcke zu schreiben, erhöht die Lesbarkeit auch dann, wenn nur Einzelanweisungen darin enthalten sind. In begründeten Fällen kann man davon abweichen.

Eine quadratische Gleichung in der Normalform ist nur bei lösbar, wenn deren Diskriminante nicht negativ ist. Diese Bedingung in einer if-Anweisung entscheidet, welcher Zweig von Anweisungen abgearbeitet wird:

//> cpp_basics/pq-formel.cpp
import std;
 
int main()
{
	double p = 2.0, q = -1.0;
	double D = p * p / 4 - q;
	if (D >= 0)
	{
		double x1 = -p/2 - std::sqrt(D);
		double x2 = -p/2 + std::sqrt(D);
		std::println("{} {}", x1, x2);
	}
	else
	{
		std::println("nicht lösbar");
	}
}

Sind mehr als zwei Alternativwege zu berücksichtigen (Mehrfachverzweigung, „if-else-Leiter“), sollte zwischen den Schlüsselwörtern elseund if kein geschweifter Klammerblock eröffnet werden:

	if (D > 0)
	{
		double x1 = -p/2 - std::sqrt(D);
		double x2 = -p/2 + std::sqrt(D);
		std::println("{} {}", x1, x2);
	}
	else if (D == 0)
	{
		std::println("{}", -p/2);
	}
	else // D < 0
	{
		std::println("nicht lösbar");
	}

Das erspart eine weitere Einrückung und lässt alle drei Fälle gleichberechtigt erscheinen.

Aufgaben

  1. Entwirf ein Programm, welches quadratische Gleichungen der Form nach Eingabe beliebiger reellwertiger Koeffizienten a, b, c löst.
  2. Setze den Entwurf um und teste dein Programm.
  3. Wie reagiert dein Programm auf Spezialfälle (Nullen in a,b oder c)?
  4. Wie viele Entscheidungen sind verschachtelt? Bewerte die Verständlichkeit des Quelltextes.

Anmerkungen

  1. Die Anweisung break; unterbricht nur die innerste von verschachtelten Schleifen. Soll die umgebende Funktion nicht verlassen werden, scheint ein Sprung zu einer Textmarke außerhalb der Schleifen einer der wenigen sinnvollen Anwendungsfälle für goto ...; zu sein.