函数指针和回调函数
函数指针和回调函数
我们知道,在C语言中定义一个变量就要开辟相应的空间,通常都在栈上存放。那么我们的函数呢?乃至我们的整个程序呢?这就要说到虚拟地址空间了。
虚拟地址空间
那么什么时虚拟地址空间呢?虚拟地址空间就像是一个沙箱,每个程序都单独的在各自的沙箱中运行,在我们的32位操作系统下每个程序的虚拟地址空间是4GB,那么就会有人说了,我的电脑就只有4GB的内存,咋能跑既能听歌又能写代呢?因为整是虚拟地址空间,他是要经过页表进行内存映射才会映射到我们真正的物理内存中,而在映射的过程中,只映射有数据的部分,所以说起来是4GB但是在物理内存上就可能占用个不到100M,可以先这样理解,到后面我们可以进行深入讨论。
下面给出我们虚拟内存空间的大体布局图:
大家看到没,在我们的虚拟内存空间中,地址较为低的部分就是我们的代码区,我们程序就是在这里存储,所以说我们的代码在运行时也是会被加载到内存里,既然在内存中,那么我们的代码也是有地址的。
函数指针
上面已经说过,我们的代码是在内存中存储的,代码也是有地址的,那么我们的函数也是代码,那么我们可以对函数取地址吗?答案是肯定的。
我们可以写一段小程序来测试一下:
#include <stdio.h>
int Fun1(){
}
int Fun2(){
}
int main(){
int aa = 0;
int bb = 0;
printf("&aa = %p\n",&aa);
printf("&bb = %p\n",&bb);
printf("&Fun1 = %p\n", Fun1); //注意这里没有取地址符
printf("&Fun2 = %p\n", &Fun2);
return 0;
}
运行结果为:
&aa = 0x7ffe87521f80
&bb = 0x7ffe87521f84
&Fun1 = 0x55e8090476aa
&Fun2 = 0x55e8090476b1
是不是Fun1和Fun2的地址比变量aa和bb地址低了好多呢?是不是从侧边也印证了我们上面的图呢?也同时印证了我们的函数是可以取地址的,既然能够取地址,那么这个地址就可以被存起来,就成为了我们的函数指针。
我们的函数名就是函数的地址,对我们的函数名取地址也是函数的地址,那么我们该用那个呢?当然是那和简单用那个了。
函数指针就是指向函数的指针那么我们如何去定义呢?
int (*p)(int , int );
先是返回值类型,然后就是我们的指针名,最后就是指向函数的参数类型;如何理解呢,星号先和p结合形成一个指针所以就是一个指针变量,后面的是参数类型,最前面是指向函数的返回值类型。
那么这个如何理解呢?
int *p(int , int );
这个有木有很像我们定义函数呢?其实这就是一个返回值的 int* 参数为int int的函数,所以我们在用的时候一定要小心。
那么在日常写代码中怎么用呢?
我们可以看一下这个简易的计算器:
#include <stdio.h>
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 select , op1 , op2 ;
int result = 0;
int (*arr[5])(int x, int y) = {0 , add , sub , mul , div}; //函数指针数组
printf(" 请输入你的选项 :\n");
printf("**** 1 . 加法 2. 减法 ****\n");
printf("**** 3 . 加法 4. 减法 ****\n");
scanf("%d", &select);
printf("请输入两个操作数: ");
scanf("%d %d", &op1 , &op2);
result = (*arr[select])(op1, op2);
printf("结果是 : %d\n", result);
return 0;
}
如果用一般的方法是不是要一堆的if else,或者使用switch ,但是用函数指针的方式就简便了好多呢?
回调函数
我们的函数指针是不是一个指针变量?那么它能不能作为一个函数的参数呢?答案时是肯定的。在我们的操作系统源码,以及在linux中许多系统调用中,有许多这样的函数。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当 这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调 用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
我们C语言库函数中就有一个典型的案例:qsort 函数
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
该函数的作用是对所给定的缓冲区的元素进行排序,元素个数为num ,元素大小为width ,最后一个是一个 函数指针,指向你提供的一个比较函数。
对于 void* 类型的理解 , void* 是一个空指针类型 ,理论上可以接收任何指针,但是不能够直接使用,需要强转后才可以使用。
我们可以用qsort来对一个整型数组来进行排序:
#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void *x ,const void *y){
int *_x_ = (int *)x;
int *_y_ = (int *)y;
if(*_x_ > *_y_){
return 1;
}else if(*_x_ > *_y_){
return -1;
}else{
return 0;
}
}
void PrintArr(const int *Arr , int len){
int i = 0;
for( ; i<len ; i++){
printf("%d ", Arr[i]);
}
printf("\n");
}
int main(){
int Arr[10] = {11,22,55,66,44,22,33,77,88,11};
printf("Before Qsort:\n");
PrintArr(Arr,sizeof(Arr)/sizeof(Arr[0]));
qsort(Arr, 10 ,4, cmp_int);
printf("After Qsort:\n");
PrintArr(Arr,sizeof(Arr)/sizeof(Arr[0]));
exit(0);
}
这样我们就利用库函数qsort实现了对一个数组的排序,是不是很简单呢,只需要按照要求写一个判断两个数字大小的函数传入到我们的qsort中就可以了。
总结
以上就是我们今天所讨论的函数指针和回调函数,这哪里只是让大家认识一下这两个东西,以后大家在遇到或者是在用的时候不至于手忙脚乱,希望对大家有所帮助。
由于本人才疏学浅,若有疏忽还望不吝赐教。
@YeLing0119