Advanced Function Pointer Concepts in C: A Guide for Programmers
Learn how to use function pointers as return types, parameters, and with structs and typedef
Introduction
Previously, we have written on Function Pointers, you can check it here, as it covers the fundamentals and explains from scratch what function pointers are.
In this article, I will dwell more on using function pointers in the following cases:
using function pointers with typedef.
using function pointers as a return type.
using function pointers as a parameter.
using function pointers with structs.
So you can check the previous article to catch up on the basics. So we will tackle all these 4 above one after the other, with an example for each of them.
Using Function Pointers with Typedef
We have written an article on typedef before, so you can check it in case it is new to you, or if you want to revise it.
We can use typedef to define a function pointer as a type since a function pointer is a type, and that is why it is possible to use it as a return type because mostly it is a data type that can be used as a return type for a function.
Now, let us look at an example here:
#include <stdio.h>
typedef int (*fptr)(int, int);
int add(int x, int y)
{
return (x + y);
}
int main(void)
{
int sum;
fptr Add = add;
sum = Add(5, 8);
printf("%d\n", sum);
return (0);
}
In this example, we used the typedef to redefine the function pointer (*fptr)(int, int)
, in this case, you don't need to give it a name while using typedef to declare a data type for the function pointer, the data type will be the name of the function pointer. In this case, the function pointer data type is now the name of the function pointer, which is the fptr.
So coming to the main function, we can now use it to declare a variable that will now act as the variable that will be performing the function of the function pointer that is typedef. That is why we now have fptr Add
. With fptr as the data type and Add as the variable that will now hold the address of the function. So literally, Add
becomes the function pointer.
Using Function Pointers as a Return Type
In a situation where we have more than a single function, we want to point to using a function pointer, instead of having to do all that in our main function either by assigning a function pointer to each of them or by grouping all the functions in an array and using array index to access them.
Instead of doing all these, we can simply take advantage of Function Pointers as a return type to create another function that will do the work of selection for us, while we just return that function in our main function.
We can do this either using an array or a switch case, all depending on our preference, but irrespective of which method we decide to work with, one thing is sure, which are options that will be unique to each of the functions, so it means that such function will have to take in an option as a parameter, and this option could be either a string, a character or even an integer.
We are going to be looking at it from the angle of using just an array, to the angle of using switch cases to the angle of using an array of a struct.
Always note that this type of function will always have a function pointer as the return type, as that is what it is returning.
So it means that we have to declare it using the function pointer as the return data type since that is what the function will be returning.
We can do this in two ways, we can either use the function pointer as it is in full, or we take advantage of typedef (type definition) to help us reduce the complexity of having to keep calling our full function pointer as it is every time we need it in our main function.
Let us say we want to create a function that will return a function pointer to the appropriate function based on arithmetic operands (+, -, *, /, % etc).
Below are the two ways this function can be declared:
int (*getfptr(char op))(int, int);
In this first code, the function is having an int return type, because all the functions it will be pointing to will also have an int return data type. If the functions will be having a void return data type, then the return data type for this function would have also been void too.
Next, we will have the name of the function that points to the function pointer, in our case here we used the name getfptr, in other words, means get-function-pointer.
Now this function will definitely in this case have a parameter so that it can accept arguments which will help the function to point at a function when the right option supplied to the function as an argument matches that of the function it is meant to point to. So the parameter depends on the type, it could be a char, an int, a float or even some other data type etc.
Finally, we will have the function that will be returning the function pointer also have two parameters, because the functions which the function pointer is going to be returning are also each taking in two parameters.
So this is how to declare a function that will be returning a function pointer, or better still we can say that this function has a function pointer as a return type, so it returns a function pointer.
The second way of declaring a function that returns a function pointer is by taking advantage of typedef.
typedef int (*fptr)(int, int);
fptr getfptr(char op);
This method involves using the typedef to first of all declare the function pointer as a data type, and then afterward the function can now have the name of the function pointer, which in this case is fptr, as the return type for the function that will be returning the function pointer.
Although, it all depends on individual preference, however, I prefer this second method, as it makes my code clean and more understandable.
Now let us put this knowledge into use, to utilize this, we have to make use of our conditional statements, for our function to be able to return the function pointer whenever the appropriate option for that function pointer is passed as an argument to the function.
The most common conditional statements are used as the if-else statement and also switch cases. We will be using the two for the same example.
So let us consider the same example that is being used in our first article on function pointers. Here we will be using the if-else statement.
#include <stdio.h>
typedef int (*fptr)(int, int);
int add(int x,int y)
{
return (x + y);
}
int sub(int x,int y)
{
return (x - y);
}
int mul(int x,int y)
{
return (x * y);
}
int div(int x,int y)
{
return (x / y);
}
fptr getfptr(char op)
{
if (op == '+')
return (add);
else if (op == '-')
return (sub);
else if (op == '*')
return (mul);
else if (op == '/')
return (div);
else
return (NULL);
}
int main(void)
{
char option = '+';
int result;
int num1 = 40, num2 = 5;
fptr Calc = getfptr(option);
if (Calc)
{
result = Calc(num1, num2);
printf("%d\n", result);
}
else
{
printf("Invalid Operator");
}
return (0);
}
So in the code above, we first define the function pointer using typedef, which we can now use the name of the function pointer directly as the return type for the function that will be returning the function pointer.
So next, using the return type that was defined by our typedef (fptr), we defined the function that will be returning the function pointer. Which is the getfptr, then the function will take a single parameter, which is the operator, as that is what will direct the function on where/which function should be pointed to.
In the main function, we have an option variable declared, which will be what will take the operator, we have the result declared and also the two numbers we will be running the arithmetic operation on are both declared and defined as 40 and 5 respectively.
Next, we have to store whatsoever our getfptf is returning in a variable, and since the function has a return type of fptr, then we will also have to declare the variable that will be holding whatsoever the function will be returning by that same type fptr, next, we feed in the argument to the getfptr function as it takes a single parameter in the definition, so also in the calling, we have to feed it with the appropriate argument. In this case, we are feeding it with option, and option in this case is given a value of an addition operator (+).
The variable storing this answer is the Calc, so next, we have to print whatsoever is stored in the Calc, but before we do that, we have to equally check whether what the function is returning is Valid or not, in this case, if Calc has a value, that means it is valid, else the operation was not successful.
So in the case where we want to carry out other operations like subtraction, multiplication or division, we can simply change the value of the option to the appropriate operator as defined in the getfptr function.
So based on our code, we are going to have 45. You can tweak the code by checking other operators as well.
Using Function Pointer as a Parameter
Ordinarily, we can never use functions as parameters, at least in C, I don't know about other programming languages, but I am sure that for C programming is it impossible to pass a function as an argument to another function, but with the power of Function pointers, we can achieve that, as the function pointer is pointing to the code of the function whose address it is pointing to, so it's more like having the function itself as the parameter.
Using a function pointer as a parameter is not too different from using other normal variables as a parameter, the only difference in this place is that we are using a function pointer instead, and of course, we are fully aware of what a function pointer points to, it points to a code and not a data.
So let us consider the same example we have above, but this time around we will be passing the function pointer as a parameter instead:
#include <stdio.h>
typedef void (*fptr)(int, int);
void add(int x,int y)
{
printf("%d + %d = %d\n", x, y, (x + y));
}
void sub(int x,int y)
{
printf("%d - %d = %d\n", x, y, (x - y));
}
void mul(int x,int y)
{
printf("%d x %d = %d\n", x, y, (x * y));
}
void div(int x,int y)
{
printf("%d / %d = %d\n", x, y, (x / y));
}
void display(fptr Calc)
{
(*Calc)(40, 5);
}
int main(void)
{
display(add);
display(sub);
display(mul);
display(div);
return (0);
}
From this code, we have about 4 arithmetic functions, which add, subtract, multiply and divide.
Then to illustrate using a function pointer as a parameter, I simply created a function and named it display, this function points to whichever function is passed as an argument to the display function. So here based on the function that is passed as the argument, the function which is passed as an argument to the display function itself has two parameters each, so it means they too will need two arguments, and that is why that was defined inside the display function, where we have the function pointer void (*fptr)(int, int)
defined using typedef, so instead of using void (*fptr)(int, int)
we will be using fptr
and thus, that was what we used as the data type used in defining the parameter we will be using as our function pointer, which we defined as Calc
, so that means that the Calc
now becomes the function pointer we are passing as the parameter.
So inside our display function, what is happening is that we are calling the function pointer that is our parameter, and then we are passing two numbers as arguments to the function pointer so that whichsoever function the function pointer points to, those two parameters will be passed to the functions, as all the functions, in this case, require two arguments.
Finally, we call the display function inside the main function, we called the display function 4 times, and with each call, we pass in one of the four functions, all so we could be able to display the result for each of the functions.
Using Function Pointers with Structs
In this case, we will be using function pointers with structs. Although whatsoever we can use function pointers with struct, can also be achieved without the function pointer, it is just that using function pointers with struct makes the code shorter, simple and readable. And it makes the code easier to maintain and even easier to upgrade.
We will be using the code for Function Pointers as a return type, and refactoring it to make use of Function Pointers with Structs.
We will be taking everything bit by bit, before combining all the code together.
#include <stdio.h>
int op_add(int x,int y);
int op_sub(int x,int y);
int op_mul(int x,int y);
int op_div(int x,int y);
int op_mod(int x,int y);
First, we have to define our header, which is the stdio.h, since we will be printing to the standard output using printf. Next, we will have to declare the functions' prototypes. Here we will be using 5 functions, each one with its operation, we have addition, subtraction, multiplication, division and modulus.
typedef struct op
{
char *op;
int (*f)(int a, int b);
} op_t;
next, we will be defining our struct, here we are defining a struct with the name op
, and then using a typedef to define the struct, so it could have a data type of op_t
.
The struct here contains the op
, which is a pointer to a character, in this case, the op
includes the operations that will correspond to each of our 5 functions ("+", "-", "*", "/", "%").
Next thing the struct contains a function pointer with the name f, which will be pointing to functions with two arguments. And it is having a return type of int, that is to say, all the functions it will be pointing to have a return type of int.
int op_add(int x,int y)
{
return (x + y);
}
int op_sub(int x,int y)
{
return (x - y);
}
int op_mul(int x,int y)
{
return (x * y);
}
int op_div(int x,int y)
{
return (x / y);
}
int op_mod(int x,int y)
{
return (x % y);
}
Next, we have to define our functions, that is the 5 functions we declared initially, which are op_add, op_sub, op_mul, op_div and op_mod, with each of them returning addition, subtraction, multiplication, division and modulus respectively.
int main(void)
{
op_t ops[] = {
{"+", op_add},
{"-", op_sub},
{"*", op_mul},
{"/", op_div},
{"%", op_mod},
{NULL, NULL}
};
int i = 0;
int x = 120, y = 30;
while (ops[i].op)
{
int res = ops[i].f(x, y);
printf("%d\n", res);
i++;
}
return (0);
}
Finally, in our main function, we build our array called ops[ ]
, with the struct data type of op_t
. The array comprises the struct we defined, so technically it is the array of structs, with operators and their corresponding functions.
Next, using a while loop, I decided to go through the array of struct and then with a value of x = 120 and y = 30, I printed out the values from each of the functions that are handling the addition, subtraction, multiplication, division and modulus.
This is an example of using a function pointer with struct.
Conclusion
These are some of the various advanced applications of function pointers. Next, we will look at variadic functions, and afterward, we are going to build our custom printf( ) by combining all the knowledge of C programming we have.
Thank you for reading. You can connect with me on Twitter and LinkedIn.