文章目录
1.回调函数的定义2.回调函数的eg13.回调函数的eg24.同步调用和异步调用(注册回调函数)5.回调函数eg3:简单版5.回调函数eg4:简单版
1.回调函数的定义
如果参数是一个函数指针,则调用者可以传递给一个函数的地址给实现者,实现者去调用它,这称之为回调函数Callback Function。eg:C语言中qsort(3)和bsearch(3)。一个回调函数eg:void func(void (*f)(void *), void *p);
2.回调函数的eg1
下面的代码实现了一个repeat_three_times函数,可以把调用者传来的任何回调函数连续执行三次(未编译)
/*para_callback
.h
*/
/*
1.callback_t就是声明一个函数指针,入参类型是void
*,callback_t的函数类型也是void
*
2.上面这句话不理解的话,就去看c primer plus有关函数指针的章节
*/
typedef void
(*callback_t
)(void
*);
extern void repeat_three_times
(callback_t
, void
*);
/*para_callback
.c
*/
/*
1.实现者是repeat_three_times,调用者是say_hello和count_numbers
2.参数类型都是由实现者规定,对于实现者来说,f就是一个void
*的指针,实现者
只负责将这个指针转交给回调函数,而不关心它到底指向什么数据类型
*/
void repeat_three_times
(callback_t f
, void
*para
)
{
f
(para
);
f
(para
);
f
(para
);
}
/*main
.c
*/
/*
1.回调函数的参数按什么类型解释由调用者规定
2.调用者知道自己传的参数是char
*型的,所以在回调函数中,就应该知道参数要转换成char
*型来解释
*/
void say_hello
(void
*str)/**/
{
printf
("Hello %s\n",(const char
*)str);/* “Gays”字符串的类型是char
*的,参数要转换成char
*型
*/
}
void count_numbers
(void
*num
)
{
int i
;
for (i
=1;i
<=(int)num
;i
++)
printf
("%d", i
);
putchar
('\n');
}
int main
(void
)
{
repeat_three_times
(say_hello
,"Guys");
repeat_three_times
(count_numbers
, (void
*)4);
return 0;
}
3.回调函数的eg2
回调函数的一个典型应用就是实现类似C++的泛型算法(Generics Algorithm)下面实现的max函数可以在任意一组对象中找出最大值,可以是一组int、一组char或者一组结构体,但是实现者并不知道怎样去比较两个对象的大小,调用者需要提供一个做比较操作的回调函数。
/*generics
.h
*/
typedef
int (*cmp_t
)(void
*, void
*);
extern void
*max(void
*data
[], int num
, cmp_t
cmp);
/**generics
.c
/
/*
1.max函数之所以能对一组任意类型的对象进行操作,关键在于传给
max的是指向对象的指针所构成的
数组,而不是对象本身所构成的数组,这样
max不必关心对象到底是什么类型,只需转给比较函数
cmp,
然后根据比较结果做相应操作即可
2.cmp是调用者提供的回调函数,调用者当然知道对象是什么类型以及如何比较。
*/
void
*max(void
*data
[],int num
, cmp_t
cmp)
{
int i
;
void
*temp
=data
[0];
for (i
=1;i
<num
;i
++)
{
if (cmp(temp
,data
[i
])<0)
temp
=data
[i
];
}
return temp
;
}
/*main
.c
*/
typedef struct
{
const char
*name
;
int score
;
}student_t
;
/*
1.cmp_student是做比较的回调函数
*/
int cmp_student
(void
*a
, void
*b
)
{
if (((student_t
*)a
->score
)>((student_t
*)b
->score
))
return 1;
else if (((student_t
*)a
->score
)==((student_t
*)b
->score
))
return 0;
else
return -1;
}
int main
(void
)
{
student_t
list[4]={{"Tom",68},{"Jerry",72},{"Moby",60},{"Kirby",89}};
student_t
*plist
[4]={&list[0],&list[1],&list[2],&list[3]};
student_t
*pmax
=max((void
**)plist
,4,cmp_student
);
printf
("%s gets the highest score %d\n",pmax
->name
,pmax
->score
);
return 0;
}
4.同步调用和异步调用(注册回调函数)
以上举例eg1,eg2的回调函数是被同步调用的,调用者调用max函数, max函数则调用cmp函数,相当于调用者间接调了自己提供的回调函数。在实际系统中,异步调用也是回调函数的一种典型用法,调用者首先将回调函数传给实现者,实现者记住这个函数,这称为注册一个回调函数,然后当某个事件发生时实现者再调用先前注册的函数。 比如sigaction(2)注册一个信号处理函数,当信号产生时由系统调用该函数进行处理,再比如pthread_create(3)注册一个线程函数,当发生调度时系统切换到新注册的线程函数中运行,在GUI编程中异步回调函数更是有普遍的应用,例如为某个按钮注册一个回调函数,当用户点击按钮时调用它。以下是一个异步调用的代码框架。
/* registry
.h
*/
typedef void
(*registry_t
)(void
);
extern void register_func
(registry_t
);
/* registry
.c
*/
/*
1.注意注册的回调函数func是全局的,存储在全局存储区,你当作他已经实现了
2.别的
.c文件include这个registry
.h头文件,可以在外面使用 register_func函数,而不需要知道
注册函数func是如何实现的
*/
static registry_t func
;
void register_func
(registry_t f
)
{
func
= f
;
}
static void on_some_event
(void
)
{
...
func
();
...
}
既然参数可以是函数指针,返回值同样也可以是函数指针,因此可以有func()();这样的调用。返回函数的函数在C语言中很少见,在一些函数式编程语言(例如LISP)中则很常见,基本思想是把函数也当作一种数据来操作,输入、输出参与运算,操作函数的函数称为高阶函数(High-order Function) 。
5.回调函数eg3:简单版
int add_ret
() ;
int add
(int a
, int b
, int (*add_value
)())
{
return (*add_value
)(a
,b
);
}
int main
(void
)
{
int sum = add
(3,4,add_ret
);
printf
("sum:%d\n",sum);
return 0 ;
}
int add_ret
(int a
, int b
)
{
return a
+b
我们把函数的指针(地址),这里也就是add_ret,作为参数int add(int a , int b , int (*add_value)()) , 这里的参数就是int(*add_value)() , 这个名字可以随便取,但是要符合C语言的命名规范。当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。我们看到add函数内部,return (*add_value)(a,b) ; 这个(*add_value)(a,b)相当于对指针进行了简引用,我们在main函数中,传入具体要实现功能的函数,add_ret,这个函数很简单,就是实现两数相加并返回,这里刚刚好,简引用,相当于取出指针返回地址里的值,这个值就是return a+b,也就是我们传入a和b两数相加的结果。回调函数究竟有什么作用呢? 说到这里,就有了用户和开发者之间的概念,比方说,刚刚说的add()这个函数,假设一下,用户是实现add_value这个函数,而开发者是实现add_value这个函数,用户做的工作不多,就是想要通过开发者实现的这么一个接口,然后在函数中通过调用开发者实现的这个接口的返回值,然后来实现我们的功能。这个开发者角色就很多了,可以是自己公司的核心开发人物,也可以是别的工作的外包商的人物,这时候,他作为一个开发者的角色完完全全可以将add_value实现的add_ret这个函数封装起来并且加密,然后扔一个.so或者.a给用户,那么用户就看不到具体add_ret的实现内容,用户只需要开发者给他提供一个.h和.so即可,这样,作为开发者,他就将他实现的函数功能给保密了。
5.回调函数eg4:简单版
接下来,我们用Linux来演示下这个结果:
我们在目录下创建三个文件,main.c,vendor.c,vendor.h
Main.c是用户开发的
Vendor.c和vendor.h是开发者实现的。
在main.c中,代码如下:
int add
(int a
, int b
, int (*add_value
)())
{
return (*add_value
)(a
,b
);
}
int main
(void
)
{
int sum = add
(3,4,add_ret
);
printf
("sum:%d\n",sum);
return 0 ;
}
vendor.c,代码如下:
int add_ret
(int a
, int b
)
{
return a
+b
;
}
vendor.h,代码如下:
int add_ret
(int a
, int b
) ;
接下来,我们制作一个动态链接库,最终开发者把vendor.c的内容封起来,把vendor.h提供给用户使用。
在linux下制作动态链接库,将vendor
.c和vendor
.h打包成一个动态链接库
先明白以下几个命令是什么意思:
生成动态库
:
gcc
-shared
-fPIC dvendor
.c
-o libvendor
.so
-shared
: 生成动态库
;
-fPIC
: 生成与位置无关代码
;
-o
:指定生成的目标文件
;
使用动态库
:
gcc main
.c
-L
. –lvendor
-o main
-L
: 指定库的路径
(编译时
); 不指定就使用默认路径
(/usr
/lib
/lib
)
-lvendor
: 指定需要动态链接的库是谁
;
代码运行时需要加载动态库
:
./main 加载动态库
(默认加载路径
:/usr
/lib
/lib
./ ...)
./main
我们将编译动态库生成的libvendor
.so拷贝到
/usr
/lib后,现在就不需要vendor
.c了,此时我们将vendor
.c移除,也可以正常的编译并且执行main函数的结果,这就是回调函数的作用之一。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
参考: 《Linux编程一站式学习》第24章-第5节 https://www.cnblogs.com/zhonglongbo/p/8410464.html https://www.cnblogs.com/zzb-Dream-90Time/p/6860004.html