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

25 Funktionszeiger

25 Funktionszeiger

Bisher habe ich eine kleine Tatsache unterschlagen, nämlich, dass man auch Zeiger auf Funktionen zeigen lassen kann und genau darum wird es in diesem Kapitel gehen. Normalerweise hätte man dieses Kapitel schon eher bringen können, aber in meinen Augen macht dieses Thema erst bei Klassen sinn.

Zum Seitenanfang
Zum Inhaltsverzeichnis

25.1 Wozu braucht man Funktionszeiger

25.1 Wozu braucht man Funktionszeiger

Im ersten Augenblick macht es nicht viel Sinn, einen Zeiger auf eine Funktion zu definieren, aber dieser Mechanismus ist eigentlich sehr interessant, wenn man Klassen bauen will, deren Funktionalität nachträglich erweitert werden soll. Nun könnte man denken, dass man dies auch mit Vererbung hin bekommt, aber ich meine was anderes.

Stellen Sie sich vor, Sie bauen eine Klasse, welche diverse Attribute besitzt und eine Menge für Sie erledigt. Jetzt soll die Klasse noch zwei Methoden besitzen, um ihre Attributwerte abzuspeichern bzw. sie zu laden. Allerdings wollen Sie sich jetzt noch nicht festlegen, wie und wo die Daten abgelegt werden sollen (z.B. nur in einer Textdatei, oder evtl. auch in einer Datenbank). Dann könnte es ja auch vorkommen, dass sich manche Objekte gar nicht abspeichern sollen. Wie bekommen Sie so etwas hin?

Ein Ansatz wäre, eine Basisklasse zu bauen, die sich weder laden noch speichern kann und dann für jede Erdenkliche Art der Datenhaltung, eine extra Klasse ableiten, welche dann lediglich jeweils die zwei Methoden überschreibt. Aber macht es wirklich Sinn, wegen einer oder zwei Methoden gleich eine neue Klasse zu bauen? Nicht unbedingt und genau hier setzen Funktionszeiger an.

Man kann in die Klasse einfach zwei Zeiger einbauen, die auf Funktionen Zeigen können, welche diese Aufgaben erledigen. Wenn man sich dann eine Instanz der Klasse erzeugt, kann man eine beliebige Funktion an diese Zeiger dranhängen und wenn dann in der Klasse festgestellt wird, dass die Zeiger auf etwas sinnvollen zeigen, können die Funktionen, auf welche die Zeiger verweisen, aufgerufen werden. So erweitert man also ggf. die Funktionalität einer Klasse, ohne sie umschreiben oder neu definieren zu müssen.

Dieses Konzept wird auch sehr oft bei Fensterklassen (grafische Programmierung) benutzt. Beispielsweise gibt es bei Schaltflächen mehrere Funktionszeiger oder auch Funktionshandler genannt, welche für diverse Aufgaben gedacht sind. Eine ist z.B. das reagieren auf einen Mausklick. Genauso gibt es Möglichkeiten, auf das drüber Ziehen der Maus zu reagieren, um die Schaltfläche einzufärben. Da diese Methoden einer Schaltfläche aber nicht zwingend zu ihr gehören, bieten sie nur Zeiger an, an welche man seine eigenen Methoden hängen kann und die auch nur aufgerufen werden, wenn es sie gibt. Normalerweise gehört zu dieser Thematik noch ein s.g. Eventhandling und ganz so, wie ich es eben erklärt habe, funktioniert es in der Realität nicht, aber ich denke, dass man hier gut versteht, warum Funktionszeiger benutzt werden bzw. notwendig sind.

Zum Seitenanfang
Zum Inhaltsverzeichnis

25.2 Ein kleines Beispiel

25.2 Ein kleines Beispiel

Da ich Ihnen jetzt bestimmt schon den Mund wässrig gemacht habe, will ich jetzt zeigen, wie man Funktionszeiger baut und sie einbindet. Für diesen Zweck habe ich eine kleine Testklasse gebaut, welche nur zwei Funktionszeiger für die Demonstration besitzt, sowie zwei Methoden, welche ggf. die Funktionen, auf welche die Zeiger verweisen, aufrufen. Schauen Sie sich zunächst den Header an.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
					
#pragma once

#include <stdlib.h>

typedef void (*FAusgabe)();
typedef int (*FSumme)(int, int);

class CTest {
	private:
		FAusgabe	m_pFunc1;
		FSumme		m_pFunc2;

	public:
		// Standardkonstruktor - Initialisieren
		CTest(FAusgabe = NULL, FSumme = NULL);

		// Übergebene Funktion 1 ausführen
		void Execute1(void);

		// Übergebene Funktion 2 ausführen
		int Execute2(int iSummand1, int iSummand2);
};
					

Da die Schreibweise von Funktionszeigern etwas länger ausfallen und zudem Verwirrung stiften kann, habe ich in Zeile 5 und 6, zwei Typdefinitionen gemacht. Erstere bedeutet, dass ich mir einen Funktionszeiger mit dem Typnamen "FAusgabe" gebaut habe, welcher auf eine Funktion zeigen kann, welche weder einen Rückgabewert, noch Übergabeparameter besitzt. An diesen Zeiger können dann auch nur solche Funktionen gehangen werden. Die zweite Typendefinition bedeutet jetzt, dass ich einen Funktionszeiger, mit dem Typnamen "FSumme" definiere, welcher auf Funktionen zeigen kann, welche einen Integer zurückgeben und zwei Integer als Übergabeparameter besitzen. Wie diese Parameter im einzelnen heißen, spielt hier keinerlei Rolle.

In Zeile 10 und 11 definiere ich mir jetzt die zwei besagten Zeiger. Hier benutze ich jetzt die eben definierten Typennamen. Auffällig ist hier, dass man kein "*" benutzt. Um aber irgendwie trotzdem kenntlich zu machen, dass es sich um einen Pointer handelt, leite ich den Typnamen gerne mit einem großen "F" ein und lasse in den Variablennamen das kleine "p" einfließen. Manchmal sieht man bei dem Variablen - bzw. Attributnamen, auch den Präfix "pof", was so viel heißen soll wie "Pointer of Function".

In Zeile 15 definiere ich den Konstruktor und gebe dem Benutzer dieser Klasse die Möglichkeit, entsprechende Funktionen zu übergeben. Standardmäßig gehe ich aber davon aus, dass es keine gibt, was den Defaultwert für die Zeiger erklärt. Alternativ braucht man die Funktionen aber nicht im Destruktor mit übergeben und kann sich entsprechende Set-Methoden bauen. Dies wäre auch der übliche weg, aber das hätte das Beispiel zum einen unnötig aufgebläht und ich wollte auch mal einen anderen Weg aufzeigen.

Der Rest der Header-Datei sollte soweit klar sein und deswegen schauen wir uns nun die entsprechende CPP zu dieser Klasse 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
					
// Standardkonstruktor - Initialisieren ///////////////////////////////////////
CTest::CTest(FAusgabe pFunc1, FSumme pFunc2)
	: m_pFunc1(pFunc1)
	, m_pFunc2(pFunc2)
{} // CTest ///////////////////////////////////////////////////////////////////



// Übergebene Funktion 1 ausführen ////////////////////////////////////////////
void CTest::Execute1() {
	// Wenn dem Funktionszeiger etwas zugewiesen wurde
	if (this->m_pFunc1 != NULL) {
		this->m_pFunc1();
	} // end of if
} // Execute1 /////////////////////////////////////////////////////////////////



// Übergebene Funktion 2 ausführen ////////////////////////////////////////////
int CTest::Execute2(int iSummand1, int iSummand2) {
	if (this->m_pFunc2 != NULL) {
		return this->m_pFunc2(iSummand1, iSummand2);
	} else {
		return 0;
	} // end of if
} // Execute2 /////////////////////////////////////////////////////////////////
					

In Zeile 2 bis 5 sehen Sie jetzt die Implementierung des Konstruktors. Wie man sieht, kann man Funktionszeiger ganz normal initialisieren wie andere Attribute auch.

Die Methode "Execute1", welche in den Zeilen 10 bis 15 implementiert ist, sorgt dafür, dass die erste Funktion aufgerufen wird. Dies geschieht aber nur, wenn der Funktionszeiger auf etwas verweist. Der eigentliche Aufruf der Funktion sieht nun so aus, dass man so tut, als wäre der Pointer der Funktionsname und würde zur Klasse gehören.

Die Methode "Execute2", welche in Zeile 20 bis 26 implementiert ist, sorgt jetzt dafür, dass die zweite Funktion aufgerufen wird. Das Verhalten ist wieder genauso wie in der vorhergehenden Methode. Hier wird auch wieder die Funktion aufgerufen, auf welche der Zeiger verweist und dessen Ergebnis zurück gegeben.

Schauen Sie sich nun die "main" an, in welcher ich die zwei auszuführenden Funktionen implementiert habe und mir ein Objekt der eben definierten Klasse erzeuge, welches dann meine externen Funktionen benutzen wird.

 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
					
// Einfache Ausgabefunktion ///////////////////////////////////////////////////
void MeineAusgabeFunktion() {
	printf("Hallo Welt\n");
} // MeineAusgabeFunktion /////////////////////////////////////////////////////



// Einfache Summenfunktion ////////////////////////////////////////////////////
int MeineSummenFunktion(int iSummand1, int iSummand2) {
	return iSummand1 + iSummand2;
} // MeineSummenFunktion //////////////////////////////////////////////////////



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	CTest oTest(&MeineAusgabeFunktion, &MeineSummenFunktion);

	oTest.Execute1();

	int iErgebnis	= oTest.Execute2(3, 4);
	printf("%i\n", iErgebnis);

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Ausgabe:

Hallo Welt
7
		

In den Zeilen 2 bis 11 sehen Sie nun die zwei Funktionen, auf welche die Zeiger später verweisen sollen. Ich betone nochmals, dass die Funktionsköpfe genau so aussehen müssen, wie es die Definition der Funktionszeiger vorsieht.

In Zeile 17 erzeuge ich jetzt besagtes Objekt und übergebe die Adressen der beiden Funktionen, an den Konstruktor. Wichtig ist hier, dass man wirklich vor den Funktionsnamen den Adressoperator "&" schreibt, weil man sonst nichts übergeben würde, sondern die Funktionen aufruft und ggf. deren Rückgabewert übergibt.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012