函数指针
函数指针的定义
函数指针和我们在对指针的详细认识(二)中学习的数组指针非常相似。
我们知道,整型指针是指向整型的指针,数组指针是指向数组的指针,其实,函数指针就是指向函数的指针。
和学习数组指针一样,学习函数指针我们也需要知道三点:
- ( )的优先级要高于 * 。
- 一个变量除去了变量名,便是它的变量类型。
- 一个指针变量除去了变量名和 * ,便是指针指向的内容的类型。
举个例子:
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*p)(int, int) = &Add;//取出函数的地址放在函数指针p中
return 0;
}
那么,函数指针p的类型我们是如何创建的呢?
首先,p是一个指针,所以必须先与 * 结合,而( )的优先级高于 * ,所以我们要把 * 和p用括号括起来,让它们先结合。
指针p指向的内容,即函数Add的类型是int (int,int),所以函数指针p就变成了int(*p)(int,int)。
去掉变量名p后,便是该函数指针的变量类型int( * )(int,int)。
函数指针的使用
知道了如何创建函数指针,那么函数指针应该如何使用呢?
1.函数指针的赋值
对于数组来说,数组名和&数组名它们代表的意义不同,数组名代表的是数组首元素地址,而&数组名代表的是整个数组的地址。
但是对于函数来说,函数名和&函数名它们代表的意义却是相同的,它们都代表函数的地址(毕竟你也没有听说过函数有首元素这个说法吧)。
所以,当我们对函数指针赋值时可以赋值为&函数名,也可以赋值为函数名。
int(*p)(int, int) = &Add;
int(*p)(int, int) = Add;
2.通过函数指针调用函数
方法一:我们知道,函数指针存放的是函数的地址,那么我们将函数指针进行解引用操作,便能找到该函数了,于是就可以通过函数指针调用该函数。
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int(*p)(int, int) = &Add;
int ret = (*p)(a, b);//解引用找到该函数
printf("%d\n", ret);
return 0;
}
我们可以理解为, * 和&是两个相反的操作符,像正号(+)和负号(-)一样,一个 * 操作符可以抵消一个&操作符。
方法二:我们在函数指针赋值中说到,函数名和&函数名都代表函数的地址,我们可以赋值时直接赋值函数名,那么通过函数指针调用函数的时候我们就可以不用解引用操作符就能找到函数了。
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int(*p)(int, int) = Add;
int ret = p(a, b);//不用解引用
printf("%d\n", ret);
return 0;
}
函数指针数组
函数指针数组的定义
我们知道,数组是一个存放相同类型数据的空间,我们已经认识了指针数组,比如:
int* arr[10];//数组arr有10个元素,每个元素的类型是int*
那如果要将一系列相同类型的函数指针存放到一个数组中,那么这个数组就叫做函数指针数组,比如:
int(*pArr[10])(int, int);
//数组pArr有10个元素,每个元素的类型是int(*)(int,int)
函数指针数组的创建只需在函数指针创建的基础上加上[ ]即可。
比如,你要创建一个函数指针数组,这个数组中存放的函数指针的类型均为int(*)(int,int),如果你要创建一个函数指针为该类型,那么该函数指针的写法为int(*p)(int,int),现在你要创建一个存放该指针类型的数组,只需在变量名的后面加上[ ]即可,int(*pArr[10])(int,int)。
函数指针数组的使用 - 模拟计算器
函数指针数组一个很好的运用场景,就是计算机的模拟实现:
#include<stdio.h>
void menu()
{
printf("|-----------------------|\n");
printf("| 1.Add 2.Sub |\n");
printf("| 3.Mul 4.Div |\n");
printf("| 0.exit |\n");
printf("|-----------------------|\n");
}//菜单
double Add(double x, double y)
{
return x + y;
}//加法函数
double Sub(double x, double y)
{
return x - y;
}//减法函数
double Mul(double x, double y)
{
return x*y;
}//乘法函数
double Div(double x, double y)
{
return x / y;
}//除法函数
int main()
{
int input = 0;
double x = 0;//第一个操作数
double y = 0;//第二个操作数
double ret = 0;//运算结果
double(*pArr[])(double, double) = { 0, Add, Sub, Mul, Div };
//函数指针数组-转移表
int sz = sizeof(pArr) / sizeof(pArr[0]);//计算数组的大小
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
if (input == 0)
printf("退出程序\n");
else if (input > 0 && input < sz)
{
printf("请输入两个操作数:>");
scanf("%lf %lf", &x, &y);
ret = pArr[input](x, y);
printf("ret=%lf\n", ret);
}
else
printf("选择错误,请重新选择!\n");
} while (input);//当input不为0时循环继续
return 0;
}
代码中,函数指针数组存放的是一系列参数和返回类型相同的函数名,即函数指针。将0放在该函数指针数组的第一位是为了让用户输入的数字input与对应的函数指针下标相对应。
该代码若不使用函数指针数组,而选择使用一系列的switch分支语句当然也能达到想要的效果,但会使代码出现许多重复内容,而且当以后需要增加该计算机功能时又需要增加一个case语句,而使用函数指针数组,当你想要增加计算机功能时只需在数组中加入一个函数名即可。
指向函数指针数组的指针
既然存在函数指针数组,那么必然存在指向函数指针数组的指针。
int(*p)(int, int);
//函数指针
int(*pArr[5])(int, int);
//函数指针数组
int(*(*pa)[5])(int, int) = &pArr;
//指向函数指针数组的指针
那指向函数指针数组的指针的类型是如何写的呢?
所以pa就是一个指向函数指针数组的指针,该函数指针数组中每个元素类型是int(*)(int, int)。
回调函数
回调函数的定义
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
举个简单的例子:
#include<stdio.h>
void test1()
{
printf("hello\n");
}
void test2(void(*p)())
{
p(); //指针p被用来调用其所指向的函数
}
int main()
{
test2(test1);//将test1函数的地址传递给test2
return 0;
}
在该代码中test1函数不是由该函数的实现方直接调用,而是将其地址传递给test2函数,在test2函数中通过函数指针间接调用了test1函数,那么函数test1就被称为回调函数。
回调函数的使用 - qsort函数
其实回调函数并不是很难见到,在用于快速排序的库函数qsort中便运用了回调函数。
void qsort(void*base,size_t num,size_t width,int(*compare)(const void*e1,const void*e2));
qsort函数的第一个参数是待排序的内容的起始位置;第二个参数是从起始位置开始,待排序的元素个数;第三个参数是待排序的每个元素的大小,单位是字节;第四个参数是一个函数指针。qsort函数的返回类型为void。
qsort函数的第四个参数是一个函数指针,该函数指针指向的函数的两个参数的参数类型均为const void*,返回类型为int。当参数e1小于参数e2时返回小于0的数;当参数e1大于参数e2时返回大于0的数;当参数e1等于参数e2时返回0。
列如,我们要排一个整型数组:
#include<stdio.h>
int compare(const void* e1, const void* e2)
{
return *((int*)e1) - *((int*)e2);
}//自定义的比较函数
int main()
{
int arr[] = { 2, 5, 1, 8, 6, 10, 9, 3, 5, 4 };
int sz = sizeof(arr) / sizeof(arr[0]);//元素个数
qsort(arr, sz, 4, compare);//用qsort函数将arr数组排序
return 0;
}
最终arr数组将被排为升序。
注意:qsort函数默认将待排序的内容排为升序,如果我们要排为降序可将自定义的比较函数的两个形参的位置互换一下即可。
在qsort函数中我们传入了一个函数指针,最终qsort函数会在其内部通过该函数指针调用该函数,那么我们的这个自定义比较函数就被称为回调函数。