*欢迎来到博主的专栏——C语言进阶指南
博主id
文章目录
- 函数指针
- 函数指针的应用——回调函数
- 函数指针数组
函数指针
函数也有地址(函数在调用的时候会占用内存空间,所以函数是有地址的),因此我们也可以用一个指针指向函数
1
函数指针的声明
type (*funcpointer)(element type……)
type是指向的函数的返回值,*与指针进行结合,声明这个标识符是指针而不是函数,element type是指针指向的函数的原型参数
函数也有地址,因此我们也可以用一个指针指向函数,数组名是数组首元素的地址,类似的,函数名也代表着函数的地址,因此函数的地址可以用函数名或者&函数名来代表
int add(int x, int y)
{
return x+y;
}
int main()
{
int(*pf)(int, int)=add;
return 0;
}
pf是一个指向函数add的函数指针。通过对指针进行解引用,可以对函数进行调用
(*pf)(10,20);
函数指针的应用——回调函数
如果函数指针的作用仅仅是为了调用某个函数的话就有点脱裤子放屁的感觉了,事实上函数指针可以指向任何一个符合类型的函数。比如上述的pf可以指向任何一个返回类型为int,函数参数为(int,int)的函数。
由于这个特性,函数指针常常用于回调函数当中。
回调函数是函数参数有函数指针类型,且在程序的执行过程会调用传递的函数指针,根据传递的函数指针的不同,实现不同的效果的函数
回调函数的功能比一般的函数更加强大,这是因为回调函数能够用函数指针来完成自定义部分的内容(相当于是回调函数将部分的功能交由程序员自己定义)
以库函数qsort为例,qsort是一个在<stdlib.h>的库函数,他的功能是实现任何数据类型的数据的快速排序。
博主在先前文章中的自定义的快速排序和冒泡排序的函数都有一个不足之处,那就是只能被设定为排序固定的一中数据类型的数据
void bubble_sort(int* arr, int sz);
(用于排序int类型的冒泡排序)
void quick_sort(int arr[], int low,int high);
(用于排序int类型的快速排序)
如果想要程序排序float类型的数据,就需要在函数原型参数上修改数据类型
void bubble_sort(float* arr, int sz);
void quick_sort(int arr[], int low,int high);
而库函数qsort能够排序任何的数据类型,这就是回调函数功能更加强大的原因,首先来看看qsort的函数原型
函数原型参数base,base是指向待排序的数据的起始地址的void*指针。
为什么使用void*呢?因为qsort被设计成可以接收任何数据类型的函数,所以待排序的数据可以是char类型,int类型,float类型,它们对应的指针类型是char*,int*,float*。如果设置char*作为base的类型,那么它无法对其他指针类型进行操作
其他指针类型也是同理,只有void*被允许接收任意类型的指针参数
num是size_t类型的数据,是待排序的数据的个数
wideth是size_t类型的数据,是单个待排序的数据的字节数
compare是一个函数指针,这个函数指针指向一个返回值为int类型,参数类型为(void*,void*)的函数。
qsort函数的原理如下,第一个参数上传需要排序的数据的首元素地址,第二个参数上传数据的个数,第三个参数上传单个数据的字节。第四个函数传递的是自定义函数的函数指针,也是qosrt当中最主要的部分。
以排序int类型的数据为例,我们要定义compare函数是能够比较int类型数据的函数,根据MSDN中关于qsort的使用指南所说。
compare的第一个元素大于第二个元素。返回>0的值。
第一个元素等于第二个元素,返回0
第一个元素小于第二个元素,返回<0的值。
再根据函数指针的类型必须符合qsort中对于compare的函数指针的类型的要求是int*(void*,void*)。可以得出自定义compare函数的函数原型是
int compare(void*elem1,void*elem2);
为了使conpare函数能够实现比较int类型的数据。需要将elem1和elem2的数据类型改成int*。
这是因为void*类型的指针虽然可以接收任何类型的指针,但是不能对void*类型的指针进行操作,比如解引用,指针的加减算术运算等,解决方法就是将void*类型的指针根据需要,强制类型转换成其他类型的指针,再对指针进行操作
那么比较int的campare函数可以写成
int int_compare(void* elem1, void* elem2)
{
return *(int*)elem1 - *(int*)elem2;
}
这样就满足了第一个元素大与第二个元素返回>0的值的效果。
如果想要一个比较float类型的compare函数可以写成
int float_compare(void* elem1, void* elem2)
{
if (*(float*)elem1 > *(float*)elem2)
return 1;
else if (*(float*)elem1 == *(float*)elem2)
return 0;
else return -1;
}
这里不直接用elem1-elem2作为函数返回值。是因为conpare被qsort指定为一个返回类型为int类型的函数,两个浮点数相减的结果被转换成int类型可能会导致数据丢失,造成比较结果出现误差
前面提到了,函数名本身可以作为函数指针使用,那么使用qsort函数的例子为
int int_compare(void* elem1, void* elem2)
{
return *(int*)elem1 - *(int*)elem2;
}
int main()
{
int arr[10] = { 22,55,33,77,44,11,99,88,66,10 };
qsort(arr, sizeof(arr) / sizeof(arr[0]), //qsort调用
sizeof(arr[0]), int_compare);//qosrt调用
return 0;
}
运行发现,arr被排序成升序了。
回调函数qsort的原理如下:
(1)qsort函数中只定义了排序部分,并没有定义比较元素的部分,比较元素的函数需要使用者提供,qsort根据使用者定义的比较函数来进行比较元素大小(通过函数指针来调用,因此称为回调函数)
(2)qsort中需要传入元素的个数和大小,是因为qsort接收的指针是void的,而void的指针不能进行加减算术运算,需要使用者提示元素的大小和个数来使指针定位元素的位置。
函数指针数组
函数指针数组是一个数组,数组中的元素都是函数指针
函数指针数组的声明形式如下:
type *iden[](element type)
和指针数组的声明一致,先让标识符与[]结合形成数组,再声明数组元素的类型(type*(element type))
函数指针数组的主要作用是将一些类型一致的函数的指针集合在一起,可以让程序变得简洁。
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;
}
假设有这么4个函数,如果我们想要根据不同的输入来调用不同的函数的话,可以用switch语句。
int main()
{
int x = 0, y = 0;
int input = 0;
while (1)
{
printf("请选择:");
scanf("%d", &input);
if (input > 0 && input <= 4)
{
printf("请输入计算的两个数:");
scanf("%d%d", &x, &y);
}
switch (input)
{
case 1:
add(x, y);
break;
case 2:
sub(x, y);
break;
case 3:
mul(x, y);
break;
case 4:
div(x, y);
break;
case 0:
return 0;
default:
printf("输入错误,请重试\n");
}
}
}
如果创建一个函数指针数组
int (*pf[5])(int,int)={NULL,add,sub,div,mul};
int main()
{
int x=0, y=0,input=0;
int (*pf[5])(int,int) = {NULL,add,sub,mul,div};
do
{
scanf("%d", &input);
if (input > 0 && input <= 4)
{
printf("请输入两个数");
scanf("%d%d", &x, &y);
pf[input](x, y);//使用函数指针数组来调用函数
}
else if (!input)
return 0;
else
printf("输入错误,请重试\n");
} while (input);
}
从这么多的语句。
变成了
如果需要调用更多的函数的话,函数指针数组的作用会更加明显。