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.