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,
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.
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 alsconst
deklarieren. Da außerdem der Zugriff auf Datenelemente in Elementfunktionen mittels des Zeigersthis
erfolgt, bleibt den statischen Elementfunktionen der Zugriff auf nicht statische Elementvariablen verwehrt!
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.
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
}
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.
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.
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.
long (*pFktEins) (int, int);
long EineFunktion (int, int);
pFktEins = EineFunktion;
pFktEins (5,7);
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.
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 Typlong
zurückgibt, und man hatFunktion
eine passende Funktion zugewiesen, so kann man diese Funktion entweder mitpFunktion(x);(*pFunktion)(x);aufrufen. Beide Formen sind identisch. Die erste ist nur eine Kurzversion der zweiten.
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 (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.
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.
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.
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.
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.
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.
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.
privat
sein?
long
zurückgibt und zwei int
-Parameter übernimmt.
Car
ist.
privat
.
© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH