Willemers Informatik-Ecke
Die Attribute stat und
const sind im Zusammenhang mit Variablen bereits erläutert worden.
Im Zusammenhang mit Klassen haben sie eine etwas andere Bedeutung.
Statische Variablen und Funktionen in Klassen
Statische Elementvariablen in Klassen haben wenig mit den statischen Variablen
in Funktionen gemeinsam.
Während ansonsten Elementvariablen für jedes Objekt einmal angelegt
werden, existieren statische Elementvariablen pro Klasse exakt einmal, egal
wie viele Objekte dieser Klasse erzeugt werden.
Sie werden angelegt, bevor das erste Objekt der Klasse erzeugt wird.
Eine Anwendung für eine solche Variable könnte das Zählen der aktiven
Instanzen sein. Dazu wird die statische Variable in jedem Konstruktor
inkrementiert und in jedem Destruktor dekrementiert.
In der statischen Variable kann dann abgelesen werden, wie viele Objekte dieser
Klasse derzeit existieren.
Deklaration
Eine statische Elementvariable wird genauso wie jede andere Elementvariable
der Klasse deklariert. Es wird ihr allerdings das Schlüsselwort static
vorangestellt. Wohlgemerkt wird sie in der Klasse nur deklariert. Sie muss noch
separat von der Klasse definiert werden, sonst beklagt sich der Linker.
class StatKlasse
{
...
static int Zaehler;
};
Initialisierung
Eine statische Variable einer Klasse kann nicht im Konstruktor initialisiert
werden, da sie sonst bei jeder Erzeugung eines Objekts wieder zurückgesetzt
würde. Stattdessen wird sie wie eine globale Variable definiert, der der
Klassenname vorangestellt wurde. Bei dieser Gelegenheit wird sie auch gleich
initialisiert:
int StatKlasse::Zaehler = 0;
Statische Funktionen
Auf den ersten Blick scheint es unsinnig zu sein,
statische Funktionen zu definieren, denn jede Elementfunktion existiert ja
nur einmal im Speicher. Allerdings haben statische Funktionen ihre besondere
Aufgabe. Da sie nicht zu einem Objekt gehören, können sie auch nicht auf die
Datenelemente eines Objekts zugreifen. Stattdessen haben sie meist
die Aufgabe, als Schnittstelle für die statischen Variablen zu dienen.
So kann sichergestellt werden, dass beispielsweise der interne Zähler nicht
von außen manipuliert wird: Die statische Variable wird privat deklariert
und der Stand immer nur durch eine statische Funktion ermittelt.
Statische Funktionen haben mit statischen Variablen gemein, dass sie
zur Klasse gehören, aber unabhängig von der Zahl oder der Existenz von
Objekten verwendet werden können.
Aufruf
Statische Funktionen werden aufgerufen, indem der
Klassenname, zwei Doppelpunkte und der Funktionsname genannt werden.
Für den Aufruf muss also kein Objekt der Klasse existieren.
Das folgende Beispiel enthält eine statische Variable namens Zaehler,
die mitzählt, wie viele Instanzen der Klasse derzeit existieren. Die Variable
ist privat, aber durch eine statische Funktion wieviele() kann
der aktuelle Zählerstand ermittelt werden. Zum Testen werden Arrays von
StatKlasse angelegt und der Zählerstand immer wieder ausgegeben.
[Statische Variablen und Funktionen in Klassen (statclass.cpp)]
#include <iostream>
using namespace std;
class StatKlasse
{
public:
StatKlasse() {Zaehler++;}
~StatKlasse() {Zaehler--;}
static int wieviele() {return Zaehler;}
private:
static int Zaehler;
};
int StatKlasse::Zaehler=0;
int main()
{
cout << StatKlasse::wieviele() << endl;
StatKlasse feld[4];
cout << StatKlasse::wieviele() << endl;
{
StatKlasse feld[5];
cout << StatKlasse::wieviele() << endl;
}
cout << StatKlasse::wieviele() << endl;
}
Nach dem Start des Programms ist der Zähler durch die Initialisierung 0.
Das Anlegen eines Arrays mit vier Elementen der StatKlasse führt dazu, dass
der Konstruktor viermal aufgerufen wird.
Das Array feld wird in dem inneren Block nochmals definiert und
überdeckt damit zwar das äußere Array feld. Trotzdem das äußere
Array derzeit nicht erreichbar ist, ist es dennoch vorhanden. Also gibt es
innerhalb des Blocks neun Elemente.
Durch das Verlassen des inneren Blocks, wird das innere Array freigegeben.
Der Destruktor wird für jedes Element einmal aufgerufen.
Entsprechend liefert das Programm die Zahlen 0, 4, 9 und 4.
Konstanten
Insbesondere Funktionsparameter werden gern als konstant deklariert, um dem
Aufrufer zu signalisieren, dass sie innerhalb der Funktion nicht verändert
werden können.
Ist dieser Parameter ein Objekt einer Klasse, darf es innerhalb des
Gültigkeitsbereichs dieser Deklaration keinen verändernden Zugriff auf die
Datenelemente geben, weder direkt noch mittelbar.
Das folgende Beispiel stammt aus dem Abschnitt über Ausgabeoperatoren.
Dabei wird der Bruch als Referenz übergeben. Um zu signalisieren,
dass der Bruch sich durch die Ausgabe auf dem Bildschirm nicht ändert,
wird der zweite Parameter als konstant deklariert.
[Ausgabe eines Bruchs (bruch.cpp)]
ostream& operator<<(ostream& Stream, const tBruch& B)
{
return Stream << B.GetZaehler() << "/" << B.GetNenner();
}
Verdächtige Aufrufe
Innerhalb der Funktion wird aber für den konstanten Parameter eine
Elementfunktion aufgerufen. Dem Compiler ist es nicht so einfach möglich
festzustellen, ob die Funktion GetZaehler() die Werte von
B ändert. Dazu müsste er prüfen, ob diese Elementfunktion direkt
oder indirekt über andere Elementfunktionen Datenelemente verändert
oder nicht. Hier nimmt der Compiler den schlechtesten Fall an und geht davon
aus, dass eine Elementfunktion Änderungen vornehmen kann. Er wird also den
Aufruf von GetZaehler() bemängeln.
Konstante Funktionen
Damit der Compiler sicher sein kann, dass nicht eine der aufgerufenen
Elementfunktionen ein Datenelement ändert, können Sie die dort aufgerufenen
Funktionen als konstant deklarieren. Das erreichen Sie, indem Sie in der
Klassendefinition das Schlüsselwort const hinter die Parameterklammer
der Funktion setzen.
[Konstante Funktionen (bruch.cpp)]
class tBruch
{
...
long GetNenner() const {return nenner;}
long GetZaehler() const {return zaehler;}
...
};
Erst, wenn nur konstante Funktionen aufgerufen und Elementvariablen nicht
verändert werden, kann der Compiler sicher sein, dass das Objekt konstant
bleibt.
Wird nämlich innerhalb einer als konstant deklarierten Elementfunktion auf
eine Variable der Klasse verändernd zugegriffen, wird der Compiler diese
Funktion nicht übersetzen.
mutable
Für die Frage, ob ein Objekt einer Klasse konstant bleibt, werden alle
Elementvariablen herangezogen. In manchen Fällen enthält die Klasse aber
Elementvariablen, die für die Frage, ob ein Objekt in seinem Wert
verändert wird, irrelevant ist.
So beeinflusst beispielsweise das Datenelement wochentag in der Klasse
tDatum nicht das Datum als solches.
Die Variable wochentag kann für ein und dasselbe Datum entweder den
korrekten Wochentag oder eine -1 enthalten, wenn der Wochentag noch nicht
berechnet wurde. Das Datum des Objekts kann dennoch gleich sein.
Um solche Elementvariablen zu kennzeichnen, können Sie ihnen das Schlüsselwort
mutable voranstellen.
Ein so gekennzeichnetes Element kann auch von konstanten Funktionen verändert
werden.
Konstanten initialisieren
Konstanten
Konstanten können auch innerhalb der Klassen deklariert werden, auch wenn
es scheint, als ob der Compiler eine Initialisierung innerhalb der Klasse
nicht zulässt.
Tatsächlich ist die übliche Initialisierung mit einem Gleichheitszeichen und
dem darauf folgenden Wert in der Klassendefinition nicht möglich.
Eine Zuweisung im Konstruktor funktioniert auch nicht, weil dort ja eine
Zuweisung auf eine Konstante erfolgen würde.
Es ist aber möglich, die Konstante über einen Initialisierer am Konstruktor
vorzubelegen.
class tKonstant
{
public:
const int MaxPuffer;
tKonstant() : MaxPuffer(500) {}
tKonstant(long size);
};
tKonstant::tKonstant(long size): MaxPuffer(size)
{
Der Initialisierer wird also immer bei der Definition der Konstruktorfunktion
angebracht, nicht bei der Deklaration.