vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 2

Tag 14

Spezielle Themen zu Klassen und Funktionen

C++ bietet eine Reihe von Möglichkeiten, um den Gültigkeitsbereich und den Einfluß von Variablen und Zeigern einzuschränken. So wissen Sie bereits, wie man globale Variablen, lokale Variablen in Funktionen, Zeiger auf Variablen und Elementvariablen erzeugt. Heute lernen Sie,

Statische Datenelemente

Bis jetzt haben Sie die Daten von Klassen wahrscheinlich nur als einzigartig zu den einzelnen Objekten und nicht als gemeinsam genutzte Daten zwischen mehreren Objekten einer Klasse gesehen. Wenn man zum Beispiel fünf Cat-Objekte erzeugt hat, verfügt jedes Objekt über eigene Variablen für Alter, Gewicht und andere Daten. Das Alter des einen Objekts beeinflußt nicht das Alter eines anderen Objekts.

Manchmal möchte man aber Daten verwalten, die alle Objekte einer Klasse gemeinsam nutzen. Vielleicht möchten Sie festhalten, wie viele Objekte einer bestimmten Klasse bis zu einem gegebenen Zeitpunkt erzeugt wurden und wie viele es noch gibt. Im Gegensatz zu normalen Elementvariablen werden statische Elementvariablen von allen Instanzen einer Klasse gemeinsam genutzt. Sie bilden einen Kompromiß zwischen globalen Daten, die allen Teilen eines Programms zur Verfügung stehen, und Datenelementen, die normalerweise nur dem einzelnen Objekt verfügbar sind.

Man kann sich das so erklären, daß ein statisches Element zur Klasse und nicht zum Objekt gehört. Normale Datenelemente sind je einmal pro Objekt vorhanden, während statische Elemente nur einmal pro Klasse auftreten. Listing 14.1 deklariert ein Cat-Objekt mit dem statischen Datenelement HowManyCats. In dieser Variable wird die aktuelle Anzahl der erzeugten Cat-Objekte festgehalten, wozu die Klasse die statische Variable HowManyCats bei jeder Erzeugung inkrementiert und bei jeder Zerstörung dekrementiert.

Listing 14.1: Statische Datenelemente

1:     // Listing 14.1 Statische Datenelemente
2:
3: #include <iostream.h>
4:
5: class Cat
6: {
7: public:
8: Cat(int age):itsAge(age){HowManyCats++; }
9: virtual ~Cat() { HowManyCats--; }
10: virtual int GetAge() { return itsAge; }
11: virtual void SetAge(int age) { itsAge = age; }
12: static int HowManyCats;
13:
14: private:
15: int itsAge;
16:
17: };
18:
19: int Cat::HowManyCats = 0;
20:
21: int main()
22: {
23: const int MaxCats = 5; int i;
24: Cat *CatHouse[MaxCats];
25: for (i = 0; i<MaxCats; i++)
26: CatHouse[i] = new Cat(i);
27:
28: for (i = 0; i<MaxCats; i++)
29: {
30: cout << "Es bleiben ";
31: cout << Cat::HowManyCats;
32: cout << " Katzen uebrig!\n";
33: cout << "Diejenige loeschen, die ";
34: cout << CatHouse[i]->GetAge();
35: cout << " Jahre alt ist.\n";
36: delete CatHouse[i];
37: CatHouse[i] = 0;
38: }
39: return 0;
40: }

Es bleiben 5 Katzen uebrig!
Diejenige loeschen, die 0 Jahre alt ist.
Es bleiben 4 Katzen uebrig!
Diejenige loeschen, die 1 Jahre alt ist.
Es bleiben 3 Katzen uebrig!
Diejenige loeschen, die 2 Jahre alt ist.
Es bleiben 2 Katzen uebrig!
Diejenige loeschen, die 3 Jahre alt ist.
Es bleiben 1 Katzen uebrig!
Diejenige loeschen, die 4 Jahre alt ist.

In den Zeilen 5 bis 17 wird eine einfache Cat-Klasse deklariert. In Zeile 12 wird HowManyCats als statische Elementvariable vom Typ int deklariert.

Die Deklaration von HowManyCats definiert noch kein Integer-Objekt. Es wird damit auch kein Speicher reserviert. Anders als bei nicht-statischen Elementen wird auch kein Speicher reserviert, wenn man ein Cat-Objekt instantiiert, da sich die statische Elementvariable (im Beispiel HowManyCats) nicht im Objekt befindet. Aus diesem Grund wird die Variable in Zeile 19 definiert und initialisiert.

Es ist ein verbreiteter Fehler, die Definition der statischen Elementvariablen von Klassen zu vergessen. Achten Sie darauf, daß Ihnen das nicht passiert! Natürlich kommt es trotzdem irgendwann vor, und der Linker reagiert daraufhin mit einer Fehlermeldung wie der folgenden:

error LNK2001: Nichtaufgeloestes externes Symbol "public: static int  Cat::HowManyCats"

Für die Variable itsAge ist keine derartige Definition erforderlich, da es sich um eine nicht-statische Elementvariable handelt. Diese wird definiert, sobald man ein Cat-Objekt erzeugt, wie es im Beispiel in Zeile 26 geschieht.

Der Konstruktor für Cat inkrementiert die statische Elementvariable in Zeile 8. Der Destruktor dekrementiert sie in Zeile 9. Dadurch steht in HowMayCats zu jedem Zeitpunkt die Anzahl der erzeugten und noch nicht zerstörten Cat-Objekte.

Das Rahmenprogramm in den Zeilen 21 bis 40 instantiiert fünf Katzen und legt sie in einem Array ab. Die Instantiierung ist mit fünf Aufrufen von Cat-Konstruktoren verbunden. Demzufolge wird HowManyCats fünfmal vom Anfangswert 0 aus inkrementiert.

Das Programm durchläuft dann alle fünf Positionen im Array und gibt den Wert von HowManyCats aus, bevor der aktuelle Cat-Zeiger gelöscht wird. Die Ausgabe zeigt, daß der Anfangswert gleich 5 ist (schließlich wurden fünf Objekte konstruiert) und daß nach jedem Schleifendurchlauf ein Cat-Objekt weniger übrigbleibt.

Beachten Sie, daß HowManyCats öffentlich ist und main() direkt darauf zugreift. Es gibt keinen Grund, diese Elementvariable in dieser Form freizulegen. Besser ist es, sie mit den anderen Elementvariablen als privat zu deklarieren und eine öffentliche Zugriffsmethode bereitzustellen, solange man auf die Daten immer über eine Instanz von Cat zugreift. Will man andererseits auf diese Daten direkt zugreifen, ohne daß unbedingt ein Cat-Objekt verfügbar sein muß, gibt es zwei Möglichkeiten: die Variable öffentlich halten, wie in Listing 14.2 geschehen, oder eine statische Elementfunktion bereitstellen, eine Möglichkeit, die wir später noch besprechen werden.

Listing 14.2: Zugriff auf statische Elemente über die Klasse

1:     //Listing 14.2 Statische Datenelemente
2:
3: #include <iostream.h>
4:
5: class Cat
6: {
7: public:
8: Cat(int age):itsAge(age){HowManyCats++; }
9: virtual ~Cat() { HowManyCats--; }
10: virtual int GetAge() { return itsAge; }
11: virtual void SetAge(int age) { itsAge = age; }
12: static int HowManyCats;
13:
14: private:
15: int itsAge;
16:
17: };
18:
19: int Cat::HowManyCats = 0;
20:
21: void TelepathicFunction();
22:
23: int main()
24: {
25: const int MaxCats = 5; int i;
26: Cat *CatHouse[MaxCats];
27: for (i = 0; i<MaxCats; i++)
28: {
29: CatHouse[i] = new Cat(i);
30: TelepathicFunction();
31: }
32:
33: for ( i = 0; i<MaxCats; i++)
34: {
35: delete CatHouse[i];
36: TelepathicFunction();
37: }
38: return 0;
39: }
40:
41: void TelepathicFunction()
42: {
43: cout << "Es sind ";
44: cout << Cat::HowManyCats << " Katzen am Leben!\n";
45: }

Es sind 1 Katzen am Leben!
Es sind 2 Katzen am Leben!
Es sind 3 Katzen am Leben!
Es sind 4 Katzen am Leben!
Es sind 5 Katzen am Leben!
Es sind 4 Katzen am Leben!
Es sind 3 Katzen am Leben!
Es sind 2 Katzen am Leben!
Es sind 1 Katzen am Leben!
Es sind 0 Katzen am Leben!

Listing 14.2 ist bis auf die neue Funktion TelepathicFunction() identisch mit Listing 14.1. Diese neue Funktion erzeugt weder ein Cat-Objekt noch übernimmt sie ein Cat- Objekt als Parameter. Ihre Aufgabe ist es, auf die Elementvariable HowManyCats zuzugreifen. Es sollte jedoch erwähnt werden, daß diese Elementvariable sich nicht in einem bestimmten Objekt befindet, sondern in der Klasse als Ganzes; falls öffentlich, kann jede Funktion im Programm darauf zugreifen.

Die Alternative zu einer öffentlichen Elementvariablen wäre, die Variable privat zu machen. In diesem Falle können Sie nur über eine Elementfunktion darauf zugreifen, wofür Sie allerdings ein Objekt dieser Klasse erzeugen müssen. Dieser Ansatz wird in Listing 14.3 verfolgt. Direkt nach der Analyse von 14.3 schließt sich die Betrachtung der Alternative statischer Elementfunktionen an.

Listing 14.3: Zugriff auf statische Elemente mittels nicht-statischer Elementfunktionen

1:     //Listing 14.3 private statische Datenelemente 
2:
3: #include <iostream.h>
4:
5: class Cat
6: {
7: public:
8: Cat(int age):itsAge(age){HowManyCats++; }
9: virtual ~Cat() { HowManyCats--; }
10: virtual int GetAge() { return itsAge; }
11: virtual void SetAge(int age) { itsAge = age; }
12: virtual int GetHowMany() { return HowManyCats; }
13:
14:
15: private:
16: int itsAge;
17: static int HowManyCats;
18: };
19:
20: int Cat::HowManyCats = 0;
21:
22: int main()
23: {
24: const int MaxCats = 5; int i;
25: Cat *CatHouse[MaxCats];
26: for (i = 0; i<MaxCats; i++)
27: CatHouse[i] = new Cat(i);
28:
29: for (i = 0; i<MaxCats; i++)
30: {
31: cout << "Es sind ";
32: cout << CatHouse[i]->GetHowMany();
33: cout << " Katzen uebrig!\n";
34: cout << "Diejenige loeschen, die ";
35: cout << CatHouse[i]->GetAge()+2;
36: cout << " Jahre alt ist\n";
37: delete CatHouse[i];
38: CatHouse[i] = 0;
39: }
40: return 0;
41: }

Es sind 5 Katzen übrig!
Diejenige löschen, die 2 Jahre alt ist.
Es sind 4 Katzen übrig!
Diejenige löschen, die 3 Jahre alt ist.
Es sind 3 Katzen übrig!
Diejenige löschen, die 4 Jahre alt ist.
Es sind 2 Katzen übrig!
Diejenige löschen, die 5 Jahre alt ist.
Es sind 1 Katzen übrig!
Diejenige löschen, die 6 Jahre alt ist.

Zeile 17 deklariert HowManyCats als statische Elementvariable mit privatem Zugriff. Über eine Nicht-Elementfunktion wie TelepathicFunction() aus dem vorangehenden Listing können Sie nicht mehr auf diese Variable zugreifen.

Auch wenn HowManyCats statisch ist, befindet sie sich dennoch im Gültigkeitsbereich der Klasse. Jede Klassenfunktion, wie beispielsweise GetHowMany(), kann darauf zugreifen, vergleichbar den Elementfunktionen, die auf alle Datenelemente zugreifen können. Bedingung ist jedoch, daß die Funktion, die GetHowMany() aufruft, ein Objekt haben muß, auf dem sie die Funktion aufruft.

Was Sie tun sollten

... und was nicht

Verwenden Sie statische Elementvariablen, um gemeinsame Daten für alle Instanzen einer Klasse einzurichten.

Machen Sie statische Elementvariablen protected oder privat, wenn Sie den Zugriff auf die Variablen beschränken wollen.

Verwenden Sie keine statischen Elementvariablen, um Daten für ein Objekt zu speichern. Statische Datenelemente werden von allen Objekten der Klasse geteilt.

Statische Elementfunktionen

Statische Elementfunktionen sind vergleichbar mit statischen Elementvariablen: Sie existieren nicht in einem Objekt, sondern im Gültigkeitsbereich der Klasse. Demzufolge kann man sie aufrufen, ohne ein Objekt der Klasse verfügbar haben zu müssen. Listing 14.4 zeigt dazu ein Beispiel.

Listing 14.4: Statische Elementfunktionen

1:     // Listing 14.4 Statische Elementfunktionen
2:
3: #include <iostream.h>
4:
5: class Cat
6: {
7: public:
8: Cat(int age):itsAge(age){HowManyCats++; }
9: virtual ~Cat() { HowManyCats--; }
10: virtual int GetAge() { return itsAge; }
11: virtual void SetAge(int age) { itsAge = age; }
12: static int GetHowMany() { return HowManyCats; }
13: private:
14: int itsAge;
15: static int HowManyCats;
16: };
17:
18: int Cat::HowManyCats = 0;
19:
20: void TelepathicFunction();
21:
22: int main()
23: {
24: const int MaxCats = 5;
25: Cat *CatHouse[MaxCats]; int i;
26: for (i = 0; i<MaxCats; i++)
27: {
28: CatHouse[i] = new Cat(i);
29: TelepathicFunction();
30: }
31:
32: for ( i = 0; i<MaxCats; i++)
33: {
34: delete CatHouse[i];
35: TelepathicFunction();
36: }
37: return 0;
38: }
39:
40: void TelepathicFunction()
41: {
42: cout << "Es sind " << Cat::GetHowMany() << " Katzen am Leben!\n";
43: }

Es sind 1 Katzen am Leben.
Es sind 2 Katzen am Leben.
Es sind 3 Katzen am Leben.
Es sind 4 Katzen am Leben.
Es sind 5 Katzen am Leben.
Es sind 4 Katzen am Leben.
Es sind 3 Katzen am Leben.
Es sind 2 Katzen am Leben.
Es sind 1 Katzen am Leben.
Es sind 0 Katzen am Leben.

Zeile 15 der Cat-Deklaration deklariert die statische Elementvariable HowManyCats für den privaten Zugriff. Zeile 12 deklariert die Zugriffsfunktion GetHowMany() als öffentlich und als statisch.

Da GetHowMany() öffentlich ist, kann man darauf aus jeder Funktion zugreifen. Wegen ihres statischen Charakters benötigt man kein Objekt vom Typ Cat, um die Funktion aufzurufen. Demzufolge kann die Funktion TelepathicFunction() die öffentliche Zugriffsfunktion aufrufen (Zeile 42), obwohl sie selbst keinen Zugriff auf ein Cat-Objekt hat. Natürlich könnte man GetHowMany() auch über die in main() verfügbaren Cat-Objekte aufrufen - genau wie bei allen anderen Zugriffsfunktionen.

Statische Elementfunktionen haben keinen this-Zeiger. Demzufolge kann man sie nicht als const deklarieren. Da außerdem der Zugriff auf Datenelemente in Elementfunktionen mittels des Zeigers this erfolgt, bleibt den statischen Elementfunktionen der Zugriff auf nicht statische Elementvariablen verwehrt!

Statische Elementfunktionen

Statische Elementfunktionen können entweder über ein Objekt der Klasse (wie jede andere Elementfunktion auch), oder ohne ein Objekt durch vollständige Angabe der Klasse und des Funktionsnamens aufgerufen werden.

Beispiel:

     class Cat
{
public:
static int GetHowMany() { return HowManyCats; }
private:
static int HowManyCats;
};
int Cat::HowManyCats = 0;
int main()
{
int howMany;
Cat theCat; // Cat definieren
howMany = theCat.GetHowMany(); // Zugriff über Objekt
howMany = Cat::GetHowMany(); // Zugriff ohne Objekt
}

Zeiger auf Funktionen

Genau wie ein Array-Name ein konstanter Zeiger auf das erste Element des Arrays ist, stellt ein Funktionsname einen konstanten Zeiger auf die Funktion dar. Es ist auch möglich, eine Zeigervariable als Zeiger auf eine Funktion zu deklarieren und die Funktion mit Hilfe dieses Zeigers aufzurufen. Damit kann man Programme schreiben, die erst zur Laufzeit - etwa in Abhängigkeit von einer Benutzereingabe - entscheiden, welche Funktion aufzurufen ist.

Eine knifflige Sache bei der Programmierung mit Funktionszeigern ist allerdings die korrekte Angabe des Objekttyps, auf den man zeigen möchte. Ein Zeiger auf int verweist auf eine Integer-Variable, und ein Zeiger auf eine Funktion muß auf eine Funktion des passenden Rückgabetyps und der passenden Signatur zeigen.

In der Deklaration

long (* fktZeiger) (int);

wird fktZeiger als Zeiger deklariert (beachten Sie das * vor dem Namen). Er zeigt auf eine Funktion, die einen Integer-Parameter übernimmt und einen Wert vom Typ long zurückgibt. Die Klammern um * fktZeiger sind erforderlich, da die Klammern um int enger binden - das heißt, einen höheren Vorrang als der Indirektionsoperator (*) haben. Ohne die ersten Klammern würde diese Anweisung eine Funktion deklarieren, die einen Integer übernimmt und einen Zeiger auf einen long zurückgibt. (Denken Sie daran, daß Leerzeichen hier bedeutungslos sind.)

Sehen Sie sich die folgenden Deklarationen an:

long * Funktion (int);
long (* fktZeiger) (int);

Die erste Deklaration, Funktion(), ist eine Funktion, die einen Integer übernimmt und einen Zeiger auf eine Variable vom Typ long zurückgibt. Die zweite Deklaration, fktZeiger , ist ein Zeiger auf eine Funktion, die einen Integer übernimmt und eine Variable vom Typ long zurückgibt.

Die Deklaration eines Funktionszeigers schließt immer den Rückgabetyp und die Klammern zur Kennzeichnung der Parametertypen (falls vorhanden) ein. Listing 14.5 demonstriert die Deklaration und Verwendung von Funktionszeigern.

Listing 14.5: Zeiger auf Funktionen

1:     // Listing 14.5 Zeiger auf Funktionen
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: void PrintVals(int, int);
10:
11: int main()
12: {
13: void (* pFunc) (int &, int &);
14: bool fQuit = false;
15:
16: int valOne=1, valTwo=2;
17: int choice;
18: while (fQuit == false)
19: {
20: cout << "(0)Beenden (1)Werte aendern (2)Quadrat "
"(3)Dritte Potenz (4)Vertauschen: ";
21: cin >> choice;
22: switch (choice)
23: {
24: case 1: pFunc = GetVals; break;
25: case 2: pFunc = Square; break;
26: case 3: pFunc = Cube; break;
27: case 4: pFunc = Swap; break;
28: default : fQuit = true; break;
29: }
30:
31: if (fQuit)
32: break;
33:
34: PrintVals(valOne, valTwo);
35: pFunc(valOne, valTwo);
36: PrintVals(valOne, valTwo);
37: }
38: return 0;
39: }
40:
41: void PrintVals(int x, int y)
42: {
43: cout << "x: " << x << " y: " << y << endl;
44: }
45:
46: void Square (int & rX, int & rY)
47: {
48: rX *= rX;
49: rY *= rY;
50: }
51:
52: void Cube (int & rX, int & rY)
53: {
54: int tmp;
55:
56: tmp = rX;
57: rX *= rX;
58: rX = rX * tmp;
59:
60: tmp = rY;
61: rY *= rY;
62: rY = rY * tmp;
63: }
64:
65: void Swap(int & rX, int & rY)
66: {
67: int temp;
68: temp = rX;
69: rX = rY;
70: rY = temp;
71: }
72:
73: void GetVals (int & rValOne, int & rValTwo)
74: {
75: cout << "Neuer Wert fuer ValOne: ";
76: cin >> rValOne;
77: cout << "Neuer Wert fuer ValTwo: ";
78: cin >> rValTwo;
79: }

(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 1
x: 1 y: 2
Neuer Wert fuer ValOne: 2
Neuer Wert fuer ValTwo: 3
x: 2 y: 3
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 3
x: 2 y: 3
x: 8 y: 27
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 2
x: 8 y: 27
x:64 y: 729
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 4
x:64 y: 729
x:729 y: 64
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 0

Die Zeilen 5 bis 8 deklarieren vier Funktionen mit gleichem Rückgabetyp und gleicher Signatur. Diese Funktionen geben void zurück und übernehmen zwei Referenzen auf Integer.

In Zeile 14 wird pFunc als Zeiger auf eine Funktion deklariert, die void zurückgibt und zwei Referenzen auf int als Parameter übernimmt. Damit läßt sich mit pFunc auf die eingangs erwähnten vier Funktionen zeigen. Der Anwender hat mehrmals die Auswahl unter den aufzurufenden Funktionen. Nach dieser Auswahl wird die entsprechende Funktion an pFunc zugewiesen. Die Zeilen 34 bis 36 geben die aktuellen Werte der beiden Ganzzahlen aus, rufen die momentan zugewiesene Funktion auf und geben dann die Werte erneut aus.

Zeiger auf Funktionen

Ein Funktionszeiger wird genauso aufgerufen wie die Funktion, auf die er zeigt, mit dem Unterschied, daß man den Namen des Funktionszeigers und nicht den Namen der Funktion verwendet.

Um einen Funktionszeiger auf eine spezielle Funktion zu richten, weisen Sie dem Zeiger den Funktionsnamen ohne Klammern zu. Der Funktionsname ist ein konstanter Zeiger auf die Funktion selbst. Verwenden Sie den Funktionszeiger genauso wie den Funktionsnamen. Ein Zeiger auf Funktionen muß im Rückgabetyp und Signatur mit der Funktion, der er zugewiesen wird, übereinstimmen.

Beispiel:

     long (*pFktEins) (int, int);
long EineFunktion (int, int);
pFktEins = EineFunktion;
pFktEins (5,7);

Warum sollte man Funktionszeiger einsetzen?

Sie könnten das Programm aus Listing 14.5 auch ohne weiteres ohne Funktionszeiger schreiben. Doch die Verwendung dieser Zeiger macht Absicht und Ziel des Programms deutlicher: Wähle eine Funktion aus einer Liste und rufe sie dann auf.

Listing 14.6 verwendet die Funktionsprototypen und Definitionen aus Listing 14.5, aber keine Funktionszeiger. Schauen Sie sich die Unterschiede zwischen diesen zwei Listings an.

Listing 14.6: Neufassung von Listing 14.5 ohne Funktionszeiger

1:     // Listing 14.6 Ohne Funktionszeiger
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: void PrintVals(int, int);
10:
11: int main()
12: {
13: bool fQuit = false;
14: int valOne=1, valTwo=2;
15: int choice;
16: while (fQuit == false)
17: {
18: cout << "(0)Beenden (1)Werte aendern (2)Quadrat "
"(3)Dritte Potenz (4)Vertauschen:";
19: cin >> choice;
20: switch (choice)
21: {
22: case 1:
23: PrintVals(valOne, valTwo);
24: GetVals(valOne, valTwo);
25: PrintVals(valOne, valTwo);
26: break;
27:
28: case 2:
29: PrintVals(valOne, valTwo);
30: Square(valOne,valTwo);
31: PrintVals(valOne, valTwo);
32: break;
33:
34: case 3:
35: PrintVals(valOne, valTwo);
36: Cube(valOne, valTwo);
37: PrintVals(valOne, valTwo);
38: break;
39:
40: case 4:
41: PrintVals(valOne, valTwo);
42: Swap(valOne, valTwo);
43: PrintVals(valOne, valTwo);
44: break;
45:
46: default :
47: fQuit = true;
48: break;
49: }
50:
51: if (fQuit)
52: break;
53: }
54: return 0;
55: }
56:
57: void PrintVals(int x, int y)
58: {
59: cout << "x: " << x << " y: " << y << endl;
60: }
61:
62: void Square (int & rX, int & rY)
63: {
64: rX *= rX;
65: rY *= rY;
66: }
67:
68: void Cube (int & rX, int & rY)
69: {
70: int tmp;
71:
72: tmp = rX;
73: rX *= rX;
74: rX = rX * tmp;
75:
76: tmp = rY;
77: rY *= rY;
78: rY = rY * tmp;
79: }
80:
81: void Swap(int & rX, int & rY)
82: {
83: int temp;
84: temp = rX;
85: rX = rY;
86: rY = temp;
87: }
88:
89: void GetVals (int & rValOne, int & rValTwo)
90: {
91: cout << "Neuer Wert fuer ValOne: ";
92: cin >> rValOne;
93: cout << "Neuer Wert fuer ValTwo: ";
94: cin >> rValTwo;
95: }

(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 1
x: 1 y: 2
Neuer Wert fuer ValOne: 2
Neuer Wert fuer ValTwo: 3
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 3
x: 2 y: 3
x: 8 y: 27
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 2
x: 8 y: 27
x: 64 y: 729
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 4
x: 64 y: 729
x: 729 y: 64
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 0

Wie Sie sehen, hat sich an der Ausgabe nichts geändert, aber der Rumpf des Programms hat sich von 22 auf 46 Zeilen vergrößert. Die Aufrufe von PrintVals() müssen für jeden Fall (Case) wiederholt werden.

Ich war stark in Versuchung, PrintVals()an den Anfang der while-Schleife und an das Ende zu setzen, statt die Funktion in jede case-Anweisung aufzunehmen. Doch damit würde PrintVals() auch für den Fall des Abbruchs aufgerufen, und das war nicht Teil der Spezifikation.

Abgesehen von dem etwas umfangreicheren Code und den wiederholten Aufrufen zur Ausführung des immer gleichen Befehls, hat das Programm an Klarheit verloren. Dies ist jedoch nur ein Beispielprogramm, um zu zeigen, wie Zeiger auf Funktionen arbeiten. Unter praxisnahen Bedingungen sind die Vorteile deutlicher zu sehen: Mit Funktionszeigern können Sie doppelten Code entfernen, Ihr Programm klarer gestalten und Tabellen von Funktionen anlegen, die in Abhängigkeit von Laufzeitbedingungen aufgerufen werden.

Verkürzter Aufruf

Zeiger auf Funktionen muß man nicht dereferenzieren, obwohl dies möglich ist. Angenommen, pFunktion ist ein Zeiger auf eine Funktion, die einen Integer übernimmt und eine Variable vom Typ long zurückgibt, und man hat Funktion eine passende Funktion zugewiesen, so kann man diese Funktion entweder mit

pFunktion(x);

oder mit

(*pFunktion)(x);

aufrufen. Beide Formen sind identisch. Die erste ist nur eine Kurzversion der zweiten.

Arrays mit Zeigern auf Funktionen

Genau wie man ein Array mit Zeigern auf Integer deklarieren kann, läßt sich auch ein Array mit Zeigern auf Funktionen deklarieren, die einen bestimmten Werttyp zurückgeben und mit einer bestimmten Signatur versehen sind. Listing 14.7 ist ebenfalls eine Neufassung von Listing 14.5 und verwendet ein Array, um alle ausgewählten Funktionen direkt hintereinander in einer Schleife aufzurufen.

Listing 14.7: Ein Array von Zeigern auf Funktionen

1:     // Listing 14.7 Array mit Zeigern auf Funktionen
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: void PrintVals(int, int);
10:
11: int main()
12: {
13: int valOne=1, valTwo=2;
14: int choice, i;
15: const MaxArray = 5;
16: void (*pFuncArray[MaxArray])(int&, int&);
17:
18: for (i=0;i<MaxArray;i++)
19: {
20: cout << "(1)Werte aendern (2)Quadrat "
"(3)Dritte Potenz (4)Vertauschen: ";
21: cin >> choice;
22: switch (choice)
23: {
24: case 1:pFuncArray[i] = GetVals; break;
25: case 2:pFuncArray[i] = Square; break;
26: case 3:pFuncArray[i] = Cube; break;
27: case 4:pFuncArray[i] = Swap; break;
28: default:pFuncArray[i] = 0;
29: }
30: }
31:
32: for (i=0;i<MaxArray; i++)
33: {
34: if ( pFuncArray[i] == 0 )
35: continue;
36: pFuncArray[i](valOne,valTwo);
37: PrintVals(valOne,valTwo);
38: }
39: return 0;
40: }
41:
42: void PrintVals(int x, int y)
43: {
44: cout << "x: " << x << " y: " << y << endl;
45: }
46:
47: void Square (int & rX, int & rY)
48: {
49: rX *= rX;
50: rY *= rY;
51: }
52:
53: void Cube (int & rX, int & rY)
54: {
55: int tmp;
56:
57: tmp = rX;
58: rX *= rX;
59: rX = rX * tmp;
60:
61: tmp = rY;
62: rY *= rY;
63: rY = rY * tmp;
64: }
65:
66: void Swap(int & rX, int & rY)
67: {
68: int temp;
69: temp = rX;
70: rX = rY;
71: rY = temp;
72: }
73:
74: void GetVals (int & rValOne, int & rValTwo)
75: {
76: cout << "Neuer Wert fuer ValOne: ";
77: cin >> rValOne;
78: cout << "Neuer Wert fuer ValTwo: ";
79: cin >> rValTwo;
80: }

(1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 1
(1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 2
(1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 3
(1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 4
(1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 2
Neuer Wert fuer ValOne: 2
Neuer Wert fuer ValTwo: 3
x: 2 y: 3
x: 4 y: 9
x: 64 y: 729
x: 729 y: 64
x: 531441 y:4096

Zeile 16 deklariert das Array pFuncArray als Array mit fünf Zeigern auf Funktionen, die void zurückgeben, und zwei Integer-Referenzen als Parameter übernehmen.

In der Schleife in den Zeilen 18 bis 30 wählt der Anwender die aufzurufenden Funktionen aus. Den Elementen des Arrays wird die Adresse der entsprechenden Funktion zugewiesen. Die Schleife in den Zeilen 32 bis 38 ruft die Funktionen der Reihe nach auf. Das Ergebnis erscheint nach jedem Aufruf auf dem Bildschirm.

Zeiger auf Funktionen an andere Funktionen übergeben

Zeiger auf Funktionen (und übrigens auch Arrays mit Zeigern auf Funktionen) kann man an andere Funktionen übergeben, die Aktionen ausführen und dann die richtige Funktion unter Verwendung des Zeigers aufrufen.

Beispielsweise kann man Listing 14.5 verbessern, indem man den gewählten Funktionszeiger an eine andere Funktion (außerhalb von main()) übergibt. Diese Funktion gibt dann die Werte aus, ruft die gewählte Funktion auf und zeigt die Werte erneut an. Listing 14.8 zeigt diese Variante.

Listing 14.8: Übergabe von Zeigern auf Funktionen als Funktionsargumente

1:     // Listing 14.8 Ohne Funktionszeiger
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: void PrintVals(void (*)(int&, int&),int&, int&);
10:
11: int main()
12: {
13: int valOne=1, valTwo=2;
14: int choice;
15: bool fQuit = false;
16:
17: void (*pFunc)(int&, int&);
18:
19: while (fQuit == false)
20: {
21: cout << "(0)Beenden (1)Werte aendern (2)Quadrat "
"(3)Dritte Potenz (4)Vertauschen: ";
22: cin >> choice;
23: switch (choice)
24: {
25: case 1:pFunc = GetVals; break;
26: case 2:pFunc = Square; break;
27: case 3:pFunc = Cube; break;
28: case 4:pFunc = Swap; break;
29: default:fQuit = true; break;
30: }
31: if (fQuit == true)
32: break;
33: PrintVals ( pFunc, valOne, valTwo);
34: }
35:
36: return 0;
37: }
38:
39: void PrintVals( void (*pFunc)(int&, int&),int& x, int& y)
40: {
41: cout << "x: " << x << " y: " << y << endl;
42: pFunc(x,y);
43: cout << "x: " << x << " y: " << y << endl;
44: }
45:
46: void Square (int & rX, int & rY)
47: {
48: rX *= rX;
49: rY *= rY;
50: }
51:
52: void Cube (int & rX, int & rY)
53: {
54: int tmp;
55:
56: tmp = rX;
57: rX *= rX;
58: rX = rX * tmp;
59:
60: tmp = rY;
61: rY *= rY;
62: rY = rY * tmp;
63: }
64:
65: void Swap(int & rX, int & rY)
66: {
67: int temp;
68: temp = rX;
69: rX = rY;
70: rY = temp;
71: }
72:
73: void GetVals (int & rValOne, int & rValTwo)
74: {
75: cout << "Neuer Wert fuer ValOne: ";
76: cin >> rValOne;
77: cout << "Neuer Wert fuer ValTwo: ";
78: cin >> rValTwo;
79: }

(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 1
x: 1 y: 2
Neuer Wert fuer ValOne: 2
Neuer Wert fuer ValTwo: 3
x: 2 y: 3
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 3
x: 2 y: 3
x: 8 y: 27
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 2
x: 8 y: 27
x: 64 y: 729
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 4
x: 64 y: 729
x: 729 y: 64
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 0

Zeile 17 deklariert pFunc als Zeiger auf eine Funktion, die void zurückgibt und zwei Referenzen auf int als Parameter übernimmt. Zeile 9 deklariert PrintVals() als Funktion mit drei Parametern. Der erste ist ein Zeiger auf eine Funktion, die void zurückgibt und zwei Integer-Referenzen als Parameter übernimmt. Die beiden anderen Argumente an PrintVals() sind Integer-Referenzen. Der Anwender wählt wieder aus, welche Funktion aufzurufen ist. In Zeile 33 wird dann PrintVals() aufgerufen.

Fragen Sie doch mal einen C++-Programmierer, was die folgende Deklaration bedeutet:

void PrintVals(void (*)(int&, int&),int&, int&);

Diese Art von Deklaration werden Sie selten verwenden und wahrscheinlich jedesmal im Buch nachsehen, wenn Sie sie brauchen. Manchmal rettet aber gerade eine derartige Deklaration Ihren Programmentwurf.

typedef bei Zeigern auf Funktionen

Die Konstruktion void (*)(int&, int&) ist bestenfalls unbequem. Das ganze läßt sich mit typdef vereinfachen. Man deklariert einen Typ VPF als Zeiger auf eine Funktion, die void zurückgibt und zwei Integer-Referenzen übernimmt. In Listing 14.9 ist der erste Teil von Listing 14.8 unter Verwendung von typedef neu geschrieben.

Listing 14.9: Einsatz von typedef, um einen Code mit Zeiger auf Funktionen lesbarer zu machen

1:     // Listing 14.9 Einsatz von typedef 
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: typedef void (*VPF) (int&, int&) ;
10: void PrintVals(VPF,int&, int&);
11:
12: int main()
13: {
14: int valOne=1, valTwo=2;
15: int choice;
16: bool fQuit = false;
17:
18: VPF pFunc;
19:
20: while (fQuit == false)
21: {
22: cout << "(0)Beenden (1)Werte aendern (2)Quadrat "
"(3)Dritte Potenz (4)Vertauschen: ";
23: cin >> choice;
24: switch (choice)
25: {
26: case 1:pFunc = GetVals; break;
27: case 2:pFunc = Square; break;
28: case 3:pFunc = Cube; break;
29: case 4:pFunc = Swap; break;
30: default:fQuit = true; break;
31: }
32: if (fQuit == true)
33: break;
34: PrintVals ( pFunc, valOne, valTwo);
35: }
36: return 0;
37: }
38:
39: void PrintVals( VPF pFunc,int& x, int& y)
40: {
41: cout << "x: " << x << " y: " << y << endl;
42: pFunc(x,y);
43: cout << "x: " << x << " y: " << y << endl;
44: }
45:
46: void Square (int & rX, int & rY)
47: {
48: rX *= rX;
49: rY *= rY;
50: }
51:
52: void Cube (int & rX, int & rY)
53: {
54: int tmp;
55:
56: tmp = rX;
57: rX *= rX;
58: rX = rX * tmp;
59:
60: tmp = rY;
61: rY *= rY;
62: rY = rY * tmp;
63: }
64:
65: void Swap(int & rX, int & rY)
66: {
67: int temp;
68: temp = rX;
69: rX = rY;
70: rY = temp;
71: }
72:
73: void GetVals (int & rValOne, int & rValTwo)
74: {
75: cout << "Neuer Wert fuer ValOne: ";
76: cin >> rValOne;
77: cout << "Neuer Wert fuer ValTwo: ";
78: cin >> rValTwo;
79: }

(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 1
x: 1 y: 2
Neuer Wert fuer ValOne: 2
Neuer Wert fuer ValTwo: 3
x: 2 y: 3
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 3
x: 2 y: 3
x: 8 y: 27
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 2
x: 8 y: 27
x: 64 y: 729
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 4
x: 64 y: 729
x: 729 y: 64
(0)Beenden (1)Werte aendern (2)Quadrat (3)Dritte Potenz (4)Vertauschen: 0

In Zeile 9 wird VPF mittels typedef vom Typ »Funktion, die void zurückgibt und zwei Referenzen auf int als Parameter übernimmt« deklariert.

Zeile 10 deklariert die Funktion PrintVals() mit drei Parametern: einem Parameter vom Typ VPF und zwei Referenzen auf int. Zeile 18 deklariert nun pFunc vom Typ VPF.

Nach Einführung des Typs VPF sind alle folgenden Deklarationen von pFunc und PrintVals() wesentlich besser zu lesen.

Zeiger auf Elementfunktionen

Bis jetzt haben wir nur Funktionszeiger für allgemeine Funktionen, die nicht zu Klassen gehören, erzeugt. Genausogut kann man Zeiger auf Funktionen erzeugen, die Elemente von Klassen sind.

Für Zeiger auf Elementfunktionen verwendet man die gleiche Syntax wie bei Zeigern auf Funktionen, schließt aber den Klassennamen und den Zugriffsoperator (::) ein. Zeigt zum Beispiel pFunc auf eine Elementfunktion Shape(), die zwei Integer übernimmt und void zurückgibt, lautet die Deklaration für pFunc folgendermaßen:

void (Shape::*pFunc) (int, int);

Zeiger auf Elementfunktionen setzt man in genau der gleichen Weise ein wie Zeiger auf Funktionen. Allerdings erfordern sie ein Objekt der richtigen Klasse, auf dem sie aufgerufen werden. Listing 14.10 zeigt die Verwendung von Zeigern auf Elementfunktionen.

Listing 14.10: Zeiger auf Elementfunktionen

1:     // Listing 14.10 Zeiger auf Elementfunktionen
2:
3: #include <iostream.h>
4:
5: class Mammal
6: {
7: public:
8: Mammal():itsAge(1) { }
9: virtual ~Mammal() { }
10: virtual void Speak() const = 0;
11: virtual void Move() const = 0;
12: protected:
13: int itsAge;
14: };
15:
16: class Dog : public Mammal
17: {
18: public:
19: void Speak()const { cout << "Wuff!\n"; }
20: void Move() const { cout << "Bei Fuß gehen ...\n"; }
21: };
22:
23:
24: class Cat : public Mammal
25: {
26: public:
27: void Speak()const { cout << "Miau!\n"; }
28: void Move() const { cout << "Schleichen...\n"; }
29: };
30:
31:
32: class Horse : public Mammal
33: {
34: public:
35: void Speak()const { cout << "Wieher!\n"; }
36: void Move() const { cout << "Gallopieren...\n"; }
37: };
38:
39:
40: int main()
41: {
42: void (Mammal::*pFunc)() const =0;
43: Mammal* ptr =0;
44: int Animal;
45: int Method;
46: bool fQuit = false;
47:
48: while (fQuit == false)
49: {
50: cout << "(0)Beenden (1)Hund (2)Katze (3)Pferd: ";
51: cin >> Animal;
52: switch (Animal)
53: {
54: case 1: ptr = new Dog; break;
55: case 2: ptr = new Cat; break;
56: case 3: ptr = new Horse; break;
57: default: fQuit = true; break;
58: }
59: if (fQuit)
60: break;
61:
62: cout << "(1)Sprechen (2)Bewegen: ";
63: cin >> Method;
64: switch (Method)
65: {
66: case 1: pFunc = Mammal::Speak; break;
67: default: pFunc = Mammal::Move; break;
68: }
69:
70: (ptr->*pFunc)();
71: delete ptr;
72: }
73: return 0;
74: }

(0)Beenden (1)Hund (2)Katze (3)Pferd: 1
(1)Sprechen (2)Bewegen: 1
Wuff!
(0)Beenden (1)Hund (2)Katze (3)Pferd: 2
(1)Sprechen (2)Bewegen: 1
Miau!
(0)Beenden (1)Hund (2)Katze (3)Pferd: 3
(1)Sprechen (2)Bewegen: 2
Galoppieren...
(0)Beenden (1)Hund (2)Katze (3)Pferd: 0

Die Zeilen 5 bis 14 deklarieren den abstrakten Datentyp Mammal mit den zwei abstrakten Methoden Speak() und Move(). Von Mammal leiten sich die Klassen Dog, Cat und Horse ab, die jeweils Speak() und Move() überschreiben.

Das Rahmenprogramm in main() fordert den Anwender auf, ein Tier auszuwählen. Dann erzeugt es auf dem Heap eine neues Unterklassenobjekt von Animal und weist es in den Zeilen 54 bis 56 an ptr zu.

Der Anwender wird nun aufgefordert, die aufzurufende Methode auszuwählen. Diese wird dem Zeiger pFunc zugewiesen. In Zeile 70 ruft das erzeugte Objekt die gewählte Methode auf. Dabei wird der Zeiger ptr für den Zugriff auf das Objekt und pFunc für den Zugriff auf die Funktion verwendet.

Schließlich wird in Zeile 71 delete aufgerufen, um den für das Objekt im Heap reservierten Speicher zurückzugeben. Ein Aufruf von delete auf pFunc ist nicht erforderlich, da es sich um einen Zeiger auf den Code und nicht auf ein Objekt im Heap handelt. Ein entsprechender Versuch erzeugt sogar einen Compiler-Fehler.

Arrays mit Zeigern auf Elementfunktionen

Genau wie Zeiger auf Funktionen lassen sich auch Zeiger auf Elementfunktionen in Arrays speichern. Das Array kann mit den Adressen der verschiedenen Elementfunktionen initialisiert werden, und diese können durch Indizierung des Array aufgerufen werden. Listing 14.11 verdeutlicht diese Technik.

Listing 14.11: Array mit Zeigern auf Elementfunktionen

1:      // Listing 14.11 Array mit Zeigern auf Elementfunktionen
2:
3: #include <iostream.h>
4:
5: class Dog
6: {
7: public:
8: void Speak()const { cout << "Wuff!\n"; }
9: void Move() const { cout << "Laufen...\n"; }
10: void Eat() const { cout << "Fressen...\n"; }
11: void Growl() const { cout << "Grrrrr\n"; }
12: void Whimper() const { cout << "Heulen...\n"; }
13: void RollOver() const { cout << "Herumwaelzen...\n"; }
14: void PlayDead() const { cout <<"Ist dies das Ende von Caesar?\n"; }
15: };
16:
17: typedef void (Dog::*PDF)()const ;
18: int main()
19: {
20: const int MaxFuncs = 7;
21: PDF DogFunctions[MaxFuncs] =
22: { Dog::Speak,
23: Dog::Move,
24: Dog::Eat,
25: Dog::Growl,
26: Dog::Whimper,
27: Dog::RollOver,
28: Dog::PlayDead };
29:
30: Dog* pDog =0;
31: int Method;
32: bool fQuit = false;
33:
34: while (!fQuit)
35: {
36: cout <<"(0)Beenden (1)Sprechen (2)Bewegen (3)Fressen (4)Knurren";
37: cout << " (5)Winseln (6)Herumwaelzen (7)Totstellen: ";
38: cin >> Method;
39: if (Method == 0)
40: {
41: fQuit = true;
42: }
43: else
44: {
45: pDog = new Dog;
46: (pDog->*DogFunctions[Method-1])();
47: delete pDog;
48: }
49: }
50: return 0;
51: }

(0)Beenden (1)Sprechen (2)Bewegen (3)Fressen (4)Knurren (5)Winseln (6)Herumwaelzen (7)Totstellen: 1
Wuff!
(0)Beenden (1)Sprechen (2)Bewegen (3)Fressen (4)Knurren (5)Winseln (6)Herumwaelzen (7)Totstellen: 4
Grrrrr
(0)Beenden (1)Sprechen (2)Bewegen (3)Fressen (4)Knurren (5)Winseln (6)Herumwaelzen (7)Totstellen: 7
Ist dies das Ende von Caesar?
(0)Beenden (1)Sprechen (2)Bewegen (3)Fressen (4)Knurren (5)Winseln (6)Herumwaelzen (7)Totstellen: 0

Die Zeilen 5 bis 15 erzeugen die Klasse Dog mit sieben Elementfunktionen. Rückgabetyp und Signatur sind bei allen Funktionen gleich. Zeile 17 deklariert PDF mit Hilfe von typedef als Zeiger auf eine Elementfunktion von Dog, die keine Parameter übernimmt, keine Werte zurückgibt und konstant ist - entsprechend der Signatur der sieben Elementfunktionen von Dog.

In den Zeilen 21 bis 28 wird das Array DogFunctions für die Aufnahme von sieben derartigen Elementfunktionen deklariert und mit den Adressen dieser Funktionen initialisiert.

Die Zeilen 36 und 37 fordern den Anwender zur Auswahl einer Methode auf. Je nach Auswahl - außer bei Beenden - wird ein neues Dog-Objekt auf dem Heap erzeugt und dann die gewünschte Methode aus dem Array aufgerufen (Zeile 46). Die folgende Zeile können Sie ebenfalls einem eingefleischten C++-Programmierer in Ihrer Firma vorlegen und ihn um eine Erklärung bitten:

(pDog->*DogFunctions[Method-1])();

Diese etwas esoterische Konstruktion läßt sich insbesondere bei Tabellen mit Elementfunktionen hervorragend einsetzen. Das Programm ist dadurch besser zu lesen und verständlicher.

Was Sie tun sollten

... und was nicht

Rufen Sie Zeiger auf Elementfunktionen über spezielle Objekte der Klasse auf.

Verwenden Sie typedef, um Zeiger auf Deklarationen von Elementfunktionen besser lesbar zu machen.

Verwenden Sie keine Zeiger auf Elementfunktionen, wenn es einfachere Lösungen gibt.

Zusammenfassung

Heute haben Sie gelernt, wie man statische Elementvariablen in einer Klasse erzeugt. Statische Elementvariablen gibt es einmal pro Klasse statt einmal für jedes Objekt. Falls das statische Element für den öffentlichen Zugriff deklariert ist, kann man auf diese Elementvariable durch Angabe des vollständigen Namens auch ohne ein Objekt des Klassentyps zugreifen.

Statische Elementvariablen lassen sich als Zähler für Klasseninstanzen einsetzen. Da sie nicht zum Objekt gehören, reserviert die Deklaration keinen Speicher. Statische Elementvariablen sind außerhalb der Klassendeklaration zu definieren und zu initialisieren.

Statische Elementfunktionen sind wie die statischen Elementvariablen Teil der Klasse. Für den Zugriff auf statische Elementfunktionen ist kein Objekt der entsprechenden Klasse erforderlich, und man kann mit diesen Funktionen auf statische Datenelemente zugreifen. Da statische Elementfunktionen keinen this-Zeiger haben, bleibt der Zugriff auf nicht statische Datenelemente verwehrt.

Aus dem gleichen Grund kann man statische Elementfunktionen auch nicht als konstant deklarieren. const in einer Elementfunktion zeigt an, daß der this-Zeiger konstant ist.

Außerdem haben Sie gelernt, wie man Zeiger auf Funktionen und Zeiger auf Elementfunktionen deklariert und verwendet. Es wurde Ihnen gezeigt, wie man Arrays dieser Zeiger erzeugt und wie man sie an Funktionen übergibt.

Zeiger auf Funktionen und Zeiger auf Elementfunktionen können genutzt werden, um Tabellen von Funktionen anzulegen, die zur Laufzeit ausgewählt werden. Damit erhält Ihr Programm eine Flexibilität, die Sie anders nicht so leicht erhalten.

Fragen und Antworten

Frage:
Warum verwendet man statische Daten, wenn man auch mit globalen Daten arbeiten kann?

Antwort:
Der Gültigkeitsbereich von statischen Daten beschränkt sich auf die Klasse. Demzufolge sind die Daten nur über ein Objekt der Klasse zugänglich, über einen expliziten und vollständigen Aufruf unter Verwendung des Klassennamens (bei öffentlichen Daten) oder über eine statische Elementfunktion. Allerdings sind statische Daten an den Klassentyp gebunden. Durch den eingeschränkten Zugriff und die strenge Typisierung sind statische Daten sicherer als globale Daten.

Frage:
Warum setzt man statische Elementfunktionen ein, wenn man globale Funktionen verwenden kann?

Antwort:
Der Gültigkeitsbereich statischer Elementfunktionen ist auf die Klasse beschränkt. Man kann diese Funktionen nur mit Hilfe eines Objekts der Klasse oder einer expliziten und vollständigen Spezifikation (wie zum Beispiel KlassenName::FunktionsName ) aufrufen.

Frage:
Ist es häufig der Fall, daß man Zeiger auf Funktionen und Zeiger auf Elementfunktionen einsetzt?

Antwort:
Nein. Sie haben ihren speziellen Anwendungsbereich, gehören aber nicht zu den häufig verwendeten Konstrukten. Viele komplexe und leistungsstarke Programme kommen ohne solche Zeiger aus.

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. Können statische Elementvariablen privat sein?
  2. Geben Sie die Deklaration für eine statische Elementvariable an.
  3. Geben Sie die Deklaration für eine statische Funktion an.
  4. Geben Sie die Deklaration für einen Zeiger auf eine Funktion, die long zurückgibt und zwei int-Parameter übernimmt.
  5. Modifizieren Sie den Zeiger in Frage 4 so, daß er ein Zeiger auf eine Elementfunktion der Klasse Car ist.
  6. Geben Sie die Deklaration für ein Array von zehn Zeigern an, die wie in Frage 5 definiert sind.

Übungen

  1. Schreiben Sie ein kurzes Programm, das eine Klasse mit einer Elementvariablen und einer statischen Elementvariablen deklariert. Der Konstruktor soll die Elementvariable initialisieren und die statische Elementvariable inkrementieren. Der Destruktor soll die Elementvariable dekrementieren.
  2. Verwenden Sie das Programm aus Übung 1, schreiben Sie ein kleines Rahmenprogramm, das drei Objekte erzeugt und dann den Inhalt der Elementvariablen und der statischen Elementvariablen anzeigt. Zerstören Sie danach jedes Objekt und zeigen Sie die Auswirkung auf die statische Elementvariable.
  3. Modifizieren Sie das Programm aus Übung 2, indem Sie eine statische Elementfunktion für den Zugriff auf die statische Elementvariable verwenden. Machen Sie die statische Elementvariable privat.
  4. Schreiben Sie einen Zeiger auf Elementfunktionen, der auf die nicht statischen Datenelemente des Programm aus Übung 3 zugreift, und verwenden Sie den Zeiger, um den Wert der Elemente auszugeben.
  5. Ergänzen Sie die Klasse aus der vorigen Frage um zwei Elementvariablen. Fügen Sie Zugriffsfunktionen hinzu, die den gleichen Rückgabewert und die gleiche Signatur aufweisen und die die Werte dieser Elementvariablen auslesen. Greifen Sie auf diese Funktionen über einen Zeiger auf Elementfunktionen zu.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


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