前言:
今天我们继续学习指针的进阶,本篇内容主要围绕函数指针与回调函数进行,并且会模拟实现库函数qsort来深入理解回调函数。
你可以解释下面的代码么?
//代码1
(*( void(*)() ) 0) ();
//代码2
void(* signal(int , void(*)(int) )) (int);
一、函数指针
函数指针是指针,本质是指针,比如:
void (*pfun1)(); void* pfun2();
我们知道函数指针本质是指针,因此pfun应首先与*结合变为指针。
明显pfun1为函数指针,其指向的函数没有参数,返回值类型为void,而pfun2是返回值类型为void*的函数。
了解了函数指针的概念我们看文章开头的两端复杂代码。
//代码1 (*(void(*)()) 0) (); //代码2 void(*signal(int, void(*)(int))) (int);
代码1分析:
1、将0强制类型转换为void(*)()类型的函数指针。
2、调用0地址处的函数。
代码2分析:
signal是一个参数类型为整型int,函数指针类型void(*)(int),返回类型为函数指针类型void(*)(int)的函数。
但是这样的写法太过复杂,我们可以利用typedef重定义。如下:
typedef void(*pfun_t)(int); pfun_t signal(int, pfun_t);
二、函数指针数组
函数指针数组是数组,本质是数组。
因为[]的优先级高于*,那么只需要让变量(数组名)先与[]结合变成数组,其数组内容为函数指针即可。
int (*parr[10])();//数组中元素的数据类型为int(*)()
下面给出一个函数指针应用实例:
int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return a / b; } int main() { int x, y; int input = 1; int ret = 0; int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表 while (input) { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf("*************************\n"); printf("请选择:"); scanf("%d", &input); if ((input <= 4 && input >= 1)) { printf("输入操作数:"); scanf("%d %d", &x, &y); ret = (*p[input])(x, y);//此处*可省略 } else printf("输入有误\n"); printf("ret = %d\n", ret); } return 0; }
在应用时,利用函数指针运行函数时,函数指针前的解引用操作符*可省略,此处主要是方便读者理解,对于编译器而言,此处的解引用符号无意义。
三、指向函数指针数组的指针
指向函数指针数组的指针是指针,本质是指针,它指向一个数组,数组中的元素都是函数指针。
为了更好的区分,我把函数指针,函数指针数组和指向函数指针数组的指针在这里统一在进行区分讲解。
int (*pf)(int, int); //函数指针 int (*pfArr[])(int, int); //函数指针数组 int (*(*p)[])(int, int) = &pfArr;//指向函数指针数组的指针
其实我们只需要知道我们要定义的是指针还是数组这一基本原则就可以很好的区分,搞清楚变量是先与*结合还是先与[]结合。
对于函数指针来说,pf与*结合就决定了他是指针,指针类型就是去掉pf,即int(*)(int ,int),指针指向的就去掉*p,是int (int,int)一个参数为int,int,返回值为int的函数。
对于函数指针数组来说,pfArr先与[]结合就决定了他是数组,数组中元素的数据类型为去掉pfArr[],即int (*)(int, int)。
对于指向函数指针数组的指针来说,p先与*结合就决定了他是指针,指针类型就是去掉p,即int (*(*)[])(int, int),指针指向的就去掉*p,是int (*[])(int, int)函数指针数组。
四、回调函数(模拟实现库函数qsort)
回调函数就是一个通过函数指针调用的函数。
通俗的讲回调函数是将自己的地址作为参数传递给另一个函数,由这个函数调用使用的,回调函数不由该函数实现方直接调用,而是间接的需要另外一个函数调用使用。
接下来我会通过对库函数qsort的模拟实现讲解回调函数。
(一)void*类型指针的作用
在模拟实现库函数qsort之前,我们先来讲解以下void*的作用。
void*类型的指针不能直接进行解引用操作,也不能直接进行指针运算。
但是void*类型的指针可以接收任意类型的地址,所以它广泛应用于函数参数。
比如:
int main() { int a = 10; int* pa = &a; char* pc = &a;//err void* pd = &a;//ok pd++; //err *pd; //err }
(二)模拟实现库函数qsort()
模拟实现qsort的目的是为了更好的理解回调函数,所以这里模拟的qsort排序方法我们使用冒泡排序。
首先要实现qsort我们需要了解qsort的返回值,参数以及功能等信息,我们进入cplusplus.com - The C++ Resources Network查询qsort函数。
根据查询的内容,我们了解到,该函数有四个参数,分别为base、num、size、compar,大致的意思我已经标在图中,这里我着重说一下compar,以及回调函数为何要利用qsort来讲解。
qsort的优点在于,它可以排序任意数据类型的数据,可以是整型也可以是字符型还可以是结构体等等,而它如此灵活的关键就在与它利用的回调函数,它将排序依据交给使用者,利用函数指针compar传参,并利用其余三个参数的灵活配合就能在不知道待排序元素数据类型的情况下实现排序功能。
完整代码如下:
int int_cmp(const void* p1, const void* p2)//排序依据 { return (*(int*)p1 - *(int*)p2); } void _swap(void* p1, void* p2, int size)//交换元素 { int i = 0; for (i = 0; i < size; i++) { char tmp = *((char*)p1 + i); *((char*)p1 + i) = *((char*)p2 + i); *((char*)p2 + i) = tmp; } } void bubble(void* base, int count, int size, int(*cmp)(void*, void*))//冒泡函数主体 { int i = 0; int j = 0; for (i = 0; i < count - 1; i++) { for (j = 0; j < count - i - 1; j++) { if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0) { _swap((char*)base + j * size, (char*)base + (j + 1) * size, size); } } } } int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; //char *arr[] = {"aaaa","dddd","cccc","bbbb"}; int i = 0; bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp); for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
在这里再理解一下回调函数的概念:
回调函数就是一个通过函数指针调用的函数。
通俗的讲回调函数是将自己的地址作为参数传递给另一个函数,由这个函数调用使用的,回调函数不由该函数实现方直接调用,而是间接的需要另外一个函数调用使用。
需要注意的是我们需要将base强制转换为char*,令该指针加减整数的步长设为1,这是qsort可以不考虑数据类型排序的原理。
第二部分进阶指针就讲到这,下一篇内容我会引入笔试题实战为大家带来更加优质的内容,关注博主不迷路🔥🔥🔥