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.
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++.
Gehen Sie auf die Registerkarte Projekte (siehe Abbildung A.1)
Abbildung A.1:
Den Arbeitsbereich für das Projekt Hello einrichten
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
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.
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
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 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.
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.
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);
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.
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.
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.
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: }
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 |
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.
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.
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: }
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.
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
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 |
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.
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.
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
.....
.....
}
public
deklariert sein.
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:
~
(Tilde) voranzustellen.
public
zu deklarieren.
class farm_house
{
public:
farm_house (); // Konstruktor
~farm_house(); // Destruktor
.....
}
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:
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: };
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.
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.
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.