vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Tag A

C++ im Überblick

Dieser Anhang soll Ihnen einen schnellen Überblick über die Grundlagen der Programmiersprache C++ verschaffen. Nachdem Sie sich mit diesem Anhang beschäftigt haben, sollten Sie ein umfassendes Verständnis für die verschiedenen Aspekte von C++ und seiner Syntax besitzen.

Die erste Anwendung erstellen

Das erste Beispiel ist ein einfaches Programm, das die Meldung "Hello World" auf dem Bildschirm ausgibt. Dazu erstellen Sie einen Arbeitsbereich und die für das Programm erforderliche C++-Datei. Mit Visual C++ läßt sich ein C++-Programm schnell und einfach schreiben. Führen Sie die folgenden Schritte aus:

1. Starten Sie Visual C++ über Start / Programme / Microsoft Visual Studio 6 / Microsoft Visual C++ 6.0.

2. Wählen Sie Datei / Neu im Menü von Visual C++.

3. Markieren Sie Win32-Konsolenanwendung im linken Fensterbereich.

4. Tippen Sie Hello in das Eingabefeld Projektname ein.

5. Klicken Sie auf OK. Übernehmen Sie im ersten und einzigen Schritt des Assistenten die Option Leeres Projekt. Klicken Sie auf Fertigstellen und im nächsten Dialogfeld auf OK.

Visual C++ erstellt den Arbeitsbereich Ihrer Anwendung. Zu diesem Zweck legt Visual C++ ein Verzeichnis Hello an, so daß Sie alle Dateien, die sich auf ein bestimmtes Projekt beziehen, in einem bestimmten Bereich unterbringen können. Als nächstes fügen Sie die erforderlichen Dateien für das Projekt hinzu:

1. Wählen Sie wieder Datei / Neu.

2. Gehen Sie auf die Registerkarte Dateien, falls diese nicht bereits ausgewählt ist.

3. Markieren Sie den Eintrag C++-Quellcodedatei.

4. Schalten Sie das Kontrollkästchen Dem Projekt hinzufügen im rechten Fensterbereich ein.

5. In das Eingabefeld Dateiname geben Sie Helloworld ein (siehe Abbildung A.2).

Abbildung A.2:
Das Projekt Helloworld einrichten

6. Klicken Sie auf OK.

In die Datei Helloworld.cpp schreiben Sie den C++-Quellcode. Alle C++-Quellcodedateien weisen die Erweiterung .cpp auf. Später kommen wir noch auf andere Dateitypen zu sprechen.

Alle Lehrbeispiele in diesem Abschnitt erstellen Sie in ähnlicher Weise, sie unterscheiden sich einzig in den Namen der Arbeitsbereiche und der Dateien.

Helloworld.cpp

Das Programm Helloworld zeigt den Text HELLO WORLD auf dem Bildschirm an. Listing A.1 enthält den Code. Tippen Sie den Code exakt wie angegeben in das Fenster Helloworld.cpp ein. Die Zeilennummern lassen Sie aber bitte weg, sie sind in den Listings nur angegeben, um im Text einfacher auf bestimmte Codeabschnitte verweisen zu können. C++ beachtet die Groß-/Kleinschreibung. Demzufolge ist main nicht dasselbe wie MAIN oder Main.

Listing A.1: Die Quellcodedatei Helloworld.cpp

1: // Arbeitsbereich: Hello
2: // Programmname: Helloworld.cpp
3:
4: # include <iostream.h>
5:
6: int main()
7:
8: {
9: cout<< "HELLO WORLD \n";
10: return 0;
11: }

Das Programm führen Sie folgendermaßen aus:

1. Wählen Sie Datei / Speichern, um Ihre Arbeit zu sichern.

2. Wählen Sie Erstellen / Aktive Konfiguration festlegen (siehe Abbildung A.3).

Abbildung A.3:
Die aktive Konfiguration festlegen

3. Markieren Sie Hello - Win32 Debug, und klicken Sie auf OK (siehe Abbildung A.4).

Abbildung A.4:
Win32 Debug auswählen

4. Wählen Sie Erstellen / Hello.exe erstellen.

Visual C++ kompiliert und linkt das Programm, um eine ausführbare Datei zu erzeugen. Das Ausgabefenster informiert Sie über den Erfolg oder das Scheitern der Kompilierung. Eine erfolgreiche Kompilierung sollte die Ausgabe

Hello.exe - 0 Fehler, 0 Warnung(en)

liefern. Falls irgendwelche Fehler auftreten, prüfen Sie bitte nach, ob Sie alle Programmzeilen genau wie im Listing (ohne Zeilennummern) eingegeben haben.

Um das Programm zu starten, wählen Sie Erstellen / Ausführen von Hello.exe.

Das Programm öffnet eine MS-DOS-Eingabeaufforderung und zeigt den Text HELLO WORLD an (siehe Abbildung A.5).

Abbildung A.5:
Die Anzeige HELLO WORLD

Komponenten von Helloworld.cpp

Die beiden ersten Zeilen des Programms sind Kommentare:

// Arbeitsbereich: Hello
// Programmname: Helloworld.cpp

Der Befehl mit doppelten Schrägstrichen (//) weist den Compiler an, alle nachfolgenden Zeichen auf der Zeile zu ignorieren. Es gehört zum guten Programmierstil, die eigene Arbeit zu kommentieren, da sich dadurch das Programm leichter lesen läßt, insbesondere für jemanden, der es selbst nicht geschrieben hat. Kommentare zahlen sich vor allem aus, wenn Sie an einem komplexen Programm über Monate hinweg arbeiten. Bei allen Änderungen helfen Ihnen Kommentare, Ihre vor längerer Zeit gesponnenen Gedankengänge auf Anhieb nachvollziehen zu können.

Zeile 4 beginnt mit dem Nummernzeichen (#):

# include <iostream.h>

Das ist eine Direktive an den Präprozessor, die angegebene Datei (iostream.h) einzubinden (include). Die spitzen Klammern (< >) weisen den Präprozessor an, nach der Datei in den Standardverzeichnissen zu suchen. Die Datei iostream.h enthält Definitionen für die Ausgabe- (<<) und Eingabe- (>>) Operatoren. Die Direktive ist erforderlich, um die Anweisung cout zu verarbeiten, die in Zeile 9 des Programms definiert ist. Die Datei iostream.h ist ein vorkompilierter Header, der zum Lieferumfang des Compilers gehört. Mit dem Programm Helloworld können Sie experimentieren, indem Sie die Include-Zeile auskommentieren. Fügen Sie dazu die doppelten Schrägstriche (/ /) vor dem Nummernzeichen (#) ein. Wenn Sie das Programm jetzt kompilieren und ausführen, erhalten Sie einen Fehler:

Helloworld.cpp
f:\vcp621\anhanga\hello\helloworld.cpp(9) : error C2065: 'cout' : nichtdeklarierter Bezeichner
f:\vcp621\anhanga\hello\helloworld.cpp(9) : error C2297: '<<' : Ungültig, da der rechte Operand vom Typ 'char [14]' ist
Fehler beim Ausführen von cl.exe.

Hello.exe - 2 Fehler, 0 Warnung(en)

Ohne die Datei iostream.h erkennt das Programm weder den Befehl cout noch den Einfügeoperator (<<).

In der nächsten Codezeile (Zeile 6) beginnt die eigentliche Programmausführung. Hier befindet sich der Eintrittspunkt Ihres Codes:

int main()

Diese Zeile weist den Compiler an, eine Funktion namens main zu verarbeiten. Jedes C++-Programm stellt eine Sammlung von Funktionen dar. Auf Funktionen gehen wir weiter hinten in diesem Anhang näher ein. Momentan sollten Sie wissen, daß man eine Funktion mit einem bestimmten Namen als Eintrittspunkt für einen Codeblock definiert. Die leeren Klammern zeigen an, daß die Funktion keinerlei Parameter übernimmt. Die Übergabe von Parametern in Funktionen behandelt der Abschnitt »Funktionen und Variablen« weiter hinten in diesem Anhang.

Jedes C++-Programm muß die Funktion main() enthalten. Sie stellt den Eintrittspunkt für die Programmausführung dar. Wenn eine Funktion einen Rückgabewert liefert, muß vor dem Funktionsnamen der Typ des zurückgegebenen Wertes stehen. In diesem Fall gibt die Funktion main() einen Wert vom Typ int zurück.

Der von einer Funktion definierte Codeblock ist in geschweifte Klammern ({ }) einzuschließen:

{
cout<< "HELLO WORLD \n";
return 0;
}

Der gesamte Code innerhalb dieser geschweiften Klammern gehört zur benannten Funktion - in diesem Fall main().

Die nächste Zeile führt das cout-Objekt aus. Daran schließt sich der Ausgabeoperator (<<) an, der die anzuzeigenden Informationen übergibt. Der anzuzeigende Text ist in Anführungszeichen zu setzen. Auf den eigentlichen Text folgt das Zeichen \n, das den Wechsel auf eine neue Zeile bewirkt. Der Ausgabe- oder Einfügeoperator (<<) bedeutet, daß alle nachfolgenden Zeichen in cout einzufügen sind.

Zeile 9 endet mit einem Semikolon. Alle Anweisungen in C++ sind mit einem Semikolon abzuschließen.

Zeile 10 des Codebeispiels enthält eine return-Anweisung. Programmierer geben damit in der Regel bestimmte Werte oder Fehlercodes zurück. Sehen Sie sich noch einmal Zeile 7 an, in der die Funktion main() steht. Dort ist auch der Rückgabetyp als ganze Zahl - oder Integer-Zahl - mit int definiert. Führen Sie den Code erneut aus, diesmal aber ohne die return-Anweisung in Zeile 10. Dazu ist Zeile 7 wie folgt zu modifizieren:

void main()

Es gehört zum guten Programmierstil, daß man bei komplexen Programmen Rückgabecodes vorsieht. Damit lassen sich Fehler im Programm besser erkennen und verfolgen.

Funktionen und Variablen

Das Programm Helloworld enthält nur eine Funktion, main(). Ein funktionales C++- Programm besteht normalerweise aus wesentlich mehr Funktionen. Um eine Funktion zu verwenden, muß man sie zuerst deklarieren. Eine Funktionsdeklaration bezeichnet man auch als Prototyp. Ein Prototyp liefert eine knappe Repräsentation der gesamten Funktion. Wenn man den Prototyp einer Funktion angibt, schreibt man eigentlich eine Anweisung. Wie bereits weiter oben erwähnt, müssen alle Anweisungen in C++ mit Semikolons enden. Ein Funktionsprototyp besteht aus einem Rückgabetyp, dem Namen der Funktion und einer Parameterliste. Der Rückgabetyp in der Funktion main() lautet int, der Name main und die Parameterliste ist () oder leer.

Eine Funktion muß einen Prototyp und eine Definition haben. Der Prototyp und die Definition einer Funktion müssen bezüglich Rückgabetyp, Name und Parameterliste übereinstimmen. Der einzige Unterschied besteht darin, daß der Prototyp eine Anweisung ist und demzufolge mit einem Semikolon endet. Listing A.2 verdeutlicht diesen Punkt mit einem einfachen Programm, das die Fläche eines Dreiecks berechnet.

Listing A.2: Das Programm Area.cpp

1: // Arbeitsbereich: Dreieck
2: // Programmname: Area.cpp
3: // Die Dreiecksfläche berechnet sich zu Grundseite mal Höhe durch 2
4: // Dreiecksfläche = (Grundseite * Höhe)/2
5:
6: #include <iostream.h> // Vorkompilierter Header
7:
8: double base,height,area; // Variablen deklarieren
9: double Area(double,double); // Prototyp/Deklaration der Funktion
10:
11: int main()
12: {
13: cout << "Hoehe des Dreiecks eingeben: "; // Eine Zahl eingeben
14: cin >> height; // Eingabe in Variable speichern
15: cout << "Grundseite des Dreiecks eingeben: "; // EinZahl eingeben
16: cin >> base; // Eingabe in Variable speichern
17:
18: area = Area(base,height); // Ergebnis der Funktion Area in der
19: // Variablen area speichern
20: cout << "Die Dreiecksfläche betraegt: "<< area << endl ; // Fläche ausgeben
21:
22: return 0;
23: }
24:
25: double Area (double base, double height) // Funktionsdefinition
26: {
27: area = (0.5*base*height);
28: return area;
29: }

Das Programm deklariert in Zeile 8 drei Variablen, base, height und area. Variablen speichern Werte, auf die das Programm zurückgreift. Der Typ der Variablen spezifiziert die in der Variablen zu speichernden Werte. Tabelle 22.1 zeigt die von C++ unterstützten Typen.

Tabelle A.1: Datentypen von Variablen

Datentyp

Wertebereich

unsigned short int

0 bis 65,535

short int

-32,768 bis 32,767

unsigned long int

0 bis 4,294,967,925

long int

-2,147,483,648 bis 2,147,483,647

int

-2,147,483,648 bis 2,147,483,647 (32 Bit)

unsigned int

0 bis 4,294,967,925 (32 Bit)

char

256 Zeichenwerte

float

1.2e-38 bis 3.4e38

double

2.2e-308 bis 1.8e308

Um eine Variable zu definieren, geben Sie zuerst den Typ an und daran anschließend den Namen. Der Variablen lassen sich mit dem Zuweisungsoperator (=) Werte zuweisen, wie es die beiden folgenden Beispiele zeigen:

double base = 5;
unsigned long int base =5;

In C++ kann man auch eine eigene Typdefinition festlegen. Dazu dient das Schlüsselwort typedef, auf das der vorhandene Typ und der Name folgen:

typedef unsigned long int ULONG;
ULONG base = 5;

Wenn man eigene Typen festlegt, kann man sich den Aufwand sparen, die gesamte Deklaration zu schreiben.

Die nächste Codezeile (Zeile 9) definiert den Prototyp der Funktion:

double Area (double, double);

Diese Funktion hat den Typ double, den Namen Area und eine Parameterliste mit zwei Variablen vom Typ double. Bei der Definition des Prototyps ist es zwar nicht erforderlich, die eigentlichen Parameter zu definieren, dennoch gehört es zum guten Programmierstil, die Parameternamen aufzuführen. Dieses Programm übernimmt zwei Eingaben vom Benutzer, nämlich base (die Länge der Grundseite) und height (die Höhe) des Dreiecks, und berechnet dessen Fläche (area). Die Angaben base, height und area sind Variablen. Das Beispiel Helloworld.cpp hat den Ausgabeoperator (<<) verwendet. Im jetzigen Beispiel kommt der Eingabeoperator (>>) vor. Das Programm fordert den Benutzer in Zeile 13 auf, einen Wert für die Höhe des Dreiecks einzugeben. Wenn der Benutzer einen Wert für height eingibt, werden die Daten vom Bildschirm geholt und in die Variable height geschrieben. Dieser Ablauf wiederholt sich für die Grundseite des Dreiecks in den Zeilen 15 und 16. Nach der Übernahme der Benutzereingaben übergibt die Funktion main() die Programmausführung an die Funktion Area(base, height) zusammen mit den Parameterwerten für base und height. Wenn main() die Ausführung an die Funktion Area(base, height) übergibt, erwartet sie einen Wert vom Typ double als Rückgabe der Funktion. Die Berechnung der Dreiecksfläche findet in Zeile 27 statt:

area = (0.5*base*height);

Es ist zu unterscheiden zwischen Area als dem Namen der Funktion und area als Variablenname. Da C++ die Groß-/Kleinschreibung beachtet, handelt es sich hier um zwei vollkommen verschiedene Namen.

Die Anweisung verwendet die Standardoperatoren für Zuweisung (=) und Multiplikation (*). Der Zuweisungsoperator weist das Ergebnis aus (0.5*base*height) an die Variable area zu. Der Multiplikationsoperator (*) berechnet das Ergebnis aus den Werten (0.5*base*height). Für den Zuweisungsoperator gilt eine Auswertungsreihenfolge von rechts nach links. Demzufolge wird die Multiplikation vor der Zuweisung der Werte an area ausgeführt. Zu den fünf grundlegenden mathematischen Operatoren gehören Addition (+), Subtraktion (-), Multiplikation (*), Division (/) und Modulo-Division (%).

In Zeile 28 gibt die Funktion Area den Wert der Variablen area an die Funktion main() zurück. Das Programm setzt nun die Ausführung in der Funktion main() ab Zeile 18 nach dem Funktionsaufruf von Area fort. Der Rest des Programms zeigt das Ergebnis von area auf dem Bildschirm an.

Die if-Anweisung, Operatoren und Polymorphismus

In größeren und komplexeren Programmen ist es häufig erforderlich, den Programmablauf in Abhängigkeit von einem bestimmten Wert - beispielsweise von einer Benutzereingabe - fortzusetzen. Das läßt sich mit einer if-Anweisung (if - wenn) erreichen. Das nächste Beispiel demonstriert den Einsatz einer if-Anweisung. Das Format der Anweisung sieht folgendermaßen aus:

if (dieser_Ausdruck)
tue dies;

Ein weiteres Format der if-Anweisung lautet:

if (dieser_Ausdruck)
tue dies;
else
tue das;

Da man in if-Anweisungen häufig relationale Operatoren verwendet, sehen wir uns zunächst diese Operatoren im Überblick an. Mit relationalen Operatoren bestimmt man, ob zwei Ausdrücke oder Zahlen gleich sind. Sind sie nicht gleich, liefert die Anweisung entweder 0 oder false. Tabelle 22.2 listet die sechs relationalen Operatoren von C++ auf.

Tabelle A.2: Relationale Operatoren

Operator

Name

==

Vergleich

!=

Nicht gleich

>

Größer als

<

Kleiner als

>=

Größer als oder gleich

<=

Kleiner als oder gleich

C++ verfügt außerdem über logische Operatoren. Der Vorteil der logischen Operatoren liegt darin, daß sie zwei einzelne Ausdrücke vergleichen und daraus schließen können, ob sie wahr (true) oder falsch (false) sind. Tabelle 22.3 führt die drei logischen Operatoren auf.

Tabelle A.3: Logische Operatoren

Symbol

Operator

&&

AND

||

OR

!

NOT

Ein wichtiges und leistungsfähiges Merkmal von C++ ist das Überladen von Funktionen, der sogenannte Polymorphismus. Dabei handelt es sich um die Möglichkeit, mehrere Funktionen mit dem gleichen Namen zu definieren, die sich aber in ihren Parameterlisten unterscheiden. Das nächste Beispiel erweitert den vorherigen Code zur Dreiecksberechnung. Zunächst soll der Benutzer entscheiden, ob er die Fläche eines Dreiecks oder eines Kreises berechnen will. In Abhängigkeit von seiner Antwort - 1 für das Dreieck und 2 für den Kreis - ruft das Programm die erforderlichen Eingaben zur Berechnung der Fläche ab. In Listing A.3 wird die Funktion Area überladen. Unter demselben Funktionsnamen verbirgt sich sowohl die Berechnung des Dreiecks als auch des Kreises. Die Funktionen unterscheiden sich lediglich in ihren Parameterlisten.

Listing A.3: Das Programm Overload.cpp

1: // Arbeitsbereich: Overload
2: // Programmname: Overload.cpp
3:
4: # include <iostream.h>
5:
6: double base,height,radius; // Globale Variablen
7: double Area_of_triangle,Area_of_circle; // Globale Variablen
8: int choice; // Globale Variable
9:
10: double Area (double,double); // Funktionsprototyp
11: double Area (double); // Funktionsprototyp
12:
13: const double pi = 3.14; // Konstante Variable
14:
15: void main() // Hauptfunktion
16:
17: {
18: cout << "Fuer Dreiecksflaeche 1 eingeben\n";
19: cout << "Fuer Kreisflaeche 2 eingeben\n";
20: cin >> choice;
21:
22: if (choice == 1)
23:
24: {
25: cout << "Grundseite des Dreiecks eingeben: ";
26: cin >> base;
27: cout << "Hoehe des Dreiecks eingeben: ";
28: cin >> height;
29:
30: Area_of_triangle = Area(base,height);
31:
32: cout << "Die Dreiecksflaeche betraegt: "<<Area_of_triangle<<endl;
33: }
34:
35: if (choice == 2)
36:
37: {
38: cout << "Radius des Kreises eingeben: ";
39: cin >> radius;
40: Area_of_circle = Area(radius);
41: cout << "Die Kreisflaeche betraegt: "<<Area_of_circle<<endl;
42: }
43:
44: if (choice != 1 && choice != 2)
45:
46: {
47: cout << "Sorry! Sie muessen entweder 1 oder 2 eingeben.\n";
48: }
49: }
50:
51: double Area (double base, double height)
52: {
53: return (0.5*base*height)
54: }
55:
56: double Area(double radius)
57: {
58: return (pi*radius*radius);
59: }

Globale und lokale Variablen

In den bisher gezeigten Beispielen wurden die Variablen am Anfang des Programms deklariert, noch vor der Definition der Funktion main(). Diese Vorgehensweise entspricht eher einem C- als einem C++-Programm. Die Variablen sind in diesem Fall global und allen Funktionen zugänglich. Allerdings kann man auch lokale Variablen deklarieren. Der Gültigkeitsbereich lokaler Variablen beschränkt sich auf die Funktion, in der sie deklariert sind. Lokale Variablen können den gleichen Namen wie lokale Variablen haben, ändern aber nicht den Wert der globalen Variablen. Lokale Variablen beziehen sich nur auf die Funktion, in der sie definiert sind. Dieser Unterschied kann verwirrend sein und zu rätselhaften Ergebnissen führen.

Das Programm in Listing A.4 zeigt klar den Unterschied zwischen globalen und lokalen Variablen. Die Flächenberechnung für einen Kreis führen Sie sowohl mit globalen als auch lokalen Variablen aus.

Listing A.4: Das Programm Global.cpp

1: // Arbeitsbereich: Variable
2: // Programmname: Global.cpp
3:
4: #include <iostream.h>
5:
6: double area;
7: double Area (double);
8: const double pi = 3.14;
9: double radius = 5;
10:
11: int main()
12:
13: {
14: cout<<"Das Programm berechnet die Kreisflaeche\n";
15: area = Area (radius);
16: cout << "Die Kreisflaeche betraegt: "<<area<<endl;
17: cout << "Der Radius in der Funktion Main() lautet: "<<radius<<endl;
18: return 0;
19: }
20:
21: double Area (double radius)
22: {
23: area = (pi*radius*radius);
24: cout<<"Der Radius in der Funktion Area() lautet: "<<radius<<endl;
25: return area;
26: }

Die Variable radius ist sowohl in der Funktion main() als auch in der Funktion Area() zugänglich und in beiden Funktionen identisch. Das Ergebnis des ausgeführten Programms zeigt Abbildung A.6.

Abbildung A.6:
Das Programm Global.cpp - mit einer globalen Variablen

Bei der Ausführung zeigt das Programm den Wert der Variablen radius in verschiedenen Funktionen an. Jetzt ändern Sie die globale Variable in eine lokale Variable. Fügen Sie in die Funktion Area eine zusätzliche Zeile ein, um eine lokale Variable zu definieren:

radius = 2;

Kompilieren und starten Sie das Programm. Die Ergebnisse sind in Abbildung A.7 dargestellt.

Abbildung A.7:
Das modifizierte Programm Global.cpp - mit globalen und lokalen Variablen

Der Wert der Variablen radius bleibt in der Funktion main() unverändert, während er sich lokal in der Funktion Area() ändert. Die Kreisfläche wird mit dem Wert der lokalen Variablen berechnet, während gleichzeitig der Wert der globalen Variablen nicht geändert wird und vor der Funktion Area() verborgen bleibt.

Es empfiehlt sich immer, globale und lokale Variablen im Quelltext zu unterscheiden. Zum Beispiel können Sie vor den Variablennamen das Präfix g für global und l für lokal schreiben.

Zeiger

Zeiger gehören zu den wichtigsten Einrichtungen von C++. Allerdings stiften sie bei Einsteigern in die C++-Programmierung oftmals Verwirrung. Zeiger ermöglichen den direkten Zugriff auf die Originaldaten, was die Effizienz eines Programms erhöht. Hauptsächlich verwendet man Zeiger mit zwei Operatoren, dem Indirektionsoperator (*) und dem Adreßoperator (&). Es ist üblich, vor den Namen einer Zeigervariablen ein p (für pointer - Zeiger) zu setzen, um ihn von anderen Variablennamen zu unterscheiden. Prinzipiell stellt ein Zeiger eine Variable dar, er enthält aber nicht den Wert der Variablen selbst, sondern eine Adresse auf eine Speicherstelle. Um einen Zeiger zu deklarieren, schreibt man ein Sternchen (*) vor den Zeigernamen. Auf die Adresse der Variablen greift man zu, indem man den Operator & vor den Variablennamen setzt.

Zum Verständnis der Zeiger muß man wissen, wie Variablen gespeichert werden. In Tabelle 22.1 weiter vorn in diesem Anhang haben Sie verschiedene Datentypen von Variablen kennengelernt. Tabelle 22.4 zeigt die von den Variablentypen belegte Größe im Arbeitsspeicher.

Tabelle A.4: Größe der Variablentypen

Variablentyp

Größe in Byte

unsigned short int

2 Byte

short int

2 Byte

unsigned long int

4 Byte

long int

4 Byte

int

4 Byte (32 Bit)

insigned int

2 Byte (32 Bit)

char

1 Byte

float

4 Byte

double

8 Byte

Im Programm Address.cpp in Listing A.5 belegen die Variablen base und radius 8 bzw. 4 Byte. Nehmen wir an, daß im Speicher Ihres Computers ein bestimmter Platz für diese Variablen zur Verfügung steht. Diese Speicherzellen sind fortlaufend von 1 bis 12 numeriert. Jeder Schritt ist 1 Byte groß. Wenn Sie die Variable base vom Typ double deklarieren, belegt sie 8 Byte. Nehmen wir an, daß sich diese 8 Byte an den Speicherstellen von 1 bis 8 befinden. Weiterhin ist eine Variable radius vom Typ int deklariert, die 4 Byte einnimmt und die Plätze 9 bis 12 belegen soll. Die Position dieser Variablen bezeichnet man als Adresse. Demzufolge hat die Variable base eine Anfangsadresse von 1 und eine Endadresse von 8. Analog dazu hat die Variable radius die Anfangsadresse 9 und die Endadresse 12. Wenn Sie den Adreßoperator (&) auf eine Variable anwenden, liefert er die Adresse der Variablen zurück. Die Variable base belegt einen Adreßbereich von 1 bis 8, der Adreßoperator liefert aber nur die Adresse 1 zurück. Intern weiß das System bereits, daß zu dieser Adresse 8 aufeinanderfolgende Speicherstellen gehören, da Sie den Typ der Variablen als double definiert haben.

Die in Tabelle A.4 angegebenen Größen liegen nicht genau fest und hängen vom jeweiligen Compiler und der verwendeten Hardware ab. Die Größe der Variablen für Ihren Compiler und Ihre Hardware können Sie mit der Funktion sizeof() bestimmen, wie es in Listing A.5 in den Zeilen 13 und 16 implementiert ist.

Das Programm in Listing A.5 zeigt, wie man auf die Speicheradresse von Variablen zugreift.

Listing A.5: Das Programm Address.cpp

1: // Arbeitsbereich: Zeiger
2: // Programmname: Address.cpp
3:
4: #include <iostream.h>
5:
6: double base = 5.0;
7: int radius = 2;
8:
9: void main()
10: {
11: cout<<"Der Wert WERT von base lautet: "<<base<<endl;
12: cout<<"Die ADRESSE von base lautet: "<<&base<<endl;
13: cout<<"Die GROESSE von double base ist: "<<sizeof(double)<< " Byte\n";
14: cout<<"Der WERT von radius lautet: "<<radius<<endl;
15: cout<<"Die ADRESSE von radius lautet: "<<&radius<<endl;
16: cout<<"Die GROESSE von integer radius ist: "<<sizeof(int)<<" Byte\n";
17: }

Die Zeilen 12 und 15 greifen mit dem Adreßoperator (&) direkt auf die Adresse der Variablen zu. Abbildung A.8 zeigt die Adressen der Variablen base und radius. Die konkreten Adressen der Variablen hängen von Ihrem System und den laufenden Programmen ab, so daß sie nicht mit der Abbildung übereinstimmen brauchen.

Abbildung A.8:
Der Adreßoperator im praktischen Einsatz

Der Indirektionsoperator (*) liefert den Zugriff auf den Wert, der an der Adresse der Variablen gespeichert ist. Wenn ein Zeiger für einen bestimmten Variablentyp (beispielsweise int) deklariert ist, sollte man den Zeiger nicht für einen anderen Typ einsetzen, solange man keine Typumwandlung auf den anderen Typ vornimmt. Erinnern wir uns: Ein Zeiger ist eine Variable, und wie jede andere Variable muß man sie deklarieren und initialisieren. Ein nicht initialisierter Zeiger kann gefährlich sein. Listing A.5 wurde modifiziert, um auf die Werte der Variablen radius und base zuzugreifen. Das modifizierte Programm ist in Listing A.6 wiedergegeben.

Listing A.6: Das modifizierte Programm Address.cpp

1: // Arbeitsbereich: Zeiger
2: // Programmname: Address.cpp
3:
4: #include <iostream.h>
5:
6: double base =5.0;
7: int radius =2;
8:
9: double *pBase =0; // Zeigervariable initialisieren
10: int *pRadius =0; // Zeigervariable initialisieren
11:
12: void main()
13: {
14: pBase = &base; // Adresse von base zuweisen
15: pRadius = &radius; // Adresse von radius zuweisen
16: cout<<"Der WERT von base lautet: "<<base<<endl; // Wert von base ausgeben
17: cout<<"Die ADRESSE von base lautet: "<<&base<<endl; // Adresse von base ausgeben
18: cout<<"Die GROESSE von double base ist: "<<sizeof(double)<< " Byte\n
19: cout<<"Der WERT von pBase lautet: "<<*pBase<<endl; // Umgeleiteten Wert von base ausgeben
20:
21: cout<<"Der WERT von radius lautet: "<<radius<<endl; // Wert von radius ausgeben
22: cout<<"Die ADRESSE von radius lautet: "<<&radius<<endl; // Adresse von radius ausgeben
23: cout<<"Die GROESSE von integer radius ist: "<<sizeof(int)<<" Byte\n";
24: cout<<"Der WERT von pRadius lautet: "<<*pRadius<<endl; // Umgeleiteten Wert von radius ausgeben
25:
26: }

Referenzen

Ein weiteres wichtiges Merkmal von C++ sind Referenzen, die man oft in Verbindung mit Funktionsparametern einsetzt. Eine Referenz ist einfach ein Synonym für eine Variable. Bis jetzt haben Sie Parameter an Funktionen als Wert übergeben. Jetzt geht es darum, wie man Parameter als Referenz übergibt. Um eine Referenzvariable zu erzeugen, legt man deren Typ fest und setzt vor den Namen den Referenzoperator (&). Für eine Variable float radius erzeugen Sie eine Referenz mit

void Funktionsname (float &rfradius);

Die Referenzvariable kann einen beliebigen Namen erhalten. Im folgenden Beispiel steht vor dem Namen der Referenzvariablen das Präfix rf. Der Vorteil einer Referenz liegt darin, daß man sie wie jede andere Variable als Parameter übergeben kann. Im Gegensatz zu normalen Parametern wirken sich aber die in einer Funktion am Wert der Referenz vorgenommenen Änderungen auf die Originalvariable aus. Das Beispiel in Listing A.7 zeigt, wie die Referenz den Wert der Variablen in der Funktion main() ändert.

Listing A.7: Das Programm Refer.cpp

1: // Arbeitsbereich: Referenz
2: // Programmname: Refer.cpp
3:
4: #include <iostream.h>
5:
6: void squareit (float &num);
7: int main()
8:
9: {
10: float num=5.0;
11:
12: cout<<"In Main: vor dem Quadrieren: "<<num*num<<"\n";
13:
14: squareit (num);
15: cout<<"In Main: nach dem Quadrieren: "<<num*num<<"\n";
16: return 0;
17:
18: }
19:
20: void squareit (float &rfnum)
21: {
22:
23: cout<<"In Squareit: vor dem Quadrieren: "<<rfnum*rfnum<<"\n";
24:
25: rfnum = rfnum+5;
26:
27: cout<<"In Squareit: nach dem Quadrieren: "<<rfnum*rfnum<<"\n";
28:
29: }

In Zeile 6 steht der Prototyp der Funktion squareit, die den übergebenen Wert quadriert. Der Parameter ist eine Referenz. Zeile 10 setzt den Wert der Variablen num auf 5. Das Quadrat der Zahl zeigt der Code in Zeile 15 auf dem Bildschirm an. In Zeile 14 wird die Funktion squareit aufgerufen.

Sie übergeben hier die Variable num und nicht deren Adresse.

Nur wenn die Programmausführung von Zeile 14 zu Zeile 20 springt, sind die Variablen als Referenzen gekennzeichnet. Zeile 27 quadriert die Referenzen und zeigt das Ergebnis an. Die Referenzen sollten mit den Variablen übereinstimmen, da es sich lediglich um Aliase für die Variablen handelt. Zeile 25 addiert 5 zur Referenz, die wiederum die Variable num verändert. Der höhere Wert wird quadriert und auf dem Bildschirm angezeigt. Die Ausführung kehrt in die Funktion main() zurück. Zeile 15 zeigt zur Kontrolle an, daß sich die Variable geändert hat. Die Ausgabe des Programms ist in Abbildung A.9 zu sehen.

Abbildung A.9:
Parameter als Referenz übergeben

Klassen

In den vorherigen Abschnitten haben Sie Datentypen (int, float usw.) verwendet, die fester Bestandteil von C++ sind. In großen komplexen Programmen ist es einfacher, eigene Typen zu definieren, die man aus den vordefinierten Typen zusammensetzen kann. Hauptsächlich zu diesem Zweck wurden in C++ Klassen aufgenommen - um dem Programmierer zu ermöglichen, benutzerdefinierte Datentypen und Methoden zu definieren. Das Konzept der Klassen in C++ entwickelte sich aufgrund bestimmter Beschränkungen im Konzept der Strukturen von C. Ein tiefergehendes Verständnis der Klassen erfordert es, daß wir zuerst die Strukturen von C untersuchen.

Eine Struktur stellt in C/C++ ein Mittel dar, eigene benutzerdefinierte Daten darzustellen. Bei der Definition von Variablen definieren Sie zuerst deren Datentypen und schließen die Variablennamen an:

int radius;

Zur Definition eigener Datentypen verwendet man das Schlüsselwort struct. Die Syntax für die Deklaration einer Struktur lautet:

struct [StrukturName]
{
Datenelemente
}

Die Datenelemente einer Struktur sind Variablen und Funktionen. Wenn von Funktionen im Zusammenhang mit Klassen die Rede ist, spricht man besser von Methoden. Von jetzt an verwenden wir den Begriff Funktion für Programmcode, der nicht Teil einer Struktur oder Klasse ist. Ein Bezug auf Methoden weist darauf hin, daß die Funktion mit einer Klassenstruktur gekoppelt ist. Um zu verstehen, wie man Strukturen einsetzt, sehen wir uns das Beispiel in Listing A.8 an:

Listing A.8: Das Programm Struct.cpp

1: // Arbeitsbereich: Klasse1
2: // Programmname: Struct.cpp
3: #include <iostream.h>
4:
5: struct farm_house
6: {
7: int pig_values;
8: };
9:
10: int main()
11: {
12: farm_house pig1, pig2, pig3;
13:
14: pig1.pig_values = 12;
15: pig2.pig_values = 13;
16: pig3.pig_values = 14;
17:
18: cout << "Der Wert von pig1 lautet: " << pig1.pig_values<< "\n";
19: cout << "Der Wert von pig2 lautet: " << pig2.pig_values << "\n";
20: cout << "Der Wert von pig3 lautet: " << pig3.pig_values << "\n";
21:
22: return 0;
23: }

In Zeile 5 folgt dem Schlüsselwort struct der Name der Struktur. Die eigentliche Definition der Struktur ist in die geschweiften Klammern eingeschlossen. Die hier gezeigte Struktur definiert ein Datenelement vom Typ int mit dem Namen pig_values. Wie bereits erwähnt, definieren Sie mit einer Struktur grundsätzlich einen benutzerdefinierten Datentyp. Alle Datentypen enden mit einem Semikolon, so daß die Struktur ebenfalls mit einem Semikolon abzuschließen ist. Zeile 12 definiert drei Instanzen des gleichen Typs von farm_house, von denen jede eine einzelne int-Variable enthält.

Wenn Sie sich streng an die Syntax von C halten, sind die Instanzen in Zeile 12 mit Hilfe des Schlüsselworts struct zu definieren:

struct farm_house pig1, pig2, pig3;

In C++ ist das nicht mehr erforderlich.

Die Zeilen 14 bis 16 weisen Werte an die Elementvariablen jeder Struktur zu. Der auch als Punktoperator bezeichnete Strukturelementoperator (.) dient dem Zugriff auf die Elementvariablen der Struktur. Die Zeilen 18 bis 20 geben die zugewiesenen Werte auf dem Bildschirm aus. Abbildung A.10 zeigt die Ausgabe dieses Programms.

Abbildung A.10:
Ausgabe des Strukturprogramms

Das wichtigste Konzept der objektorientierten Programmierung ist die Kapselung, die sich auf eine oder mehrere Klassen erstrecken kann. Kapselung fördert Schutzmaßnahmen und das Verbergen von Daten. Das Programm struct.cpp wies keine Kapselung oder Klassen auf. Was bedeuten nun Kapselung und Klassen in der objektorientierten Programmierung?

Beschreiben wir zunächst die Syntax und die Komponenten einer Klasse:

class Klassenname
{
public:
Klassenname_Konstruktor;
~Klassenname_Destruktor;
Klassenmethode_Prototypen();
Elementvariablen_der_Klasse;
private:
Klassenmethode_Prototypen();
Elementvariablen_der_Klasse;
};

Die fettgedruckten Wörter sind Schlüsselwörter. Eine Klasse deklariert man mit dem Schlüsselwort class. Daran schließt sich der Name der Klasse an. Die Daten und Methoden einer Klasse sind in geschweifte Klammern ({ }) eingeschlossen. Die Methoden einer Klasse stellen Prototypen von Funktionen dar und bestimmen das Verhalten der Objekte einer Klasse. Die Elementvariablen - oder Member-Variablen - sind die Variablen der Klasse. Weiterhin verfügen Klassen über Konstruktoren und Destruktoren. Die Methoden und Variablen lassen sich entweder als public (öffentlich) oder private (privat) klassifizieren.

Das vorherige Beispiel des Programms Struct.cpp in Listing A.8 erstellen Sie nun neu und nutzen dazu die Methodik der Klasse und der Kapselung. Die Ausgabe des Programms in Listing A.9 ist identisch mit dem vorherigen Beispiel Struct.cpp.

Listing A.9: Das Programm Clasfarm.cpp

1: // Arbeitsbereich: Klasse2
2: // Programmname: Clasfarm.cpp
3: #include <iostream.h>
4:
5: class farm_house
6: {
7: int pig_values;
8: public:
9: void set(int input);
10: int get(void);
11: };
12:
13: void farm_house::set(int input)
14: {
15: pig_values = input;
16: }
17:
18: int farm_house::get(void)
19: {
20: return pig_values;
21: }
22:
23: int main()
24: {
25: farm_house pig1, pig2, pig3;
26:
27:
28: pig1.set(12);
29: pig2.set(13);
30: pig3.set(14);
31:
32: cout << "Der Wert von pig1 lautet: " << pig1.get() << "\n";
33: cout << "Der Wert von pig2 lautet: " << pig2.get() << "\n";
34: cout << "Der Wert von pig3 lautet: " << pig3.get() << "\n";
35:
36: return 0;
37:
38: }

Vergleichen Sie die Strukturdeklaration des Programms Struct.cpp in Listing A.8 (Zeilen 5 bis 7) mit der Klassendeklaration des Programms Clasfarm.cpp in Listing A.9 (Zeilen 5 bis 11). Der Unterschied liegt in den privaten und öffentlichen Teilen der jeweiligen Deklarationen. In der struct-Deklaration ist alle öffentlich, während die Klassendeklaration mit einem privaten Abschnitt beginnt. Alle Daten und Methoden am Beginn einer Klasse sind privat. Das bedeutet, daß die Elementvariable

int pig_values;

privat ist und den Methoden außerhalb der Klasse verborgen bleibt. Die Variable pig_values ist demnach nicht in der Funktion main() zugänglich. Mit anderen Worten ist die Elementvariable versteckt. Den Methoden ihrer Klasse ist sie zugänglich, und zwar

void set (int input);
int get(void);

Diese Methoden sind als öffentlich definiert. Aus diesem Grund lassen sich die Methoden durch beliebige Objekte der Klasse ansprechen. Zeile 25 definiert pig1, pig2 und pig3 als Instanzen oder Objekte der Klasse. Sicherlich fragen Sie sich jetzt, warum pig1 ein Objekt ist.

Zeile 5 definiert eine Klasse farm_house. Wie bereits erwähnt, deklarieren Sie mit einer Klasse eigentlich nur einen neuen Typ. Wen Sie eine Variable deklarieren, geben Sie zuerst ihren Typ und dann den Variablennamen an, wie es folgendes Beispiel zeigt:

long EineVariable, EineAndere, NochEine;

Bei einem Objekt einer Klasse definieren Sie analog dazu den Typ, der in diesem Fall farm_house lautet, und den Objektnamen, hier pig1:

farm_house pig1, pig2, pig3;

In Zeile 28 setzen Sie den Wert von pig1 auf 12. Dabei kommt der Punktoperator (.) zum Einsatz. Das Objekt pig1 hat Zugriff auf die Methode set(). Die Methode set() ist eine Methode der Klasse farm_house, so daß sie Zugriff auf deren private Daten hat. Die Implementierung der Methode set() zeigt Zeile 13. Damit das Programm weiß, daß die Methode set() innerhalb des Gültigkeitsbereichs der Klasse farm_house liegt, verwendet man den Zugriffsoperator (::), der verschiedentlich auch als Bereichs- Auflösungsoperator firmiert. In Zeile 15 wird die Variable input auf die Variable pig_values gesetzt.

In der Klasse farm_house sind zwei öffentliche Methoden deklariert. Die andere Methode, get(), ist in Zeile 18 implementiert. Die Methode get() übernimmt keine Parameter, gibt aber pig_values zurück, da sie ebenfalls innerhalb des Gültigkeitsbereichs der Klasse farm_house liegt.

In den Zeilen 32 bis 34 wird die Methode get() erneut aufgerufen, und zwar durch die Objekte pig1, pig2 und pig3, um die Werte von pig_values auf dem Bildschirm auszugeben.

Wenn Sie die beiden Programme Struct.cpp und Clasfarm.cpp vergleichen, fällt auf, daß ein Programm nur 23 Zeilen umfaßt, während das andere 38 Zeilen lang ist. Der Code ist durch die Implementierung von Klassen umfangreicher geworden! Das stimmt zwar. Allerdings treten die Vorteile aus der Verwendung von Klassen erst in komplexeren und größeren Programmen zutage. Da sie außerdem sensible Daten vor dem Benutzer verbergen können, sind Klassen sicherer und weniger fehleranfällig. Der Compiler kann bereits Fehler erkennen, bevor sie sich unangenehm im laufenden Programm bemerkbar machen.

Konstruktoren und Destruktoren

Weiter oben haben Sie bereits die Syntaxdefinition einer Klasse kennengelernt. In diesem Zusammenhang war auch von Konstruktoren und Destruktoren die Rede. Im Beispiel Clasfarm.cpp finden sich jedoch weder Konstruktoren noch Destruktoren. Wenn ein Konstruktor oder Destruktor nicht expliziert definiert ist, erzeugt ihn der Compiler automatisch.

Die Konstruktorfunktion

Ein Konstruktor ist eine Funktion, die eine Klasse initialisiert. Der Aufruf des Konstruktors erfolgt, wenn man die Instanz einer Klasse erzeugt. Für einen Konstruktor gelten folgende Regeln:

class farm_house
{
public:
farm_house(); // Konstruktor
.....
.....
}

Die Destruktorfunktion

Eine Destruktorfunktion stellt das Gegenteil zur Konstruktorfunktion dar. Der Aufruf des Destruktors erfolgt automatisch, wenn man den Block, indem das Objekt initialisiert wurde, verläßt. Ein Destruktor gibt das Objekt und demzufolge auch den zugewiesenen Speicher frei. Für einen Destruktor gelten die folgenden Regeln:

class farm_house
{
public:
farm_house (); // Konstruktor
~farm_house(); // Destruktor
.....
}

Friend-Funktionen und Friend-Klassen

Als privat deklarierte Methoden und Elemente sind nur dem Teil des Programms zugänglich, der zur Klasse gehört. Allerdings kann man eine Funktion außerhalb der Klasse oder eine andere Klasse als Friend-Klasse oder -Funktion definieren. Die Friend-Definition läßt sich auf die gesamte Klasse oder nur auf einzelne Funktionen anwenden. Bei der Deklaration von Friend-Funktionen sind folgende wichtige Regeln zu beachten:

Deklarationen und Definitionen von Klassen

Klassen verfügen über eigene private und öffentliche Elementvariablen und Methoden. Wie Sie im vorherigen Beispiel Clasfarm.cpp gesehen haben, ist das Programm umfangreicher als ein Programm ohne Klassen. Es gibt keine strengen Regeln, es haben sich aber bestimmte Standardtechniken durchgesetzt, die fast alle Programmierer einhalten. Zuerst einmal stellt man alle Klassendeklarationen in die Header-Dateien (mit der Erweiterung .h oder .hpp). Alle Klassendefinitionen bringt man in der Quellcodedatei (mit der Erweiterung .cpp) unter. Am Anfang der Quellcodedatei steht eine Include-Anweisung für die Header-Datei. Beispielsweise läßt sich das Programm Clasfarm in die Dateien Clasfarm.h und Clasfarm.cpp aufspalten. Die Datei Clasfarm.h sieht dann wie in Listing A.10 aus.

Listing A.10: Die Header-Datei Clasfarm.h

1: // Arbeitsbereich: Klasse2
2: // Programmname: Clasfarm.h
3: #include <iostream.h>
4:
5: class farm_house
6: {
7: int pig_values;
8: public:
9: void set(int input);
10: int get(void);
11: };

Die Quelldatei Clasfarm.cpp ist in Listing A.11 wiedergegeben.

Listing A.11: Die Quelldatei Clasfarm.cpp

1: #include "clasfarm.h"
2: void farm_house::set(int input)
3: {
4: pig_values = input;
5: }
6:
7: int farm_house::get(void)
8: {
9: return pig_values;
10: }
11:
12: int main()
13: {
14: farm_house pig1, pig2, pig3;
15:
16:
17: pig1.set(12);
18: pig2.set(13);
19: pig3.set(14);
20:
21: cout << "Der Wert von pig1 lautet: " << pig1.get() << "\n";
22: cout << "Der Wert von pig2 lautet: " << pig2.get() << "\n";
23: cout << "Der Wert von pig3 lautet: " << pig3.get() << "\n";
24:
25: return 0;
26:
27: };

Klassen innerhalb einer Klasse

Es ist durchaus zulässig, innerhalb einer Klasse eine andere Klassendeklaration vorzusehen. Man spricht in diesem Fall von verschachtelten Klassen. Das folgende Beispiel deklariert zwei Klassen, Parzelle und Steuer_bescheid. Das Objekt steuern der Klasse Steuer_bescheid ist innerhalb der Klasse Parzelle definiert. Die Methode main() hat keine Objekte der Klasse Steuer_bescheid, so daß die Methoden oder Elemente der Klasse Steuer_bescheid nicht direkt aus der Funktion main() heraus zugänglich sind. Untersuchen wir das Programm in Listing A.12 etwas näher.

Listing A.12: Das Programm Class3.cpp

1: // Arbeitsbereich Name: Klasse3
2: // Programmname: Klasse3.cpp
3: #include <iostream.h>
4:
5: class Steuer_bescheid
6: {
7: int Grund_steuer;
8: int Verm_steuer;
9: public:
10: void set(int in_stadt, int in_verm)
11: {Grund_steuer = in_stadt; Verm_steuer = in_verm; }
12: int get_Verm_steuer(void) {return Verm_steuer;}
13: int get_Grund_steuer(void) {return Grund_steuer;}
14: };
15:
16:
17: class Parzelle {
18: int laenge;
19: int breite;
20: Steuer_bescheid steuern;
21: public:
22: void set(int l, int b, int s, int p) {
23: laenge = l;
24: breite = b;
25: steuern.set(s, p); }
26: int get_area(void) {return laenge * breite;}
27: int get_data(void) {return steuern.get_Verm_steuer() ;}
28: int get_data2(void) {return steuern.get_Grund_steuer() ;}
29: };
30:
31:
32: int main()
33: {
34: Parzelle klein, mittel, gross;
35:
36: klein.set(5, 5, 5, 25);
37: mittel.set(10, 10, 10, 50);
38: gross.set(20, 20, 15, 75);
39:
40:
41: cout << "Bei einer kleinen Parzelle von "<< klein.get_area ()<< "\n";
42: cout << "betragen die Grundsteuern DM "<< klein.get_data2 () << "\n";
43: cout << "und die Vermoegenssteuern DM " << klein.get_data ()<< "\n";
44:
45: cout << "Bei einer mittleren Parzelle von "<< mittel.get_area ()<< "\n";
46: cout << "betragen die Grundsteuern DM "<< mittel.get_data2 () << "\n";
47: cout << "und die Vermoegenssteuern DM " << mittel.get_data ()<< "\n";
48:
49: cout << "Bei einer grossen Parzelle von "<< gross.get_area ()<< "\n";
50: cout << "betragen die Grundsteuern DM "<< gross.get_data2 () << "\n";
51: cout << "und die Vermoegenssteuern DM " << gross.get_data ()<< "\n";
52: return 0;
53: }

Dieses Programm gibt die Fläche eines Rechtecks und auch die hypothetischen Steuern für einen rechteckigen Bereich aus. Die Ausgabe ist in Abbildung A.11 zu sehen.

Abbildung A.11:
Ausgabe des Programms Class3.cpp

Die Zeilen 5 bis 14 definieren die Klasse Steuer_bescheid. Die Klasse enthält die zwei privaten Datenelementen int Grund_steuer und int Verm_steuer sowie drei öffentliche Methoden. Insbesondere sollten Sie sich die Deklaration und Definition dieser Methoden ansehen. In den früheren Beispielen haben Sie nur die Methoden in der Klasse deklariert. Der Zugriff auf die Funktionsdefinitionen erfolgte mit dem Zugriffsoperator (::). Im vorliegenden Beispiel deklarieren Sie die Methode und schreiben auch gleich die Definition. Man bezeichnet diese Technik als Inline-Implementierung der Funktion. Diese Technik bietet sich an, wenn die Definition einer Funktion kurz und knapp ist. Darüber hinaus läßt sich damit die Effizienz des Programms (Ausführungsgeschwindigkeit) verbessern, weil das Programm keine Sprünge in die und aus der Funktionsdefinition unternehmen muß.

Die Datenelemente Grund_steuer und Verm_steuer sind privat, so daß man darauf nur über deren Elementmethoden - nämlich set(), get_Verm_steuer() und get_Grund_steuer() - zugreifen kann.

Die Zeilen 17 bis 29 deklarieren die Klasse Parzelle mit ihren Datenelementen und Methoden. In Zeile 20 wird die Klasse Steuer_bescheid in die Klasse eingebettet. In dieser Zeile wird auch das Objekt steuern deklariert, das unter dem Schutz der Klasse Parzelle steht. Die einzigen Methoden, über die man auf dieses Objekt zugreifen kann, sind diejenigen, die zur Klasse Parzelle gehören. Die Klasse Parzelle verfügt über vier öffentliche Methoden, die in Zeile 22 und den Zeilen 26 bis 28 deklariert und definiert sind. Zeile 25 der Methode set() läßt eine weitere set()-Methode definieren. Es handelt sich hier nicht um eine rekursive Methode, sondern eher um ein weiteres Beispiel für das Überladen von Funktionen. Die set()-Methoden in Zeile 10 und Zeile 22 unterscheiden sich in der Anzahl der Parameter. Die Methode set() in Zeile 25 kann auf das Objekt steuern zugreifen, da es unter der Klasse Steuer_bescheid in Zeile 20 definiert ist.

Die Funktion main() beginnt in Zeile 32 und hat den Rückgabetyp int. In Zeile 34 werden die Objekte der Klasse Parzelle deklariert. Die Zeilen 36 bis 38 setzen die Werte der Objekte mittels der Methode set(). Hierbei ist besonders zu beachten, daß die Klasse Steuer_bescheid keine Objekte in der Methode main() hat, so daß Sie auf keine Datenelemente oder Methoden dieser Klasse von main() aus zugreifen können.

Zeile 41 gibt die Fläche der Parzelle durch Aufruf der Methode get_area() auf einem Objekt der Klasse Parzelle aus. Die Zeile 42 enthält die Ausgabe der Grundsteuer durch Aufruf der Methode get_data2() auf einem Objekt der Klasse Parzelle. Dieses Vorgehen ist erforderlich, weil die Grund_steuer ein Datenelement der Klasse Steuer_bescheid ist, auf die man in der Methode main() nicht direkt zugreifen kann. Man verwendet hier die Methode get_data2(), die eine Methode der Klasse Parzelle ist und Zugriff auf das Objekt steuern hat, auf das man wiederum über get_Grund_steuer zugreifen kann.

Vererbung

Einer der Vorteile bei der Programmierung in C++ oder einer anderen objektorientierten Sprache besteht darin, daß man eine globale Herangehensweise in eine lokale überführen kann. Nehmen wir an, Sie entwickeln ein Programm, das alle Metalle und deren Eigenschaften einschließt. Wenn Sie mit der Klassenlösung des vorherigen Abschnitts arbeiten, haben Sie wahrscheinlich eine Klasse namens Metalle. Die Datenelemente der Klasse Metalle könnten Dichte und Volumen sein. Weiterhin könnten Sie eine Klasse Gold und eine Klasse Aluminium erstellen. Die Datenelemente, die Gold und Aluminium beschreiben, brauchen alle Eigenschaften der Metalle und daneben ihre eigenen Datenelementen wie Farbe und Glanz. Wenn Sie eine Hierarchie von Klassen entwickeln, so daß etwa die Klassen für Gold und Aluminium nur ihre individuellen Datenelemente enthalten, aber die allgemeinen Eigenschaften von der übergeordneten Klasse Metalle übernehmen - dann arbeiten Sie mit Vererbung.

Vererbung bezeichnet man auch als Ableitung. Die neue Klasse erbt die Funktionalität einer vorhandenen Klasse. Die vorhandene Klasse heißt Basisklasse und die neue Klasse abgeleitete Klasse. Eine ähnliche Vererbung läßt sich für Tiere, Säugetiere und Hunde ableiten.

Mit dem Doppelpunktoperator (:) kann man eine neue Klasse von einer Basisklasse ableiten:

class Mensch : public Saeugetier

In diesem Beispiel ist die neue Klasse Mensch von der Basisklasse Saeugetier abgeleitet. Die abgeleitete Klasse Mensch erhält die gesamte Funktionalität der Basisklasse Saeugetier. Darüber hinaus kann die Klasse Mensch weitere Funktionen enthalten, wie etwa die Fähigkeit, Auto zu fahren oder seinen Lebensunterhalt zu verdienen. Das Beispiel in Listing A.13 zeigt, wie man die Objekte vom Typ Mensch erzeugt und auf dessen Daten und Funktionen zugreift.

Listing A.13: Das Programm Vererbg1.cpp

1: // Arbeitsbereich Name: Vererbung
2: // Programmname : Vererbg1.cpp
3:
4: #include <iostream.h>
5: enum GESCHLECHT { MAENNLICH, WEIBLICH };
6:
7: class Saeugetier
8: {
9: public:
10: // Konstruktoren
11: Saeugetier():seinAlter(35), seinGewicht(90){}
12: ~Saeugetier(){}
13:
14: int GetAlter()const { return seinAlter; }
15: void SetAlter(int alter) { seinAlter = alter; }
16: int GetGewicht() const { return seinGewicht; }
17: void SetGewicht(int gewicht) { seinGewicht = gewicht; }
18:
19:
20: protected:
21: int seinAlter;
22: int seinGewicht;
23: };
24:
25: class Mensch : public Saeugetier
26: {
27: public:
28:
29: // Konstruktoren
30: Mensch():seinGeschlecht(MAENNLICH){}
31: ~Mensch(){}
32:
33: GESCHLECHT GetGeschlecht() const { return seinGeschlecht; }
34: void SetGeschlecht(GESCHLECHT geschlecht) { seinGeschlecht = geschlecht; }
35:
36: void Fahren() { cout << "Zur Arbeit fahren...\n"; }
37: void Arbeiten() { cout << "Arbeiten...\n"; }
38:
39: private:
40: GESCHLECHT seinGeschlecht;
41: };
42:
43: void main()
44: {
45: Mensch Otto_normal;
46: Otto_normal.Fahren();
47: Otto_normal.Arbeiten();
48: cout << "Otto_normal ist " << Otto_normal.GetAlter() << " Jahre alt\n";
49: cout << "und wiegt " <<Otto_normal.GetGewicht() << " Kg \n";
50: }

Die Ausgabe des Programms von Listing A.13 zeigt Abbildung A.12.

Abbildung A.12:
Ausgabe des Vererbungsprogramms

Zeile 5 enthält eine Definition mit dem Schlüsselwort enum. Aufzählungen (enumerations) definieren einen neuen Datentyp mit einer Liste von Bezeichnern. Den Bezeichnern sind feste Wert in aufsteigender Reihenfolge zugeordnet. In diesem Beispiel hat die Variable MAENNLICH den Wert 0 und die Variable WEIBLICH den Wert 1. Es handelt sich dabei um die automatisch erzeugten Standardwerte. Man kann die Werte auch selbst neu festlegen:

enum Alphabet { A, B, C=5, D=1 }

In diesem Beispiel hat A den Wert 0, B den Wert 1, C ist 5 und D gleich 1. Wenn Sie keine speziellen Werte angeben, gilt die folgende Zuordnung:

A ist 0
B ist 1
C ist 2
D ist 3

In Zeile 20 finden Sie ein weiteres neues Schlüsselwort in der Definition der Klasse Saeugetier: protected (geschützt). Auf die Schlüsselwörter public und private sind wir bereits weiter oben eingegangen, als Klassen besprochen wurden, deren Datenelemente alle unter dem Schlüsselwort private definiert waren. Sobald ein Datenelement als private definiert ist, kann die abgeleitete Klasse nicht mehr darauf zugreifen. Damit den abgeleiteten Klassen die Datenelemente und Methoden der Basisklasse zugänglich sind, muß man sie als protected definieren. Das Schlüsselwort protected erlaubt den Zugriff nur durch die abgeleiteten Klassen. Alternativ kann man diese Methoden und Elemente als public definieren, wodurch man allen Klassen freien Zugriff gewährt. Diese Lösung empfiehlt sich allerdings nicht, da man von der Kapselung abkommt.

Zeile 7 deklariert die Basisklasse Saeugetier. Der Konstruktor steht in Zeile 11, der Destruktor in Zeile 12. Der Aufruf des Konstruktors einer Klasse erfolgt immer dann, wenn ein Objekt der Klasse erzeugt wird. Die Konstruktorklasse führt eine zusätzliche Funktion aus, um die Datenelemente seinAlter(35) und seinGewicht(90) zu initialisieren. Das hätte man auch im Rumpf des Konstruktors wie im folgenden Beispiel erreichen können:

Saeugetier()
{
seinAlter = 35;
seinGewicht = 180;
};

Diese Art der Initialisierung der Datenelemente in der Konstruktordeklaration (wie in Zeile 11 von Listing A.13) ist infolge der internen Initialisierung von Klassen in C++ weitaus effizienter. Greifen Sie möglichst immer auf dieses Verfahren zurück, da es die Effizienz des Codes verbessert.

Wenn man ein Objekt einer abgeleiteten Klasse erzeugt, wird zuerst der Konstruktor der Basisklasse und danach der Konstruktor der abgeleiteten Klasse aufgerufen. Wenn im Beispiel Otto_normal erstmals erzeugt wird, erfolgt der Aufruf des Konstruktors der Basisklasse Saeugetier. Das Objekt Otto_normal wird erst dann erstellt, wenn sowohl der Konstruktor der Basisklasse als auch der Konstruktor der abgeleiteten Klasse aufgerufen wurden. Bei Destruktoren ist die Reihenfolge umgekehrt. Nachdem das Objekt Otto_normal nicht mehr existiert, wird der Destruktor der abgeleiteten Klasse vor dem Destruktor der Basisklasse aufgerufen. Zeile 25 definiert den Namen der abgeleiteten Klasse und gibt den Bezug zur betreffenden Basisklasse an.

Im Hinblick auf den Zugriff und die Ausgabe der Daten verdienen die Zeilen 48 und 49 eine besondere Beachtung. In diesen Zeilen greift das Mensch-Objekt Otto_normal direkt auf die Informationen aus der Basisklasse Saeugetier zu. Wie Sie noch aus dem Beispiel Klasse3.cpp wissen, mußte man indirekt auf die Klasse Steuer_bescheid zugreifen, um Daten aus einer verschachtelten Klasse auszugeben.

Die Vererbung stellt ein bedeutsames Werkzeug in der objektorientierten Programmierung dar. Und wenn man sie effektiv einsetzt, gewährleistet sie die Wiederverwendbarkeit von Code. Das Programm Vererbg1.cpp hat Ihnen einen ersten Gesamteindruck der Vererbung und ihrer Eigenschaften gebracht. Wenn man »richtige« Programme schreibt, strukturiert man sie effizienter. Das nächste Programm geht das Ganze logischer und formeller an.

Nehmen wir an, daß Sie ein Programm für den Automarkt schreiben. Hier haben wir es mit PKWs, LKWs, Minivans und Sportwagen zu tun. Die übergeordnete oder Basisklasse ist Autos, die anderen Klassen sind abgeleitet. Definieren wir zunächst die Klasse Autos. Listing A.14 zeigt den Code.

Listing A.14: Die Header-Datei Auto.h

1: // Arbeitsbereich: Vererbg2
2: // Programmname: Auto.h
3:
4: #ifndef AUTO_H
5: #define AUTO_H
6:
7: class autos
8: {
9: protected:
10: int verbrauch;
11: float tankfuellung;
12: public:
13: void initialize(int in_verbr, float in_tank);
14: int get_verbr(void);
15: float get_tank(void);
16: float reichweite(void);
17: };
18:
19: #endif

Die Zeilen 4 und 5 enthalten Direktiven an den Präprozessor. Auf die Direktive in Zeile 4 gehen wir gegen Ende dieses Abschnitts näher ein. Zeile 7 definiert die Klasse Autos . Diese Klasse umfaßt zwei Datenelemente und vier Methoden. In der Header-Datei steht nur die Deklaration der Klasse. Die Definition der zugehörigen Methoden befindet sich in der Datei Auto.cpp, die in Listing A.15 wiedergegeben ist.

Listing A.15: Die Quellcodedatei Auto.cpp

1: // Arbeitsbereich: Vererbg2
2: // Programmname : Auto.cpp
3: #include "Auto.h"
4:
5:
6: void autos::initialize(int in_verbr, float in_tank)
7: {
8: verbrauch = in_verbr;
9: tankfuellung = in_tank;
10: }
11:
12: // Durchschnittsverbrauch bestimmen
13: int autos::get_verbr()
14: {
15: return verbrauch;
16: }
17:
18: // Tankkapazität ermitteln
19: float autos::get_tank()
20: {
21: return tankfuellung;
22: }
23:
24: // Mögliche Reichweite zurückgeben
25: float autos::reichweite()
26: {
27: return verbrauch * tankfuellung;
28: }

Die Methode get_verbr liefert den Wert für den Kraftstoffverbrauch für ein bestimmtes Fahrzeug. Über die Methode get_tank erhält man das Fassungsvermögen des Tanks zurück. Als nächstes definieren Sie die erste abgeleitete Klasse, eine Wagen- Klasse gemäß Listing A.16.

Listing A.16: Die Header-Datei Wagen.h

1: // Arbeitsbereich: Vererbg2
2: // Programmname: Wagen.h
3:
4: #ifndef WAGEN_H
5: #define WAGEN_H
6:
7: #include "Auto.h"
8:
9: class Wagen : public Autos
10: {
11: int anzahl_tueren;
12: public:
13: void initialize(int in_verbr, float in_tank, int tuer = 4);
14: int tueren(void);
15: };
16:
17: #endif

Die Klasse Wagen ist von der Klasse Autos abgeleitet. In dieser Eigenschaft hat sie Zugriff auf alle Methoden der Basisklasse Autos. Zusätzlich verfügt die Klasse Wagen über ein Datenelement für die Anzahl der Türen. Die Methoden der Klasse Wagen sind in der Datei Wagen.cpp gemäß Listing A.17 definiert.

Listing A.17: Die Quellcodedatei Wagen.cpp

1: // Arbeitsbereich: Vererbg2
2: // Programmname: Wagen.cpp
3:
4: #include "Wagen.h"
5:
6: void Wagen::initialize(int in_verbr, float in_tank, int tuer)
7: {
8: anzahl_tueren = tuer;
9: verbrauch = in_verbr;
10: tankfuellung = in_tank;
11: }
12:
13:
14: int Wagen::tueren(void)
15: {
16: return anzahl_tueren;
17: }

Die Methode initialization ist in den Zeilen 6 bis 11 definiert. Hierbei ist zu beachten, daß auch die Basisklasse Autos (Auto.h) über eine Methode initialization verfügt. Die Methode initialization in der Klasse Wagen überschreibt die Methode initialization der Basisklasse. Zu guter Letzt folgt die Definition von main(). Die Methode main() ist in der Datei Fuhrpark.cpp (Listing A.18) definiert.

Listing A.18: Die Quellcodedatei Fuhrpark.cpp

1: // Arbeitsbereich: Vererbg2
2: // Programmname: Fuhrpark.cpp
3:
4: #include <iostream.h>
5: #include "Auto.h"
6: #include "Wagen.h"
7:
8: int main()
9: {
10:
11: Wagen sedan;
12:
13: sedan.initialize(24, 20.0, 4);
14: cout << "Der Sedan hat eine Reichweite von " << sedan.reichweite() <<
15: " Kilometern.\n";
16: cout << "Der Sedan hat " << sedan.tueren() << " Tueren.\n";
17:
18: return 0;
19: }

In main() ist nur ein einziges Objekt definiert. Zeile 11 deklariert ein Objekt der Klasse Wagen. Die Initialisierung steht in Zeile 13. Die Methode initialization übergibt den Durchschnittsverbrauch des Autos (verbrauch) und das Fassungsvermögen des Tanks. Mit diesen Informationen erfolgt der Zugriff auf die Methode reichweite in der Basisklasse, die in Auto.cpp definiert ist. Die abgeleitete Klasse hat Zugriff auf die Methoden der Basisklasse. Darüber hinaus übergibt die abgeleitete Klasse Informationen an ihr eigenes Datenelement zur Anzahl der Fahrzeugtüren. Das Ergebnis bei Ausführung des Programms zeigt Abbildung A.13.

Abbildung A.13:
Klassenergebnisse von Vehicle

Sie können nun weitere Klassen für anderen Fahrzeugtypen hinzufügen. Erstellen Sie eigene Klassen für einen LKW und einen Minivan, und leiten Sie sie von der Basisklasse genauso ab wie die Klasse Wagen.

Wenn Sie eine andere Klasse für LKWs hinzufügen, achten Sie darauf, die Präprozessordirektiven der Zeilen 4 und 19 von Listing A.14 einzubinden. Diese Zeilen seien hier noch einmal genannt:

4 #ifndef AUTO_H
5 #define AUTO_H
.
.
.
19 #endif

Da die Klasse LKW von der übergeordneten Klasse Autos abgeleitet ist, muß sie die Datei Auto.h in LKW.h einbinden. Der Header der Klasse Wagen, Wagen.h, bindet bereits Auto.h aus dem gleichen Grund ein. Wenn Sie nun eine Methode erzeugen, die sowohl die Klasse LKW als auch die Klasse Wagen verwendet, binden Sie Auto.h möglicherweise zweimal ein, was zu einem Compilerfehler führt. Um das zu verhindern, fügen Sie die Zeilen 4 und 5 von Listing A.14 hinzu. Durch den Befehl in Zeile 4 prüft der Compiler, ob die Klasse Autos (anhand des Bezeichners AUTO_H) bereits definiert ist. Wenn das nicht der Fall ist, springt das Programm zu Zeile 5, definiert den Bezeichner und dann die Klasse. Ist die Klasse bereits definiert, springt das Programm zu Zeile 19 und endet hier.

Zusammenfassung

Gratulation! Sie haben nun fast alle Merkmale und Eigenschaften von C++ kennengelernt. Damit besitzen Sie eine solide Grundlage, um die Vorteile von Visual C++ und der objektorientierten Programmierung umfassend nutzen zu können.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Visual C++ 6 in 21 Tagen, ISBN: 3-8272-2035-1