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...

16 Meine erste Klasse

16 Meine erste Klasse

Als erstes sei erwähnt, dass üblicherweise jede Klasse in separate Header - und CPP Dateien implementiert wird. Nur in sehr speziellen Fällen, packt man in ein Dateipaar zwei oder mehrere Klassen und das auch nur, wenn die Klassen sehr klein sind. Ein Beispiel dafür ist z.B. eine spezielle Listenklasse, deren Inhalte aus kleinen Objekten anderer eigener Klassen bestehen.

Das folgende Beispiel ist sehr rudimentär und mit Absicht auch sehr schlank gehalten, da ich zunächst das Grundgerüst erklären möchte.

Ich fange zunächst mit der Header-Datei an. Prinzipiell kann man in neueren IDE's Assistenten benutzen, welche einem beim Erstellen von Klassen, sehr unter die Arme greifen (in Visual Studio z.B. erledigt dies der Klassenassistent), aber für den Anfang empfehle ich, dies händisch zu tun. Falls Sie irgendwann nicht mehr weiter kommen oder Sie sich unschlüssig sind, können Sie ihn benutzen. Dann sollten Sie sich aber genau anschauen, welcher Code generiert wurde.

Zum Seitenanfang
Zum Inhaltsverzeichnis

16.1 Die Datei TestKlasse.h

16.1 Die Datei TestKlasse.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
					
// Definition eines neuen Klassendatentyps
class CTestKlasse {
	public:
		// Standardkonstruktor-Methode
		CTestKlasse(void);
		// 1. Benutzerdefinierte Konstruktor-Methode
		CTestKlasse(int iValue);
		// 2. Benutzerdefinierte Konstruktor-Methode
		CTestKlasse(int iValue1, int iValue2);
		// Kopierkonstruktor-Methode
		CTestKlasse(const CTestKlasse oTestObjekt);
		// Destruktor
		~CTestKlasse(void);

		// Speichert eine Zahl
		int m_iZahl1;
		// Speichert eine andere Zahl
		int m_iZahl2;

		// Gibt interne Werte aus
		void Ausgabe(void);
};
					

In Zeile 2 sehen Sie den Klassenkopf. Er erinnert an die Definition einer Struktur. Später werde ich Ihnen aber noch zeigen, dass dies hier nur die halbe Wahrheit ist, aber fürs erste soll das reichen.

In Zeile 3 steht jetzt etwas neues, auf was ich bisher noch nicht weiter eingegangen bin. Gemeint ist der Modifikator "public". Er ist dafür verantwortlich, dass die Attribute und Methoden, nach außen sichtbar sind. Weiter möchte ich an dieser Stelle nicht darauf eingehen, da ich im Kapitel der Vererbung, diese Sachen sehr ausführlich erläutern werde.

In den Zeilen 5 bis 13 ist jetzt die Definition der Konstruktoren und des Destruktors. Auffällig an ihnen ist wie erwähnt, dass diese zwei Methoden keinen Rückgabewert besitzen.

In Zeile 16 und 18 habe ich zwei Attribute definiert. Auffällig ist hier das "m_"; vor dem Variablenname. Diese Konvention ist üblich, aber nicht Pflicht. Man bringt mit ihr zum Ausdruck, dass es sich um eine Membervariable handelt. Dies soll später sicher stellen, dass man bei der Implementierung der Methoden genau sieht, ob es sich bei einer Variablen um eine Member - oder eine lokal definierte Variable handelt. Wie gesagt, es ist eine von vielen Möglichkeiten, seinen Quelltext zu strukturieren und Sie müssen sich nicht daran halten, aber ich lege es Ihnen ans Herz, dies doch zu tun, da dies, gerade für Andere, die später evtl. mit Ihrem Zeug klar kommen müssen, das Leben sehr erleichtert.

Zum Schluss, in Zeile 21, habe ich noch eine Methode "Ausgabe" definiert, welche später nur die Werte von "m_iZahl1" und "m_iZahl2"zurück geben soll. Hier macht man in der Regel kein "m_" davor, da es in der Praxis nicht so wichtig ist, da man ja eine Klasse so aufbaut, dass sie sich selbst verwaltet und somit die Funktionsaufrufe größtenteils Methodenaufrufe sind.

Zum Seitenanfang
Zum Inhaltsverzeichnis

16.2 Die Datei TestKlasse.cpp

16.2 Die Datei TestKlasse.cpp

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
					
#include <stdio.h>
#include "TestKlasse.h"



// Standardkonstruktor ////////////////////////////////////////////////////////
CTestKlasse::CTestKlasse()
	:m_iZahl1(0)
	,m_iZahl2(0)
{} // CTestKlasse /////////////////////////////////////////////////////////////



// 1. Benutzerdefinierter Konstruktor /////////////////////////////////////////
CTestKlasse::CTestKlasse(int iValue)
	:m_iZahl1(iValue)
	,m_iZahl2(iValue)
{} // CTestKlasse /////////////////////////////////////////////////////////////



// 2. Benutzerdefinierter Konstruktor /////////////////////////////////////////
CTestKlasse::CTestKlasse(int iValue1, int iValue2)
	:m_iZahl1(iValue1)
	,m_iZahl2(iValue2)
{} // CTestKlasse /////////////////////////////////////////////////////////////



// Kopierkonstruktor //////////////////////////////////////////////////////////
CTestKlasse::CTestKlasse(const CTestKLasse& oTestObjekt)
	:m_iZahl1(oTestObjekt.m_iZahl1)
	,m_iZahl2(oTestObjekt.m_iZahl1)
{} // CTestKlasse /////////////////////////////////////////////////////////////



// Destruktor /////////////////////////////////////////////////////////////////
CTestKlasse::~CTestKlasse() {
} // ~CTestKlasse /////////////////////////////////////////////////////////////



// Gibt interne Werte aus /////////////////////////////////////////////////////
void CTestKlasse::Ausgabe(void) {
	// Variante 1
	printf("%i"\n, m_iZahl1);

	// Variante 2
	printf("%i"\n, this->m_iZahl2);
} // Ausgabe //////////////////////////////////////////////////////////////////
					

Ab Zeile 7 sehen Sie jetzt die Implementierungen, der zuvor definierten Methoden. Auffällig ist hier der s.g. Namespace vor den Methodennamen. Dies ist zwingend notwendig, damit der Compiler später weiß, welche Methode zu welcher Klassendefinition gehört. Es könnte ja sein, dass es noch eine andere Klasse im Projekt gibt, die eine Methode mit dem gleichen Namen hat und spätestens dann würde es zu Verwirrungen kommen.

Das Schema sieht so aus:
<Rückgabewert> <Klassenname>::<Methodenname>([<Parameterliste>]) {<Anweisung>}

Weiterhin auffällig ist Zeile 8 und 9. Hier tritt etwas auf, was Sie so noch nicht gesehen haben und was auch nur bei Konstruktoren funktioniert. Wie Sie vielleicht schon erahnen können, werden so die Attribute einer Klasse initialisiert. Sie werden später noch sehen, dass dies Teilweise sogar notwendig ist (Stichwort statische Klassenvariablen oder Basisklassenaufrufe).

Eine weitere Auffälligkeit ist, dass ich, wie Sie in den Zeilen 7, 16, 25 und 34 sehen, gleich vier verschiedene Konstruktoren gebaut habe, welche sich, so sagt man, gegenseitig überladen. Dem Standardkonstruktor erkennt man daran, dass ihm keine Werte übergeben werden. Jener kann vom Compiler automatisch erstellt werden, allerdings werden dann keine Membervariablen initialisiert. Falls Sie also sicherstellen wollen, dass vernünftige Startwerte vorliegen, müssen Sie selber einen Konstruktor bauen. Die Benutzerdefinierten Konstruktoren sind dafür da, dass Sie von außen sinnvolle Startwerte übergeben können. Der Kopierkonstruktor spielt eine Besondere Rolle. Für kleinere Sachen benötigt man ihn meistens nicht, aber spätestens, wenn man Operatoren überladen will, ist er notwendig. Wie der Name schon sagt, ist er dafür da, sich anhand eines anderen Objektes zu erzeugen.

Bis zur Zeile 51 sollte jetzt alles klar sein, aber ab dieser Zeile möchte ich Ihnen zeigen, wie Klassen auf ihre eigenen Attribute zugreifen können (das Gleiche gilt dann auch für die Methoden). Wie Sie sehen, gibt es zwei verschiedene Varianten. Variante 1 spart Code, aber Variante 2 ist eindeutiger und in anderen Sprachen sogar Vorschrift. Hier sehen Sie das erste mal den s.g. "this" Pointer, welcher eine Referenz auf die Klasse selbst darstellt. Prinzipiell kann man sich dies in C++ sparen, weil man an dem "m_" bereits erkennt, dass es sich um ein Attribut und keine lokale Variable handelt und somit das "this" doppelt gemoppelt wäre. Aber wie gesagt, in anderen Sprache ist dies sogar Pflicht und gerade für Anfänger ist es ratsam, das "this" immer mit hinzuschreiben (es sticht mehr ins Auge).

Zum Seitenanfang
Zum Inhaltsverzeichnis

16.3 Die Datei main.cpp

16.3 Die Datei main.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
					
#include "TestKlasse.h"



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	// Variante 1
	CTestKlasse oObjekt1;					// Standardkonstruktor
	oObjekt1.Ausgabe();
	
	CTestKlasse oObjekt2(1, 2);				// Benutzerdefinierter Konstruktor
	oObjekt2.Ausgabe();

	// Variante 2
	CTestKlasse* pObjekt3 = new CTestKlasse(oObject2);	// Kopierkonstruktor
	pObjekt->Ausgabe();
	delete pObjekt;
	
	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Ausgabe:

0
0
1
2
1
2			
		

Mit oben stehenden Code, können Sie nun die kleine Testklasse Testen. Hierfür gibt es zwei Varianten. Wie Sie in Zeile 5 und 6 sehen, habe ich ein Objekt der Klasse "CTestKlasse" auf dem Stack erzeugt und rufe die Methode "Ausgabe" auf. Bei Objekten auf dem Stack, braucht man sich nicht um die Speicherverwaltung zu kümmern und man greift auf die Attribute und Methoden über den Punk zu, genauso wie man dies bei Strukturen macht. In den Zeilen 8 und 9 wird ein weiteres Objekt auf dem Stack erzeugt, allerdings unter Verwendung eines benutzerdefinierten Konstruktors.

In den Zeilen 12 bis 14 zeige ich, wie man sich ein dynamisches Objekt erzeugt und damit arbeitet. Wie Sie vielleicht bereits vermuten, benötigt man dazu Pointer. Auch die Speicherverwaltung muss selbst gemanagt werden und der Zugriff auf die Attribute und Methoden geschieht über den Pfeil.

Dem aufmerksamen Leser ist vielleicht aufgefallen, dass ich für die Variablennamen, unterschiedliche Schreibweisen verwendet habe. Die Objekte auf dem Stack habe ich mit einem kleinen "o" eingeleitet, was signalisieren soll, dass ich mich nicht um die Freiage kümmern muss. Das dynamische Objekt, habe ich allerdings wieder mit einem kleinen "p" eingeleitet, was darauf schließen soll, dass die Speicherverwaltung hier selbst durchgeführt werden muss. Man kann allerdings nicht mehr erkennen, dass es sich um ein Objekt handelt. Hier kommt man an einen Punkt, wo es immer wichtiger wird, den Variablen, sprechende Namen zu verpassen, da man irgendwann nicht mehr alles mit einleitenden Buchstaben erklären kann. In diesem Fall hätte ich die Variable zwar auch mit "poftk" einleiten können (was soviel heißen soll wie Pointer of TestKlasse), aber wer soll da durch sehen und spätestens wenn es mal Klassen gibt deren Anfangsbuchstaben gleich sind, wird auch dieser Ansatz einen nicht weit bringen. Von daher empfehle ich nur das kleine "p" zu benutzen und den Rest so zu benennen, dass jeder erkennt dass hinter dem Pointer, ein Objekt klemmt.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012