函数指针:一个用于存放一个函数地址的指针,可以用于回调函数的实现。
目录
1、认识函数地址
假设有一个数组int arr[10],arr 代表首元素的地址,而 &arr 代表整个数组的地址
假设有一个函数void Add(int x, int y),Add就是函数的地址,&Add也是函数的地址,和数组不一样,这两者是等价的。
2、认识函数指针
(1) 函数指针的声明
函数指针的声明和数组指针的声明比较类似,我们要让一个指针指向一个数组,需要知道所指向数组的大小、数组元素类型。同理,要让一个指针指向一个函数,那就需要知道函数形参(包括形参类型、形参个数) 和 函数返回值
假设我们要保存下面这个函数的地址
int Add(int x, int y)
{
return x + y;
}
声明的步骤如下:
- 先用一个变量去接收函数的地址:ptr = &Add;
- 因为这个变量是一个指针,所以:*ptr = &Add;
- 这个指针所指向的函数形参类型是两个int类型:(*ptr)(int , int) = &Add
- 这个指针指向的函数返回值是int类型:int (*ptr)(int , int) = &Add
因此,完整的函数声明方式是
int (*ptr)(int , int) = &Add;
(2) 函数指针的指针类型
数组如果将数组名和数组大小去掉,剩下的的就是数组元素的类型;
指针如果将指针名去掉,剩下的就是指针类型;
因此,函数指针的指针类型为
int (*)(int , int)
(3) 函数指针的使用方法
函数指针保存了一个函数的地址,通过解引用,我们可以找到这个函数,因此调用方式如下:
(*ptr)(2 , 3); //等同于调用Add(2 , 3)
// ptr(2 , 3); —— 这种写法是等价的
注意:上面的 * 是一个摆设,只是便于初学者知道,函数指针解引用可以找到对应的函数,但是实际上不解引用也是可以的,即便是多几个 * 也是一样的。
(4) 函数指针的应用 —— 回调函数
=================== 提出问题 ===================
下面是qsort函数的声明,qsort函数用于给一组数据排序,这组数据可以是任意类型。
那么问题来了,既然是排序,必然要比较两个数据的大小,如果传入的是自定义类型,该怎么比较大小呢?
================ 分析问题 ================
qsort函数的第三个参数需要传入一个函数指针, 这个参数其实就是在告诉qsort函数,如果我传入一个结构体类型,我是希望通过结构体大小比较,还是通过结构体中的某个成员比较。
我们可以实际调用试试。假设我们要给一个结构体数组排序,排序方式是通过结构体中的 name 成员来比较大小。
struct Person
{
char name[20];
int age;
};
int main() {
struct Person arr[3] = { { "张三",15 },{ "李四",30 },{ "王五",10 } };
return 0;
}
第三个参数已经明确了函数的返回值和形参类型,返回值为int,形参2个,都是const void* 类型,实际声明和调用如下:
struct Person
{
char name[20];
int age;
};
int my_compare(const void* e1, const void* e2)
{
return strcmp(((Person*)e1)->name, ((Person*)e2)->name);
}
int main() {
struct Person arr[3] = { { "张三",15 },{ "李四",30 },{ "王五",10 } };
qsort(arr, 3, sizeof(Person), my_compare);
for (size_t i = 0; i < 3; i++)
{
printf("%s\n", arr[i].name);
}
return 0;
}
打印结果如下:
3、拓展:函数指针数组
函数指针数组其实就是一个保存函数指针的数组,去掉数组名和数组大小就是函数指针的类型。假设我们要保存的函数指针类型为
int (*)(int,int)
我们加上数组名和数组大小,即ptrArr[10],那么函数指针数组为
int (*ptrArr[10])(int,int)
上述函数指针数组 ptrArr 保存的函数必须满足:
- 返回值是int
- 形参有两个,均为int类型