Function Pointers as Function Arguments in C
In this lesson, we will explore how to pass functions as arguments to other functions using function pointers in C language.
This advanced technique allows for creating more flexible and modular code, enabling the definition of custom behaviors and implementing different strategies within functions. Understanding how to use function pointers as arguments is essential to fully exploit the capabilities of C language and write more efficient and reusable code.
Passing Functions as Function Arguments
A first, very powerful application of function pointers that we studied in the previous lesson, is the ability to pass functions as function arguments.
So far, we have written functions that accept as arguments variables of various types: integers, floats, strings, arrays, structures, etc. But we have never written a function that accepts another function as an argument.
To clarify the concept, let's consider an example borrowed from mathematics. Suppose we want to implement a function that numerically calculates the definite integral of the function
#include <stdio.h>
#include <math.h>
double integral_x3_minus_1(double a, double b, double dx) {
double x, integral = 0;
for (x = a; x < b; x += dx) {
integral += pow(x, 3) - 1;
}
return integral * dx;
}
int main() {
double a = 0, b = 1, dx = 0.001;
double integral = integral_x3_minus_1(a, b, dx);
printf("Definite integral of x^3 - 1 in [%f, %f] = %f\n", a, b, integral);
return 0;
}
Let's try to compile and execute the program (Remember that to compile it with gcc, since we use C's mathematical library, we need to add the -lm option). The result will be as follows:
$ gcc integral_x3_minus_1.c -o integral_x3_minus_1 -lm
$ ./integral_x3_minus_1
Definite integral of x^3 - 1 in [0.000000, 1.000000] = -0.750500
The result is very similar to the true result which is:
The result is very close to the true result, but it is not exact. This is due to the fact that we are approximating the definite integral with a numerical method, namely with the Riemann sum. The smaller the integration step
Next, suppose we want to also calculate the definite integral of another function, for example
#include <stdio.h>
#include <math.h>
double integral_x2_plus_1(double a, double b, double dx) {
double x, integral = 0;
for (x = a; x < b; x += dx) {
integral += pow(x, 2) + 1;
}
return integral * dx;
}
int main() {
double a = 0, b = 1, dx = 0.001;
double integral = integral_x2_plus_1(a, b, dx);
printf("Definite integral of x^2 + 1 in [%f, %f] = %f\n", a, b, integral);
return 0;
}
The result will be as follows:
$ gcc integral_x2_plus_1.c -o integral_x2_plus_1 -lm
$ ./integral_x2_plus_1
Definite integral of x^2 + 1 in [0.000000, 1.000000] = 1.332834
The result is very similar to the true result which is:
Now, let's analyze the two functions integral_x3_minus_1 and integral_x2_plus_1. The two functions are very similar, they differ only in the function being integrated. In particular, the function being integrated is the part that changes.
If we wanted to calculate the definite integral of another function, for example integral_x4_minus_2 that is very similar to the previous two. This is an example of repeated code, which should always be avoided.
Thanks to function pointers we can avoid repeated code. In particular, we can write an integral function that calculates the definite integral of a generic function
#include <stdio.h>
#include <math.h>
/* First we define the three functions we want to integrate */
double x3_minus_1(double x) {
return pow(x, 3) - 1;
}
double x2_plus_1(double x) {
return pow(x, 2) + 1;
}
double x4_minus_2(double x) {
return pow(x, 4) - 2;
}
/* We define the function that calculates the definite integral */
double integral(double a, double b, double dx, double (*f)(double)) {
double x, integral = 0;
for (x = a; x < b; x += dx) {
integral += f(x);
}
return integral * dx;
}
int main() {
double a = 0, b = 1, dx = 0.001;
double integral1 = integral(a, b, dx, x3_minus_1);
printf("Definite integral of x^3 - 1 in [%f, %f] = %f\n", a, b, integral1);
double integral2 = integral(a, b, dx, x2_plus_1);
printf("Definite integral of x^2 + 1 in [%f, %f] = %f\n", a, b, integral2);
double integral3 = integral(a, b, dx, x4_minus_2);
printf("Definite integral of x^4 - 2 in [%f, %f] = %f\n", a, b, integral3);
return 0;
}
In this way we have defined a generic integral function that is able to calculate the definite integral of a generic function integral function accepts as an argument a function f that represents the function to integrate. In this way, we can calculate the definite integral of any function
As can be noted, the syntax for using a function pointer as an argument is identical to that for defining a function pointer. In particular, the syntax is as follows:
return_type function(return_type_f (*pointer_name)(argument_type_f));
The parameter name is the name of the function pointer: pointer_name.
Summarizing:
Passing Functions as Function Arguments
A function can be passed as an argument to another function through function pointers.
A function that accepts a function pointer as a parameter has the following syntax:
return_type function(..., return_type_f (*pointer_name)(argument_type_f), ...) {
...
}
The qsort Function
An interesting application of functions passed as arguments concerns the qsort function of the C standard library.
The qsort function is a library function defined in the <stdlib.h> header and is able to sort an array of any elements. It is a very generic and powerful function that requires as input precisely a function pointer. Let's see why.
Since the elements of the array that we pass as an argument to qsort can be of any type, for example even data structures, qsort cannot know how to compare two elements of the array. To do this, qsort requires a function pointer that represents the comparison function between two elements. This comparison function must be able to determine, given two elements, which of the two is the smallest. For this reason, this function that we must provide takes the name comparison function.
This function takes as input two pointers to two elements of the array and returns an integer that represents the result of the comparison. In particular, the comparison function must return:
- a negative value if the first element is less than the second,
- a positive value if the first element is greater than the second,
- zero if the two elements are equal.
The prototype of the qsort function is as follows:
void qsort(void *base,
size_t nmemb,
size_t size,
int (*compar)(const void *, const void *));
Where:
baseis the pointer to the array to sort,nmembis the number of elements in the array,sizeis the size in bytes of each element of the array,comparis the pointer to the comparison function.
Let's try to apply the qsort function to a first simple case: sorting an array of double in ascending order. To do this, we must write a comparison function that compares two double and returns a negative value if the first is less than the second, a positive value if the first is greater than the second, and zero if the two are equal.
#include <stdio.h>
#include <stdlib.h>
/* Comparison Function for double */
int comparison_double(const void *a, const void *b) {
double x = *(double *)a;
double y = *(double *)b;
if (x < y) return -1;
if (x > y) return 1;
return 0;
}
int main() {
double array[] = {3.14, 2.71, 1.41, 1.61, 1.73};
size_t n = sizeof(array) / sizeof(array[0]);
qsort(array, n, sizeof(double), comparison_double);
for (size_t i = 0; i < n; i++) {
printf("%f ", array[i]);
}
printf("\n");
return 0;
}
The result will be as follows:
$ gcc qsort_double.c -o qsort_double
$ ./qsort_double
1.410000 1.610000 1.730000 2.710000 3.140000
The result is correct: the array has been sorted in ascending order.
If we wanted to sort the array in descending order, we can write a comparison function that returns the opposite value:
int comparison_double_descending(const void *a, const void *b) {
return -comparison_double(a, b);
}
And, subsequently, invoke qsort with the comparison function comparison_double_descending:
qsort(array, n, sizeof(double), comparison_double_descending);
The result will be as follows:
$ gcc qsort_double_descending.c -o qsort_double_descending
$ ./qsort_double_descending
3.140000 2.710000 1.730000 1.610000 1.410000
This great flexibility is what makes function pointers very powerful.
We can reuse the qsort function also on arrays of completely different types. Suppose, for example, we want to implement a program that sorts an array of strings in alphabetical order. In this case, we must write a comparison function that compares two strings and returns a negative value if the first is less than the second, a positive value if the first is greater than the second, and zero if the two are equal.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Comparison Function for strings */
int comparison_strings(const void *a, const void *b) {
const char *x = *(const char **)a;
const char *y = *(const char **)b;
return strcmp(x, y);
}
int main() {
const char *array[] = {"dog", "cat", "mouse", "elephant", "ant"};
size_t n = sizeof(array) / sizeof(array[0]);
qsort(array, n, sizeof(char *), comparison_strings);
for (size_t i = 0; i < n; i++) {
printf("%s ", array[i]);
}
printf("\n");
return 0;
}
The result will be as follows:
$ gcc qsort_strings.c -o qsort_strings
$ ./qsort_strings
ant cat dog elephant mouse
To sort the strings in descending order, just write the comparison function this way:
int comparison_strings_descending(const void *a, const void *b) {
const char *x = *(const char **)a;
const char *y = *(const char **)b;
/* Reverses the value of strcmp */
return -strcmp(x, y);
}
Let's see one last example. Suppose we have an array of Person structures, where each element is composed as follows:
struct Person {
char name[20];
char surname[20];
int age;
};
We want to implement a program that sorts, first, the people in alphabetical order by surname. Then we want to sort the people in ascending order by age. To do this, we must write two comparison functions: one for the surname and one for the age.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Person {
char name[20];
char surname[20];
int age;
};
/* Comparison Function for surname */
int comparison_surname(const void *a, const void *b) {
const struct Person *x = (const struct Person *)a;
const struct Person *y = (const struct Person *)b;
return strcmp(x->surname, y->surname);
}
/* Comparison Function for age */
int comparison_age(const void *a, const void *b) {
const struct Person *x = (const struct Person *)a;
const struct Person *y = (const struct Person *)b;
return x->age - y->age;
}
int main() {
struct Person array[] = {
{"Mario", "Red", 30},
{"Luca", "White", 25},
{"Paolo", "Green", 35},
{"Giuseppe", "Black", 20},
{"Giovanni", "Yellow", 40}
};
size_t n = sizeof(array) / sizeof(array[0]);
qsort(array, n, sizeof(struct Person), comparison_surname);
for (size_t i = 0; i < n; i++) {
printf("%s %s %d\n", array[i].name, array[i].surname, array[i].age);
}
printf("\n");
qsort(array, n, sizeof(struct Person), comparison_age);
for (size_t i = 0; i < n; i++) {
printf("%s %s %d\n", array[i].name, array[i].surname, array[i].age);
}
return 0;
}
The result will be as follows:
$ gcc qsort_person.c -o qsort_person
$ ./qsort_person
Giuseppe Black 20
Paolo Green 35
Mario Red 30
Luca White 25
Giovanni Yellow 40
Giuseppe Black 20
Luca White 25
Mario Red 30
Paolo Green 35
Giovanni Yellow 40
As can be noted, the people have been sorted first by surname and then by age.
Summarizing:
The qsort Function of the C Standard Library
The qsort function of the C standard library is able to sort an array of any elements. To do this, it requires a function pointer that represents the comparison function between two elements.
The prototype of the qsort function is as follows:
#include <stdlib.h>
void qsort(void *base,
size_t nmemb,
size_t size,
int (*compar)(const void *, const void *));
Where:
baseis the pointer to the array to sort,nmembis the number of elements in the array,sizeis the size in bytes of each element of the array,comparis the pointer to the comparison function.
The comparison function must have the following prototype:
int compar(const void *a, const void *b);
The comparison function must return:
- a negative value if the first element is less than the second,
- a positive value if the first element is greater than the second,
- zero if the two elements are equal.
In Summary
In this lesson we have seen how to pass functions as function arguments thanks to function pointers. This technique is very powerful and allows us to write more generic and flexible code. We have seen two examples of application of function pointers: the calculation of the definite integral of a generic function and the sorting of an array of any elements.
In particular, we have seen that:
- A function can be passed as an argument to another function through function pointers.
- The
qsortfunction of the C standard library is able to sort an array of any elements. To do this, it requires a function pointer that represents the comparison function between two elements.