Mit der Definition einer Klasse erzeugt man normalerweise einen Datentyp, für dessen Datenelemente erstmal noch kein Platz geschaffen wird. Erst beim Anlegen von Variablen dieses neuen Typs (global, oder lokal, statisch oder automatisch oder auf dem Heap mit new, siehe Freier Speicher new und delete) wird -wie mit Variablen einfachen Typs auch- zur Laufzeit des Programms Platz im Speicher belegt.
Der Speicher für Datenelemente wird also nicht durch die Klassendefinition geschaffen, sondern erst durch die Instanziierung, und existiert zur Laufzeit des Programms für jede Instanz, also jedes gerade ,,lebende`` Objekt der Klasse.
Ähnlich wird in C mit:
typedef struct { int i; double d; } neuer_t; // hier wird noch kein Platz belegt! neuer_t glob; // eine Instanz von neuer_t; belegt Platz void up() { neuer_t a, b; // zwei weitere Instanzen von neuer_t; belegt Platz // ... }mit der Typdefinition
typedef struct{
...} neuer_t;
kein
Speicher belegt; erst mit der Vereinbarung der globalen Variablen glob und (solange die Funktion up() aktiv ist) mit der
Definition der Variablen a und b wird jeweils Platz für
eine int und eine double belegt.
In C++ ist es mit Klassen normalerweise ähnlich4.2:
class NeuerTyp { public: int i; double d; void setzeWerte( int neuesi, double neuesd ) { i = neuesi; d = neuesd; } }; // hier wird noch kein Platz belegt! NeuerTyp glob; // eine Instanz von NeuerTyp; belegt Platz void up() { NeuerTyp a, b; // zwei weitere Instanzen von NeuerTyp; belegt Platz // ... }
In diesem Beispiel gibt es drei Elemente mit dem Namen i, nämlich glob.i
,
und (solange up() aktiv ist) noch a.i und b.i;
ebenso gibt es das Element d einmal je Instanz. Falls
up() direkt oder indirekt rekursiv aufgerufen wird, kann es zu
einem Zeitpunkt auch noch mehr Instanzen
von a und b geben.
Im Gegensatz zu den Datenelementen werden die Methoden zu Maschinencode kompiliert, der zur Laufzeit nur einmal pro Klasse vorhanden ist. Der Code ist also generell statisch. Auch wenn man die Funktion setzeWerte() als glob.setzeWerte(...) oder a.setzeWerte(...) oder b.setzeWerte(...) aufruft, so wird doch in allen drei Fällen derselbe Code ausgeführt.
Das drückt sich auch darin aus, daß die Adresse der Funktion immer dieselbe ist,
egal über welches Objekt man sie bestimmt (&(glob.setzeWerte)
,
&(a.setzeWerte)
, &(b.setzeWerte)
).
Möchte man dieses Verhalten (ein gemeinsames Vorkommen für alle Instanzen) auch für Datenelemente haben, dann kann man diese Elemente innerhalb der Klasse einfach als static deklarieren:
class NeuerTyp { public: static int i; // jetzt statisch! double d; void setzeWerte( int neuesi, double neuesd ) { i = neuesi; d = neuesd; } }; // hier wird noch kein Platz belegt!
Jetzt gibt es ab Programmstart genau ein Element i, egal wieviele Instanzen der Klasse es zu einem bestimmten Zeitpunkt gerade gibt.
Weil i faktisch gar nicht mehr an ein bestimmtes Objekt gebunden
ist, ist es möglich und auch sinnvoll, es gleich über seinen Klassennamen
anzusprechen (NeuerTyp::i
anstatt
glob.i
oder a.i
oder b.i
).
Wie kann man auf ein solches statisches Element zugreifen? Zum einen
kann man den Wert ja von außerhalb ansprechen (im Beispiel
als NeuerTyp::i
), falls er im
public:
-Teil der Klassendefinition steht. Dies ist natürlich
nicht im Sinne der gewünschten Kapselung, und wird deshalb meistens
durch eine private:
- oder protected:
-Deklaration
unterbunden.
Weiterhin kann jede Methode das Element mit seinem Namen ansprechen, ebenso wie ein nichtstatisches Element.
Zur Initialisierung von statisch deklarierten Elementen gibt es zudem die Möglichkeit, sie wie eine normale statische Variable außerhalb der class-Anweisung zu initialisieren; man muß nur den Klassennamen mit angeben, um dem Compiler zu sagen, welche Klasse man meint:
int NeuerTyp::i = 25;
Diese Initialisierung darf ebenso wie bei statischen Variablen nur in
einem der Quelltexte vorkommen, aus denen das Programm zusammengesetzt
wird.
Die Initialisierung findet wie bei allen statischen Variablen bei
Programmstart statt, bevor main()
aufgerufen wird.
Die gezeigte Initialisierung ist auch zulässig, wenn das Element
im private:
- oder protected:
-Bereich einer Klasse steht.
An dieser Stelle ist es übrigens nicht erlaubt, das Element nochmals als static
zu deklarieren4.3.
Diese Art der Initialisierung ist sicher besser, als eine Initialisierung innerhalb des Konstruktors der Klasse, und zwar aus zwei Gründen:
Eine mögliche Verwendung von solchen statischen Elementen ist ein Zähler, wieviele Objekte einer Klasse existieren:
#include <iostream> class A { private: static size_t nInstanzen; // für die gesamte Klasse: Anzahl Objekte public: // Konstruktor: A() { nInstanzen++; // eine Instanz mehr } // Destruktor: ~A() { nInstanzen--; // eine Instanz weniger } // liefert die Anzahl der gerade aktiven Objekte: static size_t zeigeAnzahl() { return nInstanzen; } }; size_t A::nInstanzen = 0; int main( int nargs, char **args ) { A a, b; // ab hier gibt es 2 Objekte { A c; // jetzt gibt es 3 Objekte cout << A::zeigeAnzahl() << " Objekte" << endl; } // jetzt wieder nur 2 Objekte cout << A::zeigeAnzahl() << " Objekte" << endl; } // main( int nargs, char **args )
Dieses Programm liefert als Ausgabe:
3 Objekte 2 Objekte
Eine andere Möglichkeit wäre es, jedem Objekt eine ,,Seriennummer`` zu geben:
class A { private: static size_t NextSerial; // für gesamte Klasse: nächste freie Nummer size_t Serial; // jedes Objekt: Seriennummer public: // Konstruktor: A() { Serial = NextSerial++; } // Destruktor: ~A() { } }; size_t A::NextSerial = 1;
So bekommen alle Objekte eine fortlaufende Nummer (Serial).
Statische Elemente gehören also zur Klasse, nichtstatische Elemente gehören zu einem Objekt.
AnyWare@Wachtler.de