回调函数

mac2024-08-04  59

文章目录

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*/ #ifndef PARA_CALLBACK_H #define 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*/ #include "para_callback.h" /* 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*/ #include <stdio.h> #include "para_callback.h" /* 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*/ #ifndef GENERICS_H #define GENERICS_H typedef int (*cmp_t)(void*, void*); extern void *max(void *data[], int num, cmp_t cmp); /**generics.c/ #include "generics.h" /* 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*/ #include <stdio.h> #include "generics.h" 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 */ #ifndef REGISTRY_H #define REGISTRY_H typedef void (*registry_t)(void); extern void register_func(registry_t); /* registry.c */ #include <unistd.h> #include "registry.h" /* 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:简单版

#include <stdio.h> 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中,代码如下: #include <stdio.h> #include "vendor.h" 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,代码如下: #include "vendor.h" int add_ret(int a , int b) { return a+b ; } vendor.h,代码如下: #ifndef __VENDOR_H #define __VENDOR_H int add_ret(int a, int b) ; #endif 接下来,我们制作一个动态链接库,最终开发者把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

最新回复(0)