Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

23.2 Zuweisungen (=, +=)

23.2 Zuweisungen (=, +=)

Bevor ich anfange etwas zu berechnen, benötige ich einen Zuweisungsoperator. Im folgenden werden Sie sehen, wie meine Klasse definiert ist und wie man dann das "=" realisiert.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
					
// Klasse, welche einen Integer repräsentiert
class CInteger {
	private:
		int m_iValue;

	public:
		// Eigener Konstruktor
		CInteger(int);

		// (binäre) Zuweisung
		CInteger&	operator=(const CInteger&);
		CInteger&	operator=(const int&);
		CInteger&	operator+=(const CInteger&);
		CInteger&	operator+=(const int&);
};
					

Dies ist erst einmal die Grundlegende Klassenstruktur, welche in der Header-Datei steht und welche ich im Verlauf dieses Kapitels erweitern werde. Warum ich die einzelnen Methoden so definiert habe, wie ich sie definiert habe, werde ich dann bei der Implementierung jeder einzelnen, erläutern.

Auffällig ist hier eine neue Art der Schreibweise. Ich habe bei den Methoden-Prototypen nur noch den Übergabetyp angegeben und keinen Namen der Variable. Dies ist bei Prototypen immer möglich und ich habe dies lediglich gemacht, um Ihnen mal zu zeigen, dass es auch anders geht. Es wäre nicht falsch, wenn Sie Variablennamen mit hinschreibt. In der Regel lässt man sie aber nur Weg, wenn keine Missverständnisse auftreten können (ist nur für die Codevervollständigung notwendig). Hat man Beispielsweise eine Matrixklasse und übergibt im Konstruktor eine Zeilen - und Spaltenanzahl, wäre es fatal, den Variablennamen nicht mit anzugeben, da man sonst nicht wüsste, welcher der beiden was ist.

 1
 2
 3
 4
					
// Eigener Konstruktor ////////////////////////////////////////////////////////
CInteger::CInteger(int iValue)
	: m_iValue(iValue)
{} // CInteger ////////////////////////////////////////////////////////////////
					

Der Konstruktor ist nicht wirklich spannend und sollte soweit klar sein. Mit ihm realisiere ich die Initialisierung der Art:

 1
 2
 3
					
CInteger	oMeinObjekt(15);
// bzw.
CInteger*	pZeigerAufMeinObjekt = new CInteger(15);
					

Aber nun wird es spannend!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
					
// Einfache Zuweisungen ///////////////////////////////////////////////////////
CInteger& CInteger::operator=(const CInteger& oInt) {
	this->m_iValue = oInt.m_iValue;
	return *this;
}

CInteger& CInteger::operator=(const int& iInt) {
	this->m_iValue = iInt;
	return *this;
} // operator= ////////////////////////////////////////////////////////////////

// ...

// Ermöglicht wird jetzt z.B. so etwas
CInteger oObjekt1(5);
CInteger oObjekt2(7);

oObjekt1			= oObjekt2;
oObjekt2			= 14;

// oder auch
CInteger* pZeigerAufObjekt1	= new CInteger(5);
CInteger* pZeigerAufObjekt2	= new CInteger(7);

*pZeigerAufObjekt1		= *pZeigerAufObjekt2;
*pZeigerAufObjekt2		= 14;

// pZeigerAufObjekt1 = pZeigerAufObjekt2 wäre böse, da dort nicht der = Operator aufgerufen
// werden würde, sondern lediglich die Adressen kopiert und das erste Objekt somit in der Luft
// hängen würde.

delete pZeigerAufObjekt1;
delete pZeigerAufObjekt2;
					

Die erste Methode sorgt dafür, dass man einem Objekt der Instanz "CInteger", ein anderes Objekt des gleichen Typs, zuweisen kann. Wie Sie hier sehen, braucht man dann bei der Implementierung, natürluch einen Variablennamen, weil man sonst nicht auf den entsprechenden Wert zugreifen könnte.

Das Objekt was zugewiesen werden soll, steckt im Methodenparameter. Auffällig hierbei ist das "&". Dies bedeutet, dass ich kein neues Objekt benötige (im Hintergrund würde sonst der Kopierkonstruktor aufgerufen werden), sondern dass mir die Referenz auf das original Objekt genügt (Stichwort call-by-reference). Da ich jetzt das übergebene Objekt normalerweise verändern könnte, habe ich noch das "const" vorangestellt, damit ich dies zum einen nicht darf und zum anderen der Benutzer weiß, dass mit seinem Original nichts passiert.

Der Rückgabewert ist jetzt allerdings erläuterungsbedürftig. Sie sehen, dass ich wieder eine Referenz zurück gebe und wenn Sie sich die Zeile 3 und 8 anschauen, sehen Sie, dass ich die Referenz auf den dereferenzierten Zeiger auf sich selbst zurück gebe oder anders ausgedrückt, ich nehme das Objekt, worauf der eigene Zeiger zeigt (also sich selbst) und gebe das als Referenz zurück, was nicht zu verwechseln mit der Adresse des Objektes ist. Das ist eher wie call-by-reference, nur rückwärts. Nun, was bedeutet und warum mach ich das? Angenommen Sie schreiben "oA = oB". Dann wird die Operatormethode "=" des Objektes "oA" aufgerufen. Somit bezieht sich das "*this" auf "oA". Wenn Sie dann einfach nur ein Objekt zurückgeben würden, wird "oA" mit einer Kopie überschrieben. Dies scheint nicht weiter tragisch, aber schreiben wir "*pA = *pB", zeigt "pA" auf einmal auf etwas ganz anderes und das ursprüngliche Objekt wäre nicht mehr erreichbar und somit entstünde eine Speicherleiche. Um dies zu verhindern geben wir nicht eine Kopie, sondern das eigentliche Objekt, also die Referenz (und noch einmal nicht zu verwechseln mit der Adresse) zurück, damit ein Zeiger immer noch auf das Selbe zeigt und keine Speicherlecks entstehen. Ich weiß, das klingt jetzt erst einmal verwirrend, aber Sie müssen nur lange genug darüber nachdenken und dann werden Sie es einsehen. Dafür sollte aber der Rest der ersten Methode klar sein.

Die Zweite Methode ermöglicht es nun, einem Objekt ein Integer zuzuweisen. Auch hier wird wieder nur eine konstante Referenz übergeben und zurück kommt die eigene Referenz des Objektes. Über diesen Mechanismus kann man noch weitere "=" Operatoren überladen, damit dies auch für long, float usw. funktioniert.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
					
// Auf Objekt drauf addieren //////////////////////////////////////////////////
CInteger& CInteger::operator+=(const CInteger& oInt) {
	this->m_iValue += oInt.m_iValue;
	return *this;
}

CInteger& CInteger::operator+=(const int& iInt) {
	this->m_iValue += iInt;
	return *this;
} // operator+= ///////////////////////////////////////////////////////////////

// ...

// Ermöglicht wird jetzt z.B. so etwas
CInteger oObjekt1(5);
CInteger oObjekt2(7);

oObjekt1			+= oObjekt2;
oObjekt2			+= 14;

// oder auch
CInteger* pZeigerAufObjekt1	= new CInteger(5);
CInteger* pZeigerAufObjekt2	= new CInteger(7);

*pZeigerAufObjekt1		+= *pZeigerAufObjekt2;
*pZeigerAufObjekt2		+= 14;

delete pZeigerAufObjekt1;
delete pZeigerAufObjekt2;
					

Die erste Methode ermöglicht jetzt ein Objekt mit sich selbst und einem anderen zu Addieren. Da dies auch wieder eine unäre Operation ist, sehen die Übergabe - und Rückgabewerte, genauso aus, wie zuvor. Der einzige Unterschied ist lediglich, dass jetzt innerhalb der Methode, nicht mehr nur das "=", sondern das "+=" für die Werte angewendet wird.

Die zweite Methode ist wieder das Äquivalent mit einem Integer. Auch hier kann man weitere Methoden deklarieren, um die Zuweisung mit Floats usw. zu realisieren. Die Implementierung für "-=", "*=" und "/=" bzw. "%=", sehen dann entsprechend ähnlich aus.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012