vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 1

Tag 6



Klassen

Klassen erweitern die in C++ vordefinierten Fähigkeiten und unterstützen damit die Darstellung und Lösung komplexer Probleme in der Praxis.

Heute lernen Sie,

Neue Typen erzeugen

Mittlerweile haben Sie eine Reihe von Variablentypen kennengelernt, einschließlich vorzeichenloser ganzer Zahlen und Zeichen. Der Typ einer Variablen sagt eine Menge über die Variable aus. Wenn Sie zum Beispiel Height und Width als ganze Zahlen des Typs unsigned short deklarieren, wissen Sie, daß jede dieser Variablen eine Zahl zwischen 0 und 65.535 aufnehmen kann (vorausgesetzt, daß unsigned short eine Länge von 2 Byte hat). Und genau das ist mit unsigned short int gemeint. Irgend etwas anderes in diesen Variablen abzulegen, führt zu einer Fehlermeldung. Es ist nicht möglich, Ihren Namen in einer Integer-Variablen vom Typ unsigned short unterzubringen, und Sie sollten es am besten gar nicht erst versuchen.

Somit ergibt sich allein aus der Deklaration von Height und Width als unsigned short int, daß man Height und Width addieren und diese Zahl einer anderen Zahl zuweisen kann.

Der Typ dieser Variablen sagt etwas aus über

Allgemein betrachtet, ist ein Typ eine Kategorie. Zu den alltäglichen Typen gehören Auto, Haus, Person, Frucht und Figur. Ein C++-Programmierer kann jeden benötigten Typ erzeugen, und jeder dieser neuen Typen kann über die gesamte Funktionalität und Leistungsfähigkeit der vordefinierten, elementaren Typen verfügen.

Warum einen neuen Typ erzeugen?

Programme schreibt man in der Regel, um praxisgerechte Probleme zu lösen, wie etwa die Erfassung von Mitarbeiterdaten oder die Simulation der Abläufe eines Heizungssystems. Obwohl man komplexe Probleme auch mit Programmen lösen kann, die nur mit ganzen Zahlen und Zeichen arbeiten, bekommt man große, komplexe Probleme weitaus einfacher in den Griff, wenn man die Objekte, mit denen man es zu tun hat, direkt präsentieren kann. Mit anderen Worten läßt sich die Simulation der Abläufe eines Heizungssystems leichter umsetzen, wenn man Variablen für die Repräsentation von Räumen, Wärmesensoren, Thermostaten und Dampfkesseln erzeugen kann. Je besser diese Variablen der Realität entsprechen, desto einfacher ist es, das Programm zu schreiben.

Klassen und Elemente

Einen neuen Typ erzeugt man durch die Deklaration einer Klasse. Eine Klasse ist einfach eine Sammlung von Variablen - häufig mit unterschiedlichen Typen - kombiniert mit einer Gruppe zugehöriger Funktionen.

Ein Auto kann man sich als Sammlung von Rädern, Türen, Sitzen, Fenstern usw. vorstellen. Eine andere Möglichkeit ist es, das Auto als Sammlung seiner Funktionen aufzufassen: Es fährt, beschleunigt, bremst, stoppt, parkt und so weiter. Mit einer Klasse können Sie diese verschiedenen Elemente und Funktionen kapseln beziehungsweise in einer Sammlung bündeln, die dann auch Objekt genannt wird.

Die Kapselung aller Informationen über ein Auto in einer Klasse bietet für den Programmierer eine Reihe von Vorteilen. Alles ist an einer Stelle zusammengefaßt, wodurch man die Daten leicht ansprechen, kopieren und manipulieren kann. Des weiteren können Klienten Ihrer Klasse - das sind Teile des Programms, die Ihre Klasse nutzen - das Objekt nutzen, ohne sich um seinen Inhalt oder seine Funktionsweise kümmern zu müssen.

Eine Klasse kann aus jeder Kombination von Variablentypen, einschließlich anderer Klassentypen, bestehen. Variablen, die Teil einer Klasse sind, bezeichnet man als Elementvariablen oder Datenelemente. Eine Klasse Auto könnte beispielsweise über Elementvariablen zur Darstellung von Sitzen, Radiotypen, Reifen usw. besitzen.

Elementvariablen oder Datenelemente sind die Variablen Ihrer Klasse. Sie gehören zu Ihrer Klasse genau wie Räder und Motor Teile Ihres Autos sind.

Normalerweise manipulieren die Funktionen der Klasse die Elementvariablen. Man bezeichnet diese Funktionen als Elementfunktionen oder Methoden der Klasse. Methoden der Klasse Auto können zum Beispiel Starten() und Bremsen() sein. Eine Klasse Cat (Katze) könnte über Datenelemente verfügen, die das Alter und Gewicht repräsentieren, als Methoden sind Sleep(), Meow() und ChaseMice() vorstellbar.

Elementfunktionen - oder Methoden - sind die Funktionen in einer Klasse. Elementfunktionen gehören ebenso zur Klasse wie Elementvariablen und bestimmen, was Ihre Klasse leisten kann.

Klassen deklarieren

Um eine Klasse zu deklarieren, verwendet man das Schlüsselwort class gefolgt von einer öffnenden geschweiften Klammer und einer Liste der Datenelemente und Methoden dieser Klasse. Den Abschluß der Deklaration bildet eine schließende geschweifte Klammer und ein Semikolon. Die Deklaration einer Klasse namens Cat (Katze) sieht wie folgt aus:

class Cat
{
unsigned int itsAge;
unsigned int itsWeight;
Meow();
};

Die Deklaration dieser Klasse reserviert noch keinen Speicher für Cat. Es ist nur eine Mitteilung an den Compiler, was die Klasse Cat darstellt, welche Daten sie enthält (itsAge und itsWeight) und was sie tun kann (Meow()). Außerdem wird dem Compiler mitgeteilt, wie groß Cat ist - das heißt, wieviel Platz der Compiler für jedes erzeugte Cat-Objekt reservieren muß. In diesem Beispiel ist Cat lediglich 8 Byte groß (vorausgesetzt, daß für iNT 4 Byte reserviert werden): itsAge mit 4 Byte und itsWeight mit weiteren 4. Die Methode Meow() belegt keinen Platz, da für Elementfunktionen (Methoden) kein Speicher reserviert wird.

Ein Wort zur Namensgebung

Als Programmierer muß man alle Elementvariablen, Elementfunktionen und Klassen benennen. Wie Sie in Kapitel 3, »Variablen und Konstanten«, gelernt haben, sollten diese Namen leicht verständlich und aussagekräftig sein. Katze, Rechteck und Angestellter sind geeignete Klassennamen. Miau(), MausJagen() und MaschineAnhalten() sind passende Funktionsnamen, da man daraus die Aufgabe der Funktionen ablesen kann. Viele Programmierer benennen die Elementvariablen mit dem Präfix its (zu deutsch: ihr, bezieht sich auf Klasse) wie in itsAge, itsWeight und itsSpeed (ihrAlter, ihrGewicht, ihreGeschwindigkeit). Damit lassen sich Elementvariablen leichter von anderen Variablen unterscheiden.

C++ unterscheidet Groß-/Kleinschreibung, und alle Klassennamen sollten demselben Muster folgen. Auf diese Weise braucht man nie zu prüfen, wie man den Klassennamen schreiben muß - war es Rechteck, rechteck oder RECHTECK? Einige Programmierer setzen vor den Klassennamen einen bestimmten Buchstaben (beispielsweise cKatze oder cPerson), während andere den Namen durchgängig groß oder klein schreiben. Ich beginne alle Klassennamen mit einem Großbuchstaben wie in Cat oder Person.

In gleicher Weise beginnen viele Programmierer ihre Funktionen mit Großbuchstaben und die Variablen mit Kleinbuchstaben. Zusammengesetzte Wörter werden durch einen Unterstrich (wie in Maus_Jagen()) oder durch Großschreibung der einzelnen Wörter (wie in MausJagen() oder KreisZeichnen()) auseinandergehalten.

Wichtig ist vor allem, daß Sie sich einen Stil herausgreifen und ihn in jedem Programm konsequent anwenden. Mit der Zeit vervollkommnet sich dieser Stil und umfaßt nicht nur Namenskonventionen, sondern auch Einzüge, Ausrichtung von Klammern und Kommentare.

Software-Häuser pflegen in der Regel ihre eigenen, internen Standards festzulegen. Damit wird sichergestellt, daß die Entwickler untereinander ihren Code schnell und ohne Probleme lesen können.

Objekte definieren

Ein Objekt Ihres neuen Typs definieren Sie fast genauso wie eine Integer-Variable:

unsigned int GrossWeight;       // eine unsigned Integer-Variable definieren
Cat Frisky; // ein Cat-Objekt definieren

Dieser Code definiert eine Variable namens GrossWeight vom Typ unsigned int. Die zweite Zeile zeigt die Definition von Frisky als Objekt, dessen Klasse (oder Typ) Cat ist.

Klassen und Objekte

Die Definition einer Katze kann man nicht streicheln, man streichelt einzelne Katzen. Man unterscheidet also zwischen der Vorstellung von einer Katze und der realen Katze, die gerade durchs Wohnzimmer schleicht. Auf die gleiche Weise unterscheidet C++ zwischen der Klasse Cat als Vorstellung von einer Katze und jedem individuellen Cat-Objekt. Demzufolge ist Frisky ein Objekt vom Typ Cat, genauso wie GrossWeight eine Variable vom Typ unsigned int ist.

Ein Objekt ist einfach eine selbständige Instanz einer Klasse.

Auf Klassenelemente zugreifen

Nachdem man ein Cat-Objekt definiert hat (zum Beispiel Frisky), kann man mit dem Punktoperator (.) auf die Elemente dieses Objekts zugreifen. Zum Beispiel weist man den Wert 50 der Elementvariable Weight des Objekts Frisky wie folgt zu:

Frisky.itsWeight = 50;

In gleicher Weise ruft man die Funktion Meow() auf:

Frisky.Meow();

Wenn Sie die Methode einer Klasse verwenden, rufen Sie diese Methode auf. In diesem Beispiel rufen Sie Meow() für Frisky auf.

Auf Objekte, nicht auf Klassen zugreifen

In C++ weist man die Werte nicht den Typen, sondern den Variablen zu. Beispielsweise schreibt man niemals

int = 5;               // falsch

Der Compiler moniert dies als Fehler, da man den Wert 5 nicht an einen Integer-Typ zuweisen kann. Statt dessen muß man eine Integer-Variable erzeugen und dieser Variablen den Wert 5 zuweisen:

int  x;                // x als int definieren
x = 5; // den Wert von x auf 5 setzen

Das ist eine Kurzform für die Anweisung »weise 5 der Variablen x zu, die vom Typ int ist«. In diesem Sinne schreibt man auch nicht

Cat.itsAge = 5;           // falsch

Der Compiler würde dies als Fehler anmerken, da Sie der Cat-Klasse kein Alter zuweisen können. Man muß erst ein Cat-Objekt definieren und dann diesem Objekt den Wert 5 zuweisen:

Cat Frisky;            // analog zum Beispiel int x;
Frisky.age = 5; // analog zum Beispiel x = 5;

Was Sie nicht deklarieren, gibt es in Ihrer Klasse nicht

Machen Sie mal folgenden Versuch: Gehen Sie auf ein dreijähriges Kind zu und zeigen Sie ihm eine Katze. Sagen Sie dann »Das ist Frisky, Frisky kennt einen Trick. Frisky bell' mal.« Das Kind wird sicherlich kichern und sagen »Nein, du Dummkopf, Katzen können doch nicht bellen.«

Würden Sie als Code

Cat  Frisky;        // erzeugt eine Katze namens Frisky
Frisky.Bark() // teilt Frisky mit, zu bellen

eingeben, würde der Compiler ebenfalls sagen »Nein, du Dummkopf, Katzen können doch nicht bellen.« (Der genaue Wortlaut hängt von Ihrem Compiler ab.) Der Compiler weiß, daß Frisky nicht bellen kann, da die Cat-Klasse nicht über eine Bark()-Funktion verfügt. Der Compiler würde sogar Frisky das Miauen untersagen, wenn die Meow()-Funktion nicht vorher definiert wäre.

Was Sie tun sollten

... und was nicht

Deklarieren Sie Klassen mit dem Schlüsselwort class.

Verwenden Sie den Punktoperator (.), um auf Elementfunktionen und Datenelemente zuzugreifen.

Bringen Sie Deklaration nicht mit Definition durcheinander. Eine Deklaration teilt Ihnen mit, was eine Klasse ist. Eine Definition reserviert Speicher für ein Objekt.

Verwechseln Sie nicht Klasse und Objekt.

Weisen Sie einer Klasse keine Werte zu. Weisen Sie diese Werte den Datenelementen eines Objekts zu.

Private und Public

Bei der Deklaration einer Klasse kommen noch weitere Schlüsselwörter zum Einsatz. Zwei der wichtigsten sind public (öffentlich) und private (privat)

Alle Elemente einer Klasse - Daten und Methoden - sind per Vorgabe privat. Auf private Elemente kann man nur innerhalb der Methoden der Klasse selbst zugreifen. Öffentliche Elemente sind über jedes Objekt der Klasse zugänglich. Der Unterschied zwischen beiden ist wichtig und verwirrend zugleich. Um es klarer herauszustellen, sehen wir uns ein früheres Beispiel dieses Kapitels an:

class Cat
{
unsigned int itsAge;
unsigned int itsWeight;
void Meow();
};

In dieser Deklaration sind itsAge, itsWeight und Meow() privat, da alle Elemente einer Klasse per Vorgabe privat sind. Solange man also nichts anderes festlegt, bleiben sie privat.

Wenn man allerdings in main() (zum Beispiel) schreibt

Cat  Boots;
Boots.itsAge=5; // Fehler! Kann nicht auf private Daten zugreifen!

zeigt der Compiler einen Fehler an. Praktisch hat man dem Compiler gesagt: »Ich werde auf itsAge, itsWeight und Meow() nur innerhalb der Elementfunktionen der Cat- Klasse zugreifen.« Im Beispiel findet jedoch ein Zugriff auf die Elementvariable itsAge des Boots-Objekts von außerhalb einer Cat-Methode statt. Nur weil Boots ein Objekt der Klasse Cat ist, heißt das nicht, daß man auf die privaten Teile von Boots zugreifen kann.

Dies ist ein Auslöser von häufiger Verwirrung bei Programmierneulingen in C++. Ich höre Sie förmlich schreien »Hey, ich habe gerade festgelegt, daß Boots eine Katze ist. Warum kann Boots nicht auf sein eigenes Alter zugreifen?«

Die Antwort lautet schlicht, daß Boots kann, Sie aber nicht. Boots kann innerhalb seiner eigenen Methoden auf alle seine Bestandteile - öffentlich oder privat - zugreifen. Auch wenn Sie eine Cat-Klasse erzeugt haben, bedeutet das nicht, daß Sie alle ihre Komponenten, die privat sind, auch sehen oder ändern können.

Um auf die Datenelemente von Cat zugreifen zu können, deklariert man einen Abschnitt der Klasse Cat als public:

class Cat
{
public:
unsigned int itsAge;
unsigned int itsWeight;
Meow();
};

Nunmehr sind itsAge, itsWeight und Meow() öffentlich (public). Der Compiler hat nichts mehr an Boots.itsAge=5 auszusetzen.

Listing 6.1 enthält die Deklaration einer Cat-Klasse mit öffentlichen Elementvariablen.

Listing 6.1: Zugriff auf die öffentlichen Elemente einer einfachen Klasse

1:   // Deklaration einer Klasse und 
2: // Definition eines Objekts dieser Klasse,
3:
4: #include <iostream.h> // für cout
5:
6: class Cat // Deklariert das Klassenobjekt
7: {
8: public: // folgende Elemente sind public
9: int itsAge;
10: int itsWeight;
11: };
12:
13:
14: int main()
15: {
16: Cat Frisky;
17: Frisky.itsAge = 5; // Wertzuweisung an die Elementvariable
18: cout << "Frisky ist eine Katze, die ;
19: cout << Frisky.itsAge << " Jahre alt ist.\n";
20: return 0;
21: }

Frisky ist eine Katze, die 5 Jahre alt ist.

Zeile 6 enthält das Schlüsselwort class. Damit wird dem Compiler mitgeteilt, daß jetzt eine Deklaration folgt. Der Name der neuen Klasse steht direkt hinter dem Schlüsselwort class. In diesem Fall lautet er Cat.

Der Rumpf der Deklaration beginnt mit der öffnenden geschweiften Klammer in Zeile 7 und endet mit der schließenden geschweiften Klammer und dem Semikolon in Zeile 11. Zeile 8 enthält das Schlüsselwort public, das anzeigt, daß alles, was folgt, öffentlich ist, bis der Compiler auf das Schlüsselwort private trifft oder das Ende der Klassendeklaration erreicht ist.

Die Zeilen 9 und 10 enthalten die Deklarationen der Datenelemente der Klasse: itsAge und itsWeight.

Zeile 14 beginnt mit der main()-Funktion des Programms. In Zeile 16 wird Frisky als Instanz von Cat definiert, das heißt als ein Cat-Objekt. Zeile 17 setzt Friskys Alter auf 5. Die Zeile 18 und 19 verwenden die Elementvariable itsAge, um die Meldung von Frisky auszugeben.

Versuchen Sie einmal, die Zeile 8 auszukommentieren und den Quellcode dann neu zu kompilieren. Sie werden eine Fehlermeldung zu Zeile 17 erhalten, da auf itsAge nicht länger öffentlich zugegriffen werden kann. Standardmäßig sind alle Elemente einer Klasse privat.

Private Datenelemente

Als allgemeine Entwurfsregel gilt, daß man die Datenelemente einer Klasse privat halten sollte. Daher muß man öffentliche Elementfunktionen, die sogenannten Zugriffsmethoden , aufsetzen, über die andere Teile des Programms die Werte der privaten Elementvariablen abfragen oder verändern können.

Eine öffentliche Zugriffsmethode ist eine Elementfunktion einer Klasse, mit der man den Wert einer privaten Elementvariablen entweder lesen oder setzen kann.

Warum soll man sich mit einer weiteren Ebene des indirekten Zugriffs herumplagen? Schließlich wäre es doch einfacher und bequemer, die Daten direkt zu bearbeiten, statt den Umweg über die Zugriffsfunktionen zu gehen.

Zugriffsfunktionen gestatten es, die Einzelheiten der Speicherung von der Verwendung der Daten zu trennen. Damit kann man die Speicherung der Daten verändern, ohne daß man die Funktionen neu schreiben muß, die mit den Daten arbeiten.

Eine Funktion, die das Alter von Cat benötigt und deshalb auf itsAge direkt zugreift, muß neu geschrieben werden, wenn Sie als der Autor der Cat-Klasse Änderungen an der Art und Weise, in der die betreffende Information gespeichert wird, vornehmen. Wenn diese Funktion aber GetAge() aufruft, kann Ihre Cat-Klasse dafür sorgen, daß der richtige Wert zurückgeliefert wird, unabhängig davon, wie das Alter zu ermitteln ist. Die aufrufende Funktion muß nicht wissen, ob die Klasse das Alter als Integer vom Typ unsigned short oder long speichert oder ob sie es bei Bedarf jedes Mal neu berechnet.

Auf diese Weise wird Ihr Programm einfacher zu warten. Ihr Programm wird eine längere Lebensdauer haben, da es nicht durch Änderungen am Entwurf veraltet.

In Listing 6.2 sehen Sie die Cat-Klasse mit leichten Änderungen, um private Elementdaten und öffentliche Zugriffsmethoden einzurichten. Es handelt sich dabei jedoch nicht um ein ausführbares Listing.

Listing 6.2: Eine Klasse mit Zugriffsmethoden

1:       // Deklaration der Klasse Cat 
2: // Datenelemente sind privat, oeffentliche Zugriffsmethoden
3: // helfen die Werte der privaten Daten auszulesen oder zu setzen
4:
5: class Cat
6: {
7: public:
8: // oeffentliche Zugriffsmethoden
9: unsigned int GetAge();
10: void SetAge(unsigned int Age);
11:
12: unsigned int GetWeight();
13: void SetWeight(unsigned int Weight);
14:
15: // oeffentliche Elementfunktionen
16: void Meow();
17:
18: // private Elementdaten
19: private:
20: unsigned int itsAge;
21: unsigned int itsWeight;
22:
23: };

Diese Klasse hat fünf öffentliche Methoden. Die Zeilen 9 und 10 enthalten die Zugriffsmethoden für itsAge und die Zeilen 12 und 13 die Zugriffsmethoden für itsWeight . Diese Zugriffsmethoden setzen die Elementvariablen und liefern ihre Werte zurück.

Zeile 16 deklariert die öffentliche Elementfunktion Meow(). Meow() ist keine Zugriffsfunktion. Weder liest sie eine Elementvariable aus noch setzt sie welche. Ihre Aufgabe für die Klasse ist eine andere: Sie gibt das Wort Meow() aus.

Die Zeile 20 und 21 deklarieren die Elementvariablen.

Um das Alter von Frisky zu setzen, würden Sie den Wert an die SetAge()-Methode wie folgt übergeben:

Cat Frisky;
Frisky.SetAge(); // setzt das Alter von Frisky mit Hilfe der oeffentlichen
// Zugriffsmethode

Privat und Sicher

Indem Sie Methoden und Daten als private deklarieren, ermöglichen Sie es dem Compiler, Programmschwächen und -fehler zu finden, bevor Sie zu echten Laufzeitfehlern ausarten. Jeder Programmierer, der sein Geld wert ist, kann einen Weg finden, private-Deklarationen zu umgehen. Stroustrup, der Begründer von C++, sagte: »Die Mechanismen der Zugriffssteuerung in C++ bieten Schutz gegen Zufälle - nicht gegen Betrug.« (ARM, 1990.)

Das Schlüsselwort class

Die Syntax für das Schlüsselwort class lautet:

class klassen_name
{
// hier Schluesselwort der Zugriffssteuerung
// hier Deklaration der Variablen und Methoden der Klasse
};

Das Schlüsselwort class wird verwendet, um neue Typen zu deklarieren. Eine Klasse ist Sammlung von Datenelementen, bei denen es sich um Variablen aller Typen handeln kann, einschließlich anderer Klassen. Darüber hinaus enthält die Klasse Funktionen, auch Elementmethoden genannt, mit denen die Daten in der Klasse manipuliert und andere Aufgaben für die Klasse erledigt werden.

Objekte des neuen Typs werden fast genauso definiert wie Variablen. Sie geben den Typ (class) und dann den Variablennamen (das Objekt) an. Der Zugriff auf die Datenelemente und die Elementfunktionen erfolgt über den Punktoperator (.).

Die Schlüsselwörter zur Zugriffssteuerung werden benutzt, um Abschnitte der Klasse als public oder private zu deklarieren. Die Vorgabe für die Zugriffssteuerung ist private. Jedes Schlüsselwort ändert die Zugriffssteuerung von diesem Punkt an bis zum Ende der Klasse oder bis zum nächsten Zugriffsspezifizierer. Klassendeklarationen enden mit einer schließenden geschweiften Klammer und einem Semikolon.

Beispiel 1:

class Cat
{
public:
unsigned int Age;
unsigned int Weight;
void Meow();
};

Cat Frisky;
Frisky.Age = 8;
Frisky.Weight = 18;
Frisky.Meow();

Beispiel 2

class Car
{
public: // die nächsten fünf sind public


void Start();
void Accelerate();
void Brake();
void SetYear(int year);
int GetYear();

private: // der Rest ist private

int Year;
Char Model [255];
}; // Ende der Klassendeklaration

Car OldFaithful; // eine Instanz von car
int bought; // eine lokale Integer-Variable
OldFaithful.SetYear(84) ; // weist year 84 zu
bought = OldFaithful.GetYear(); // setzt bought auf 84
OldFaithful.Start(); // ruft die start-Methode auf

Was Sie tun sollten

... und was nicht

Deklarieren Sie Elementvariablen als private.

Verwenden Sie öffentliche Zugriffsmethoden.

Greifen Sie auf private Elementvariablen nur von innerhalb der Elementfunktionen der Klassen zu.

Versuchen Sie nicht, private Elementvariablen außerhalb der Klasse zu verwenden.

Klassenmethoden implementieren

Wie Sie gesehen haben, stellen Zugriffsfunktionen eine öffentliche Schnittstelle zu den privaten Datenelementen der Klasse dar. Jede Zugriffsfunktion muß wie alle anderen Methoden einer Klasse, die Sie deklarieren, implementiert werden. Diese Implementierung wird auch Definition genannt.

Die Definition einer Elementfunktion beginnt mit dem Namen der Klasse, gefolgt von zwei Doppelpunkten, dem Namen der Funktion und ihren Parametern. Listing 6.3 zeigt die vollständige Deklaration einer einfachen Cat-Klasse mit der Implementierung einer Zugriffsfunktion und einer allgemeinen Elementfunktion.

Listing 6.3: Methoden einer einfachen Klasse implementieren

1:   // Zeigt die Deklaration einer Klasse und
2: // die Definition von Klassenmethoden
3:
4: #include <iostream.h> // für cout
5:
6: class Cat // Beginn der Klassendeklaration
7: {
8: public: // Beginn des oeffentlichen Abschnitts
9: int GetAge(); // Zugriffsfunktion
10: void SetAge (int age); // Zugriffsfunktion
11: void Meow(); // Allgemeine Funktion
12: private: // Beginn des privaten Abschnitts
13: int itsAge; // Elementvariable
14: };
15:
16: // Die oeffentliche Zugriffsfunktion GetAge gibt
17: // den Wert des Datenelements itsAge zurück.
18: int Cat::GetAge()
19: {
20: return itsAge;
21: }
22:
23: // Definition der oeffentlichen
24: // Zugriffsfunktion SetAge .
25: // Gibt Datenelement itsAge zurück.
26: void Cat::SetAge(int age)
27: {
28: // Elementvariable itsAge auf den als
29: // Parameter age uebergebenen Wert setzen.
30: itsAge = age;
31: }
32:
33: // Definition der Methode Meow
34: // Rueckgabe: void
35: // Parameter: Keine
36: // Aktion: Gibt "meow" auf dem Bildschirm aus
37: void Cat::Meow()
38: {
39: cout << "Miau.\n";
40: }
41:
42: // Eine Katze erzeugen, Alter festlegen, Miauen lassen
43: // Alter mitteilen lassen, dann erneut miauen.
44: int main()
45: {
46: Cat Frisky;
47: Frisky.SetAge(5);
48: Frisky.Meow();
49: cout << "Frisky ist eine Katze, die " ;
50: cout << Frisky.GetAge() << " Jahre alt ist.\n";
51: Frisky.Meow();
52; return 0;
53: }

Miau.
Frisky ist eine Katze, die 5 Jahre alt ist.
Miau.

Die Zeilen 6 bis 14 enthalten die Definition der Klasse Cat. Das Schlüsselwort public in Zeile 8 teilt dem Compiler mit, daß die nachfolgenden Elemente öffentlich sind. Zeile 9 deklariert die öffentliche Zugriffsmethode GetAge(), die den Zugriff auf die in Zeile 13 deklarierte private Elementvariable itsAge realisiert. In Zeile 10 steht die öffentliche Zugriffsfunktion SetAge(). Diese Funktion übernimmt eine ganze Zahl als Argument und setzt itsAge auf den Wert dieses Arguments.

Zeile 11 deklariert die Klassenmethode Meow(), bei der es sich nicht um eine Zugriffsmethode handelt. In diesem Falle ist es eine allgemeine Methode, die das Wort »Meow« auf dem Bildschirm ausgibt.

Zeile 12 leitet den privaten Abschnitt ein, der nur die Deklaration in Zeile 13 für die private Elementvariable itsAge enthält. Die Klassendeklaration endet mit einer schließenden geschweiften Klammer und dem Semikolon in Zeile 14.

Die Zeilen 18 bis 21 enthalten die Definition der Elementfunktion GetAge(). Diese Methode übernimmt keine Parameter, gibt aber eine ganze Zahl zurück. Beachten Sie, daß Klassenmethoden den Klassennamen enthalten, gefolgt von zwei Doppelpunkten und dem Namen der Funktion (siehe Zeile 18). Diese Syntax weist den Compiler an, daß die hier definierte Funktion GetAge() diejenige ist, die in der Klasse Cat deklariert wurde. Mit Ausnahme der Kopfzeile wird die Funktion GetAge() wie jede andere Funktion erzeugt.

Die Funktion GetAge() besteht nur aus einer Zeile und gibt den Wert in itsAge zurück. Beachten Sie, daß die Funktion main() nicht auf itsAge zugreifen kann, da itsAge privat zur Klasse Cat ist. Die Funktion main() hat Zugriff auf die öffentliche Methode GetAge() . Da GetAge() eine Elementfunktion der Klasse Cat ist, hat sie uneingeschränkten Zugriff auf die Variable itsAge. Dieser Zugriff erlaubt GetAge(), den Wert von itsAge an main() zurückzugeben.

In Zeile 26 beginnt die Definition der Elementfunktion SetAge(). Diese Funktion übernimmt einen ganzzahligen Parameter und setzt in Zeile 30 den Wert von itsAge auf den Wert dieses Parameters. Da es sich um eine Elementfunktion der Klasse Cat handelt, hat SetAge() direkten Zugriff auf die Elementvariable itsAge.

In Zeile 37 beginnt die Definition - oder Implementierung - der Methode Meow() der Klasse Cat. Es handelt sich um eine einzeilige Funktion, die das Wort Meow gefolgt von einer Zeilenschaltung auf dem Bildschirm ausgibt. (Wie Sie wissen, bewirkt das Zeichen \n eine neue Zeile auf dem Bildschirm.)

Zeile 44 beginnt den Rumpf des Programms mit der bekannten Funktion main(). Die Funktion main() übernimmt hier keine Argumente. In Zeile 46 deklariert main() ein Cat-Objekt namens Frisky. In Zeile 47 wird der Wert 5 an die Elementvariable itsAge mit Hilfe der Zugriffsmethode SetAge() zugewiesen. Beachten Sie, daß der Aufruf dieser Methode mit dem Klassennamen (Frisky) gefolgt vom Punktoperator (.) und dem Methodennamen (SetAge()) erfolgt. In der gleichen Weise kann man jede andere Methode in einer Klasse aufrufen.

Zeile 48 ruft die Elementfunktion Meow() auf, und Zeile 49 gibt eine Meldung mittels der Zugriffsmethode GetAge() aus. In Zeile 51 steht ein weiterer Aufruf von Meow().

Konstruktoren und Destruktoren

Für die Definition einer Integer-Variablen gibt es zwei Verfahren. Man kann die Variable definieren und ihr später im Programm einen Wert zuweisen:

int Weight;            // eine Variable definieren
... // hier steht ein anderer Code
Weight = 7; // der Variablen einen Wert zuweisen

Man kann die Ganzzahl auch definieren und sie sofort initialisieren:

int Weight = 7;        // definieren und mit 7 initialisieren

Die Initialisierung kombiniert die Definition der Variablen mit einer anfänglichen Zuweisung. Nichts hindert Sie daran, den Wert später zu ändern. Die Initialisierung stellt sicher, daß eine Variable niemals ohne einen sinnvollen Wert vorkommt.

Wie initialisiert man nun die Datenelemente einer Klasse? Klassen haben eine spezielle Elementfunktion, einen sogenannten Konstruktor. Der Konstruktor kann bei Bedarf Parameter übernehmen, aber keinen Rückgabewert liefern - nicht einmal void. Der Konstruktor ist eine Klassenmethode mit demselben Namen wie die Klasse selbst.

Zur Deklaration eines Konstruktors gehört in der Regel auch die Deklaration eines Destruktors . Genau wie Konstruktoren Objekte der Klasse erzeugen und initialisieren, führen Destruktoren Aufräumungsarbeiten nach Zerstörung des Objekts aus und geben reservierten Speicher frei. Ein Destruktor hat immer den Namen der Klasse, wobei eine Tilde (~) vorangestellt ist. Destruktoren übernehmen keine Argumente und haben keinen Rückgabewert. Der Destruktor der Klasse Cat ist demnach wie folgt deklariert:

~Cat();

Standardkonstruktoren und -destruktoren

Wenn Sie keinen Konstruktor oder Destruktor deklarieren, erzeugt der Compiler einen für Sie. Dieser Standardkonstruktor und der Standarddestruktor übernehmen keine Argumente und tun auch nichts.

Verdankt der Standardkonstruktor seinen Namen der Tatsache, daß er keine Argumente übernimmt oder daß er vom Compiler erzeugt wird, wenn ich keinen eigenen Konstruktor deklariere?

Antwort: Ein Konstruktor, der keine Argumente übernimmt, heißt Standardkonstruktor - egal ob Sie oder der Compiler ihn erzeugen. Sie erhalten standardmäßig einen Standardkonstruktor.

Etwas verwirrend ist, daß man als Standarddestruktor den Destruktor bezeichnet, der vom Compiler gestellt wird. Da kein Destruktor irgendwelche Argumente übernimmt, zeichnet sich der Standarddestruktor dadurch aus, daß er keine Aktion ausführt - er hat einen leeren Funktionskörper.

Der Standardkonstruktor im Einsatz

Wozu braucht man einen Konstruktor, der nichts bewirkt? Zum einem ist es eine Frage der richtigen Form. Alle Objekte müssen mit einem Konstruktor erzeugt und mit einem Destruktor wieder aufgelöst werden, und diese »nichts tuenden« Funktionen werden eben hierfür bei Bedarf aufgerufen. Um jedoch ein Objekt ohne Übergabe von Parametern zu deklarieren, wie in

Cat Rags;           // Rags übernimmt keine Parameter

müssen Sie einen Konstruktor der folgenden Form haben

Cat();

Wenn Sie ein Objekt einer Klasse definieren, wird der Konstruktor aufgerufen. Übernimmt der Cat-Konstruktor zwei Parameter, können Sie ein Cat-Objekt wie folgt definieren:

Cat Frisky (5,7);

Hätte der Konstruktor nur einen Parameter, würden Sie schreiben

Cat Frisky (3);

Für den Fall, daß der Konstruktor keine Parameter übernimmt (es sich also um einen Standardkonstruktor handelt), lassen Sie die Klammern weg und schreiben

Cat Frisky;

Dies ist die Ausnahme zu der Regel, daß alle Funktionen Klammern erfordern, auch wenn sie keine Parameter übernehmen. Deshalb ist die Schreibweise

Cat Frisky;

gültig. Interpretiert wird dies als Aufruf des Standardkonstruktors. Er liefert keine Parameter und weist auch keine Klammern auf.

Beachten Sie, daß Sie den Standardkonstruktor des Compilers nicht verwenden müssen. Es steht Ihnen jederzeit frei, einen eigenen Standardkonstruktor zu schreiben - das heißt, einen Konstruktor, der keine Parameter übernimmt. Es steht Ihnen frei, Ihren Standardkonstruktor mit einem Funktionskörper auszustatten, in der die Klasse bei Bedarf initialisiert werden kann.

Aus Formgründen sollte man einen Destruktor deklarieren, wenn man einen Konstruktor deklariert, auch wenn der Destruktor keine weiteren Aufgaben wahrnimmt. Obwohl der Standarddestruktor durchaus korrekt arbeitet, stört es nicht, einen eigenen Destruktor zu deklarieren. Der Code wird dadurch verständlicher.

In Listing 6.4 wurde die Klasse Cat so umgeschrieben, daß ein Konstruktor zur Initialisierung des Cat-Objekts verwendet wird, wobei das Alter (age) auf den von Ihnen übergebenen Anfangswert gesetzt wird. Außerdem zeigt das Listing, wo der Aufruf des Destruktors stattfindet.

Listing 6.4: Konstruktoren und Destruktoren

1:   // Zeigt die Deklaration eines Konstruktors und
2: // eines Destruktors für die Klasse Cat
3:
4: #include <iostream.h> // für cout
5:
6: class Cat // Beginn der Klassendeklaration
7: {
8: public: // Beginn des oeffentlichen Abschnitts
9: Cat(int initialAge); // Konstruktor
10: ~Cat(); // Destruktor
11: int GetAge(); // Zugriffsfunktion
12: void SetAge(int age); // Zugriffsfunktion
13: void Meow();
14: private: // Beginn des privaten Abschnitts
15: int itsAge; // Elementvariable
16: };
17:
18: // Konstruktor von Cat
19: Cat::Cat(int initialAge)
20: {
21: itsAge = initialAge;
22: }
23:
24: Cat::~Cat() // Destruktor, fuehrt keine Aktionen aus
25: {
26: }
27:
28: // Die oeffentliche Zugriffsfunktion GetAge gibt
29: // den Wert des Datenelements itsAge zurück.
30: int Cat::GetAge()
31: {
32: return itsAge;
33: }
34:
35: // Definition von SetAge als oeffentliche
36: // Zugriffsfunktion
37:
38: void Cat::SetAge(int age)
39: {
40: // Elementvariable itsAge auf den als
41: // Parameter age uebergebenen Wert setzen.
42: itsAge = age;
43: }
44:
45: // Definition der Methode Meow
46: // Rueckgabe: void
47: // Parameter: Keine
48: // Aktion: Gibt "Miau" auf dem Bildschirm aus
49: void Cat::Meow()
50: {
51: cout << "Miau.\n";
52: }
53:
54: // Eine Katze erzeugen, Alter festlegen, Miauen lassen
55 // Alter mitteilen lassen, dann erneut miauen.
56: int main()
57: {
58: Cat Frisky(5);
59: Frisky.Meow();
60: cout << "Frisky ist eine Katze, die " ;
61: cout << Frisky.GetAge() << " Jahre alt ist.\n";
62: Frisky.Meow();
63: Frisky.SetAge(7);
64: cout << "Jetzt ist Frisky " ;
65: cout << Frisky.GetAge() << " Jahre alt.\n";
66; return 0;
67: }

Miau.
Frisky ist eine Katze, die 5 Jahre alt ist.
Miau.
Jetzt ist Frisky 7 Jahre alt.
Miau.

Listing 6.4 gleicht Listing 6.3, nur daß in Zeile 9 ein Konstruktor hinzufügt wurde, der eine Ganzzahl übernimmt. Zeile 10 deklariert den Destruktor. Destruktoren übernehmen niemals Parameter, und weder Konstruktoren noch Destruktoren geben Werte zurück - nicht einmal void.

Die Zeilen 19 bis 22 zeigen die Implementierung des Konstruktors, die der Implementierung der Zugriffsfunktion SetAge() ähnlich ist. Es gibt keinen Rückgabewert.

Die Zeilen 24 bis 26 zeigen die Implementierung des Destruktors ~Cat(). Diese Funktion führt nichts aus, man muß aber die Definition der Funktion einbinden, wenn man sie in der Klassendeklaration deklariert.

Zeile 58 enthält die Definition des Cat-Objekts Frisky. Der Wert 5 wird an den Konstruktor von Frisky übergeben. Man muß hier nicht SetAge() aufrufen, da Frisky mit dem Wert 5 in seiner Elementvariablen itsAge erzeugt wurde. Zeile 61 zeigt diesen Wert zur Bestätigung an. Auf Zeile 63 wird der Variablen itsAge von Frisky der neue Wert 7 zugewiesen. Zeile 65 gibt den neuen Wert aus.

Was Sie tun sollten

... und was nicht

Verwenden Sie Konstruktoren, um Ihre Objekte zu initialisieren.

Achten Sie darauf, daß Ihre Konstruktoren und Destruktoren keine Rückgabewerte haben.

Übergeben Sie Ihren Destruktoren keine Parameter.

Konstante Elementfunktionen

Wenn man eine Elementfunktion einer Klasse als const deklariert, verspricht man fest, daß die Methode die Werte der Datenelemente der Klasse auf keinen Fall ändert. Um eine Klassenmethode als konstant zu deklarieren, setzt man das Schlüsselwort const hinter die Klammern und vor das Semikolon. Die folgende Deklaration der konstanten Elementfunktion EineFunktion() übernimmt keine Argumente und gibt void zurück. Sie sieht folgendermaßen aus:

void EineFunktion() const;

Zugriffsfunktionen werden häufig mit Hilfe des Modifizierers const als konstante Funktionen deklariert. Die Klasse Cat hat zwei Zugriffsfunktionen:

void SetAge(int anAge);
int GetAge();

SetAge() kann nicht const sein, da diese Funktion die Elementvariable itsAge ändern soll. Dagegen kann und sollte man GetAge() als const deklarieren, da diese Funktion die Klasse überhaupt nicht verändert. Sie gibt einfach den aktuellen Wert der Elementvariablen itsAge zurück. Die Deklaration dieser beiden Funktionen sollte daher wie folgt aussehen:

void SetAge(int anAge);
int GetAge() const;

Wenn man eine Funktion als konstant deklariert und dann die Implementierung dieser Funktion das Objekt ändert (durch Änderung des Wertes eines ihrer Elemente), gibt der Compiler eine entsprechende Fehlermeldung aus. Wenn Sie zum Beispiel GetAge() so implementieren, daß die Methode die Anzahl der Anfragen nach dem Alter von Cat protokolliert, ernten Sie einen Compiler-Fehler, denn der Aufruf dieser Methode würde das Cat-Objekt verändern.

Verwenden Sie const wo immer möglich. Deklarieren Sie Elementfunktionen, die den Zustand des Objekts nicht ändern sollen, stets als const. Der Compiler kann Ihnen dann die Fehlersuche erleichtern. Das geht schneller und ist weniger aufwendig, als wenn man es selbst machen müßte.

Es gehört zum guten Programmierstil, so viele Methoden wie möglich als const zu deklarieren. Damit kann bereits der Compiler Fehler abfangen, und die Fehler zeigen sich nicht erst im laufenden Programm.

Schnittstelle und Implementierung

Wie Sie bereits wissen, sind Klienten die Teile des Programms, die Objekte einer bestimmten Klasse erzeugen und verwenden. Man kann sich die öffentliche Schnittstelle zu dieser Klasse - die Klassendeklaration - als Vertrag mit diesen Klienten vorstellen. Der Vertrag sagt aus, wie sich die Klasse verhält.

Durch die Klassendeklaration von Cat setzen Sie zum Beispiel einen Vertrag auf, der besagt, daß das Alter einer Katze (eines Cat-Objekts) vom Konstruktor der Klasse initialisiert, durch die Zugriffsfunktion SetAge() zugewiesen und durch die Zugriffsfunktion GetAge() gelesen werden kann. Außerdem sagen Sie zu, daß jedes Cat-Objekt weiß, wie zu Miauen ist (Funktion Meow()). Achten Sie darauf, daß Sie in der öffentlichen Schnittstelle nichts über die Elementvariable itsAge verraten. Dieses Implementierungsdetail ist nicht Teil Ihres Vertrags. Sie stellen ein Alter zur Verfügung (GetAge() ) und geben ein Alter an (SetAge()). Der Mechanismus jedoch (itsAge) ist nicht sichtbar.

Wenn man GetAge() zu einer konstanten Funktion macht (wie es empfohlen wird), verspricht der Vertrag auch, daß GetAge() nicht das Cat-Objekt ändert, auf dem die Funktion aufgerufen wird.

C++ ist äußerst typenstreng, womit gemeint ist, daß der Compiler die Vertragsbedingungen auf Einhaltung prüft und einen Fehler meldet, wenn sie verletzt werden. In Listing 6.5 sehen Sie ein Programm, das sich nicht kompilieren läßt, da Vertragsbedingungen gebrochen wurden.

Listing 6.5 läßt sich nicht kompilieren!

Listing 6.5: Fehlerhafte Schnittstelle

1:   // Demonstration von Compiler-Fehlern
2:
3:
4: #include <iostream.h> // für cout
5:
6: class Cat
7: {
8: public:
9: Cat(int initialAge);
10: ~Cat();
11: int GetAge() const; // const-Zugriffsfunktion
12: void SetAge (int age);
13: void Meow();
14: private:
15: int itsAge;
16: };
17:
18: // Konstruktor von Cat
19: Cat::Cat(int initialAge)
20: {
21: itsAge = initialAge;
21: cout << "Cat Constructor\n";
22: }
23:
24: Cat::~Cat() // Destruktor, fuehrt keine Aktion aus
25: {
26: cout << "Cat Destructor\n";
27: }
28: // GetAge, const-Funktion
29: // aber wir verletzen const!
30: int Cat::GetAge() const
31: {
32: return (itsAge++); // verletzt const!
33: }
34:
35: // Definition von SetAge, public
36: // Zugriffsfunktion
37:
38: void Cat::SetAge(int age)
39: {
40: // setzt Elementvariable itsAge auf den
41: // Wert, der vom Parameter age uebergeben wird
42: itsAge = age;
43: }
44:
45: // Definition der Meow-Methode
46: // Rueckgabewert: void
47: // Parameter: Keine
48: // Aktion: Gibt "miau" auf dem Bildschirm aus
49: void Cat::Meow()
50: {
51: cout << "Miau.\n";
52: }
53:
54: // zeigt verschiedene Verletzungen der
55 // Schnittstelle und resultierende Compiler-Fehler
56: int main()
57: {
58: Cat Frisky; // entspricht nicht der Deklaration
59: Frisky.Meow();
60: Frisky.Bark(); // Nein, du Dummkopf, Katzen bellen nicht.
61: Frisky.itsAge = 7; // itsAge ist private
62: return 0;
63: }

Wie schon oben erwähnt, läßt sich dieses Programm nicht kompilieren. Deshalb gibt es auch keine Ausgabe. (Aber es hat Spaß gemacht, ein Programm zu schreiben, das so viele Fehler enthält.)

Zeile 11 deklariert GetAge() als konstante Zugriffsfunktion - und so sollte es auch sein. Im Rumpf von GetAge() jedoch, Zeile 32, wird die Elementvariable itsAge inkrementiert. Da diese Methode als konstant deklariert wurde, darf der Wert von itsAge nicht geändert werden. Deshalb wird ein Fehler gemeldet, wenn das Programm kompiliert wird.

In Zeile 13 wird bei der Deklaration der Funktion Meow() const weggelassen. Das ist zwar nicht unbedingt ein Fehler, jedoch schlechter Programmierstil. Ein guter Entwurf würde berücksichtigen, daß diese Methode die Elementvariablen von Cat nicht ändert. Deshalb sollte Meow() konstant sein.

Zeile 58 definiert eine Cat-Objekt, Frisky. Cat hat jetzt einen Konstruktor, der einen Integer als Parameter übernimmt. Das bedeutet, daß Sie einen Parameter übergeben müssen. Da in Zeile 58 kein Parameter vorhanden ist, wird ein Fehler gemeldet.

Wenn Sie selbst einen Konstruktor stellen, erzeugt der Compiler keinen mehr. Wenn Sie also einen Konstruktor erzeugen, der einen Parameter übernimmt, gibt es keinen Standardkonstruktor, es sei denn, Sie schreiben einen.

Zeile 60 enthält den Aufruf an die Klassenmethode Bark(). Da Bark() nie deklariert wurde, ist der Aufruf unwirksam.

In Zeile 61 wird itsAge der Wert 7 zugewiesen. itsAge ist jedoch ein privates Datenelement, und deshalb wird ein Fehler ausgegeben, wenn das Programm kompiliert wird.

Warum mit dem Compiler Fehler abfangen?

Auch wenn es absolut phantastisch wäre, 100 % fehlerfreien Code zu schreiben, gibt es nur wenig Programmierer, die dazu fähig sind. Viele Programmierer haben jedoch ein System entwickelt, mit dem sie die Anzahl der Fehler auf ein Minimum senken, indem sie sie möglichst früh im Programmierablauf abfangen und beheben.

Obwohl Compiler-Fehler äußerst ärgerlich sind und einen Programmierer an den Rand der Verzweiflung treiben können, sind sie besser als die Alternative. Bei einer weniger strengen Sprache können Sie ihre Verträge verletzen, ohne daß der Compiler dies auch nur bemängelt. Und später dann, zur Laufzeit, wird Ihr Programm abstürzen - wahrscheinlich genau dann, wenn Ihnen Ihr Boss über die Schulter schaut.

Compiler-Fehler - das heißt, während der Kompilierung gefundene Fehler - sind weitaus angenehmer als Laufzeitfehler - das heißt, während der Ausführung des Programms auftretende Fehler. Das hängt damit zusammen, daß sich Compiler-Fehler zuverlässiger einkreisen lassen. Es kann durchaus sein, daß ein Programm viele Male läuft, ohne alle möglichen Wege im Code zu durchlaufen. Somit kann ein Laufzeitfehler eine ganze Weile im Verborgenen bleiben. Compiler-Fehler werden während der Kompilierung gefunden, so daß man sie leicht identifizieren und beseitigen kann. Deshalb hat es sich bewährt, bereits im frühen Stadium der Programmentwicklung Fehler mit Hilfe des Compiler abzufangen.

Klassendeklarationen und Methodendefinitionen plazieren

Jede deklarierte Funktion muß eine Definition haben. Die Definition einer Funktion bezeichnet man auch als Implementierung. Wie jede andere Funktion, hat auch die Definition einer Klassenmethode einen Funktionskopf und einen Funktionsrumpf.

Die Definition muß in einer Datei stehen, die der Compiler finden kann. Die meisten C++-Compiler erwarten, daß diese Datei auf .C oder .CPP endet. In diesem Buch kommt die Erweiterung .CPP zur Anwendung. Es empfiehlt sich aber, daß Sie in Ihrem Compiler-Handbuch nachsehen, welche Erweiterung Ihr Compiler bevorzugt.

Viele Compiler nehmen an, daß es sich bei Dateien mit der Erweiterung .C um C-Programme handelt und daß C++-Programme mit .CPP enden. Man kann zwar eine beliebige Erweiterung verwenden, mit .CPP schafft man aber klare Verhältnisse.

Obwohl man durchaus die Deklaration in der gleichen Datei unterbringen kann wie die Definition, gehört dieses Vorgehen nicht zum guten Programmierstil. Die von den meisten Programmierern anerkannte Konvention ist die Unterbringung der Deklaration in einer sogenannten Header-Datei, die gewöhnlich den gleichen Namen hat, aber auf .H, .HP oder .HPP endet. In diesem Buch werden Header-Dateien mit der Erweiterung .HPP verwendet. Auch hierzu sollten Sie im Compiler-Handbuch nachschlagen.

So schreibt man zum Beispiel die Deklaration der Klasse Cat in eine Datei namens CAT.HPP und die Definition der Klassenmethoden in der Datei CAT.CPP. Die Header- Datei bindet man dann in die .CPP-Datei ein, indem man den folgenden Code an den Beginn von CAT.CPP schreibt:

#include "Cat.hpp"

Damit weist man den Compiler an, CAT.HPP in die Datei einzulesen, gerade so als hätte man ihren Inhalt an dieser Stelle eingetippt. Bitte beachten Sie, daß einige Compiler darauf bestehen, daß die Groß- und Kleinschreibung zwischen Ihrer #include-Anweisung und Ihrem Dateisystem übereinstimmt.

Warum macht man sich die Mühe, diese Angaben in separate Dateien zu schreiben, wenn man sie ohnehin wieder zusammenführt? In der Regel kümmern sich die Klienten einer Klasse nicht um die Details der Implementierung. Es genügt demnach, die Header-Datei zu lesen, um alle erforderlichen Angaben beisammenzuhaben. Die Implementierungsdateien können dann ruhig ignoriert werden. Und außerdem könnte es Ihnen auch passieren, daß Sie die .HPP-Datei in mehr als einer .CPP-Datei einbinden müssen.

Die Deklaration einer Klasse sagt dem Compiler, um welche Klasse es sich handelt, welche Daten sie aufnimmt und welche Funktionen sie ausführt. Die Deklaration der Klasse bezeichnet man als Schnittstelle (Interface), da der Benutzer hieraus die Einzelheiten zur Interaktion mit der Klasse entnehmen kann. Die Schnittstelle speichert man in der Regel in einer .HPP-Datei, einer sogenannten Header-Datei.

Die Funktionsdefinition sagt dem Compiler, wie die Funktion arbeitet. Man bezeichnet die Funktionsdefinition als Implementierung der Klassenmethode und legt sie in einer .CPP-Datei ab. Die Implementierungsdetails der Klasse sind nur für den Autor der Klasse von Belang. Klienten der Klasse - das heißt, die Teile des Programms, die die Klasse verwenden - brauchen nicht zu wissen (und sich nicht darum zu kümmern), wie die Funktionen implementiert sind.

Inline-Implementierung

So wie man den Compiler auffordern kann, eine normale Funktion als Inline-Funktion einzubinden, kann man auch Klassenmethoden als inline deklarieren. Das Schlüsselwort inline steht vor dem Rückgabewert. Die Inline-Implementierung der Funktion GetWeight() sieht zum Beispiel folgendermaßen aus:

inline int Cat::GetWeight()
{
return itsWeight; // das Datenelement Weight zurueckgeben
}

Man kann auch die Definition der Funktion in die Deklaration der Klasse schreiben, was die Funktion automatisch zu einer Inline-Funktion macht:

class Cat
{
public:
int GetWeight() { return itsWeight; } // inline
void SetWeight(int aWeight);
};

Beachten Sie die Syntax der Definition von GetWeight(). Der Rumpf der Inline-Funktion beginnt unmittelbar nach der Deklaration der Klassenmethode. Nach den Klammern steht kein Semikolon. Wie bei jeder Funktion beginnt die Definition mit einer öffnenden geschweiften Klammer und endet mit einer schließenden geschweiften Klammer. Wie üblich spielen Whitespace-Zeichen keine Rolle. Man hätte die Deklaration auch wie folgt schreiben können:

class Cat
{
public:
int GetWeight()
{
return itsWeight;
} // inline
void SetWeight(int aWeight);
};

Die Listings 6.6 und 6.7 bringen eine Neuauflage der Klasse Cat. Dieses Mal aber wurde die Deklaration in CAT.HPP und die Implementierung der Funktionen in CAT.CPP untergebracht. Listing 6.7 realisiert außerdem sowohl die Zugriffsfunktion als auch die Funktion Meow() als Inline-Funktionen.

Listing 6.6: Die Klassendeklaration von Cat in CAT.HPP

1:  #include <iostream.h>
2: class Cat
3: {
4: public:
5: Cat (int initialAge);
6: ~Cat();
7: int GetAge() { return itsAge;} // inline!
8: void SetAge (int age) { itsAge = age;} // inline!
9: void Meow() { cout << "Miau.\n";} // inline!
10: private:
11: int itsAge;
12: };

Listing 6.7: Die Implementierung von Cat in CAT.CPP

1:   // Zeigt Inline-Funktionen
2: // und das Einbinden von Header-Dateien
3:
4: #include "cat.hpp" // Header-Dateien unbedingt einbinden!
5:
6:
7: Cat::Cat(int initialAge) // Konstruktor
8: {
9: itsAge = initialAge;
10: }
11:
12: Cat::~Cat() // Destruktor, keine Aktionen
13: {
14: }
15:
16: // Katze erzeugen, Alter festlegen, miauen lassen
17: // Alter ausgeben, dann erneut miauen.
18: int main()
19: {
20: Cat Frisky(5);
21: Frisky.Meow();
22: cout << "Frisky ist eine Katze, die " ;
23: cout << Frisky.GetAge() << " Jahre alt ist.\n";
24: Frisky.Meow();
25: Frisky.SetAge(7);
26: cout << "Jetzt ist Frisky " ;
27: cout << Frisky.GetAge() << " Jahre alt.\n";
28: return 0;
29: }

Miau.
Frisky ist eine Katze, die 5 Jahre alt ist.
Miau.
Jetzt ist Frisky 7 Jahre alt.

Der Code aus Listing 6.6 und 6.7 entspricht dem Code aus Listing 6.4 mit Ausnahme von drei Methoden, die in der Deklarationsdatei als inline implementiert wurden, und der Trennung der Deklaration in einer separaten Header-Datei CAT.HPP.

Zeile 7 deklariert die Funktion GetAge() und stellt ihre Inline-Implementierung bereit. Die Zeilen 8 und 9 enthalten weitere Inline-Funktionen, wobei aber die Funktionalität dieser Funktionen unverändert aus den vorherigen »Outline«-Implementierungen übernommen wurde.

Die Anweisung #include "cat.hpp" in Zeile 4 von Listing 6.7 bindet das Listing von CAT.HPP (Listing 6.6) ein. Durch das Einbinden von CAT.HPP teilen Sie dem Prä-Compiler mit, CAT.HPP so in die Datei einzulesen, als ob CAT.HPP direkt ab Zeile 5 eingegeben worden wäre.

Diese Vorgehensweise erlaubt es Ihnen, Ihre Deklarationen getrennt von Ihrer Implementierung in einer eigenen Datei unterzubringen und sie trotzdem verfügbar zu haben, wenn sie vom Compiler benötigt werden. In der C++-Programmierung wird häufig so verfahren: Klassendeklarationen werden in der Regel in einer .HPP-Datei abgelegt, die dann mit #include in die verbundene .CPP-Datei eingebunden wird.

In den Zeilen 18 bis 29 finden Sie die gleiche main()-Funktion wie in Listing 6.4, was beweist, daß die inline-Implementierung der Funktionen keinen Einfluß auf ihre Funktionsweise und ihre Verwendung hat.

Klassen als Datenelemente einer Klasse

Es ist nicht unüblich, komplexe Klassen aufzubauen, indem man einfachere Klassen deklariert und sie in die Deklaration der komplexeren Klasse einbindet. Beispielsweise kann man eine Klasse aWheel, eine Klasse aMotor, eine Klasse aTransmission usw. deklarieren und dann zur Klasse aCar kombinieren. Damit deklariert man eine Beziehung: Ein Auto hat einen Motor, es hat Räder, es hat eine Kraftübertragung.

Sehen wir uns ein weiteres Beispiel an. Ein Rechteck setzt sich aus Linien zusammen. Eine Linie ist durch zwei Punkte definiert. Ein Punkt ist durch eine X- und eine Y-Koordinate festgelegt. Listing 6.8 zeigt eine vollständige Deklaration einer Klasse Rectangle , wie sie in RECTANGLE.HPP stehen könnte. Da ein Rechteck mit vier Linien definiert ist, die vier Punkte verbinden, und sich jeder Punkt auf eine Koordinate in einem Diagramm bezieht, wird zuerst die Klasse Point deklariert, um die X,Y-Koordinaten jedes Punkts aufzunehmen. Listing 6.9 zeigt eine vollständige Deklaration beider Klassen.

Listing 6.8: Deklaration einer vollständigen Klasse

1:   // Beginn von Rect.hpp
2: #include <iostream.h>
3: class Point // nimmt X,Y-Koordinaten auf
4: {
5: // Kein Konstruktor, Standardkonstruktor verwenden
6: public:
7: void SetX(int x) { itsX = x; }
8: void SetY(int y) { itsY = y; }
9: int GetX()const { return itsX;}
10: int GetY()const { return itsY;}
11: private:
12: int itsX;
13: int itsY;
14: }; // Ende der Klassendeklaration von Point
15:
16:
17: class Rectangle
18: {
19: public:
20: Rectangle (int top, int left, int bottom, int right);
21: ~Rectangle () {}
22:
23: int GetTop() const { return itsTop; }
24: int GetLeft() const { return itsLeft; }
25: int GetBottom() const { return itsBottom; }
26: int GetRight() const { return itsRight; }
27:
28: Point GetUpperLeft() const { return itsUpperLeft; }
29: Point GetLowerLeft() const { return itsLowerLeft; }
30: Point GetUpperRight() const { return itsUpperRight; }
31: Point GetLowerRight() const { return itsLowerRight; }
32:
33: void SetUpperLeft(Point Location) {itsUpperLeft = Location;}
34: void SetLowerLeft(Point Location) {itsLowerLeft = Location;}
35: void SetUpperRight(Point Location) {itsUpperRight = Location;}
36: void SetLowerRight(Point Location) {itsLowerRight = Location;}
37:
38: void SetTop(int top) { itsTop = top; }
39: void SetLeft (int left) { itsLeft = left; }
40: void SetBottom (int bottom) { itsBottom = bottom; }
41: void SetRight (int right) { itsRight = right; }
42:
43: int GetArea() const;
44:
45: private:
46: Point itsUpperLeft;
47: Point itsUpperRight;
48: Point itsLowerLeft;
49: Point itsLowerRight;
50: int itsTop;
51: int itsLeft;
52: int itsBottom;
53: int itsRight;
54: };
55: // Ende von Rect.hpp

Listing 6.9: RECT.CPP

1:   // Beginn von rect.cpp
2: #include "rect.hpp"
3: Rectangle::Rectangle(int top, int left, int bottom, int right)
4: {
5: itsTop = top;
6: itsLeft = left;
7: itsBottom = bottom;
8: itsRight = right;
9:
10: itsUpperLeft.SetX(left);
11: itsUpperLeft.SetY(top);
12:
13: itsUpperRight.SetX(right);
14: itsUpperRight.SetY(top);
15:
16: itsLowerLeft.SetX(left);
17: itsLowerLeft.SetY(bottom);
18:
19: itsLowerRight.SetX(right);
20: itsLowerRight.SetY(bottom);
21: }
22:
23:
24: // Rechteckflaeche berechnen. Dazu Ecken bestimmen,
25: // Breite und Hoehe ermitteln, dann multiplizieren.
26: int Rectangle::GetArea() const
27: {
28: int Width = itsRight-itsLeft;
29: int Height = itsTop - itsBottom;
30: return (Width * Height);
31: }
32:
33: int main()
34: {
35: // Eine lokale Rectangle-Variable initialisieren
36: Rectangle MyRectangle (100, 20, 50, 80 );
37:
38: int Area = MyRectangle.GetArea();
39:
40: cout << "Flaeche: " << Area << "\n";
41: cout << "Obere linke X-Koordinate: ";
42: cout << MyRectangle.GetUpperLeft().GetX();
43: return 0;
44: }

Flaeche: 3000
Obere linke X-Koordinate: 20

Die Zeilen 3 bis 14 in Listing 6.8 deklarieren die Klasse Point, die der Aufbewahrung einer bestimmten X,Y-Koordinate eines Graphen dient. In der hier gezeigten Form greift das Programm kaum auf Point zurück. Allerdings ist Point auch für andere Zeichenmethoden vorgesehen.

Die Klassendeklaration von Point deklariert in den Zeilen 12 und 13 zwei Elementvariablen - itsX und itsY. Diese Variablen nehmen die Werte der Koordinaten auf. Bei Vergrößerung der X-Koordinate bewegt man sich im Koordinatensystem nach rechts. Mit wachsender Y-Koordinate bewegt man sich im Koordinatensystem nach oben. (Manche Systeme, beispielsweise Windows, verwenden ein Koordinatensystem, bei dem man sich mit steigender Y-Koordinate im Fenster weiter nach unten bewegt.)

Die Klasse Point verwendet Inline-Zugriffsfunktionen, um die in den Zeilen 7 bis 10 deklarierten Punkte X und Y zu lesen und zu setzen. Die Klasse Point übernimmt Standardkonstruktor und Standarddestruktor vom Compiler. Daher muß man die Koordinaten der Punkte explizit festlegen.

In Zeile 17 beginnt die Deklaration der Klasse Rectangle. Ein Rectangle-Objekt besteht aus vier Punkten, die die Ecken des Rechtecks darstellen.

Der Konstruktor für die Klasse Rectangle (Zeile 20) übernimmt vier Ganzzahlen, die als top (oben), left (links), bottom (unten) und right (rechts) bezeichnet sind. Die Klasse Rectangle kopiert die an den Konstruktor übergebenen Parameter in vier Elementvariablen (Listing 6.9) und richtet dann vier Point-Objekte ein.

Außer den üblichen Zugriffsfunktionen verfügt Rectangle über eine Funktion namens GetArea(), die in Zeile 43 deklariert ist. Die Klasse speichert die Fläche nicht in einer Variablen, sondern läßt sie berechnen. Diese Aufgabe übernimmt die Funktion GetArea() in den Zeilen 28 und 29 von Listing 6.9. Die Funktion ermittelt zunächst Breite und Höhe des Rechtecks und multipliziert dann beide Werte.

Um die X-Koordinate der oberen linken Ecke des Rechtecks zu ermitteln, muß man auf den Punkt UpperLeft zugreifen und von diesem Punkt den Wert für X abrufen. GetUpperLeft() ist eine Methode von Rectangle und kann somit direkt auf die privaten Daten von Rectangle (einschließlich itsUpperLeft) zugreifen. Da itsUpperLeft ein Point-Objekt ist und der Wert itsX von Point als privat deklariert ist, stehen GetUpperLeft() diese Daten nicht direkt zur Verfügung. Statt dessen muß man die öffentliche Zugriffsfunktion GetX() verwenden, um diesen Wert zu erhalten.

In Zeile 33 von Listing 6.9 beginnt der Rumpf des eigentlichen Programms. Bis Zeile 35 ist praktisch noch nichts passiert. Es hat nicht einmal eine Reservierung von Speicher stattgefunden. Man hat dem Compiler bislang lediglich mitgeteilt, wie ein Point- Objekt und ein Rectangle-Objekt zu erzeugen sind, falls man diese Objekte irgendwann benötigt.

Zeile 36 definiert ein Rectangle-Objekt durch Übergabe der Werte für top, left, bottom und right.

Zeile 38 deklariert die lokale Variable Area vom Typ int. Diese Variable nimmt die Fläche des erzeugten Rectangle-Objekts auf. Area wird mit dem von der Funktion GetArea() des Rectangle-Objekts zurückgegebenen Wert initialisiert.

Ein Klient von Rectangle kann ein Rectangle-Objekt erzeugen und dessen Fläche berechnen, ohne die Implementierung von GetArea() zu kennen.

RECT.HPP steht in Listing 6.8. Allein aus der Header-Datei mit der Deklaration der Klasse Rectangle kann der Programmierer ersehen, daß GetArea() einen int-Wert zurückgibt. Wie GetArea() diese magische Berechnung ausführt, ist für den Benutzer der Klasse Rectangle nicht von Belang. Im Prinzip könnte der Autor von Rectangle die Funktion GetArea() ändern, ohne die Programme zu beeinflussen, die auf die Klasse Rectangle zugreifen.

Was ist der Unterschied zwischen Deklarieren und Definieren?

Antwort: Eine Deklaration führt einen Namen für etwas ein, aber weist keinen Speicher zu. Eine Definition hingegen weist Speicher zu.

Bis auf wenige Ausnahmen sind alle Deklarationen auch Definitionen. Die vielleicht wichtigsten Ausnahmen sind die Deklaration einer globalen Funktion (ein Prototyp) und die Deklaration einer Klasse (in der Regel in einer Header-Datei).

Strukturen

Ein sehr enger Verwandter des class-Schlüsselwortes ist das Schlüsselwort struct. Es wird eingesetzt, um eine Struktur zu deklarieren. In C++ ist eine Struktur das gleiche wie eine Klasse, abgesehen davon, daß ihre Elemente standardmäßig öffentlich (public ) sind. Sie können eine Struktur genauso wie eine Klasse deklarieren, und Sie können sie mit den gleichen Datenelementen und Funktionen ausstatten. Wenn Sie sich dabei noch an die empfohlene Programmierpraxis halten und die privaten und öffentlichen Abschnitte Ihrer Klasse immer explizit ausweisen, gibt es eigentlich überhaupt keinen Unterschied.

Geben Sie das Listing 6.8 mit folgenden Änderungen noch einmal neu ein:

Starten Sie dann das Programm und vergleichen Sie die Ausgaben. Es sollte sich nichts daran geändert haben.

Warum zwei Schlüsselwörter das gleiche machen

Wahrscheinlich fragen Sie sich, warum es zwei Schlüsselwörter gibt, die das gleiche bewirken. Das ist eher dem Zufall zu verdanken. Als C++ entwickelt wurde, war es als Erweiterung von C konzipiert. Und C baut auf Strukturen auf, wenn auch C-Strukturen keine Klassenmethoden haben. Bjarne Stroustrup, der Begründer von C++, verwendete Strukturen, änderte jedoch später den Namen in Klasse, um die neue erweiterte Funktionalität besser auszudrücken.

Was Sie tun sollten

Legen Sie Ihre Klassendeklaration in einer .HPP-Datei ab und Ihre Elementfunktionen in einer .CPP-Datei.

Verwenden Sie das Schlüsselwort const so oft wie möglich.

Beschäftigen Sie sich mit den Klassen, bis Sie sie verstanden haben, und fahren Sie erst dann in der Lektüre fort.

Zusammenfassung

Heute haben Sie gelernt, wie man neue Datentypen - Klassen - erzeugt und wie man Variablen dieser neuen Typen - sogenannte Objekte - definiert.

Eine Klasse verfügt über Datenelemente, die Variablen verschiedener Typen einschließlich anderer Klassen sein können. Zu einer Klasse gehören auch Elementfunktionen, die sogenannten Methoden. Man verwendet diese Elementfunktionen, um die Datenelemente zu manipulieren und andere Aufgaben auszuführen.

Klassenelemente - sowohl Daten als auch Funktionen - können öffentlich (public) oder privat (private) sein. Öffentliche Elemente sind jedem Teil des Programms zugänglich. Auf private Elemente können nur die Elementfunktionen der Klasse zugreifen.

Es gehört zum guten Programmierstil, die Schnittstelle - oder Deklaration - der Klasse in einer separaten Datei unterzubringen. Gewöhnlich schreibt man die Schnittstelle in eine Datei mit der Erweiterung .HPP. Die Implementierung der Klassenmethoden bringt man in einer Datei mit der Erweiterung .CPP unter.

Klassenkonstruktoren initialisieren Objekte. Klassendestruktoren zerstören Objekte und werden oft genutzt, um Speicherplatz, der von den Methoden dieser Klasse zugewiesen wurde, wieder freizugeben.

Fragen und Antworten

Frage:
Wie groß ist ein Klassenobjekt?

Antwort:
Die Größe eines Klassenobjekts im Speicher wird durch die Summe aller Größen seiner Elementvariablen bestimmt. Klassenmethoden belegen keinen Platz in dem für das Objekt reservierten Hauptspeicher.

  1. Manche Compiler richten die Variablen im Speicher so aus, daß 2 Byte-Variablen tatsächlich etwas mehr Speicher verbrauchen als 2 Byte. Es empfiehlt sich, hierzu in der Dokumentation des Compilers nachzusehen, aber momentan gibt es keinen Grund, sich mit diesen Dingen im Detail zu beschäftigen.

Frage:
Wenn ich eine Cat-Klasse mit einem privaten Element itsAge deklariere und dann zwei Cat-Objekte, Frisky und Boots, definiere, kann Boots dann auf die Elementvariable itsAge von Frisky zugreifen?

Antwort:
Ja. Private Daten stehen den Elementfunktionen einer Klasse zur Verfügung, und die verschiedenen Instanzen der Klasse können auf ihre jeweiligen Daten zugreifen. In anderen Worten: Wenn Frisky und Boots beides Instanzen von Cat sind, können die Elementfunktionen von Frisky sowohl auf die Daten von Frisky als auf die von Boots zugreifen.

Frage:
Warum sollten Datenelemente nach Möglichkeit privat bleiben?

Antwort:
Indem man die Datenelemente privat macht, kann der Klient der Klasse die Daten verwenden, ohne sich um die Art und Weise ihrer Speicherung oder Verarbeitung kümmern zu müssen. Wenn zum Beispiel die Klasse Cat eine Methode GetAge() enthält, können die Klienten der Klasse Cat das Alter der Katze abrufen, ohne zu wissen oder sich darum zu kümmern, ob die Katze ihr Alter in einer Elementvariablen speichert oder es bei Bedarf berechnet.

Frage:
Wenn ich mit einer konstanten Funktion eine Klasse ändere und dadurch einen Compiler-Fehler hervorrufe, warum sollte ich dann nicht einfach das Schlüsselwort const weglassen und Compiler-Fehlern aus dem Wege gehen?

Antwort:
Wenn Ihre Elementfunktion aufgrund der Logik die Klasse nicht ändern sollte, stellt die Verwendung des Schlüsselwortes const eine gute Möglichkeit dar, den Compiler beim Aufspüren verzwickter Fehler hinzuzuziehen. Wenn zum Beispiel GetAge() keinen Grund hat, die Klasse Cat zu ändern, aber Ihre Implementierung die Zeile

   if (itsAge = 100) cout << "Hey! Du bist 100 Jahre alt\n";

Frage:
Gibt es überhaupt einen Grund, in einem C++-Programm eine Struktur zu verwenden?

Antwort:
Viele C++-Programmierer reservieren das Schlüsselwort struct für Klassen ohne Funktionen. Das ist ein Rückfall in die alten C-Strukturen, die keine Funktionen haben konnten. Offen gesagt, sind Strukturen nur verwirrend und gehören nicht zum guten Programmierstil. Die heutige Struktur ohne Methoden braucht morgen vielleicht eine Methode. Dann ist man gezwungen, entweder den Typ in class zu ändern oder die Regel zu durchbrechen und schließlich eine Struktur mit Methoden zu erzeugen.

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie die Lösungen in Anhang D lesen und zur Lektion des nächsten Tages übergehen.

Quiz

  1. Was ist der Punktoperator und wozu wird er verwendet?
  2. Wann wird Speicher reserviert - bei der Deklaration oder bei der Definition?
  3. Ist die Deklaration einer Klasse deren Schnittstelle oder deren Implementierung?
  4. Was ist der Unterschied zwischen öffentlichen (public) und privaten (private) Datenelementen?
  5. Können Elementfunktionen privat sein?
  6. Können Datenelemente öffentlich sein?
  7. Wenn Sie zwei Cat-Objekte deklarieren, können diese unterschiedliche Werte in ihren itsAge-Datenelementen haben?
  8. Enden Klassendeklarationen mit einem Semikolon? Und die Definitionen von Klassenmethoden?
  9. Wie würde die Header-Datei für eine Cat-Funktion Meow() aussehen, die keine Parameter übernimmt und void zurückliefert?
  10. Welche Funktion wird aufgerufen, um eine Klasse zu initialisieren?

Übungen

  1. Schreiben Sie einen Code, der eine Klasse namens Employee (Angestellter) mit folgenden Datenelementen deklariert: age, YearsOfService und Salary
  2. Schreiben Sie die Klasse Employee neu, mit privaten Datenelementen und zusätzlichen öffentlichen Zugriffsmethoden, um jedes der Datenelemente zu lesen und zu setzen.
  3. Schreiben Sie mit Hilfe der Employee-Klasse ein Programm, das zwei Angestellte erzeugt. Setzen Sie deren Alter (age), Beschäftigungszeitraum (YearsOfService) und Gehalt (Salary) und geben Sie diese Werte aus.
  4. Als Fortsetzung von Übung 3 nehmen Sie eine Methode in Employee auf, die berichtet, wieviel tausend Dollar der Angestellte verdient, aufgerundet auf die nächsten 1000 Dollar.
  5. Ändern Sie die Klasse Employee so, daß Sie age, YearsOfService und Salary initialisieren können, wenn Sie einen neuen Angestellten anlegen.
  6. FEHLERSUCHE: Was ist falsch an der folgenden Deklaration?
    class Square
    {
    public:
    int Side;
    }
  7. FEHLERSUCHE: Warum ist die folgende Klassendeklaration nicht besonders nützlich?
    class Cat
    {
    int GetAge()const;
    private:
    int itsAge;
    };
  8. FEHLERSUCHE: Welche drei Fehler wird der Compiler in folgendem Code finden?
    class  TV
    {
    public:
    void SetStation(int Station);
    int GetStation() const;
    private:
    int itsStation;
    };
    int main()
    {
    TV myTV;
    myTV.itsStation = 9;
    TV.SetStation(10);
    TV myOtherTv(2);
    return 0;
    }



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH