c语言 回调函数 好处,C语言陷阱与技巧25节,常说的“回调函数”是什么?为何要用它?...

本文深入探讨了C语言中的回调函数,展示了如何通过函数指针实现代码选择和动态调用,以及如何传递参数,包括使用指针和结构体的技巧。重点突出了函数指针在提高代码灵活性和效率中的作用,并揭示了C语言中函数指针的底层原理。
摘要由CSDN通过智能技术生成

上一节主要讨论了C语言中的函数指针在“运行时”代码选择中的应用,这其实是一个小技巧,仅需在需要切换代码的时候重新确定函数指针的指向,之后的代码就几乎不用动了。

e217465dc95bcd5f489099138bd1f02e.png

粗略来说,只需一次 if 判断,就可以将所有C语言代码涉及到的代码切换完成。这样的代码风格显然有利于程序员维护,也能提升C语言程序的运行效率。

事实上,C语言函数指针的用途远不止于此

在本专栏更早的章节中,我们曾讨论C语言函数的参数也可以是指针型的,“指针型”中的指针当然包括函数指针,也就是说,C语言函数的参数可以也是一个“函数”,只不过这个“函数”是通过函数指针传递的。请看下面这个例子:

#include void myprint(){printf("myprint\n");}void fun( void(*f)() ){ f();}int main(){ fun(&myprint);return0;}

5e16b725841823249865f4f53b8f6787.png

从上面这段C语言代码中,可以看出 fun() 函数接收一个参数,该参数是一个函数指针,指向返回值为空的函数。在 main() 中调用 fun() 时,将 myprint() 传递给它了。编译并执行这段C语言代码,得到如下输出:

# gcc t.c# ./a.out myprint在 fun() 中调用的 myprint() 就是所谓的“回调函数”。显然,回调函数就是一个通过函数指针调用的函数,回调函数不是由实现方直接调用,而是通过函数指针,在特定条件发生时,由另外一方调用,用于对该条件响应。

上面的例子很简单,fun() 无条件调用 f 了,但是应明白,如果需要的话,程序员能够轻易为 f 的添加调用条件。

容易产生迷惑的点

在上述例子中,main() 函数中的 fun() 在接收函数指针时,fun(&myprint) 中的 & 符号可以不写。而且有些程序员在调用 f 时,为了显式的说明它是一个函数指针,常常写作:

(*f)();但是也有程序员像本例一样,将函数指针当作普通函数使用:

f();这似乎很不可思议,但是这些写法都可以正常工作,怎么回事呢?C语言的函数指针怎么会如此混乱不堪呢?

b5519aa16a8da85a91f48eff4f5559c2.png

其实这主要是因为在C语言中,函数名,&函数名,以及 * 函数名在内存中的值是相等的,编写下面这样的C语言代码:

printf("%p, %p, %p\n", &myprint, myprint, *myprint);编译并执行,得到如下输出:

0x40057d, 0x40057d, 0x40057d显然,三者是相等的。所以究竟使用何种方式,主要取决于程序员自己的习惯了。

回调函数的意义

从上例可以看出,fun() 并不关心自己接收到的函数 f 以何种方式提供何种功能,这样一来,fun() 的一些功能就很灵活了。现在设想这种情况:

fun() 在处理数据时,需要用到排序算法,但是 fun() 的主要功能并不是排序,所以不打算在 fun() 中嵌入排序相关的C语言代码。

在这种情况下,回调函数就比较有用了,程序员可以在别处实现排序算法函数,再将该函数的地址以函数指针参数的形式传递给 fun() 就可以了。

5691c7d74c7d3d810751683344f5bd26.png

程序员甚至可以在别处实现若干个不同的排序算法函数(如冒泡排序、快速排序、shell排序、shake排序等等),根据实际情况,决定使用何种排序。

为什么不直接调用函数呢?感到迷惑的读者可以再看看上一节。

回调还可用于通知机制。例如,有时要在A程序中设置一个计时器,每到一定时间,A程序会得到相应的通知,但通知机制的实现者对A程序一无所知。那么,就需一个具有特定原型的函数指针进行回调,通知A程序事件已经发生。

回调函数的参数

上面的例子演示的 myprint() 没有参数,如果需要给回调函数传递参数,该怎么实现呢?请看下面的C语言代码:

void myprint(int a, double b){printf("myprint recieve nums: %d, %0.2f\n", a, b);}void fun( void(*f)(), int a, double b ){ f(a, b);}显然,可以在 fun() 中指定传递给 myprint() 的参数。如果需要传递给 myprint() 的参数比较多,则可以使用本专栏第21节提到的小技巧:借助指针和结构体:

struct param{char a;int b;double c; ...char str[128];};void myprint(void *data){struct param *p = (struct param*)data;printf("myprint recieve nums: %d, %0.2f...\n", p->a, p->b);}void fun( void(*f)(), void *data ){ f(data);}

e9675964de3de0b2e4bc08f4f31ac7b8.png

显然,借助于C语言的指针和结构体语法,程序员可以仅使用一个参数,传递任意多的参数。事实上,一些比较成熟的库函数也是这么干的,例如:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);小结

本节主要讨论了C语言中的回调函数,应该能够发现,其实回调函数也是借助于C语言的指针语法实现的。

另外,在文章最后还讨论了回调函数传递参数的方法,可以看出,借助指针和结构体语法,程序员能够轻易的传递任意多的复杂参数。归根结底,这些重要内容都离不开C语言中的指针,所以说指针是C语言的灵魂一点也不夸张。

384bb8967f7b7c1d06ff5cfe31a06864.png

欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。

举报/反馈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值