Programme müssen auf irgendeine Weise die verwendeten Daten speichern. Variablen und Konstanten bieten verschiedene Möglichkeiten, diese Daten darzustellen und zu manipulieren.
In C++ dient eine Variable dazu, Informationen zu speichern. Eine Variable ist eine Stelle im Hauptspeicher des Computers, in der man einen Wert ablegen und später wieder abrufen kann.
Man kann sich den Hauptspeicher als eine Reihe von Fächern vorstellen, die in einer langen Reihe angeordnet sind. Jedes Fach - oder Speicherstelle - ist fortlaufend numeriert. Diese Nummern bezeichnet man als Speicheradressen oder einfach als Adressen. Eine Variable reserviert ein oder mehrere Fächer, in denen dann ein Wert abgelegt werden kann.
Der Name Ihrer Variablen (zum Beispiel meineVariable
) ist ein Bezeichner für eines
der Fächer, damit man es leicht finden kann, ohne dessen Speicheradresse zu kennen.
Abbildung 3.1 verdeutlicht dieses Konzept. Wie die Abbildung zeigt, beginnt unsere
Variable meineVariable
an der Speicheradresse 103. Je nach Größe von meineVariable
kann die Variable eine oder mehrere Speicheradressen belegen.
Abbildung 3.1: Schematische Darstellung des Hauptspeichers
RAM steht für Random Access Memory - Speicher mit wahlfreiem Zugriff. Bei Ausführung eines Programms wird dieses von der Datei auf dem Datenträger (zum Beispiel Festplatte, Diskette) in den RAM geladen. Des weiteren werden alle Variablen im RAM angelegt. Spricht ein Programmierer vom Speicher, meint er damit gewöhnlich den RAM.
Wenn man in C++ eine Variable definiert, muß man dem Compiler nicht nur deren Namen, sondern auch den Typ der Variablen mitteilen - ob es sich zum Beispiel um eine Ganzzahl (Integer) oder ein Zeichen (Buchstaben, Ziffern etc.) handelt. Anhand dieser Information weiß der Compiler, um welche Art Variable e sich handelt und wieviel Platz im Speicher für die Aufnahme des Wertes der Variablen zu reservieren ist.
Jedes »Fach« im Speicher ist ein Byte groß. Wenn die erzeugte Variable vier Bytes benötigt,
muß man vier Bytes im Speicher - oder vier Fächer - reservieren. Der Variablentyp
(zum Beispiel int
für Integer) teilt dem Compiler mit, wie viele Speicherplätze
(oder Fächer) für diese Variable benötigt werden.
Da Computer Werte in Bits und Bytes darstellen und Speicher in Bytes gemessen wird, ist es wichtig, daß Sie diese Begriffe verstehen und verinnerlichen.
Jeder Variablentyp belegt im Speicher einen bestimmten Bereich, dessen Größe immer
gleichbleibend ist, auf verschiedenen Computern aber unterschiedlich groß sein
kann. Das heißt, ein Integer-Wert (Datentyp int
)
nimmt auf der einen Maschine zwei
Bytes, auf einer anderen vielleicht vier ein - aber auf ein und demselben Computer ist
dieser Platz immer gleich groß, tagein tagaus.
Eine Variable vom Typ char
(zur Aufnahme von Zeichen) ist gewöhnlich ein Byte lang.
Über die Aussprache von
char
wird seit langem heiß diskutiert. Manche sprechen es aus wie »char« in Charakter, andere wiederum wie »char« in Charme. Auch die Version »care« wurde schon gehört. Selbstverständlich ist »char« wie in Charakter korrekt, denn so klingt es bei mir. Sie können jedoch dazu sagen, wie Ihnen beliebt.
Eine Ganzzahl vom Typ short
belegt auf den meisten Computern zwei Bytes, eine
Ganzzahl vom Typ long
ist normalerweise vier Bytes lang, und eine Ganzzahl (ohne
das Schlüsselwort short
oder long
) kann zwei oder vier Bytes einnehmen. Die Größe
einer Ganzzahl wird vom Computer (16Bit oder 32Bit) oder vom Compiler bestimmt.
Auf einem modernen 32-Bit-PC (Pentium) mit modernem Compiler (zum Beispiel
Visual C++4 oder höher) belegen die Ganzzahlen vier Bytes. Dieses Buch geht davon
aus, daß Ganzzahlen vier Bytes groß sind. Das muß bei Ihnen jedoch nicht so sein. Mit
dem Programm in Listing 3.1 läßt sich die genaue Größe der Typen auf Ihrem Computer
bestimmen.
Listing 3.1: Die Größe der Variablentypen für einen Computer bestimmen
1: #include <iostream.h>
2:
3: int main()
4: {
5: cout << "Groesse eines int:\t\t" << sizeof(int) << " Bytes.\n";
6: cout << "Groesse eines short int:\t" << sizeof(short) << " Bytes.\n";
7: cout << "Groesse eines long int:\t" << sizeof(long) << " Bytes.\n";
8: cout << "Groesse eines char:\t\t" << sizeof(char) << " Bytes.\n";
9: cout << "Groesse eines float:\t\t" << sizeof(float) << " Bytes.\n";
10: cout << "Groesse eines double:\t\t" << sizeof(double) << " Bytes.\n";
11: cout << "Groesse eines bool:\t\t" << sizeof(bool) << " Bytes.\n";
12:
13: return 0;
14: }
Groesse eines int: 4 Bytes.
Groesse eines short int: 2 Bytes.
Groesse eines long int: 4 Bytes.
Groesse eines char: 1 Bytes.
Groesse eines float: 4 Bytes.
Groesse eines double: 8 Bytes.
Groesse eines bool: 1 Bytes.
Die tatsächliche Anzahl der angezeigten Bytes kann auf Ihrem Computer abweichen.
Der größte Teil von Listing 3.1 sollte Ihnen bekannt vorkommen. Das Neue hier ist
die Verwendung der Funktion sizeof
in den Zeilen 5 bis 11. Die Funktion sizeof
gehört
zum Lieferumfang des Compilers und gibt die Größe des als Parameter übergebenen
Objekts an. Beispielsweise wird in Zeile 5 das Schlüsselwort int
an die Funktion
sizeof
übergeben. Mittels sizeof
konnte ich feststellen, ob auf meinem Computer ein
int
gleich einem long int
ist und 4 Byte belegt.
Alle genannten Typen kommen außerdem in zwei Versionen vor: mit Vorzeichen (signed
) und ohne Vorzeichen (unsigned
). Dem liegt der Gedanke zugrunde, daß man
manchmal zwar negative Zahlen benötigt, manchmal aber nicht. Ganze Zahlen (short
und long
) ohne das Wort unsigned
werden als signed
(das heißt: vorzeichenbehaftet)
angenommen. Vorzeichenbehaftete Ganzzahlen sind entweder negativ oder positiv,
während ganze Zahlen ohne Vorzeichen (unsigned int
) immer positiv sind.
Da sowohl für vorzeichenbehaftete als auch vorzeichenlose Ganzzahlen dieselbe Anzahl
von Bytes zur Verfügung steht, ist die größte Zahl, die man in einem unsigned
int
speichern kann, doppelt so groß wie die größte positive Zahl, die man in einem
signed int
unterbringt. Ein unsigned short int
kann Zahlen von 0 bis 65535 speichern.
Bei einem signed short int
ist die Hälfte der Zahlen negativ. Daher kann ein
signed short int
Zahlen im Bereich von -32768 bis 32767 darstellen. Sollte Sie dieses
etwas verwirren, finden Sie in Anhang C eine ausführliche Beschreibung.
In C++ gibt es weitere Variablentypen, die man zweckentsprechend in ganzzahlige Variablen (die bisher behandelten Typen), Fließkommavariablen und Zeichenvariablen einteilt.
Im Englischen verwendet man als Dezimalzeichen den Punkt, im Deutschen das Komma, deshalb auch der Begriff Fließkommazahlen. Leider orientiert sich C++ an der englischen Schreibweise und akzeptiert nur den Punkt als Dezimalzeichen und das Komma als Tausendertrennzeichen.
Die Werte von Fließkommavariablen lassen sich als Bruchzahlen ausdrücken - das heißt, es handelt sich um reelle Zahlen. Zeichenvariablen nehmen ein einzelnes Byte auf und dienen der Speicherung der 256 möglichen Zeichen und Symbole der ASCII- und erweiterten ASCII-Zeichensätze.
Der ASCII-Zeichensatz ist ein Standard, der die im Computer verwendeten Zeichen definiert. ASCII steht als Akronym für American Standard Code for Information Interchange (amerikanischer Standard-Code für den Informationsaustausch). Nahezu jedes Computer-Betriebssystem unterstützt ASCII. Daneben sind meistens weitere internationale Zeichensätze möglich.
Die in C++-Programmen verwendeten Variablentypen sind in Tabelle 3.1 aufgeführt. Diese Tabelle zeigt den Variablentyp, den belegten Platz im Speicher (Grundlage ist der Computer des Autors) und den möglichen Wertebereich, der sich aus der Größe des Variablentyps ergibt. Vergleichen Sie dazu die Ausgabe des Programms aus Listing 3.1.
Die Größen der Variablen können je nach verwendetem Computer und Compiler von denen aus der Tabelle 3.1 abweichen. Wenn die Ausgabe Ihres Computers für das Listing 3.1 mit der im Buch genannten übereinstimmt, sollten die Tabellenwerte auch für Ihren Compiler gelten. Gab es beim Listing 3.1 Unterschiede in der Ausgabe, sollten Sie in dem Compiler-Handbuch nachschlagen, welche Werte Ihre Variablentypen annehmen können.
Eine Variable erzeugt oder definiert man, indem man den Typ, mindestens ein Leerzeichen,
den Variablennamen und ein Semikolon eintippt. Als Variablenname eignet
sich nahezu jede Buchstaben-/Ziffernkombination, die allerdings keine Leerzeichen
enthalten darf. Gültige Variablennamen sind zum Beispiel x
, J23qrsnf
und meinAlter
.
Gute Variablennamen sagen bereits etwas über den Verwendungszweck der Variablen
aus und erleichtern damit das Verständnis für den Programmablauf. Die folgende Anweisung
definiert eine Integer-Variable namens meinAlter
:
int meinAlter;
Wenn Sie eine Variable deklarieren, wird dafür Speicherplatz allokiert (bereitgestellt). Was auch immer zu diesem Zeitpunkt sich in dem Speicherplatz befindet, stellt den Wert dieser Variablen dar. Wie Sie dieser Speicherposition einen neuen Wert zuweisen, werden Sie gleich erfahren.
Für die Programmierpraxis möchte ich Ihnen nahelegen, wenig aussagekräftige Namen
wie J23qrsnf
zu vermeiden und kurze aus einem Buchstaben bestehende Variablennamen
(wie x
oder i
) auf Variablen zu beschränken, die nur kurz, für wenige Zeilen
Code benötigt werden. Verwenden Sie ansonsten lieber Namen wie meinAlter
oder wie viele
. Diese Namen sind leichter zu verstehen, wenn Sie sich drei Wochen
später kopfkratzend nach dem Sinn und Zweck Ihres Codes fragen.
Machen wir einen kleinen Test: Versuchen Sie anhand der ersten Codezeilen zu ergründen, was die folgenden Codefragmente bewirken:
int main()
{
unsigned short x;
unsigned short y;
unsigned short z;
z = x * y;
return 0;
}
int main ()
{
unsigned short Breite;
unsigned short Laenge;
unsigned short Flaeche;
Flaeche = Breite * Laenge;
return 0;
}
Wenn Sie dieses Programm kompilieren, wird Ihr Compiler eine Warnung ausgeben, daß diese Werte nicht initialisiert sind. Ich werde gleich darauf zu sprechen kommen, wie Sie dieses Problem lösen.
Ohne Zweifel ist die Aufgabe des zweiten Programms leichter zu erraten, und die Nachteile der längeren Variablennamen werden durch die leichtere Wartung des Programms mehr als wettgemacht.
C++ beachtet die Groß-/Kleinschreibung und behandelt demnach Großbuchstaben
und Kleinbuchstaben als verschiedene Zeichen. Eine Variable namens alter
unterscheidet
sich von Alter
und diese wiederum von ALTER
.
Bestimmte Compiler gestatten es, die Abhängigkeit von der Groß-/Kleinschreibung zu deaktivieren. Das ist allerdings nicht zu empfehlen, da Ihre Programme dann von anderen Compilern womöglich nicht übersetzt werden können und andere C++-Programmierer mit Ihrem Code nicht klarkommen.
Für die Schreibweise von Variablennamen gibt es mehrere Konventionen. Unabhängig davon, für welche Sie sich entscheiden, ist es ratsam, innerhalb eines Programms bei der einmal gewählten Methode zu bleiben.
Viele Programmierer bevorzugen für Variablennamen Kleinbuchstaben. Wenn der
Name aus zwei Wörtern besteht (zum Beispiel mein Auto
), gibt es zwei übliche Konventionen:
mein_auto
oder meinAuto
. Letztere Form wird auch als Kamel-Notation bezeichnet,
da die Großschreibung im Wort selbst an einen Kamelhöcker erinnert.
Manche finden die Schreibweise mit dem Unterstrich (mein_auto
) leichter zu lesen, andere
wiederum versuchen den Unterstrich beim Tippen möglichst zu vermeiden. In diesem
Buch finden Sie die sogenannte Kamel-Notation, in der das zweite und jedes weitere
Wort mit einem Großbuchstaben beginnt: meinAuto
, derSchnelleBrauneFuchs
etc.
Viele fortgeschrittene Programmierer schreiben Ihren Code in der sogenannten Ungarischen Notation. Dieser Notation liegt der Gedanke zugrunde, daß jede Variable mit einem oder mehreren Buchstaben beginnt, die auf den Typ der Variablen verweisen. So wird ganzzahligen Variablen (Integer) ein kleines i vorangestellt oder Variablen vom Typ
long
ein kleines l. Andere Notationen verweisen auf Konstanten, globale Variablen, Zeiger und so weiter. Dies ist jedoch für die C-Programmierung von wesentlich größerer Bedeutung als für C++, da C++ die Erzeugung benutzerdefinierter Datentypen unterstützt (siehe Tag 6, »Klassen«), und von sich aus typenstrenger ist.
In C++ sind bestimmte Wörter reserviert, die man nicht als Variablennamen verwenden
darf. Es handelt sich dabei um die Schlüsselwörter, mit denen der Compiler das
Programm steuert. Zu den Schlüsselwörtern gehören zum Beispiel if
, while
, for
und
main
. In der Dokumentation Ihres Compilers finden Sie eine vollständige Liste. Im allgemeinen
fallen aussagekräftige Name für Variablen nicht mit Schlüsselwörtern zusammen.
Eine Liste der C++-Schlüsselwörter finden Sie in Anhang B.
In einer Anweisung lassen sich mehrere Variablen desselben Typs gleichzeitig erzeugen, indem man den Typ schreibt und dahinter die Variablennamen durch Kommata getrennt aufführt. Dazu ein Beispiel:
unsigned int meinAlter, meinGewicht; //Zwei Variablen vom Typ unsigned int
long Flaeche, Breite, Laenge; //Drei Variablen vom Typ long
Wie man sieht, werden meinAlter
und meinGewicht
gemeinsam als Variablen vom Typ
unsigned int
deklariert. Die zweite Zeile deklariert drei eigenständige Variablen vom
Typ long
mit den Namen Flaeche
, Breite
und Laenge
. Der Typ (long
) wird allen Variablen
zugewiesen, so daß man in einer Definitionsanweisung keine unterschiedlichen
Typen festlegen kann.
Einen Wert weist man einer Variablen mit Hilfe des Zuweisungsoperators (=
) zu. Zum
Beispiel formuliert man die Zuweisung des Wertes 5
an die Variable Breite
wie folgt:
unsigned short Breite;
Breite = 5;
Die Typbezeichnung
long
ist eine verkürzte Schreibweise fürlong int
undshort
fürshort int
.
Diese Schritte kann man zusammenfassen und die Variable Breite
bei ihrer Definition
initialisieren:
unsigned short Breite = 5;
Die Initialisierung sieht nahezu wie eine Zuweisung aus, und bei Integer-Variablen gibt es auch kaum einen Unterschied. Bei der späteren Behandlung von Konstanten werden Sie sehen, daß man bestimmte Werte initialisieren muß, da Zuweisungen nicht möglich sind. Der wesentliche Unterschied besteht darin, daß die Initialisierung bei der Erzeugung der Variablen stattfindet.
Ebenso wie Sie mehrere Variable gleichzeitig definieren können, ist es auch möglich, mehr als eine Variable auf einmal zu erzeugen. Betrachten wir folgendes Beispiel:
//Erzeugung von zwei long-Variablen und ihre Initialisierung
long Breite = 5, Laenge = 7;
In diesem Beispiel wird die Variable Breite
vom Typ long
mit 5
und die Variable Laenge
vom Typ long
mit dem Wert 7
initialisiert. Sie können aber auch Definitionen und
Initialisierungen mischen:
int meinAlter = 39, ihrAlter, seinAlter = 40;
Listing 3.2 zeigt ein vollständiges Programm, das Sie sofort kompilieren können. Es berechnet die Fläche eines Rechtecks und schreibt das Ergebnis auf den Bildschirm.
Listing 3.2: Einsatz von Variablen
1: // Einsatz von Variablen
2: #include <iostream.h>
3:
4: int main()
5: {
6: unsigned short int Width = 5, Length;
7: Length = 10;
8:
9: // einen unsigned short int erzeugen und mit dem Ergebnis der
10: // Multiplikation von Width und Length initialisieren
11: unsigned short int Area = (Width * Length);
12:
13: cout << "Breite: " << Width << "\n";
14: cout << "Laenge: " << Length << endl;
15: cout << "Flaeche: " << Area << endl;
16: return 0;
17: }
Breite: 5
Laenge: 10
Flaeche: 50
Zeile 2 enthält die include
-Anweisung für die iostream
-Bibliothek, die wir benötigen,
um cout
verwenden zu können. In Zeile 4 beginnt das Programm.
In Zeile 6 wird die Variable Width
als vorzeichenloser short int
definiert und mit dem
Wert 5
initialisiert. Eine weitere Variable vom gleich Typ, Length
, wird ebenfalls hier
definiert, aber nicht initialisiert. In Zeile 7 erfolgt die Zuweisung des Wertes 10
an die
Variable Length
.
Zeile 11 definiert die Variable Area
vom Typ unsigned short int
und initialisiert sie
mit dem Wert, der sich aus der Multiplikation von Width
und Length
ergibt. In den Zeilen
13 bis 15 erfolgt die Ausgabe der Variablenwerte auf dem Bildschirm. Beachten
Sie, daß das spezielle Wort endl
eine neue Zeile erzeugt.
Es ist lästig, zeitraubend und vor allem fehleranfällig, wenn man häufig unsigned short
int
schreiben muß. In C++ kann man einen Alias für diese Wortfolge mit Hilfe des
Schlüsselwortes typedef
(für Typendefinition) erzeugen.
Mit diesem Schlüsselwort erzeugt man lediglich ein Synonym und keinen neuen Typ
(letzteres heben wir uns für den Tag 6, »Klassen«, auf). Auf das Schlüsselwort typedef
folgt ein vorhandener Typ und danach gibt man den neuen Namen an. Den Abschluß
bildet ein Semikolon. Beispielsweise erzeugt
typedef unsigned short int USHORT;
den neuen Namen USHORT
, den man an jeder Stelle verwenden kann, wo man sonst
unsigned short int
schreiben würde. Listing 3.3 ist eine Neuauflage von Listing 3.2
und verwendet die Typendefinition USHORT
anstelle von unsigned short int
.
Listing 3.3: Demonstration von typedef
1: // Listing 3.3
2: // Zeigt die Verwendung des Schlüsselworts typedef
3: #include <iostream.h>
4:
5: typedef unsigned short int USHORT; // mit typedef definiert
6:
7: int main()
8: {
9: USHORT Width = 5;
10: USHORT Length;
11: Length = 10;
12: USHORT Area = Width * Length;
13: cout << "Breite: " << Width << "\n";
14: cout << "Laenge: " << Length << endl;
15: cout << "Flaeche: " << Area <<endl;
16: return 0;
17: }
Breite: 5
Laenge: 10
Flaeche: 50
Zeile 5 verwendet das mit typedef
erzeugte Synonym USHORT
für unsigned short int
.
Das Programm ist ansonsten mit Listing 3.2 identisch und erzeugt auch die gleichen
Ausgaben.
Neueinsteiger in die C++-Programmierung wissen oft nicht, wann man eine Variable
als long
und wann als short
deklarieren sollte. Die Regel ist einfach: Wenn der in der
Variablen zu speichernde Wert zu groß für seinen Typ werden kann, nimmt man einen
größeren Typ.
Wie Tabelle 3.1 zeigt, können ganzzahlige Variablen vom Typ unsigned short
(vorausgesetzt,
daß sie aus 2 Bytes bestehen) nur Werte bis zu 65535 aufnehmen. Variablen
vom Typ signed short
verteilen ihren Wertebereich auf negative und positive
Zahlen. Deshalb ist das Maximum eines solchen Typs nur halb so groß.
Obwohl Integer-Zahlen vom Typ unsigned long
sehr große Ganzzahlen aufnehmen
können (bis 4.294.967.295), hat auch dieser Typ einen begrenzten Wertebereich.
Benötigt man größere Zahlen, muß man auf float
oder double
ausweichen und einen
gewissen Genauigkeitsverlust in Kauf nehmen. Variablen vom Typ float
oder double
können zwar extrem große Zahlen speichern, allerdings sind auf den meisten Computern
nur die ersten 7 bzw. 19 Ziffern signifikant. Das bedeutet, daß die Zahl nach dieser
Stellenzahl gerundet wird.
Kürzere Variablen belegen weniger Speicher. Heute jedoch ist Speicher billig und das
Leben kurz. Deshalb lassen Sie sich nicht davon abhalten, int
zu verwenden, auch
wenn damit 4 Byte auf Ihrem PC belegt werden.
Die Tatsache, daß Ganzzahlen vom Typ unsigned long
nur einen begrenzten Wertebereich
aufnehmen können, ist nur selten ein Problem. Aber was passiert, wenn der
Platz im Verlauf des Programms zu klein wird?
Wenn eine Ganzzahl vom Typ unsigned
ihren Maximalwert erreicht, schlägt der Zahlenwert
um und beginnt von vorn. Vergleichbar ist das mit einem Kilometerzähler. Listing
3.4 demonstriert den Versuch, einen zu großen Wert in einer Variablen vom
Typ short int
abzulegen.
Listing 3.4: Speichern eines zu großen Wertes in einer Variablen vom Typ unsigned integer
1: #include <iostream.h>
2: int main()
3: {
4: unsigned short int smallNumber;
5: smallNumber = 65535;
6: cout << "Kleine Zahl: " << smallNumber << endl;
7: smallNumber++;
8: cout << "Kleine Zahl: " << smallNumber << endl;
9: smallNumber++;
10: cout << "Kleine Zahl: " << smallNumber << endl;
11: return 0;
12: }
Kleine Zahl: 65535
Kleine Zahl: 0
Kleine Zahl: 1
Zeile 4 deklariert smallNumber
vom Typ unsigned short int
(auf dem Computer des
Autors 2 Bytes für einen Wertebereich zwischen 0 und 65535). Zeile 5 weist den Maximalwert
smallNumber
zu und gibt ihn in Zeile 6 aus.
Die Anweisung in Zeile 7 inkrementiert smallNumber
, das heißt, addiert den Wert 1.
Das Symbol für das Inkrementieren ist ++
(genau wie der Name C++ eine Inkrementierung
von C symbolisieren soll). Der Wert in smallNumber
sollte nun 65536 lauten.
Da aber Ganzzahlen vom Typ unsigned short
keine Zahlen größer als 65535 speichern
können, schlägt der Wert zu 0 um. Die Ausgabe dieses Wertes findet in Zeile 8
statt.
Die Anweisung in Zeile 9 inkrementiert smallNumber
erneut. Es erscheint nun der
neue Wert 1.
Im Gegensatz zu unsigned
Integer-Zahlen besteht bei einer Ganzzahl vom Typ signed
die Hälfte des Wertebereichs aus negativen Werten. Den Vergleich mit einem Kilometerzähler
stellen wir nun so an, daß er bei einem positiven Überlauf vorwärts und bei
negativen Zahlen rückwärts läuft. Vom Zählerstand 0 ausgehend erscheint demnach
die Entfernung ein Kilometer entweder als 1
oder -1
. Wenn man den Bereich der positiven
Zahlen verläßt, gelangt man zur größten negativen Zahl und zählt dann weiter
herunter bis Null. Listing 3.5 zeigt die Ergebnisse, wenn man auf die maximale positive
Zahl in einem signed short int
eine 1 addiert.
Listing 3.5: Addieren einer zu großen Zahl auf eine Zahl vom Typ signed int
1: #include <iostream.h>
2: int main()
3: {
4: short int smallNumber;
5: smallNumber = 32767;
6: cout << "Kleine Zahl: " << smallNumber << endl;
7: smallNumber++;
8: cout << "Kleine Zahl: " << smallNumber << endl;
9: smallNumber++;
10: cout << "Kleine Zahl: " << smallNumber << endl;
11: return 0;
12: }
Kleine Zahl: 32767
Kleine Zahl: -32768
Kleine Zahl: -32767
Zeile 4 deklariert smallNumber
dieses Mal als signed short int
(wenn man nicht explizit
unsigned
festlegt, gilt per Vorgabe signed
). Das Programm läuft fast genau wie das
vorherige, liefert aber eine andere Ausgabe. Um diese Ausgabe zu verstehen, muß
man die Bit-Darstellung vorzeichenbehafteter (signed
) Zahlen in einer Integer-Zahl von
2 Bytes Länge kennen.
Analog zu vorzeichenlosen Ganzzahlen findet bei vorzeichenbehafteten Ganzzahlen ein Umschlagen vom größten positiven Wert in den höchsten negativen Wert statt.
Zeichen-Variablen (vom Typ char
) sind in der Regel 1 Byte groß und können damit
256 Werte (siehe Anhang C) aufnehmen. Eine Variable vom Typ char
kann als kleine
Zahl (0-255) oder als Teil des ASCII-Zeichensatzes interpretiert werden. ASCII steht
für American Standard Code for Information Interchange (amerikanischer Standard-Code
für den Informationsaustausch). Mit dem ASCII-Zeichensatz und seinem
ISO-Gegenstück (International Standards Organization) können alle Buchstaben, Zahlen
und Satzzeichen codiert werden.
Computer haben keine Ahnung von Buchstaben, Satzzeichen oder Sätzen. Alles was sie verstehen, sind Zahlen. Im Grunde genommen können Sie nur feststellen, ob genügend Strom an einem bestimmten Leitungspunkt vorhanden ist. Wenn ja, wird dies intern mit einer 1 dargestellt, wenn nicht mit einer 0. Durch die Kombination von Einsen und Nullen erzeugt der Computer Muster, die als Zahlen interpretiert werden können. Und diese Zahlen können wiederum Buchstaben und Satzzeichen zugewiesen werden.
Im ASCII-Code wird dem kleinen »a« der Wert 97
zugewiesen. Allen Klein- und Großbuchstaben
sowie den Zahlen und Satzzeichen werden Werte zwischen 1 und 128 zugewiesen.
Weitere 128 Zeichen und Symbole sind für den Computer-Hersteller reserviert.
In der Realität hat sich aber der erweiterte IBM-Zeichensatz als Quasi-Standard
durchgesetzt.
Wenn Sie ein Zeichen, zum Beispiel »a«, in einer Variablen vom Typ char
ablegen,
steht dort eigentlich eine Zahl zwischen 0 und 255. Der Compiler kann Zeichen (dargestellt
durch ein einfaches Anführungszeichen gefolgt von einem Buchstaben, einer
Zahl oder einem Satzzeichen und einem abschließenden einfachen Anführungszeichen)
problemlos in ihren zugeordneten ASCII-Wert und wieder zurück verwandeln.
Die Wert/Buchstaben-Beziehung ist zufällig. Daß dem kleinen »a« der Wert 97 zugewiesen wurde, ist reine Willkür. So lange jedoch, wie jeder (Tastatur, Compiler und Bildschirm) sich daran hält, gibt es keine Probleme. Sie sollten jedoch beachten, daß zwischen dem Wert 5 und dem Zeichen »5« ein großer Unterschied besteht. Letzteres hat einen Wert von 53, so wie das »a« einen Wert von 97 hat.
Listing 3.6: Ausdrucken von Zeichen auf der Basis von Zahlen
1: #include <iostream.h>
2: int main()
3: {
4: for (int i = 32; i<128; i++)
5: cout << (char) i;
6: return 0;
7: }
!"#$%'()*+,./0123456789:;<>?@ABCDEFGHIJKLMNOP
_QRSTUVWXYZ[\]^'abcdefghijklmnopqrstuvwxyz{|{~
Dieses einfache Programm druckt die Zeichenwerte für die Integer 32 bis 127.
Der C++-Compiler kennt einige spezielle Formatierungszeichen. Tabelle 3.2 listet die geläufigsten auf. In Ihrem Code geben Sie diese Zeichen mit einem vorangestellten Backslash (auch Escape-Zeichen genannt) ein. Um zum Beispiel einen Tabulator in Ihren Code mit aufzunehmen, würden Sie ein einfaches Anführungszeichen, den Backslash, den Buchstaben t und ein abschließendes einfaches Anführungszeichen eingeben.
char tabZeichen = '\t';
Dies Beispiel deklariert eine Variable vom Typ char
und initialisiert sie mit dem Zeichenwert
\t
, der als Tabulator erkannt wird. Diese speziellen Druckzeichen werden
benötigt, wenn die Ausgabe entweder auf dem Bildschirm, in eine Datei oder einem
anderen Ausgabegerät erfolgen soll.
Ein Escape-Zeichen ändert die Bedeutung des darauf folgenden Zeichens. So ist das
Zeichen n zum Beispiel nur der Buchstabe n. Wird davor jedoch ein Escape-Zeichen
gesetzt (\
), steht das Ganze für eine neue Zeile.
Konstanten sind ebenso wie Variablen benannte Speicherstellen. Während sich Variablen aber ändern können, behalten Konstanten - wie der Name bereits sagt - immer ihren Wert. Sie müssen Konstanten bei der Erzeugung initialisieren und können ihr dann später keinen neuen Wert zuweisen.
C++ kennt zwei Arten von Konstanten: literale und symbolische.
Eine literale Konstante ist ein Wert, den man direkt in das Programm an der Stelle des Vorkommens eintippt. In der Anweisung
int meinAlter = 39;
ist meinAlter
eine Variable vom Typ int
, während 39
eine literale Konstante bezeichnet.
Man kann 39
keinen Wert zuweisen oder diesen Wert ändern.
Eine symbolische Konstante wird genau wie eine Variable durch einen Namen repräsentiert. Allerdings läßt sich im Gegensatz zu einer Variablen der Wert einer Konstanten nicht nach deren Initialisierung ändern.
Wenn Ihr Programm eine Integer-Variable namens Studenten
und eine weitere namens
Klassen
enthält, kann man die Anzahl der Studenten berechnen, wenn die Anzahl
der Klassen bekannt ist und man weiß, daß 15 Studenten zu einer Klasse gehören:
Studenten = Klassen * 15;
In diesem Beispiel ist 15
eine literale Konstante. Der Code wäre leichter zu lesen, zu
verstehen und zu warten, wenn man für diesen Wert eine symbolische Konstante
setzt:
Studenten = Klassen * StudentenProKlasse
Wenn man später die Anzahl der Stunden pro Klasse ändern möchte, braucht man
das nur in der Definition der Konstanten StudentenProKlasse
vorzunehmen, ohne daß
man alle Stellen ändern muß, wo man diesen Wert verwendet hat.
Es gibt zwei Möglichkeiten, eine symbolische Konstante in C++ zu deklarieren. Die
herkömmliche und inzwischen veraltete Methode erfolgt mit der Präprozessor-Direktiven
#define
.
Um eine Konstante auf die herkömmliche Weise zu definieren, gibt man ein:
#define StudentenProKlasse 15
Beachten Sie, daß StudentenProKlasse
keinen besonderen Typ (etwa int
oder char
)
aufweist. #define
nimmt eine einfache Textersetzung vor. Der Präprozessor schreibt
an alle Stellen, wo StudentenProKlasse
vorkommt, die Zeichenfolge 15
in den Quelltext.
Da der Präprozessor vor dem Compiler ausgeführt wird, kommt Ihr Compiler niemals
mit der symbolischen Konstanten in Berührung, sondern bekommt immer die Zahl 15
zugeordnet.
Obwohl #define
funktioniert, gibt es in C++ eine neue, bessere und elegantere Lösung
zur Definition von Konstanten:
const unsigned short int StudentenProKlasse = 15;
Dieses Beispiel deklariert ebenfalls eine symbolische Konstante namens StudentenProKlasse
, dieses Mal ist aber StudentenProKlasse
als Typ unsigned short int
definiert.
Diese Version bietet verschiedene Vorteile. Zum einen läßt sich der Code leichter
warten und zum anderen werden unnötige Fehler vermieden. Der größte
Unterschied ist der, daß diese Konstante einen Typ hat und der Compiler die zweckmäßige
- sprich typgerechte - Verwendung der Konstanten prüfen kann.
Konstanten können nicht während der Ausführung des Programms geändert werden. Wenn Sie gezwungen sind, die Konstante
studentsPerClass
zu ändern, müssen Sie den Code ändern und neu kompilieren.
Mit Hilfe von Aufzählungskonstanten (enum
) können Sie neue Typen erzeugen und
dann Variablen dieser Typen definieren, deren Werte auf einen bestimmten Bereich
beschränkt sind. Beispielsweise kann man FARBE
als Aufzählung deklarieren und dafür
fünf Werte definieren: ROT
, BLAU
, GRUEN
, WEISS
und SCHWARZ
.
Die Syntax für Aufzählungstypen besteht aus dem Schlüsselwort enum
, gefolgt vom Typennamen,
einer öffnenden geschweiften Klammer, einer durch Kommata getrennte
Liste der möglichen Werte, einer schließenden geschweiften Klammern und einem
Semikolon. Dazu ein Beispiel:
enum FARBE { ROT, BLAU, GRUEN, WEISS, SCHWARZ };
Diese Anweisung realisiert zwei Aufgaben:
FARBE
ist der Name der Aufzählung, das heißt, ein neuer Typ.
ROT
wird zu einer symbolischen Konstanten mit dem Wert 0, BLAU
zu einer symbolischen Konstanten mit dem Wert 1, GRUEN
zu einer symbolischen Konstanten mit dem Wert 2 usw.
Jeder Aufzählungskonstanten ist ein Integer-Wert zugeordnet. Wenn man nichts anderes festlegt, weist der Compiler der ersten Konstanten den Wert 0 zu und numeriert die restlichen Konstanten fortlaufend durch. Jede einzelne Konstante läßt sich aber auch mit einem bestimmten Wert initialisieren, wobei die Werte der nicht initialisierten Konstanten immer um 1 höher sind als die Werte ihres Vorgängers. Schreibt man daher
enum FARBE { ROT=100, BLAU, GRUEN=500, WEISS, SCHWARZ=700 };
erhält ROT
den Wert 100
, BLAU
den Wert 101
, GRUEN
den Wert 500
, WEISS
den Wert 501
und SCHWARZ
den Wert 700
.
Damit können Sie Variablen vom Typ FARBE
definieren, denen dann allerdings nur einer
der Aufzählungswerte (in diesem Falle ROT
, BLAU
, GRUEN
, WEISS
oder SCHWARZ
oder
die Werte 100
, 101
, 500
, 501
oder 700
) zugewiesen werden kann. Sie können Ihrer Variablen
FARBE
beliebige Farbwerte zuweisen, ja sogar beliebige Integer-Werte, auch
wenn es keine gültige Farbe ist. Ein guter Compiler wird in einem solchen Fall jedoch
eine Fehlermeldung ausgeben. Merken Sie sich, daß Aufzählungsvariablen vom Typ
unsigned int
sind und daß es sich bei Aufzählungskonstanten um Integer-Variablen
handelt. Es ist jedoch von Vorteil, diesen Werten einen Namen zu geben, wenn Sie
mit Farben, Wochentagen oder ähnlichen Wertesätzen arbeiten. In Listing 3.7 finden
Sie ein Programm, das eine Aufzählungskonstante verwendet.
Listing 3.7: Ein Beispiel zur Verwendung von Aufzählungskonstanten
1: #include <iostream.h>
2: int main()
3: {
4: enum Days { Sunday, Monday, Tuesday,
5: Wednesday, Thursday, Friday, Saturday };
6: int choice;
7: cout << " Geben Sie einen Tag ein (0-6): ";
8: cin >> choice;
9: if (choice == Sunday || choice == Saturday)
10: cout << "\nSie sind bereits im Wochenende!\n";
11: else
12: cout << "\nOkay, legen Sie einen Urlaubstag ein.\n";
13: return 0;
14: }
Geben Sie einen Tag ein (0-6): 6
Sie sind bereits im Wochenende!
Zeile 4 definiert einen Aufzählungstyp DAYS
mit sieben Werten. Jeder dieser Werte
entspricht einem Integer, wobei die Zählung mit 0
begonnen wird. Demzufolge ist der
Wert von Tuesday
(Dienstag) gleich 2
.
Der Anwender wird gebeten, einen Wert zwischen 0
und 6
einzugeben. Die Eingabe
von »Sonntag« als Tag ist nicht möglich. Das Programm hat keine Ahnung, wie es die
Buchstaben in Sonntag in einen der Aufzählungswerte übersetzen soll. Es kann jedoch
die Werte, die der Anwender eingibt, mit einem oder mehreren Aufzählungskonstanten
wie in Zeile 9 abgleichen. Die Verwendung von Aufzählungskonstanten verdeutlicht
die Absicht des Vergleichs besser. Sie hätten das Ganze auch mit Integer-Konstanten
erreichen können. Das Beispiel dazu finden Sie in Listing 3.8.
In diesem und allen anderen kleinen Programmen dieses Buches wird auf jeglichen Code verzichtet, der normalerweise dazu dient, ungültige Benutzereingaben abzufangen. So prüft dieses Programm im Gegensatz zu einem richtigen Programm nicht, ob der Anwender wirklich eine Zahl zwischen
0
und6
eingibt. Ich habe diese Prüfroutinen absichtlich weggelassen, um die Programme klein und einfach zu halten und auf das Wesentliche zu konzentrieren.
Listing 3.8: Das gleiche Programm mit Integer-Konstanten
1: #include <iostream.h>
2: int main()
3: {
4: const int Sunday = 0;
5: const int Monday = 1;
6: const int Tuesday = 2;
7: const int Wednesday = 3;
8: const int Thursday = 4;
9: const int Friday = 5;
10: const int Saturday = 6;
11:
12: int choice;
13: cout << "Geben Sie einen Tag ein (0-6): ";
14: cin >> choice;
15:
16: if (choice == Sunday || choice == Saturday)
17: cout << "\nSie sind bereits im Wochenende!\n";
18: else
19: cout << "\nOkay, legen Sie einen Urlaubstag ein.\n";
20:
21: return 0;
22:}
Geben Sie einen Tag ein (0-6): 6
Sie sind bereits im Wochenende!
Die Ausgabe dieses Listings entspricht der in Listing 3.7. In diesem Programm wurde
jedoch jede Konstante (Sonntag, Montag etc.) einzeln definiert, ein Aufzählungstyp
DAYS
existiert nicht. Aufzählungskonstanten haben den Vorteil, daß sie selbsterklärend
sind - die Absicht des Aufzählungstypen DAYS
ist jedem sofort klar.
In diesem Kapitel haben Sie Variablen und Konstanten für numerische Werte und Zeichen
kennengelernt, in denen Sie in C++ während der Ausführung Ihres Programms
Daten speichern. Numerische Variablen sind entweder Ganzzahlen (char
, short
und
long int
) oder Fließkommazahlen (float
und double
). Die Zahlen können darüber hinaus
vorzeichenlos oder vorzeichenbehaftet (unsigned
und signed
) sein. Wenn auch alle
Typen auf unterschiedlichen Computern unterschiedlich groß sein können, so wird jedoch
mit dem Typ für einen bestimmte Computer immer eine genaue Größe angegeben.
Bevor man eine Variable verwenden kann, muß man sie deklarieren. Damit legt man gleichzeitig den Datentyp fest, der sich in der Variablen speichern läßt. Wenn man eine zu große Zahl in einer Integer-Variablen ablegt, erhält man ein falsches Ergebnis.
Dieses Kapitel hat auch literale und symbolische Konstanten sowie Aufzählungskonstanten
behandelt und die beiden Möglichkeiten zur Deklaration symbolischer Konstanten
aufgezeigt: die Verwendung von #define
und des Schlüsselwortes const
.
Frage:
Wenn der Wertebereich einer Variablen vom Typ short int
eventuell
nicht ausreicht, warum verwendet man dann nicht immer Ganzzahlen
vom Typ long
?
Antwort:
Sowohl bei ganzen Zahlen vom Typ short
als auch bei long
können Platzprobleme
auftreten, auch wenn der Wertebereich bei Zahlen vom Typ long
wesentlich
größer ist. Der Wertebereich eines unsigned short int
reicht bis
65.535, während die Werte eines unsigned long int
bis 4.294.967.295 gehen.
Auf den meisten Maschinen nimmt aber eine Variable vom Typ long
doppelt
soviel Speicher ein wie eine Variable vom Typ short
(vier Byte im Gegensatz
zu zwei Byte) und ein Programm mit 100 solcher Variablen benötigt
dadurch gleich 200 Byte RAM mehr. Allerdings stellt das heutzutage kaum
noch ein Problem dar, da die meisten PCs über mehrere Megabyte Hauptspeicher
verfügen.
Frage:
Was passiert, wenn ich eine reelle Zahl einer Variablen vom Typ int
statt vom Typ float
zuweise? Als Beispiel dazu die folgende Codezeile:
int eineZahl = 5.4;
Antwort:
Ein guter Compiler erzeugt eine Warnung, aber die Zuweisung ist durchaus
zulässig. Die zugewiesene Zahl wird zu einer ganzen Zahl abgerundet. Wenn
man daher 5.4 einer ganzzahligen Variablen zuweist, erhält diese Variable
den Wert 5. Es gehen also Informationen verloren, und wenn man später den
Wert der Integer-Variablen einer Variablen vom Typ float
zuweist, erhält die
float
-Variable ebenfalls nur den Wert 5
.
Frage:
Warum sollte man auf literale Konstanten verzichten und sich die Mühe
machen, symbolische Konstanten zu verwenden?
Antwort:
Wenn man einen Wert an vielen Stellen im gesamten Programm hindurch
verwendet, kann man bei Verwendung einer symbolischen Konstanten alle
Werte ändern, indem man einfach die Definition der Konstanten ändert.
Symbolische Konstanten sprechen auch für sich selbst. Es kann schwer zu verstehen
sein, warum eine Zahl mit 360 multipliziert wird. Einfacher ist dagegen
eine Anweisung zu lesen, in der die Multiplikation mit GradImVollkreis
realisiert wird.
Frage:
Was passiert, wenn ich eine negative Zahl einer vorzeichenlosen Variablen
vom Typ unsigned
zuweise? Als Beispiel dient die folgende Codezeile:
Unsigned int einePositiveZahl = -1;
Antwort:
Ein guter Compiler wird eine Warnung ausgeben, auch wenn die Zuweisung
absolut gültig ist. Die negative Zahl wird als Bitmuster interpretiert und der
Variablen zugewiesen. Der Wert dieser Variablen wird dann als eine vorzeichenlose
Zahl interpretiert. Demzufolge wird -1, dessen Bitmuster 11111111
11111111
(0xFF in hex
) ist, als unsigned
-Wert die Zahl 65.535 zugewiesen. Sollte
diese Information Sie verwirren, möchte ich Sie auf Anhang C verweisen.
Frage:
Kann ich mit C++ arbeiten, auch wenn ich nichts von Bitmustern, binärer
Arithmetik und Hexadezimalzahlen verstehe?
Antwort:
Ja, aber nicht so effektiv wie mit Kenntnis dieser Themen. Im Gegensatz zu
etlichen anderen Sprachen »schützt« C++ Sie nicht unbedingt davor, was der
Computer macht. Dies kann jedoch auch als Vorteil begriffen werden, da die
Programmiersprache dadurch mächtiger ist als andere Sprachen. Wie aber bei
jedem leistungsfähigen Werkzeug, muß man verstehen, wie es funktioniert,
um das Optimum herauszuholen. Programmierer, die ohne grundlegende
Kenntnisse des Binärsystems versuchen, in C++ zu programmieren, werden
oft über das Ergebnis erstaunt sein.
Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie die Lösungen in Anhang D lesen und zur Lektion des nächsten Tages übergehen.
unsigned short int
und einer Variablen vom Typ long int
?
const
verglichen mit #define
?
BLAU
in dem folgenden Aufzählungstyp?
enum FARBE { WEISS, SCHWARZ = 100, ROT, BLAU, GRUEN = 300 }
float
und initialisieren Sie sie mit Ihrer PI-Konstanten.
© Markt+Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH