Topic : Function Pointers
Author : Jorgen Sigvardsson
Page : 1 Next >>
Go to page :


Tutorial: Function Pointers


Table of Contents
1.What are function pointers?
2.What advantages does this give me?
3.Example - Iterating through a list
  -Example 1 - Code
  -Example 2 - Code
  -Example 3 - Code
4.Some useful tips for programmers
-Short hand notation
-Module abstraction and indepenence

What are function pointers?

Function pointers are means to add another level of indirection. Remember pointers to variables? Function pointers are no different.

Let's say that there are two identical functions a and b (by identical we mean that the functions have the same return type and the same parameter type). We also declare a function pointer p. The function pointer is defined such that it shares the same return type and parameter types as a and b. By assigning the address of a to p, p = a;, calls to p will in effect yield calls to a. Likewise, p can be assigned b, where calls to p yield calls to b. Getting dizzy yet? ;-)

Here's a very simple example which works with C and C++.

#include <stdio.h>

void a(void)
{
   printf("a was called.\n");
}

void b(void)
{
   printf("b was called.\n");
}

int main(void)
{
   void (*p)(void); /* This is a function pointer declaration */

   p = a;
   (*p)();          /* Using function pointer p               */
   p = b;
   (*p)();          /* Using function pointer p (again)       */
   return 0;        /* Standard exit procedure in UNIX        */
}

 
As you can see, the function pointer declaration looks somewhat complicated. The "standard remedy" for this is to create a new function pointer type by using the typedef operator.

Same example again using the typedef operator.

#include <stdio.h>

typedef void (*FuncType)(void); /* FuncType is a function pointer type */

void a(void)
{
   printf("a was called.\n");
}

void b(void)
{
   printf("b was called.\n");
}

int main(void)
{
   FuncType p;      /* This is a function pointer declaration */

   p = a;
   (*p)();          /* Using function pointer p               */
   p = b;
   (*p)();          /* Using function pointer p (again)       */
   return 0;        /* Standard exit procedure in UNIX        */
}

 
What advantage does this give me?

As you probably learned in your first programming courses, pointers to variables allows you to modify variable contents from a non-local environment. This gives the flexibility of writing generic functions which do not alter "known" global variables, but references to "unknown" variables. Such functions can be reused. Function pointers gives you the same flexibility, but at a higher level. Instead of calling a "known" function, one can call any arbitrary "unknown" function. This is the main advantage of function pointers: greater flexibility and better code reuse.

Example - Iterating through a list

In this example I'll show a very common (and naive) solution for iterating through a list, then a somewhat "better" solution and last I'll show a general and resuable solution.

The list is a list of strings and we want to iterate it for printing to screen. As a twist, we also want to print out the a list of the string lengths rather than the strings.

Example 1 - Code:
This is a very naive implementation.

#include <stdio.h>

typedef struct ListRecord List;

struct ListRecord {
   char*       text;
   List*       next;
};

List* listInsert(List* list, const char* text)
{
   ListRecord* rec;

   rec = (ListRecord*)malloc(sizeof(ListRecord));
   rec->text = strdup(text); /* Make a copy of string */
   rec->next = list;         /* Link in old list */
   return list;
}

int main(void)
{
   List* list;
   ListRecord* i;

   list = NULL;
   list = listInsert(list, "World!");
   list = listInsert(list, "Hello");

   printf("List begins\n");
   for(i = list; i != NULL; i = i->next)
      printf("Text: %s\n", i->text);      
   printf("List ends\n");


   printf("List begins (lengths)\n");
   for(i = list; i != NULL; i = i->next)
      printf("Text Length: %d\n", strlen(i->text));
   printf("List ends\n");

   return 0;
}

 
Example 2 - Code:
This is a better implementation. Functionality has been moved out into separate functions. printList and printListStrLen are new functions.

#include <stdio.h>

typedef struct ListRecord List;

struct ListRecord {
   char*       text;
   List*       next;
};

void printList(List* list)
{
   ListRecord* i;

   printf("List begins\n");
   for(i = list; i != NULL; i = i->next)
      printf("Text: %s\n", i->text);      
   printf("List ends\n");
}

void printListStrLen(List* list)
{
   ListRecord* i;

   printf("List begins (reversed)\n");
   for(i = list; i != NULL; i = i->next)
      printf("Text Length: %s\n", strlen(i->text));
   printf("List ends\n");
}

List* listInsert(List* list, const char* text)
{
   ListRecord* rec;

   rec = (ListRecord*)malloc(sizeof(ListRecord));
   rec->text = strdup(text); /* Make a copy of string */
   rec->next = list;         /* Link in old list */
   return list;
}

int main(void)
{
   List* list;

   list = NULL;
   list = listInsert(list, "World!");
   list = listInsert(list, "Hello");

   printList(list);
   printListStrLen(list);
  
   return 0;
}

 
Example 3 - Code:
This solution is the best of the three since it allows for high code reuse. It makes use of function pointers for performing the actual printing and it separates the "for-looping" into a function of its own. The three new key features in this example is ListRecordOperation, print, printStrLen and listForEach. Note that adding new features such as printing the text in reverse is now very easy. Just add a function that is compatible with the ListRecordOperation type and call listForEach with the function as argument. No more tedious moments of writing for-loops and such, now you can focus on the problem at hand instead.

#include <stdio.h>

typedef void (*ListRecordOperation)(const char*);

typedef struct ListRecord List;

struct ListRecord {
   char*       text;
   List*       next;
};

/* function compatible with ListRecordOperation */
void print(const char* text)
{
   printf("Text: %s\n", text);      
}

/* function compatible with ListRecordOperation */
void printStrLen(const char* text)
{
   printf("Text Length: %s\n", strlen(text));
}

void listForEach(List* list, ListRecordOperation op)
{
   ListRecord* i;

   for(i = list; i != NULL; i = i->next)
      (*op)(list->text); /* Calling whatever op points to with i->text
                            as argument */
}

List* listInsert(List* list, const char* text)
{
   ListRecord* rec;

   rec = (ListRecord*)malloc(sizeof(ListRecord));
   rec->text = strdup(text); /* Make a copy of string */
   rec->next = list;         /* Link in old list */
   return list;
}

int main(void)
{
   List* list;

   list = NULL;
   list = listInsert(list, "World!");
   list = listInsert(list, "Hello");

   listForEach(list, print);
   listForEach(list, printStrLen);

   return 0;
}

 
Useful tips for programmers
Short hand notation
Function pointers may be used with a shorthand notation. Instead of dereferencing the function pointer with an asterisk:

p = a;
(*p)();

    
you can use it like this:

p = a;
p();

    
as if it was an "ordinary" function. Gives C an aesthetic appeal. ;-)

Module abstraction and indepenence
During the development phase of a project, the code is more or less always in a state of flux. Often, one wants to change the interface of some module so that it can serve some other modules needs better. Communication between modules may very well be done using function pointers. A prime example of this is the frontend.cpp and menu.cpp modules in the lab files (dsa-lab.tar). In fact this is the only way that the menu subsystem can call functions in the frontend module without having "intimate" knowledge them.

The code which creates the menu items looks like this:

Page : 1 Next >>