Revision : Function Pointers
Introduction
We have learned that the name of an array is really the address in memory of the first element of the array. Similarly, the name of a function is really the starting address in memory of the code that performs the function’s task. Pointers to functions can be passed to functions, returned from functions, stored in arrays and also assigned to other function pointers.
Basic Concept
Example:
- #include <conio.h>
- #include <iostream.h>
- int sum(int i,int j)
- {
- return i+j;
- }
- int minus(int i,int j)
- {
- return i-j;
- }
- void sayhello()
- {
- cout < "hello" <endl;
- }
- void main()
- {
- void (*q)() = sayHello;
- (*q)(); //output: hello
- q(); //also valid, output: hello
- int (*p)(int,int) = sum;
- int s = (*p)(5,6);
- cout < s < endl; //output: 11
- int t = p(4,2); //also valid
- cout < t < endl; //output: 6
- p = minus; //re-assigned
- cout < (*p)(5,3) < endl; //output: 2
- p = sayHello; //invalid, different type
- q = sum; //invalid, different type
- getch();
- }
From the above example we can see that:
Here sayHello, sum and minus in line 21, 25 and 31 respectively are addresses and can be viewed as constant pointer to the code that performs the task, therefore this address can be assigned to pointer of compatible type, that is function pointers
In the above program we have declared two function pointers p and q.
q is declared by:
void (*q)() = sayHello;
This is the simplest form for a function pointer, the asterix ‘*’ indicates that q is a pointer. The parenthesis (brackets) enclosing *q is a must (otherwsie void *q() becomes function q returning void*). The () after (*q) means the type of functions pointed by q takes no parameter. The ‘void’ means that the type of functions pointed do not return anything.
Here q has been initialized to take the address of function sayHello.
In invoke the function pointed by q, we first dereference the pointer with the asterix ‘*’ and then use it just like any other functions. This is shown below:
(*q)();
Since a function name is the address of the code and the function pointer q is also the address of the code, we could also invoke the function this way just like any other functions:
q(); //also valid
However, this is NOT recommended since the user of the program may think that there is actually such a function by this name.
The above example also declared another function pointer, which is:
int (*p)(int,int) = sum;
Here the declaration says that the function pointed by p takes 2 int parameters and returns int. We invoke the function pointed by p in either ways below:
s = (*p)(5,6);
t = p(5,6); //also valid
Note that you can only assign function pointer to be pointed to function with the similar signature (i.e. parameters and returned values). Therefore line 34 and 35 in the example above would produce compile time errors.
Array of Function Pointers
Example:
- #include <iostream.h>
- #include <conio.h>
- #include <math.h>
- #define CUBE 0
- #define CYLINDER 1
- #define SPHERE 2
- struct Thing
- {
- int id;
- float data1,data2,data3;
- };
- float cubeVolume(struct Thing cube)
- {
- cout < "Calculating volume of cube" < endl;
- return cube.data1*cube.data2*cube.data3;
- }
- float cylinderVolume(struct Thing cyl)
- {
- cout < "Calculating volume of cylinder" < endl;
- return M_PI*cyl.data1*cyl.data1*cyl.data2;
- }
- float sphereVolume(struct Thing sph)
- {
- cout < "Calculating volume of sphere" < endl;
- return M_PI*sph.data1*sph.data1*sph.data1*4/3.0;
- }
- void main()
- {
- float (*fun[3])(struct Thing)
- = { cubeVolume,cylinderVolume,sphereVolume };
- char *name[] = { "Cube","Cylinder","Sphere" };
- struct Thing t[5];
- t[0].id = CUBE;
- t[0].data1 = 5.0; //width
- t[0].data2 = 2.0; //length
- t[0].data3 = 3.0; //height
- t[1].id = SPHERE;
- t[1].data1 = 4.0; //radius
- t[2].id = CYLINDER;
- t[2].data1 = 2.0; //radius
- t[2].data2 = 6.0; //length
- t[3].id = CUBE;
- t[3].data1 = 2.0;
- t[3].data2 = 6.0;
- t[3].data3 = 5.0;
- t[4].id = SPHERE;
- t[4].data1 = 6.0;
- int i;
- float v,sumv=0;
- int thingid;
- for (i=0;i<5;i++)
- {
- thingid = t[i].id;
- v = (*fun[thingid])(t[i]);
- //or: v = fun[thingid](t[i]);
- cout < "The volume of " < name[thingid]
- < " is " < v < endl;
- sumv += v;
- }
- cout < "Total volume is " < sumv < endl;
- getch();
- }
The above example declared an array of function pointers and initialized them with the different function addresses (line 35 and 36). Note that the size of the array is written immediately after the name of the array. In this example, the functions pointed by the function pointers take in a struct Thing and return float.
To invoke the function, the array subscript also appears immediately after the name of the array. This is shown in line 67 and alternatively line 68 is also valid.
The above example shows that function pointer can be a very good tool to help us to write compact programs which are much easier to debug and maintain. Without function pointer, line 67 would have to be written with many lines using the switch keyword as below:
switch (thingid)
{
case CUBE: v = cubeVolume(t[i]);
break;
case SPHERE: v = sphereVolume(t[i]);
break;
case CYLINDER: v = cylinderVolume(t[i]);
break;
}
If we have many more types of Thing, then this would be very problematic.