5. StandardmethodenIn diesem Kapitel geht es um die Methoden, die eine Klasse
immer besitzt.
Das sind der Standardkonstruktor, der Destruktor, der Kopierkonstruktor, der
Zuweisungs- und Adressoperator.
Diese Methoden sind wirklich immer vorhanden; definiert der Programmierer
keine, übernimmt der Compiler diese Aufgabe.
Aber, damit ihr den Unterschied zu normalen Methoden seht (denn Konstruktor
und Destruktor sind Sonderfälle) ein kurzes Beispiel, wie man eine Methode
innerhalb einer Klasse definiert:
class meine_klasse
{
public:
// Methode sollte natuerlich von aussen aufrufbar sein!
// Rueckgabetyp Name Parameter - wie immer
void ausgabe(void)
{
std::cout << "ausgabe()" << std::endl;
}
};
int main(int argc, char **argv)
{
meine_klasse my;
my.ausgabe();
return 0;
}
Der Zugriff ist also ähnlich dem bei Instanzvariablen. Später wird auf Besonderheiten
usw. von Methoden eingegangen.
5.1 KonstruktorErstellt man eine Instanz einer Klasse, die neben Methoden auch Instanzvariablen
enthält, stellt sich (natürlich) die Frage, wie man diese Instanzvariablen
initialisieren kann, denn bei der Definition in der Klasse geht dies
nicht(probiert's aus!).
Genau dafür gibt es Konstruktoren. Einer von ihnen (es kann auch mehrere geben)
wird beim Erzeugen einer Instanzaufgerufen. Er ist ähnlich einer Methode,
besitzt allerdings keinen Rückgabetyp, kann aber genau wie Methoden auch
Parameter entgegennehmen.
Im Folgenden werden die verschiedenen Konstruktoren, deren Implementation und Sonderheiten
gezeigt.
5.1.1 StandardkonstruktorDer Standardkonstruktor ist die Methode, die aufgerufen wird, wenn man keinen
Konstruktor direkt aufruft. Auch wird dieser aufgerufen, wenn man ein Array von
Klasseninstanzen anlegt.
Dieser Konstruktor trägt, wie alle anderen auch, den Namen der Klasse. Was ihn
jedoch von den noch folgenden Konstruktoren unterscheidet, ist die Tatsache, dass
er ohne Paramter aufgerufen werden
kann. Das heißt, dass er entweder gar kein
Argument, oder Argumente mit Vorgabewerten nimmt, sodass diese weggelassen werden
können.
Das Beispiel der Testklasse
fax:
class fax
{
// Man sollte mindestens einen Konstruktor im public-Bereich definieren!
public:
// Standardkonstruktor
fax(void)
{
}
};
Nocheinmal: Der Konstruktor trägt den Namen der Klasse und hat keinen Rückgabetyp.
Dieser Kontruktor, wie er oben definiert ist, ist der, den der Compiler hinzfügen
würde - er macht nichts und erwartet auch nichts.
Ein weiter Standardkostruktor ist Folgender:
class fax
{
public:
fax(int arg = 0)
{
}
};
Dieser Konstruktor kann ohne Elemente aufgerufen werden. Aber wie ruft man
eigentlich einen Konstruktor auf?
Ich habe gesagt, dass der Konstruktor die Methode ist, die beim Definieren einer
Klasseninstanz automatisch aufgerufen wird. Das bedeuted aber auch, dass wird
schonmal einen Konstruktor aufgerufen haben, nämlich als wir am Anfang des
Kapitels ein Objekt der Klasse meine_klasse angelegt haben.
Wichtig: So ruft man den Standardkonstruktor auf:klassenname variable;
Aber man darf die Klammern, die bei einem normalen Funktionsaufruf nötig
sind, nicht mit mit angeben,
klassenname variable();
denn der Compiler sieht darin die Deklaration einer Funnktion vom Typ
klassenname, mit Namen variable und keinen Paramtern.
Folgendes Beispiel demonstriert den Standardkonstruktor nocheinmal:
#include <iostream>
using namespace std;
class fax
{
public:
fax(int arg = 0)
{
cout << "fax() arg = " << arg << endl;
}
};
int main(int argc, char **argv)
{
fax MeinFax;
fax NochEinFax(5);
return 0;
}
Hier sieht man, dank der eingefügten Ausgabe, wie und wann der Konstruktor
aufgerufen wird.
Aber was genau bringt uns der Konstruktor?
Nehmen wir nochmal die Instanzvariablen aus der Klasse fax dazu, haben wir
ein Array und eine Variable, die gerne einen Wert haben möchten, damit man
problemlos arbeiten kann. Und genau da springt der Konstruktor ein:
Da dieser immer aufgerufen wird, kann man dort Variablen problemlos Werte
zuweisen.
Der Standardkonstruktor wird also die Variable
verbindung_offen auf
false setzen und die übergebene "Faxnummer" speichern. Da wir einen
Standardkonstruktor haben, wird dies ein optionales Argument, als Standard
nehmen wir "000000000000000".
So sieht das ganze dann aus:
#include <iostream>
using namespace std;
class fax
{
private:
char nummer[15];
bool verbindung_offen;
public:
// Standardkonstruktor
fax( string num = "000000000000000" )
{
int i;
verbindung_offen = false;
for( i=0; i<15; i++ ) nummer[i] = num[i];
}
};
int main(int argc, char **argv)
{
fax ErstesFax; // Standardkonstruktor ohne Argumente
fax ZweitesFax("08749887493"); // Standardkonstruktor mit Argumenten
return 0;
}
Zum Schluss:
Merkt euch folgendes zu Standardkonstruktoren
- Kann ohne Argumente aufgerufen werden
- Wird automatisch vom Compiler erstellt, falls nicht definiert
- Muss ohne Klammern aufgerufen werden
5.1.2 KopierkonstruktorEin Kopierkonstruktor ist ein Konstruktor, der als Parameter eine andere
Instanz der gleichen Klasse erwartet. Aus ihr kopiert die Werte in die
eigenen Instanzvariablen.
Wichtig: Er nimmt genau
einen Paramter, nämlich diese Instanz der
Klasse.
Hier ist ein Beispiel:
class fax
{
private:
char nummer[15];
bool verbindung_offen;
public:
// Kopierkonstruktor
fax( const fax& anderesFax )
{
int i;
verbindung_offen = anderesFax.verbindung_offen;
for( i=0; i<15; i++ ) nummer[i] = anderesFax.nummer[i];
}
};
Anmerkung: Im obigen Beispiel würde der Compiler einen Standardkonstruktor
erstellen!
Was am Kopierkonstruktor auffällt:
1. Er erwartet als Argument eine konstante Referenz auf eine Instanz der
Klasse fax.
Konstant soll die Variable sein, da sie eh nicht verändert wird.
Außerdem kann man dann auch konstante Instanzen übergeben!
Eine Referenz wird benutzt, da dadurch das Kopieren der Instanz-
variablen entfällt; die Geschwindigkeit gesteigert.
2. Er kann auf die (privaten) Elemente der anderen Klasseninstanz zugreifen
Und das kann er, weil er eine Methode der Klasse fax ist. Sonst könnte er
das nicht. Mehr dazu später bei den Methoden.
Als letztes noch ein Beispiel, wie man den Kopierkonstruktor aufruft:
#include <iostream>
using namespace std;
class fax
{
private:
char nummer[15];
bool verbindung_offen;
public:
// Standardkonstruktor
fax( string num = "000000000000000" )
{
int i;
verbindung_offen = false;
for( i=0; i<15; i++ ) nummer[i] = num[i];
}
// Kopierkonstruktor
fax( const fax& anderesFax )
{
int i;
verbindung_offen = anderesFax.verbindung_offen;
for( i=0; i<15; i++ ) nummer[i] = anderesFax.nummer[i];
}
};
int main(int argc, char **argv)
{
fax ErstesFax("08749887493"); // Standardkonstruktor mit Argumenten
fax Zweites(ErstesFax); // Aufruf des Kopierkonstruktors
return 0;
}
Merkt euch zu dem Kopierkonstruktor:
- Nimmt genau ein Argument: Eine andere Instanz der eigenen Klasse.
Diese ist oft eine konstante Referenz.
5.1.3 KonvertierungskonstruktorDer Konvertierungskonstruktor arbeitet eng mit dem Zuweisungsoperator zusammen.
Seine Funktionsweise/Argumente und Besonderheiten werden in Kapitel 5.3 besprochen.
Achtung: Der Konvertierungskonstruktor ist nicht automatisch in einer Klasse
enthalten; der Compiler erstellt keinen!
5.1.4 Eigene KonstruktorenNachdem ihr nun den Standard- und Kopierkonstruktor kennt, es ist an der Zeit,
einen eigenen Konstruktor zu schreiben.
Dazu erweitern wir die Klasse um die Variable
geschwindigkeit, die die
Anzahl der Sekunden angibt, die das Gerät zum Wählen braucht. Anstatt dem Standard-
konstruktor ein weiteres optionales Argument hinzuzufügen, schreiben wir einen
eigenen Konstruktor.
Dazu verändern wir die Klasse fax ersteinmal ein wenig:
#include <iostream>
using namespace std;
class fax
{
private:
char nummer[15];
bool verbindung_offen;
double geschwindigkeit; // Neue Variable: Faxgeschwindigkeit
public:
// Standardkonstruktor
fax( string num = "000000000000000" )
{
int i;
verbindung_offen = false;
for( i=0; i<15; i++ ) nummer[i] = num[i];
// Geschwindigkeit einen Wert zuweisen
geschwindigkeit = 0.2; // 200 Millisekunden
}
// Kopierkonstruktor
fax( const fax& anderesFax )
{
int i;
verbindung_offen = anderesFax.verbindung_offen;
for( i=0; i<15; i++ ) nummer[i] = anderesFax.nummer[i];
// Geschwindigkeit zuweisen
geschwindigkeit = anderesFax.geschwindigkeit;
}
};
int main(int argc, char **argv)
{
fax ErstesFax("08749887493"); // Standardkonstruktor mit Argumenten
fax Zweites(ErstesFax); // Aufruf des Kopierkonstruktors
return 0;
}
Jetzt ist die Klasse soweit einen neuen Konstruktor zu erhalten. Dieser soll
als Parameter zum einen die Nummer, zum anderen aber auch noch die Geschwindigkeit
erhalten. Dann kopiert und setzt er die Werte entsprechend:
fax(string num, double geschw)
{
int i;
verbindung_offen = false;
for( i=0; i<15; i++ ) nummer[i] = num[i];
geschwindigkeit = geschw;
}
Diesen fügen wir nun einfach in die Klasse ein und können ihn problemlos
benutzen:
fax MeinFax; // Standardkonstruktor
fax NochEinFax(MeinFax); // Kopierkonstruktor
fax DrittesFax("027917947571",0.5); // Eigener Konstruktor
Ich denke, dass auch ein eigener Konstruktor nicht besonders schwierig zu
programmieren ist.
Merkt euch zu eigenen Konstruktoren:
- Ein eigener Konstruktor ist weder ein Kopier- noch ein Standardkonstruktor
- Er ist auch kein Konvertierungskonstruktor
5.1.5 KonstruktorlisteBei der Konstruktorliste handelt es sich nicht, wie man vielleicht annehmen kann, um eine Liste
von Konstruktoren, sondern um eine Liste, die Instanzvariablen initialisiert. Um diese Verwechslung
zu verhindern gibt es auch den Begriff
Initalisierungsliste.
Aber fangen wir mit einem Beispiel an.
Die oben verwendete Instanzvariable
geschwindigkeit[/i] soll, sobald die Instanz angelegt worden ist,
nach dem Aufruf des Konstruktors, nicht mehr verändert werden.
Was bietet sich an?
Natürlich - wir deklarieren die Variable mit const.
Beim Kompilieren gibt es eine Fehlermeldung ähnlich dieser:
g++ -Wall -pedantic -o "klassen2" "klassen2.cc" (im Verzeichnis: /home/daniel/Software/C++)
klassen2.cc: In constructor ‘fax::fax(std::string)’:
klassen2.cc:13: error: uninitialized member ‘fax::geschwindigkeit’ with ‘const’ type ‘const double’
klassen2.cc:20: error: assignment of read-only data-member ‘fax::geschwindigkeit’
klassen2.cc: In copy constructor ‘fax::fax(const fax&)’:
klassen2.cc:24: error: uninitialized member ‘fax::geschwindigkeit’ with ‘const’ type ‘const double’
klassen2.cc:31: error: assignment of read-only data-member ‘fax::geschwindigkeit’
Kompilierung fehlgeschlagen.
Vorallem
assignment of read-only data-member sagt eindeutig, dass wir versuchen, einer konstanten
Variable einen Wert zuzuweisen - und das geht nunmal einfach nicht!
Wie wir wissen, müssen konstante Variablen initialisiert und können danach nicht mehr verändert werden.
Es muss also eine andere Möglichkeit geben.
Für diese Initialisierung von Instanzvariablen gibt es natürlich eine Möglichkeit

Und sie nennt sich ...
Konstruktorliste *traraa*.
So sieht eine Konstruktorliste aus, die die Variable geschwindigkeit initalisiert:
fax( string num = "000000000000000" ) : geschwindigkeit(0.2)
{
int i;
verbindung_offen = false;
for( i=0; i<15; i++ ) nummer[i] = num[i];
}
Auf den Namen des Konstruktors und dessen Paramter folgt, nach einem Doppelpunkt, die Initalisierungsliste.
Dort stehen alle Variablen, die Initalisiert werden sollen, getrennt durch Kommata.
Die Werte, die die Variablen erhalten sollen, stehen in Klammern. Der Aufbau ähnelt also einem Konstruktoraufruf.
Das Schema nochmal:
Klassenname(Paramter) : Variable1(Wert), Variable2(Wert), ...
{
Implementation
}
Mit dieser Konstruktorliste können wir also konstante Instanzvariablen initalisieren.
Aber natürlich ist diese Initialisierungsform nicht nur auf Konstantes beschränkt.
Die Variable
verbindung_offen können wir dort ebenfalls initalisieren:
fax( string num = "000000000000000" ) : verbindung_offen(false), geschwindigkeit(0.2)
{
int i;
for( i=0; i<15; i++ ) nummer[i] = num[i];
}
Jetzt ist die Wertzuweisung der Variable einer Initalisierung in der Konstruktorliste gewichen.
Bemerkt, dass die Variable
vor geschwindigkeit steht?
Eigentlich ist die Reihenfolge egal, da ich jedoch mit dem Switch
-pedantic mit GCC arbeite,
der bei nicht ISO-C/ISO-C++ eine Warnung und bei verbotenen Erweiterungen einen Fehler ausspuckt.
Deshalb initialisiere ich die Variablen gleich in der richtigen Reihenfolge

Neben konstanten Elementen gibt es noch eine Art von Instanzvariablen, die grundsätzlich über die
Konstruktorliste initalisiert werden: eingebettete Objekte.
Dahinter steckt nichts anderes, als die Instanzen anderer Klassen (z.B. std::string).
Da jedoch nicht jeder seine Instanzvariablen immer in der Konstruktorliste initalisiert, übernimmt der
Compiler diese Aufgabe; er fügt diese Variablen der Konstruktorliste hinzu oder erstellt erstmal eine
Liste, falls keine vorhanden sein sollte.
Wir haben gelernt, dass Instanzen beim Erzeugen immer den Konstruktor aufrufen! Also muss das auch irgendwo
passieren - in der Konstruktorliste.
Habt ihr Beispielsweise ein Element der Klasse std::string in eurer Klasse, fügt der Compiler
dessen Konstruktoraufruf in die Konstruktrliste ein.
class test
{
private:
string f;
public:
test()
{
}
// Der Compiler macht daraus:
// test() : f()
};
Man sieht also, dass der Compiler den Standardkonstruktor aufruft. Das ist ein Grund, warum man
einen solchen Konstruktor haben sollte

Aber natürlich kann man in der Konstruktorliste nicht nur den Standardkonstruktor aufrufen.
Auch jeder andere ist erlaubt:
#include <iostream>
using namespace std;
class test
{
private:
string f;
public:
test(void): f(10,'x') // Konstruktoraufruf
{
cout << f << endl;
}
};
int main(int argc, char **argv)
{
test t; // nicht test t() !!!
return 0;
}
Wie man sehen kann, wird in der Konstruktorliste ein anderer Konstruktor aufgerufen.
Möchte man dieses tun, muss man es natürlich manuell machen!
Merk euch zu der Konstruktorliste:
- Initialisiert Instanzvariablen
- Beginnt nach einem Doppelpunkt nach dem Paramtern eines Konstruktors
- Die Werte für die Variable, die durch Kommata getrennt sind, stehen in Klammern