10.13    Zeiger auf Funktionen

Zeiger können auch auf Funktionen verweisen. Sie verweisen dann auf die Anfangsadresse des Codes in der Funktion. Wie bei der Deklaration eines Zeigers auf ein Array sind hierbei zusätzliche Klammern nötig. Ein solcher Zeiger kann wie folgt erstellt werden:

Rückgabetyp (*funktionsZeiger)(formale Parameter);

Sie haben bei dieser Deklaration einen Zeiger auf einen Funktionstyp mit einem int- oder mehreren formalen Parametern und einem Rückgabetyp deklariert. Ohne die Klammerungen von (*funktionsZeiger) hätten Sie lediglich den Prototyp einer Funktion und keine Definition eines Zeigers erstellt. Der Name der Funktion wird dann implizit in einen Zeiger auf diese Funktion umgewandelt. Aufrufen können Sie die Funktion über Zeiger entweder mit

( *funktionsZeiger ) (parameter);

womit der Funktionszeiger explizit dereferenziert wird, oder auch einfach mit

funktionsZeiger(parameter);

womit der Funktionszeiger – ähnlich wie beim C-Array-Namen – automatisch dereferenziert wird.

Folgendermaßen können Sie beispielsweise einen Zeiger auf die Standardfunktion printf() erstellen und verwenden:

00  // Kapitel10/funktionszeiger.c
01 #include <stdio.h>
02 #include <stdlib.h>

03 int main(void) {
04 int (*fptr)(const char*, ...);
05 fptr = printf;
06 (*fptr)("Hallo Welt mit Funktionszeigern\n");
07 return EXIT_SUCCESS;
08 }

Listing 10.9    Das letzte Beispiel ruft printf() mittels Funktionszeiger auf.

In Zeile (04) erstellen Sie einen Zeiger, der auf die Funktion printf() der Standardbibliothek verweisen kann. Natürlich müssen Sie bei einem solchen Funktionszeiger darauf achten, dass der Rückgabewert und die Parameter mit dem Prototyp der Funktion übereinstimmen, auf die Sie verweisen wollen:

// Prototyp: printf
int printf(const char*, ...);
// Unser Zeiger auf die Funktion lautet daher:
int (*fptr)(const char*, ...);

Das bedeutet auch, dass Sie mit diesem Zeiger nicht nur auf printf() verweisen können, sondern auch auf alle anderen Funktionen mit demselben Rückgabewert und Funktionsparameter. In dem letzten Beispiel könnten Sie daher auch die Funktion scanf() oder eine eigene Funktion mit denselben Rückgabe- und Parameterangaben verwenden.

In Zeile (05) bekommt der Zeiger fptr die Adresse für die Funktion printf() zugewiesen. Diese kann jetzt, wie in Zeile (06) geschehen, über den Zeiger aufgerufen werden. Alternativ könnten Sie den Aufruf der Zeile (06) auch wie folgt notieren:

06    fptr("Hallo Welt mit Funktionszeigern\n");

Zeiger auf Funktionen können aber auch in einem Array gespeichert werden. Dann können die einzelnen Funktionen anschließend über den Index aufgerufen werden. Ähnliches könnte beispielsweise bei einem Tastaturtreiber verwendet werden, bei dem je nach Tastendruck in eine entsprechende Funktion verzweigt wird. Zeiger auf Funktionen mit Arrays sehen folgendermaßen aus:

int (*funktionsZeiger[])(int parameter);

Den Klammern wurde lediglich noch der Indizierungsoperator [] hinzugefügt. Hierzu ein sehr einfaches Rechenbeispiel, das Zeiger auf eine Funktion mit Arrays demonstriert:

00  // Kapitel10/funktionszeiger_array.c
01 #include <stdio.h>
02 #include <stdlib.h>

03 double addition( double x, double y ) {
04 return (x + y);
05 }

06 double subtraktion( double x, double y ) {
07 return (x - y);
08 }

09 double multiplikation( double x, double y ) {
10 return (x * y);
11 }

12 double division( double x, double y ) {
13 return (x / y);
14 }

15 double (*fptr[4])(double d1, double d2) = {
addition, subtraktion, multiplikation, division
};

16 int main(void) {
17 double v1=0.0, v2=0.0;
18 int operator=-1;
19 printf("Zahl1 :-> ");
20 if( scanf("%lf", &v1) != 1 ) {
21 printf("Fehler bei der Eingabe\n");
22 return EXIT_FAILURE;
23 }
24 printf("Zahl2 :-> ");
25 if( scanf("%lf", &v2) != 1 ) {
26 printf("Fehler bei der Eingabe\n");
27 return EXIT_FAILURE;
28 }
29 printf("Welcher Operator soll verwendet werden?\n");
30 printf("0 = +\n1 = -\n2 = *\n3 = /\n");
31 printf("Ihre Auswahl: ");
32 if( scanf("%d", &operator) != 1 ) {
33 printf("Fehler bei der Eingabe\n");
34 return EXIT_FAILURE;
35 }
36 if( ! (operator >= 0 && operator <= 3) ) {
37 printf("Fehler beim Operator\n");
38 return EXIT_FAILURE;
39 }
40 printf("Ergebnis: %lf\n", fptr[operator](v1, v2));
41 return EXIT_SUCCESS;
42 }

Listing 10.10    Das letzte Beispiel implementiert einen einfachen »Taschenrechner« und verwendet hierfür ein Array von Zeigern auf Funktionen.

In dem letzten Beispiel werden zwei double-Zahlen eingelesen, und mit diesen wird eine einfache Berechnung durchgeführt. Die mathematischen Funktionen für die Berechnung werden über Zeiger aufgerufen, die im Array fptr stehen und in Zeile (15) deklariert wurden. In Zeile (40) wird die Funktion aufgerufen, auf die fptr[operator] verweist.

Zusammengefasst können solche Funktionszeiger an Funktionen übergeben, von Funktionen zurückgegeben, in Arrays gespeichert und anderen Funktionszeigern zugewiesen werden.

Vielleicht fragen Sie sich trotzdem noch, wozu solche Funktions-Arrays denn wirklich gut sein sollen. Sie könnten doch genauso gut eine Menüauswahl mit einem klassischen switch…case programmieren und die Funktionen direkt aufrufen. Wozu also der ganze Heckmeck? Die Antwort ist, dass der Compiler switch…case vielleicht nur in if-Ausdrücke umwandelt, die dann nach der Eingabe einer Option auch alle geprüft werden müssen. Vielleicht tut der Compiler dies aber auch nicht, und erstellt eine interne Sprungtabelle. Darauf haben Sie aber keinerlei Einfluss. Das heißt, wenn es wirklich schnell gehen muss, erstellen Sie ein Array mit Zeigern auf Funktionen.