先看两段代码,有助于你接下来的学习:
int *arr1[10]; int (*arr2)[10];
arr1先与[]结合,说明是一个数组,数组里存放的int*类型的数据,,所以arr1是指针数组。
arr2与*结合,arr2是指针,指针指向的是一个int型的数组,所以arr2是数组指针。
再来看几段可能让你晕的代码;
// //指向指针数组的指针 int* (*ppstr)[4] = &pstr; // //函数指针 int (*pfun)(int, int) = Add; // //函数指针数组 int (*pfun[4])(int, int) = { Add }; // //指向函数指针数组的指针 int (* (*ppfun)[4])(int, int) = &pfun;
正文开始学习 ->
一、函数指针
函数指针对标数组指针,只不过它指向的是一个函数,也就是存放的是函数的地址。
下面是函数指针初始化的语法
int Add(int x , int y){ return x+y; } int (*pf)(int, int) = &Add;
//(*pf)是一个指针变量
// 剩下的int (int , int),int是函数的返回值,(int , int)是函数的参数类型
通过指针调用该函数。
int ans = (*pf)(1, 2);
因为()的优先级高于*,所以一定要给指针加(),那先跟()结合会发生什么呢???
VS直接编不过去,我们去掉*在运行发现ans等于3,咦!!,难道不用解引用就能调用函数,如果真是这样的话,那*3的确会报错,也就是说&函数名 == 函数名,我们打印它两的地址看一下是不是这样。
一摸一样,得出结论函数名 == &函数名。
拓展的想一下:我们以前是这样调用函数的Add(1,2),也就是说直接用地址加(),所以我们使用指针函数时也可以这样pf(1,2),不用加*。
二、函数指针的应用(解剖qsort)
举个实际的例子,qsort这个函数是用于排序的库函数,它可以排任意类型的,并且可以规定升序还是降序,很神奇吧!!!这里就用到了函数指针。
想象一下,如果你想对一个整形数组升序排序,假如我们使用冒泡排序算法,你会写这么一个函数假如又要降序排序呢,你是不是每次都要重新写一遍这个排序算法,但是你只是改变了这个代码的以小部分。
//升序 void bubble_sort(int arr[], int n) { int i, j, temp; for (i = 0; i < n - 1; i++) { for (j = 0; j < n - 1 - i; j++) { if (arr[j] > arr[j + 1]) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } //降序 void bubble_sort(int arr[], int n) { int i, j, temp; for (i = 0; i < n - 1; i++) { for (j = 0; j < n - 1 - i; j++) { if (arr[j] < arr[j + 1]) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
我们来解析一下qsort的函数原型
qsort的参数
void* base -> 待排序的起始地址
size_t num -> 待排序元素个数
size_t size -> 每个元素大小
int (*compar)(const void*,const void*) -> 函数指针(升序还是降序)
接下来我们手动模拟实现qsort来感受下函数指针的应用
void test() { int arr[] = { 2,7,34,14,87,4,23 }; int size = sizeof(arr) / sizeof(arr[0]); my_bubble_sort(arr, size, sizeof(arr[0]), cmp); for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } } int main() { test(); return 0; }
这我想对arr这个数组进行冒泡排序,排序完打印看看
void my_bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*)) { //num个数比较num-1趟 for (int i = 0; i < num - 1; i++) { //第i趟比较num-1-i次 for (int j = 0; j < num - 1 - i; j++) { //每次那j与j+1比较 if ( cmp( (char*)base + size * j, (char*)base + size * (j + 1) )>0 ) { Swap((char*)base + size * j, (char*)base + size * (j + 1), size); } } } }
函数的设计模仿了qsort,我们发现它跟一般冒泡排序不一样的地方就在这一段
这段代码就等价于上面的如果base[j] > base[j+1],交换两个元素,区别就在于这个是以1个字节为单位交换 ,内存图可能更方便理解。
你如果是让两个元素交换是这样int tmp = a; a = b; b = tmp;那就对应一次交换4个字节
如果这个交换的对象是结构体,大小为10个字节,那你就要重写你的排序代码了。
想一下如果每次交换一个字节,交换10次为一组,那是不是就实现了两个结构体变量的交换
对应上面代码
Swap函数
void Swap(void* a, void* b, size_t width) { for (int i = 0; i < width; i++) { char tmp = ((char*)a)[i]; ((char*)a)[i] = ((char*)b)[i]; ((char*)b)[i] = tmp; } }
最后我们再看一下cmp函数的实现
int cmp(const void* a, const void* b) { return *(int*)a - *(int*) b; }
当一个函数 funa 的参数是一个函数的地址 funb ,funa通过funb的地址调用 funb,那funa就叫做回调函数。
上面qsort就是一个回调函数,它传入了cmp函数的地址,在qsort里面调用了cmp函数,而cmp函数的作用是决定升序还是降序,所以我们就可以自己决定升降序而不用修改全部的代码了。
三、函数指针数组
parr3[10]说明是一个数组,数组里存放的类型是函数指针类型,没毛病。但编译器直接报错了呀
意思上没问题,但语法是这样的
int (*parr3[10])(); //函数指针数组
四、函数指针数组应用: 转移表
函数指针数组可以用于构建转移表,即根据输入的参数值选择不同的函数进行调用。这种技术通常用于状态机、解析器、编译器等需要根据不同的输入进行不同操作的应用程序中。
例如:计算器
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; } int main() { int (*Calc[4])() = { Add, Sub, Mul, Div }; int Add_ans = Calc[0](1, 2); printf("Add_ans=%d\n", Add_ans); int Sub_ans = Calc[1](1, 2); printf("Add_ans=%d\n", Sub_ans); return 0; }
五、指向函数指针数组的指针
int (* (*ppfarr)[10] )();
了解即可,很少使用
六、函数指针笔试题
① 🐒 -> offer
//解释下面代码含义 (*(void (*)())0)();
分析:
(* ( void (*)() )0 )();
void (*)() 这是个函数指针类型
( void (*)() ) 0 将 0 地址强制转化为了函数指针类型 地址就是一个数嘛
(* ----)() 调用该函数
所以这是调用0地址处的函数
② 🐒 -> offer
//解释下面代码含义 void (*signal(int , void(*)(int)))(int);
分析:
void (* signal( int , void(*)(int) ) )(int);
signal( int , void(*)(int) ) 函数声明 声明了函数名和参数
void (* ----)(int) 函数指针 去掉函数名和参数就是返回值嘛
故这是signal的函数声明,该函数返回值为函数指针,参数为int和void(*)(int)
这里可以利用typedef简化代码
typedef void (*pf_t)(int); //把void (*)(int)重命名为pf_t //简化后的代码 pf_t *signal(int, pf_t);
① 🐒 -> offer
程序输出结果:
void fun(char ps[]) { ps = "bbb"; } int main() { char s[] = { "aaa" }; fun(s); printf(s); return 0; }