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

9.5 Vereinfachter Umgang mit Pointern

9.5 Vereinfachter Umgang mit Pointern

Wie Sie gesehen haben, ist der Umgang mit Pointern wirklich etwas gewöhnungsbedürftig, zumal man ständig mit diesen Sternen arbeiten muss und man immer überlegen muss, wie viele man braucht. In diesem Teilkapitel soll es darum gehen, wie man sich das alles vereinfachen kann.

Folgende Tabelle soll noch einmal zeigen, wie man sich merken kann, wann man wie viele Sterne braucht.

Deklarierter Datentyp Dereferenzierter Datentyp Resultierender Datentyp
Typ*** pppVariable *pppVariable Typ** ppVariable
Typ** ppVariable *ppVariable Typ* pVariable
Typ* pVariable *pVariable Typ tVariable
Typ*** pppVariable **pppVariable Typ* pVariable
Typ** ppVariable **ppVariable Typ tVariable
Typ*** pppVariable ***pppVariable Typ tVariable

Als Faustregel gilt also, mit jedem Stern vor einem Variablenname, resultiert der Datentyp mit einem Stern weniger.

Zum Seitenanfang
Zum Inhaltsverzeichnis

9.5.2 Vereinfachter Funktionsaufruf

9.5.2 Vereinfachter Funktionsaufruf

Ich habe bisher mehrfach erwähnt, dass bei einem Funktionsaufruf mehrere Sachen im Hintergrund passieren und auch was dies für den Speicherbedarf bedeutet. Gerade wenn man viele Funktionsparameter benötigt, wird der Stack schnell sehr voll. Hier kommen Pointer ins Spiel. Schauen Sie sich folgenden Quelltext an.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
					
// Zeichnet ein Rechteck //////////////////////////////////////////////////////
void Draw(int iWidth, int iHeight, int iPosLeft, int iPosTop) {
	//...
} // Draw /////////////////////////////////////////////////////////////////////

//...

int iWidth		= 200;
int iHeight		= 10;
int iPosLeft		= 10;
int iPosTop;

// 10 Rechtecke erzeugen
for (int iCount = 0; iCount < 10; ++iCount) {
	iPosTop		= (iCount + 1) * 20;
	Draw(iWidth, iHeight, iPosLeft, iPosTop);
} // end of for
					

Im Grunde sollen hier nur 10 Rechtecke erzeugt und gezeichnet werden. Wie das eigentliche Zeichnen aussieht, spielt hier keine Rolle, aber schauen wir doch mal an, was auf dem Stack passiert.

Funktionsaufruf mit Primitivtypen

Wie Sie sich bestimmt denken konnten, werden also ständig viele Werte auf den Stack gepackt und wieder abgeräumt, nur um sie dann wieder darauf zu packen und wieder abzur/auml;umen. Hier ist es also sinnvoller, sich für viele Parameter eine Struktur zu bauen und anschließend nur noch einen Pointer darauf zu übergeben.

 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
					
struct SRectangle {
	int iWidth;
	int iHeight;
	int iPosLeft;
	int iPosTop;
};

typedef SRectangle* PRectangle;



// Zeichnet ein Rechteck //////////////////////////////////////////////////////
void Draw(PRectangle& pArea) {
	//...
} // Draw /////////////////////////////////////////////////////////////////////

// ...

PRectangle	pRect	= new SRectangle;
pRect->iWidth	= 200;
pRect->iHeight	= 10;
pRect->iPosLeft	= 10;

// 10 Rechtecke erzeugen
for (int iCount = 0; iCount < 10; ++iCount) {
	pRect->iPosTop	= (iCount + 1) * 20;
	Draw(pRect);
} // end of for

delete pRect;
					

Schauen wir uns zunächst auch hier an, was im Speicher passiert.

Funktionsaufruf mit referenziertem Pointer auf Struktur

Wenn Sie sich das jetzt anschauen, sehen Sie, dass wieder ständig etwas auf den Stack gepackt wird. Allerdings ist es jetzt nur noch die Rücksprungadresse. Der Pointer selbst benötigt keinen zusätzlichen Platz, da wir ihn ja als Referenz übergeben und die eigentlichen Nutzdaten liegen im Heap. Somit wird der Funktionsaufruf wesentlich schneller.

Wenn Sie jetzt noch ein wenig weiter denken, fragen Sie sich bestimmt, warum man nicht einfach die ganzen Nutzdaten als Referenz übergibt? Das hätte doch den gleichen Effekt? Nun, bei erster Betrachtung stimmt dies, aber mit diesem Lösungsweg haben wir wesentlich mehr Vorteile.

Was passiert, wenn wir irgendwann noch die Rahmen- und Hintergrundfarbe mit übergeben wollten oder evtl. noch die Art des Stiftes, mit welchem das Rechteck gezeichnet werden soll? Der Funktionskopf würde sich nicht nur weiter Aufblähen und noch unübersichtlicher werden, nein, man müsste ggf. noch den Funktionsprototypen ändern und noch jede Stelle im Code, an welcher diese Funktion aufgerufen wird (wenn man nicht gerade Standardparameter festgelegt hat). Mit der Vorgeschlagenen Lösung ist man da viel flexibler, da man lediglich die Struktur und das Erzeugen erweitern müsste.

Eine Frage bleibt aber noch offen. Wann übergibt man Pointer und wann Referenzen? Nun das ist eigentlich ganz einfach zu beantworten. Bedenken Sie, dass Pointer auch auf nichts sinnvolles zeigen können. Wenn Sie also einer Funktion einen puren Pointer übergeben, müssen Sie immer damit rechnen, dass "NULL" übergeben wird. Dem zur Folge müssen Sie in der Funktion erst prüfen, ob es sich um einen gültigen Pointer handelt. Wenn Sie allerdings eine Referenz übergeben, schließen Sie diesen Fall aus und benötigen in der Funktion keine extra Abfrage. Sie müssen also immer überlegen, ob es tatsächlich gewollt passieren kann (beispielsweise, wenn Sie den Inhalt noch erzeugen wollen, wenn "NULL" übergeben wird), dass ein ungültiger Zeiger übergeben wird oder nicht. Wenn dies Ihrer Meinung nach nie passieren kann/darf, benutzen Sie Referenzen.

Zum Seitenanfang
Zum Inhaltsverzeichnis

9.5.1 Vereinfachung durch Typdefinitionen

9.5.1 Vereinfachung durch Typdefinitionen

In C hat man die Möglichkeit, sich neue Typen zu erzeugen bzw. für Bestehende einen Aliasnamen zu vergeben. Damit erspart man sich eine komplizierte Schreibweise

Das Schema sie so aus: typedef <ZuErsetzenderTyp> <ZuErsetzenderTyp>;

Angenommen man hat ein Array mit Pointern auf Integer, dann benötigt man folgende Schreibweise.

 1
 2
 3
					
int*	aArray[];
// oder
int**	aArray;
					

Gerade letztere kann Verwirrung stiften und deswegen gibt es von mir an dieser Stelle die Empfehlung, dies durch geeignete Typdefinitionen zu verschönern.

 1
 2
					
typedef	int*		PInteger;	// Pointer auf ein Integer
typedef	PInteger*	AIntArray;	// Array mit Pointern auf Integer
					

Wichtig ist, dass bei einer Typdefinition keine eckigen Klammern benutzt werden können, aber was bringt einem das jetzt genau? Schauen Sie sich dafür folgende zwei Funktionen und deren Aufruf an.

 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
					
// Variante 1 /////////////////////////////////////////////////////////////////
int** CreateArray1(int iLength) {
	int** pResult		= new int*[iLength];

	// Alle Integer erzeugen
	for (int iCount = 0; iCount < iLength; ++iCount) {
		pResult[iCount]	= new int;
	} // end of for

	return pResult;
} // CreateArray1 ///////////////////////////////////////////////////////////////



// Variante 2 /////////////////////////////////////////////////////////////////
AIntArray CreateArray2(int iLength) {
	AIntArray pResult	= new PInteger[iLength];

	// Alle Integer erzeugen
	for (int iCount = 0; iCount < iLength; ++iCount) {
		pResult[iCount]	= new int;
	} // end of for

	return pResult;
} // CreateArray2 ///////////////////////////////////////////////////////////////



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	int**		pArray1	= CreateArray1(10);	// Variante 1
	AIntArray	pArray2	= CreateArray2(10);	// Variante 2

	delete [] pArray1;
	delete [] pArray2;
	
	return 0;
} // main ///////////////////////////////////////////////////////////////////////
					

Wie Sie sehen können, ist die zweite Variante wesentlich besser lesbar und es wurde kein Stern mehr benutzt. Zudem kann es keine Verwirrung mehr darüber geben, ob man ein Array mit Pointern oder ein Pointer auf einen Pointer hat. Beim Kompilieren wandelt der Präprozessor alle Typdefinitionen um und es resultiert der gleiche Code. Es passiert bei beiden Varianten also haargenau das gleiche. Setzen Sie also ruhig des Öfteren Typdefinitionen ein, um sich den Umgang zu erleichtern. Achten Sie aber darauf, dass die von Ihnen vergebenen Aliasnamen aussagekräftig sind.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012