上一节主要讨论了C语言中的函数指针在“运行时”代码选择中的应用,这其实是一个小技巧,仅需在需要切换代码的时候重新确定函数指针的指向,之后的代码就几乎不用动了。
粗略来说,只需一次if判断,就可以将所有C语言代码涉及到的代码切换完成。这样的代码风格显然有利于程序员维护,也能提升C语言程序的运行效率。
事实上,C语言函数指针的用途远不止于此
在本专栏更早的章节中,我们曾讨论C语言函数的参数也可以是指针型的,“指针型”中的指针当然包括函数指针,也就是说,C语言函数的参数可以也是一个“函数”,只不过这个“函数”是通过函数指针传递的。请看下面这个例子:
#includestdio.hvoidmyprint(){printf(myprint\n);}voidfun(void(*f)()){f();}intmain(){fun(myprint);return0;}
从上面这段C语言代码中,可以看出fun()函数接收一个参数,该参数是一个函数指针,指向返回值为空的函数。在main()中调用fun()时,将myprint()传递给它了。编译并执行这段C语言代码,得到如下输出:
#gcct.c#./a.outmyprint在fun()中调用的myprint()就是所谓的“回调函数”。显然,回调函数就是一个通过函数指针调用的函数,回调函数不是由实现方直接调用,而是通过函数指针,在特定条件发生时,由另外一方调用,用于对该条件响应。
上面的例子很简单,fun()无条件调用f了,但是应明白,如果需要的话,程序员能够轻易为f的添加调用条件。
容易产生迷惑的点
在上述例子中,main()函数中的fun()在接收函数指针时,fun(myprint)中的符号可以不写。而且有些程序员在调用f时,为了显式的说明它是一个函数指针,常常写作:
(*f)();但是也有程序员像本例一样,将函数指针当作普通函数使用:
f();这似乎很不可思议,但是这些写法都可以正常工作,怎么回事呢?C语言的函数指针怎么会如此混乱不堪呢?
其实这主要是因为在C语言中,函数名,函数名,以及*函数名在内存中的值是相等的,编写下面这样的C语言代码:
printf(%p,%p,%p\n,myprint,myprint,*myprint);编译并执行,得到如下输出:
0xd,0xd,0xd显然,三者是相等的。所以究竟使用何种方式,主要取决于程序员自己的习惯了。
回调函数的意义
从上例可以看出,fun()并不关心自己接收到的函数f以何种方式提供何种功能,这样一来,fun()的一些功能就很灵活了。现在设想这种情况:
fun()在处理数据时,需要用到排序算法,但是fun()的主要功能并不是排序,所以不打算在fun()中嵌入排序相关的C语言代码。
在这种情况下,回调函数就比较有用了,程序员可以在别处实现排序算法函数,再将该函数的地址以函数指针参数的形式传递给fun()就可以了。
程序员甚至可以在别处实现若干个不同的排序算法函数(如冒泡排序、快速排序、shell排序、shake排序等等),根据实际情况,决定使用何种排序。
为什么不直接调用函数呢?感到迷惑的读者可以再看看上一节。
回调还可用于通知机制。例如,有时要在A程序中设置一个计时器,每到一定时间,A程序会得到相应的通知,但通知机制的实现者对A程序一无所知。那么,就需一个具有特定原型的函数指针进行回调,通知A程序事件已经发生。
回调函数的参数
上面的例子演示的myprint()没有参数,如果需要给回调函数传递参数,该怎么实现呢?请看下面的C语言代码:
voidmyprint(inta,doubleb){printf(myprintrecievenums:%d,%0.2f\n,a,b);}voidfun(void(*f)(),inta,doubleb){f(a,b);}显然,可以在fun()中指定传递给myprint()的参数。如果需要传递给myprint()的参数比较多,则可以使用本专栏第21节提到的小技巧:借助指针和结构体:
structparam{chara;intb;doublec;...charstr[];};voidmyprint(void*data){structparam*p=(structparam*)data;printf(myprintrecievenums:%d,%0.2f...\n,p-a,p-b);}voidfun(void(*f)(),void*data){f(data);}
显然,借助于C语言的指针和结构体语法,程序员可以仅使用一个参数,传递任意多的参数。事实上,一些比较成熟的库函数也是这么干的,例如:
intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);小结
本节主要讨论了C语言中的回调函数,应该能够发现,其实回调函数也是借助于C语言的指针语法实现的。
另外,在文章最后还讨论了回调函数传递参数的方法,可以看出,借助指针和结构体语法,程序员能够轻易的传递任意多的复杂参数。归根结底,这些重要内容都离不开C语言中的指针,所以说指针是C语言的灵魂一点也不夸张。
欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就