C++ Verarbeitung
Willemers Informatik-Ecke
In diesem Abschnitt wird beschrieben, wie ein Programm Daten in Variablen schreibt, sie wieder herausliest, mit anderen Variablen und Konstanten verrechnet und wieder in Variablen ablegt.

Zuweisung

Gleichheitszeichen

Bei der Initialisierung wurde bereits das Gleichheitszeichen verwendet, um eine Variable mit einem Wert vorzubelegen. Das Gleichheitszeichen wird auch ansonsten verwendet, wenn eine Variable einen neuen Wert bekommen soll. Dabei gibt es einen feinen Unterschied zwischen der Vorbelegung oder Initalisierung, die beim Anlegen der Variablen erfolgt, und der Zuweisung, wie die Belegung mit Werten genannt wird, wenn die Variable bereits existiert.

Links vom Gleichheitszeichen steht immer das Ziel der Zuweisung. Im Allgemeinen ist das eine Variable. Auf der rechten Seite des Gleichheitszeichens steht die Datenquelle. Das kann eine andere Variable, ein Zahlenwert oder eine Berechnung sein. Man bezeichnet die Datenquelle auf der rechten Seite einer Zuweisung allgemein als Ausdruck. Die englische Bezeichnung dafür ist »expression«. Ein Ausdruck ist Konstrukt, das einen Wert liefert und so als Datenquelle dienen kann. Das folgende Beispiel zeigt mehrere Zuweisungen. Dabei wird auch schon den Rechenoperationen ein wenig vorgegriffen.

[Zuweisungen]

MWStSatz = 16;
Netto = 200;
MWStBetrag = Netto * MWStSatz / 100;
Brutto = Netto + MWStBetrag;

L-Value

Auf der linken Seite des Gleichheitszeichens steht immer das Ziel der Zuweisung. Typischerweise ist das eine Variable. Steht dort etwas anderes, werden die meisten Compiler eine Fehlermeldung bringen, die »L-Value expected« oder ähnlich lautet. Direkt übersetzt heißt die Meldung, dass ein »Linkswert« erwartet wird, also etwas, das auf der linken Seite einer Zuweisung stehen kann. Der L-Value muss etwas sein, dem etwas anderes zugewiesen werden kann. Wie Sie später noch sehen werden, werden auch nicht alle Variablenkonstrukte als L-Value akzeptiert.

Kaskadierende Zuweisung

C++ hat die Besonderheit, dass Sie in einer Anweisung mehreren Variablen den gleichen Wert zuweisen können. Sie müssen sich das so vorstellen, dass eine Zuweisung ihren Wert nach links durchreicht. Diese Fähigkeit ermöglicht eine Zeile wie die folgende:

[Kaskadierende Zuweisung]

a = b = c = d = 5 + 2;

Die Anweisung wird von der Datenquelle her abgearbeitet. 5+2 ergibt 7. Diese 7 wird der Variablen d zugewiesen. Das Ergegnis der Zuweisung ist eben dieser Wert 7, der dann c zugewiesen wird. Von dort geht es weiter zur Variablen b und schließlich zur Variablen a. Im Ergebnis enthalten alle aufgeführten Variaben den Wert 7.

Rechenkünstler

Grundrechenarten

In vorigen Listing haben Sie bereits gesehen, wie Sie in einem Programm rechnen können. Letztlich sieht es nicht sehr viel anders aus, als wenn Sie sich eine Rechenaufgabe auf einen Zettel schreiben. Etwas ungewohnt ist lediglich, dass auf der linken Seite das Zuweisungsziel und ein Gleichheitszeichen steht. Das Multiplikationszeichen ist der Stern, und das Divisionszeichen der Schrägstrich. Plus- und Minuszeichen sehen so aus, wie man es erwartet. Sie können sogar das Minuszeichen wie gewohnt als Vorzeichen verwenden.

Rest

Eine besondere Rechenart ist die Modulo-Rechnung. Sie liefert den Rest einer ganzzahligen Division. Wenn Sie sich daran erinnern, wie Sie in den ersten Schulklassen dividiert haben, dann fallen Ihnen vielleicht noch Sätze ein wie: »25 geteilt durch 7 sind 3, Rest 4«. Diese Restberechnung gibt es auch unter C++. Man bezeichnet sie als die Modulo-Rechnung. Als Operatorzeichen wird das Prozentzeichen verwendet.

Rest = 25 % 7; // Rest ist also 4

Punkt vor Strich

Auch in C++ werden die Gesetze der Mathematik geachtet. So wird die alte Regel »Punktrechnung geht vor Strichrechnung« auch hier eingehalten. Diese Regel sagt aus, dass die Multiplikation und die Division vor einer Addition oder Subtraktion ausgeführt werden, wenn beide gleichwertig nebeneinander stehen.

Links nach rechts

Binäre Operatoren, also Rechensymbole, die zwei Ausdrücke miteinander verknüpfen, sind im Allgemeinen linksbindend. Das bedeutet, dass sie von links nach rechts ausgeführt werden, wenn die Priorität gleich ist. Das heißt, dass a*b/c als (a*b)/c ausgewertet wird. Eine Ausnahme ist die Zuweisung. Hier wird a=b=c als a=(b=c) ausgewertet. Der Zuweisungsoperator ist also rechtsbindend. Auch einstellige Operatoren sind rechtsbindend. Dazu gehört auch der Operator ++, den Sie im Laufe des Abschnitts noch kennen lernen werden.

Unklare Reihenfolge

In ganz besonderen Spezialsituationen ist nicht eindeutig zu klären, in welcher Reihenfolge die einzelnen Operatoren ausgeführt werden. Das folgende Beispiel kann auf verschiedene Weisen interpretiert werden.

a = 1
b = (a*2) + (a=2);

Die Zuweisung a=2 in der rechten Klammer ergibt als Ergebnis 2, wie oben bei den kaskadierenden Zuweisungen schon erwähnt wurde. Aber ob die Zuweisung vor oder nach der linken Klammer ausgeführt wird, bleibt unklar. Die Variable b könnte nach dieser Zeile also sowohl 4 als auch 6 enthalten.

KISS

Programme entwickeln ohnehin eine erhebliche Komplexität. Darum sollten Sie Mehrdeutigkeiten vermeiden, wo es geht. Wenn Ausdrücke unübersichtlich werden, sollten Sie Klammern benutzen oder die Berechnung sogar in mehrere Zwischenschritte aufteilen. Sie sollten das Ziel verfolgen, Ihr Programm so einfach und übersichtlich wie möglich zu halten. Im englischen Sprachraum gibt es dafür die KISS-Regel: Keep It Small and Simple (Halte es klein und einfach). Ein Programm, das aus einer Sammlung der raffiniertesten Kniffe besteht, wird unlesbar und ist damit unwartbar und darum unprofessionell.

Abkürzungen

Der Mensch neigt zur Bequemlichkeit. Nicht anders geht es Programmierern. Sie handeln nach dem Grundgedanken, niemals etwas zu tun, was ein Computer für sie tun kann, und so ist es naheliegend, dass es Wege gibt, wiederkehrende Aufgaben möglichst kurz zu formulieren.

Inkrementieren

Um den Wert einer Variablen um 1 zu erhöhen, können Sie die folgende Zeile schreiben:

[Der Inhalt der Variablen Zaehler erhöht sich um 1]

Zaehler = Zaehler + 1;

Rechte Seite zuerst auswerten

Das bedeutet, dass sich der neue Wert der Variablen Zaehler aus dem alten Wert der Variablen Zaehler plus 1 bildet. Es wird zuerst die rechte Seite des Gleichheitszeichens ausgewertet, bevor sie der Variablen auf der linken Seite zugewiesen wird. Insgesamt bewirkt die Zeile, dass sich der Inhalt der Variablen Zaehler um 1 erhöht.

Es kommt häufiger vor, dass sich der neue Wert einer Variablen aus ihrem bisherigen Wert ergibt, der mit einem anderen Wert verrechnet wird. Immer wenn ein Wert erhöht, vermindert, verdoppelt oder halbiert wird, kann eine kürzere Variante verwendet werden. In der folgenden Zeile wird wiederum der Wert der Variablen Zaehler um 1 erhöht:

[Schreibfaules Inkrementieren]

Zaehler += 1;

Hier wird das Pluszeichen mit dem Gleichheitszeichen kombiniert. Damit das Plus nicht fälschlicherweise als Vorzeichen der 1 interpretiert wird, muss es vor dem Gleichheitszeichen stehen. Zwischen Plus- und Gleichheitszeichen darf kein Leerzeichen stehen. Die Bedeutung der Zeile ist also: »Addiere der Variablen Zaehler den Wert 1 hinzu.«

Es ist ein nahe liegender Gedanke, dass dies nicht nur für das Addieren funktioniert. Sie können es beim Subtrahieren, beim Multiplizieren, bei der Division und der Modulo-Rechnung verwenden.

kurze Schreibweise lange Schreibweise
a += b a = a + b
a -= b a = a - b
a *= b a = a * b
a /= b a = a / b
a %= b a = a % b

Doppel-Plus

In dem besonderen Fall, dass der Wert um 1 erhöht wird, kann der Ausdruck noch weiter verkürzt werden. Dazu werden der Variablen einfach zwei Pluszeichen angehängt.

[Sehr schreibfaules Inkrementieren]

Zaehler++;

Sie werden schon ahnen, dass dieses Doppelplus der Sprache C++ den Namen gegeben hat. C++ entspricht der Sprache C, die um eins erhöht wurde.

Dekrementieren

Wie fast nicht anders zu erwarten, gibt es auch ein Doppelminus. Es tut genau das, was Sie schon vermuten: Es zieht von der Variablen den Wert 1 ab. Warum es weder einen Doppelstern noch einen Doppelschrägstrich gibt, werde ich Ihnen nicht verraten. Betrachten Sie es als eines der letzten Geheimnisse unserer Erde.

Sie können das doppelte Plus oder Minus auch auf der rechten Seite einer Zuweisung verwenden. Dann wird nach der Variablenauswertung ihr Wert erhöht respektive herabgesetzt. Betrachten Sie das folgende Beispiel:

[Inkrementieren auf der rechten Seite]

Zaehler = 5;
Summe = 2 + Zaehler++;

Reihenfolge

Klar ist, dass die Variable Zaehler nach diesen Befehlen den Wert 6 enthält. Etwas unklarer ist dagegen, welchen Wert die Variable Summe hat. Wenn Sie auf 7 tippen, liegen Sie richtig. Wie oben gesagt, wird das Inkrementieren der Variablen Zaehler erst nach der Auswertung durchgeführt. Sie können allerdings auch dass Doppelplus vor die Variable stellen. Dann wird die Variable erst ausgewertet, nachdem sie inkrementiert worden ist.

[Das Gleiche, leichter lesbar]

Zaehler = 5;
Summe = 2 + ++Zaehler;

In diesem Fall wird die Variable Summe den Wert 8 haben. Falls Sie das Ganze etwas unübersichtlich finden, spricht das für Ihren Geschmack. Ich würde eine solche Konstruktion im Programm vermeiden. Schreiben Sie lieber ein paar Zeichen mehr. Dann wird ein Kollege später schneller verstehen, was Sie geschrieben haben. Die folgenden Zeilen bewirken das Gleiche und sind viel einfacher zu lesen:

[Ein anderes Ergebnis]

Zaehler = 5;
++Zaehler;
Summe = 2 + Zaehler;

Das Voranstellen des Inkrementoperators nennt man Präfix. Es bewirkt, dass die Variable zuerst inkrementiert und dann ausgewertet wird. Das Nachstellen des Operators heißt Postfix. Die Variable wird zuerst ausgewertet und dann erst inkrementiert. Übrigens ist die Präfixvariante ein klein wenig effizienter zu implementieren.

In der folgenden Tabelle finden Sie eine Übersicht über die mathematischen Operatoren in aufsteigender Priorität.

Operator Bedeutung Beispiel
+ Addition a = 11 + 5; (16)
- Subtraktion a = 11 - 5; (6)
* Multiplikation a = 11 * 5; (55)
/ Division a = 11 / 5; (2)
% Modulo a = 11 % 5 (1)
++ Inkrementieren ++a; oder a++;
-- Dekrementieren --a; oder a--;

Zufallsfunktionen

Es ist vielleicht ungewöhnlich, als erstes Beispiel für mathematische Funktionen ausgerechnet auf die Zufallszahlen zu stoßen. Schließlich gibt es ja so viele seriöse mathematische Funktionen, die C++ zur Verfügung stellt. Keine Sorge: Auch die seriösen Funktionen werden noch zur Sprache kommen. Für einige Beispielprogramme werden sich Zufallszahlen als nützlich erweisen. Auch in der Praxis leisten sie gute Dienste. Sie können damit Daten erzeugen, um Programmteile zu testen.

Purer Zufall

Eine vom Computer erzeugte Zufallszahl ist nicht wirklich zufällig, sondern wird durch eine Funktion generiert. Gute Zufallszahlen haben zwei Eigenschaften: Sie sind schwer vorhersehbar und gleichmäßig verteilt. Immerhin werden sie nicht nur zum Würfeln oder für Kartenspiele gebraucht, sondern auch für Simulationen, die millionenmal durchlaufen werden.

Startwert

Damit Versuchsreihen wiederholbar sind, gibt es eine Startfunktion. Sie erhält einen Startwert als Parameter. Wenn der gleiche Startwert verwendet wird, wird anschließend immer die gleiche Folge von Zufallszahlen generiert.

srand()

Mit dem Aufruf der Funktion srand() wird der Zufallszahlengenerator initialisiert. Die Funktion hat als Parameter eine ganze Zahl, die als Startwert dient.

Zufallswert

Nachdem mit der Funktion srand() der Zufallszahlengenerator einmal initialisiert wurde, kann beliebig oft durch den Aufruf von rand() ein quasi-zufälliger Rückgabewert vom Typ long abgerufen werden. Bei jedem Neuaufruf liefert die Funktion einen neuen Zufallswert. Beide Funktionen stammen aus der Standardbibliothek stdlib.

Das folgende kleine Programm startet einmal die Zufallszahlen mit dem Wert 9. Dann wird zweimal die Funktion rand() aufgerufen und der Variablen zufall zugewiesen.

[Zufallszahlen]

#include <stdlib.h>

main()
{
    long zufall;
    srand(9);
    zufall = rand();
    zufall = rand();
}

RAND_MAX

Die Konstante RAND_MAX enthält die größtmögliche Zufallszahl. Der Rückgabewert der Funktion rand() liegt also zwischen 0 und diesem Wert. In der Praxis ist RAND_MAX gleich LONG_MAX, also meist etwa 2 Millionen.

Modulo

Damit können die meisten Programme aber wenig anfangen. Typischerweise wollen die Programme einen Würfel, eine Lottozahl oder eine Spielkarte simulieren. Um die großen Zahlen auf die Werte 6, 49 oder 52 herunterzubrechen, gibt es eine einfache Methode: Sie verwenden die Modulo-Rechnung. Wollen Sie einen Würfel simulieren, so berechnen Sie Modulo 6 und erhalten einen Wert zwischen 0 und 5. Nun müssen Sie nur noch eine 1 addieren, und Sie haben die gewohnten Augenzahlen zwischen 1 und 6.

augen = rand() % 6 + 1;

Falls Sie für ein Spiel einen wirklich nicht vorhersehbaren Startwert brauchen, empfehle ich Ihnen die Zeitfunktionen. Die Sekunden seit dem 1.1.1970 sind prima als Startwert geeignet. Und falls Ihnen das noch als zu kalkulierbar erscheint, verwenden Sie doch die Millisekunden modulo 1000, die in dem Zeitraum vergangen sind, die der Benutzer für seine Eingabe benötigte.

Typumwandlung

Um einen Ausdruck eines bestimmten Typs in einen anderen umzuwandeln, gibt es zwei Schreibweisen. In C wurde dem Ausdruck der Zieltyp in Klammern vorangestellt. In C++ wurde die Schreibweise eingeführt, dass auf den Namen des Typs eine Klammer folgt, in der der zu konvertierende Ausdruck steht.

int Wert;
Wert = (int)IrgendWas;
Wert = int(IrgendWas);

Automatisch

Einige Umwandlungen führt C++ direkt durch, ohne darüber zu reden. So wird eine short-Variable oder -Konstante direkt einer long-Variablen zugewiesen. Hier gibt es keine Interpretationsprobleme, und es kann jeder beliebige short-Wert in einer long-Variablen abgelegt werden. Der umgekehrte Weg ist schwieriger. Die Zahl 200.000 passt nicht in eine short-Variable, wenn diese nur aus zwei Bytes besteht. Hier wird der Compiler im Allgemeinen eine Warnung absetzen, dass relevante Informationen verloren gehen könnten.

Berechnungen

Besonders tückisch kann es sein, wenn der Compiler statt Fließkommazahlen ganzzahlige Werte verwendet. Als Beispiel soll ein klassischer Dreisatz verwendet werden. Drei Tomaten kosten 4 Euro. Wie viel kosten fünf Tomaten? Im Programm würde das so umgesetzt:

float SollPreis = (4/3)*5;

Der Inhalt der Variablen SollPreis dürfte überraschen: Es ist 5. Der Grund ist, dass der Compiler den Ausdruck 4/3 als Integer-Berechnung ausführt und die Nachkommastellen abschneidet. Also ist das Ergebnis der Division 1. Multipliziert mit 5 ergibt sich das oben genannte Ergebnis. Dennoch würden Sie eher erwarten, dass Sie an der Kasse 6,67 Euro zahlen müssen, und der Kaufmann wird sich Ihrer Ansicht gewiss anschließen. In solchen Fällen können Sie mit einer Typumwandlung eingreifen. Es muss mindestens ein Operand der Division zur float-Wert konvertiert werden, um eine float-Berechnung zu erzwingen.

float SollPreis = (float(4)/3)*5;

Nach dieser Anpassung ergibt die Berechnung die erwarteten 6.66667.

Überflüssige Klammern

Am Rande sei erwähnt, dass die Klammern um 4/3 aus Sicht von C++ zwar überflüssig sind, weil der Ausdruck nur Punktrechnung enthält und dann von links nach rechts ausgeführt wird. Dafür haben diese Klammern einen Dokumentationswert. Jemand, der später einen Fehler in dem Programm sucht, sieht sofort, wie der Autor die Prioritäten setzen wollte. Wenn also irgendwo Zweifel über die Prioritäten bei Ausdrücken entstehen können, ist es besser, ein paar Klammern zu viel als zu wenig zu setzen. Der Compiler wird sie sowieso wegoptimieren.