Meine Werkzeuge
Namensräume
Varianten

Funktionszeiger

Aus indiedev
Wechseln zu: Navigation, Suche
Tutorial
Funktionszeiger
Autor Roland "Glatzemann" Rosenkranz
Programmier­sprache C++
Kategorie C++ Tutorials
Diskussion Thread im Forum
Lizenz indiedev article license
C++ ist eine mächtige Programmiersprache zu deren am meisten gefürchtetsten Features die Zeiger gehören. Zeiger sind zwar grundsätzlich verhältnismäßig einfach zu verstehen, aber durch die schier unendlichen Einsatzmöglichkeiten und der dadurch stark wachsenden Komplexität fühlen sich Einsteiger - zu Recht wie ich finde - davon oft überfordert und werden abgeschreckt.

In diesem Grundlagenartikel - der sich an Einsteiger mit ein wenig Erfahrung richtet - möchte ich ein interessantes Einsatzgebiet der Zeiger erklären: die sogenannten Funktionszeiger. Dabei möchte ich darauf achten, diesen Artikel möglichst einfach zu halten und es so dem Einsteiger zu ermöglichen, diesem zu folgen und das notwendige Wissen für weiterführende Themen, die auf den Funktionszeigern aufbauen, zu erarbeiten.

Definition

Anfangen sollten wir vielleicht mit einer Erklärung, was so ein Funktionszeiger überhaupt ist. Wie der Name bereits andeutet ist ein Funktionszeiger ein Zeiger auf eine Funktion. Der Zeiger ist die Adresse der Funktion im Arbeitsspeicher. Eine Funktion kann nämlich nicht nur über deren Namen aufgerufen werden, sondern auch über deren Adresse im Speicher. Eigentlich ist es sogar so, daß eine Funktion ausschliesslich vom Prozessor über ihre Adresse aufgerufen werden kann. Der Compiler bzw. die Programmiersprache verbirgt dies nur geschickt vor uns.

Der Funktionszeiger ermöglicht es uns nun, daß wir exakt diese Adresse in einer Variablen speichern können. Wir können diese gespeicherte Funktion nun später aufrufen. Wozu dies gut ist? Es gibt eine Reihe von Beispielen. Das einfachste ist sicherlich die sogenannte Callback-Funktion. Diese wird beispielsweise bei einer asynchronen Programmausführung verwendet. Wir wollen eine lange andauernde Operation ausführen, warten aber nicht auf den Abschluss dieser Operation, sondern machen direkt mit der nächsten Anweisung weiter. Wenn die Operation abgeschlossen ist (welche üblicherweise in einem eigenen Thread parallel zum Hauptprogramm ausgeführt wird) möchten wir, daß das Ergebnis unserem Hauptprogramm durch einen Funktionsaufruf mitgeteilt wird. Diese Funktion können wir mittels eines Funktionszeigers als Parameter des asynchronen Funktionsaufrufs übergeben.

Ein weiteres Beispiel ist eine Undo/Redo Funktion. Dabei wird jede Aktion durch eine Programmfunktion repräsentiert. Für jede Programmfunktion gibt es eine Do-Funktion und eine Undo-Funktion. Do führt die Aktion aus, Undo macht sie wieder rückgängig. Führen wir eine Aktion aus, so wird der Funktionszeiger der zugehörigen Undo-Funktion auf den Undo-Stack gelegt. Machen wir eine Aktion rückgängig, so wird der Funktionszeiger der zugehörigen Do-Funktion auf den Redo-Stack gelegt. Wenn wir nun den Undo- oder Redo-Stack "abarbeiten", also die Funktionen aufrufen, die sich hinter dem Zeiger befinden, so haben wir eine Undo-/Redo-Funktion implementiert. Dazu ist lediglich eine einfache Schleife notwendig, die Adressen vom Undo- oder Redo-Stack holt und die Funktion hinter dem Funktionszeiger aufruft.

Beispiel

Wir erzeugen uns zunächst ein einfaches C++-Konsolenprogramm. Dies sieht wie folgt aus:


#include <iostream>
#include <stdio.h>

using namespace std;

int add(int a, int b)
{
	return a + b;
}

int sub(int a, int b)
{
	return a - b;
}

int mul(int a, int b)
{
	return a * b;
}

void main(void) 
{   
	cout << "Direktaufruf Funktionen" << endl;
	cout << "Addition       4 + 2: " << add(4, 2) << endl;
	cout << "Subtraktion    4 - 2: " << sub(4, 2) << endl;
	cout << "Multiplikation 4 * 2: " << mul(4, 2) << endl;

	getchar();

	return; 
}

Dieses Programm macht nichts besonderes. Wir haben drei Funktion add, sub und mul die drei bekannte Grundrechenoperationen durchführen können. Der Source-Code in der Main-Funktion sollte selbsterklärend sein. Wir rufen unsere drei Funktionen auf und geben das Ergebnis in der Konsole aus.

Als nächstes kommen wir zum Aufruf dieser drei Funktionen mittels Funktionszeigern. Dazu fügen wir zunächst den folgenden Code in unser Programm ein und zwar zwischen der letzten Ausgabe und getchar().


	cout << "Funktionsaufruf mittels Funktionszeiger" << endl;
	int (*func_ptr)(int, int);

	func_ptr = add;
	cout << "Addition       4 + 2: " << func_ptr(4, 2) << endl;

	func_ptr = sub;
	cout << "Subtraktion    4 - 2: " << func_ptr(4, 2) << endl;

	func_ptr = mul;
	cout << "Multiplikation 4 * 2: " << func_ptr(4, 2) << endl;

Interessant ist zunächst die Definition des Funktionszeigers in Zeile 2. Wir legen fest, daß wir eine Variable mit dem Namen func_ptr erzeugen wollen. Durch die Klammern und den führenden Asterisk teilen wir dem Compiler mit, daß dies ein Funktionszeiger ist. Die Funktion die sich dahinter verbirgt hat als Rückgabe einen Wert vom Datentyp int (links von der ersten Klammer) und erwartet als Parameter zwei int (innerhalb der zweiten Klammer).

In Zeile 4 (sowie den Zeilen 7 und 10) laden wir nun die Adresse einer Funktion in diesen Funktionszeiger und rufen diesen in den Zeilen 5, 8 und 11 direkt auf um die dahinter verborgene Funktion auszuführen.

Etwas deutlicher werden die Anwendungsgebiete vielleicht, wenn wir nicht mit einzelnen Funktionszeigern arbeiten, sondern mit einem Array. Dazu ändern wir den gerade eingefügten Code in den folgenden um:


	cout << "Funktionsaufruf mittels Funktionszeiger" << endl;

	typedef int (*func_ptr)(int, int);

	func_ptr myFunctions[3]; 
	myFunctions[0] = &add;
	myFunctions[1] = &sub;
	myFunctions[2] = &mul;

	for (int i = 0; i < sizeof(myFunctions) / sizeof(func_ptr); i++)
	{
		cout << myFunctions[i](4, 2) << endl;
	}

Die Änderungen beginnen in Zeile 3. Dort definieren wir uns einen eigenen Datentyp. Dieser hat den Namen func_ptr und soll - genau wie bisher - ein Funktionszeiger sein, der zwei Integerparameter erwartet und einen Integer als Ergebnis zurück gibt. Der typedef - falls du diesen noch nicht kennen solltest - ist eine gute Möglichkeit Schreibarbeit für komplexe Datentypen zu sparen. Wir müssen ab der Definition nicht jedesmal int (*func_ptr)(int, int) schreiben, sondern können einfach den Namen unseres eigenen Typs func_ptr verwenden.

In Zeile 5 definieren wir ein passendes Array mit 3 Elementen, die wir dann auch in den Zeilen 6 bis 8 mit den Adressen auf die jeweiligen Funktionen befüllen.

Das interessante ist in den Zeilen 10 bis 13. Dort werden einfach alle Funktionen aufgerufen, die sich in unserem Funktionszeiger-Array befinden. Das sieht auf den ersten Blick recht umständlich aus, wenn man aber bedenkt, daß das Array nicht wie in diesem sehr rudimentären Beispiel durch Code befüllt wird, sondern z.B. aus einer XML-Datei geladen wird, dann hätten wir bereits eine ganz einfache Art einer Skriptsprache. Ohne das Programm neu kompilieren zu müssen, könnten wir Funktionen durch Definition in einer Konfigurationsdatei aufrufen.

Abschluss

In diesem kurzen Tutorial habe ich erklärt, was ein Funktionszeiger ist und wie dieser verwendet wird. Dabei habe ich zwei einfache Beispiele vorgestellt, die die Grundlagen zur Verwendung aufzeigen.

Ich werde in weiterführenden Artikeln diesen hier als Referenz angeben. So können wir uns diese einfachen Grundlagen in späteren Artikeln sparen und kommen dann mit deutlich weniger Text zum Ziel.

Ich hoffe, daß euch dieser Artikel gefallen hat. Falls ihr Fragen oder Anmerkungen habt, dann verwendet bitte die Diskussionsfunktion, die euch ins indiedev-Forum führt (Link ist links oben).

Navigation
Tutorials und Artikel
Community Project
Werkzeuge