Alternativen

(mit einem Exkurs zur Logik)

Lass uns wandern

Wer sagt, dass man beim Programmierenlernen im stickigen Zimmer vor der Rechenkiste sitzen muss? Du wirst erstaunt sein, wie viel man draußen im Grünen über Informatik lernen kann. Und genau deswegen begeben wir uns jetzt auf einen kleinen Wanderausflug. Ja, richtig gehört. Jetzt gleich. Worauf wartest du? Los geht es!
Du gehst durch eine idyllische Berglandschaft spazieren, hörst die Vögel zwitschern und erfreust dich an den endlosen grünen Weiden. Doch plötzlich kommst du an einer Weggabelung an. Ein Schild weist dich darauf hin: Links geht es einen gemächlichen Hügel entlang, doch rechts erwarten dich steile Bergklippen. Und nein, du hast nicht deine besten Wanderschuhe dabei. Schade! Wenn du die doch nur hättest, wärst du sicher nach rechts gegangen. Bleibt wohl nur der Weg nach links.
Gratulation, du hast gerade eines der wichtigsten Programmierkonstrukte überhaupt kennengelernt!
Hä?
Berechtigte Frage. Okay, zurück ins stickige Zimmer.
Genauso wie du beim Wanderausflug nämlich entscheiden musstest, ob du aufgrund gewisser Bedingungen entweder da lang oder in die andere Richtung gehst, wird auch beim Programmieren häufig – was sagen wir da, immer! - mit solchen Weggabelungen gearbeitet. Oder in der Sprache der Informatiker: Verzweigungen. Wenn ich meine Wanderschuhe dabei habe, gehe ich nach rechts. Genau nach dem Muster sind auch Verzweigungen in C++ aufgebaut:

    if (Bedingung) {
         Anweisungen;
    }


Verzweigungen werden also mit dem Schlüsselwort „if“ eingeleitet, dem englischen Begriff zum deutschen „wenn“. Als Programmierer musst du auf alles gefasst sein. Stelle dir ein Programm zur Berechnung des Flächeninhalts eines Kreises vor:

   float radius;
    float pi=3.14159;
    cin >> radius;
    float flaeche=pi*radius*radius;
    cout << Der Flächeninhalt beträgt: " << flaeche;


Dieses Programm fragt den Anwender zunächst, einen Radius einzugeben. Nun kann ein Scherzkeks auf die Idee kommen, eine negative Zahl einzugeben und schon haben wir den Salat! Die Lösung: Wir fügen eine Verzweigung ein. Wenn der Radius größer als 0 ist, berechne den Flächeninhalt. Als Programm:

   
float radius;
    float pi=3.14159;
    cin >> radius;

    if (radius > 0) {
         float flaeche=pi*radius*radius;
         cout << Der Flächeninhalt beträgt: " << flaeche;
    };


Gibt nun jemand eine negative Zahl ein, wird die Berechnung gar nicht erst ausgeführt. Die nach dem Schlüsselwort „if“ stehende Bedingung wird nämlich geprüft. Wird als Wahrheitswert „true“ geliefert, kann die Abarbeitung des if-Blocks beginnen.

Eng verbunden mit der if-Anweisung ist der else-Block. Der Blick ins Wörterbuch verrät uns, dass „else“ am besten mit „sonst“ übersetzt werden kann. Die Syntax einer if-else-Anweisung sieht nun wie folgt aus:

    if (Bedingung) {
        Anweisungen;
    }
    else {
        Anweisungen;
    }


Falls also die Bedingung zur Abarbeitung des if-Blocks nicht erfüllt ist, wird in den else-Block gesprungen und eine alternative Anweisung ausgeführt. Als Beispiel nehmen wir wieder unser Flächeninhalts-Programm:

    float radius;
    float pi=3.14159;
    cin >> radius;

    if (radius > 0) {
        float flaeche=pi*radius*radius;
        cout <<  "Der Flächeninhalt beträgt: " << flaeche;
    }
    else {
        cout << „Bitte gib eine positive Zahl ein!“ << endl;
    }


Merke, dass ein else-Block nie alleine stehen kann!

Das lassen wir erst einmal sacken und gehen wieder wandern. An der altbekannten Weggabelung angekommen, merken wir jedoch, dass ein lokales Tourismusunternehmen einen dritten Pfad gebaut hat, der direkt zu einem bisher unerschlossenen Badesee führt. Und auch das Schild wurde um einen weiteren Wegweiser ergänzt: Links zum gemächlichen Hügel, rechts der steile Berg, geradeaus der See. Und genau wie man sich beim Wandern meist zwischen mehr als zwei Alternativen entscheiden muss, kann man auch beim Programmieren eine Vielzahl von Wegweisern einbauen:

    if (Bedingung1) {
         Anweisungen;
    }
    else if (Bedingung2) { 
        Anweisungen;
    }
    else if (Bedingung3) {
        Anweisungen;
    }
    else {
        Anweisungen;
    }


Wenn ich meine Wanderschuhe dabei habe, gehe ich nach rechts, sonst, wenn ich meine Badesachen im Gepäck habe, gehe ich nach links, und sonst, wenn ich beides nicht mit habe, gehe ich geradeaus. So kannst du also mehrere if-Anweisungen verstehen. Als angehender Lehrer kannst du mit diesem Wissen auch eine automatische Notenvergabe programmieren:

    int punkte;
    cin >> punkte;

    if (punkte >=18) {
        cout << "Note: 1“;
    }
    else if (punkte >=15) {
        cout << "Note: 2“;
    }
    else if (punkte >=10) {
       cout << "Note: 3“;
   }
   else {
       cout << "Nicht bestanden“;
   }


Als Bedingung kannst du alles Erdenkliche abfragen, am häufigsten wirst du aber Vergleiche abprüfen. Dafür benötigst du die im obigen Programm schon verwendeten Vergleichsoperatoren. Hier findest du eine kleine Übersicht: 

Vergleichsoperation

gleich
ungleich
kleiner
größer
kleiner gleich
größer gleich

Vergleichsoperator

==
!=
<
>
<=
>=

Was ist aber, wenn ich im Winter wandern gehe, erneut vor der Weggabelungen stehe und merke: Auf den Gipfeln stürmt und schneit es! Für den rechten Weg brauche ich ja nicht nur Wanderschuhe, sondern auch eine dickere Winterjacke! Anders gefragt: Was, wenn man kombinierte Bedingungen prüfen möchte? 

Auch dies lässt sich leicht mittels so genannter logischer Operatoren realisieren. Von denen gibt es genau drei: not (nicht), and (und), or (oder), in der C++-Syntax mit den folgenden Symbolen realisiert: !, &&, ||. Wenn du Wanderschuhe UND eine dickere Winterjacke hast, gehe rechts. Wenn du eine dickere Winterjacke hast ODER dir Kälte nichts ausmacht, gehe rechts. Im Sommer: Wenn du NICHT Wanderschuhe hast, gehe links. Für UND gilt dabei: Beide Bedingungen müssen wahr sein, damit der Anweisungsblock ausgeführt wird. Für ODER lässt sich merken: Eine der beiden Bedingungen muss wahr sein (auch beide können wahr sein, dies ist kein ausschließendes ODER), damit in den if-Block gesprungen wird. Warum gerade diese Merkregeln gelten, lässt sich aus der so genannten Aussagenlogik herleiten. In diesem Gebiet der Mathematik betrachtet man Aussagen wie „Ein Schaf ist weiß“ oder „3<2“ und ordnet denen die Werte true (wahr) oder false (falsch) zu. Abstrahieren wir mal und betrachten zwei beliebige Aussagen A und B. Wann gilt nun die Aussage „A and B“ bzw. "A or B"? Dazu schaue man sich die vier möglichen Wahrheitsbelegungen von A und B in dieser Tabelle an: 

Wie du siehst, ist axiomatisch festgelegt, dass „A and B“ nur wahr ist, wenn A wahr ist und B wahr ist. Im Fall von „A or B“ folgt, dass „A or B“ wahr ist, wenn mindestens eine der beiden Aussagen wahr ist. 


Auch verschachtelten Aussagen wie „(A or (B and C))“ können nun mittels Wahrheitstabellen Wahrheitswerte zugeordnet werden. Fürs Erste belassen wir es jedoch bei diesem kleinen Exkurs und fragen uns nun, wie man kombinierte Aussagen konkret in C++ programmieren kann. Wie wär's, wenn wir ein Programm zur Berechnung des Umfangs eines Rechtecks schreiben? 


    float a; 

    float b; 

    float umfang; 

    cin >> a; 

    cin >> b; 


    if ((a >=0) && (b >= 0)) { 

        umfang= 2*(a+b); 

    } 

    else { 

        cout << "ERROR!“; 

    } 


Kenner der Aussagenlogik wissen nun: Nur, wenn a und b größer als 0 sind, wird der Umfang berechnet. Ist auch nur einer der beiden Zahlen negativ, spuckt das Programm eine Fehlermeldung aus. 

Nun soll die ODER-Verknüpfung an einem Beispiel demonstriert werden: 


    int klausurnote1; 

    int klausurnote2;


    if ((klausurnote1<5) || (klausurnote2<5)) { 

        cout << "Zur Prüfung zugelassen“; 

    } 

    else { 

        cout << "Nicht zur Prüfung zugelassen“; 

    } 


Wenn also mindestens eine Klausurnote über 5 liegt, ist man zur Prüfung zugelassen. Hat man beide Klausuren vergeigt, erhält man keine Prüfungszulassung. 


Da siehst du mal, wie viel man auf einem Wanderausflug übers Programmieren lernen kann! 

Schleifen

Im Kreisverkehr der Programmierung

Die Quadratzahlenfolge... wie ging die nochmal? 1, 4, 9 – das klappt. Aber was war nochmal die Quadratzahl von 16? Bevor wir uns den Kopf zerbrechen, können wir doch ein kleines C++-Programm schreiben, das uns die Quadratzahlen ausgibt. Gesagt, getan:


    cout << 1*1;

    cout << 2*2;

    cout << 3*3;

    cout << 4*4;


Okay, stop! Das einzutippen wird uns hier zu mühselig. Die Ausgabe von Quadratzahlen als eine ganze Sequenz, also eine Folge mehrfacher Befehle zu schreiben, ist nicht nur enormer Schreibaufwand (immer das Gleiche zu wiederholen ist teuer und typischerweise auch fehleranfällig), sondern auch eine sehr unelegante Art des Programmierens. Gibt es nicht irgendein Programmierkonstrukt, das uns Abhilfe schaffen kann?


Das gibt’s! Mit so genannten Schleifen lässt sich mit nur ein paar Programmzeilen eine Anweisung wiederholt ausführen lassen. In der Programmiersprache C++ hast du dabei drei Möglichkeiten, eine Schleife zu programmieren. Gehen wir diese mal Schritt für Schritt durch. 

Als erstes schauen wir uns die while-Schleife an. Die Syntax ist leicht und einprägsam:


    while (Bedingung) {
        Anweisungen;

    }


Wer der englischen Sprache mächtig ist, weiß, dass man „while“ am besten mit „solange“ übersetzen kann. Solange also eine bestimmte Bedingung erfüllt ist, werden die Anweisungen immer wieder im while-Block ausgeführt. Immer und immer und immer wieder. Aber größte Vorsicht ist geboten! Irgendwann willst du ja aus der Schleife hinaus. Schließlich möchtest du dir bestimmt nicht unendlich viele Quadratzahlen ausrechnen lassen – und dein Computer, glaub uns, hat auch keine Lust drauf. Wenn man also nicht achtsam ist, kann man sehr schnell eine Endlosschleife programmieren. Deshalb ist es wichtig, dass sich während des Schleifendurchlaufs deine Bedingung (das Zeug in den runden Klammern) irgendwann verändert. Denn dann ist sie ja nicht erfüllt – und die Schleife wird nicht mehr ausgeführt.


Alles so abstrakt. Lass es uns an einem Beispiel demonstrieren. Nehmen wir dazu wieder unsere Quadratzahlen.


    int i=1;

    while (i<=10) {

        cout << i*i << endl;

        i++;
    }


Dieses Programm gibt dir die ersten zehn Quadratzahlen aus. Besonderes Augenmerk solltest du auf die Zeile „i++“ legen. Denn nachdem eine Quadratzahl berechnet und ausgegeben wurde, erhöht sich die anfangs deklarierte Integer-Variable jeweils um 1. Sprich, nach dem zehnten Schleifendurchlauf besitzt i den Wert 11. Die Bedingung sagt jedoch: Nur solange i kleiner gleich 10 ist, wird die Schleife ausgeführt. Da 11<=10 eine falsche Aussage ist, endet das Programm. An dieser Zeile siehst du, wie wichtig es ist, die Variable der Bedingung innerhalb der Schleife zu verändern. Ohne die Zeile „i++“ hätte i stets den Wert 1 gehabt, die Bedingung wäre also bis in alle Ewigkeit erfüllt, das Programm würde in diesem Fall nicht terminieren (also nicht aufhören, die Quadratzahl zur 1 zu berechnen und auszugeben). 

Eng verwandt mit der while-Schleife ist die do-while-Schleife. Auch hier werfen wir erstmal einen Blick auf die Syntax:


    do {
        Anweisungen;

    } while (Bedingung);


Und eigentlich könnten wir mit unseren Ausführungen über die do-while-Schleife schon aufhören. Denn groß unterscheidet sich die do-while-Schleife von der bereits gesehenen while-Schleife nicht. Alles, was wir für die while-Schleife vereinbart haben, ist auch für die do-while-Schleife gültig. Warum gibt’s die dann trotzdem? Worin liegt der entscheidende Unterschied?


Eine while-Schleife muss nicht ausgeführt werden. Betrachte dazu nochmal unser Quadratzahlen-Programm von oben. Hätten wir den Integer i gleich auf 11 gesetzt, wäre das Programm nie in die Schleife gesprungen – die Bedingung ist schließlich nicht erfüllt. Eine do-while-Schleife hingegen lässt sich davon nicht beirren – und wird immer mindestens ein Mal ausgeführt. While-Schleifen sind Pessimisten – die Katzen der Programmierung. Sie beäugen alles erst einmal kritisch. Die Variable „i“ ist nicht kleiner gleich 10? Dann springe ich gar nicht erst an! Do-While-Schleifen hingegen sind Optimisten, die Hunde unter den Programmierkonstrukten. Fröhlich und offen führen sie die Anweisungen aus, stürzen sich ins Getümmel und merken erst dann: Ach so, i ist ja nicht kleiner gleich 10. Was soll's, ich hatte meinen Spaß! 


Von dieser Sache abgesehen, sind while- und do-while-Schleifen jedoch gleich. Die Eigenschaft, dass eine do-while-Schleife mindestens ein Mal durchlaufen wird, kann jedoch manchmal von Vorteil sein. Denn wie gesagt: Eine while-Schleife kann oft gar nicht erst anspringen, eine do-while-Schleife tut es immer. Dies ist zum Beispiel hilfreich, wenn du ein Programm schreibst, dass in einem Text nach einem bestimmten Wort sucht. Hier eignet sich die do-while-Schleife, weil sie garantiert einmal durch den Text laufen wird. Näheres dazu findest du jedoch im Kapitel über Strings.


Kenntnisse über while- und do-while-Schleifen bereichern deine Programme schon enorm. Wir wollen uns noch die so genannte for-Schleife ansehen, die mit Abstand beliebteste und am häufigsten verwendete Schleifenart. Schauen wir uns die Syntax einmal näher an:


    for (Anweisung1; Bedingung; Anweisung2) {

        Anweisungen;

    }


Sieht auf den ersten Blick deutlich komplizierter und abstrakter aus als die vorherigen Schleifenarten. Aber lass dich davon nicht beirren – die for-Schleife ist unglaublich nützlich. Doch klären wir zunächst, was all die Ausdrücke in den Klammern zu bedeuten haben:


-Anweisung1: In dieser deklarierst du eine Zählvariable, einen so genannten Iterator. Dieser steht für die Schleifendurchläufe.

-Bedingung: Hier vereinbarst du, unter welchen Umständen die Schleife durchlaufen wird. Solange diese Bedingung erfüllt ist, wird die Schleife weiter ausgeführt. In gewisser Weise stellst du also ein Abbruchkriterium auf.

-Anweisung2: Mit dieser veränderst du die Zählvariable, erhöhst sie also beispielsweise um 1. Diese Anweisung wird nach jedem Schleifendurchlauf ausgeführt.


Vielleicht mag das für dich immer noch abstrakt anmuten, deshalb folgt sogleich ein kleines Beispiel:


    for (int i=1; i<=10; i++) {

        cout << i*i << endl;

    }


Und schon haben wir ein kleines, effizientes Programm zur Ausgabe der ersten zehn Quadratzahlen. An diesem Beispiel merkt man, wie praktisch for-Schleifen sind: Alles, was man nämlich bei einer while-Schleife beachten musste, wird einfach zwischen die Klammern nach dem Schlüsselwort „for“ gepackt: Eine Zählvariable, eine Bedingung, für die die Schleife ausgeführt wird, und eine Veränderung der Variable aus der Bedingung. Mit diesen drei Ausdrücken ist für alles vorgesorgt, sodass man sich im for-Block selbst nur um die wiederholt auszuführenden Anweisungen bemühen muss.  Das ist auch der Grund dafür, warum diese Schleifenart so häufig verwendet wird. Viele Berechnungen laufen von einem Start- bis zu einem Endewert, bei jedem Berechnungsschritt erfolgt eine inkrementelle Änderung. All ds lässt sich im Kopf einer for-Schleife (innerhalb der runden Klammern) angeben. 


Da wir sowohl mit einer for-, als auch mit einer while-Schleife denselben Umstand programmieren konnten, liegt nahe, dass eine gewisse Analogie zwischen beiden vorherrscht. Und in der Tat lässt sich aus jeder for- eine while-Schleife basteln. Vergleiche dazu einfach das Quadratzahlenprogramm von oben mit unserer for-Schleife. Der Ausdruck1, also die Deklaration der Variablen i, wird an den Anfang gesetzt. Die Bedingung bleibt in den Klammern und die Veränderung der Zählvariablen steht im Schleifenkörper. Vergleiche dazu folgendes Abbildung: