函数指针的重要用途——回调函数

什么是回调函数?

粗暴的说,如果一个函数作为另一个函数的参数传入,这种函数就可以称为回调函数(这句话并不严谨,但为了说明问题可以这么理解)。C语言里面,一般就是一个函数的参数列表中有函数指针,函数指针指向的函数就是一个回调函数。

为什么要有回调函数?

那为什么不直接在函数体内调用,而非要把函数指针作为参数呢?

举一个例子:系统提供一个排序函数sort(int a[ ]),排序函数默认升序,但如果我们想要降序排列呢?那系统还要提供一个降序版本?

显然,系统可以提供这样一个接口sort(int a[ ], void(*p)(int*) ),后者是一个指向排序方法的函数指针。用户可以自行定义排序方法。即,我们可以认为,回调函数有以下的功能:

  • 一个接口,可以实现不同的功能。这种思想不就是多态吗?本质上还是为了实现地址的晚绑定。C++中的仿函数,其实是对函数指针做了某种程度的简化,使用户使用更简单罢了,使用到的思想是一样的。
  • 通过回调函数可以修改一个黑盒子内部默认的功能,这也是业务上经常用的。

Linux操作系统用到了大量回调的思想,比如对于不同厂家提供的驱动,厂家按照Linux提供的接口标准编写自己的方法,而Linux接口的参数列表中就有指向对应方法的函数指针,这样尽管厂家不同,Linux也可以使用。

再举一个例子,默认情况下,当我们按下CTRL + C时,进程会终止。那我们想改变这样的默认行为怎么办,linux提供了signal这样一个接口。

 #include <signal.h>
  void (*signal(int sig, void (*func)(int)))(int);

第一个参数是信号的编号,此处的ctrl + C对应的是2号信号,第二个参数就是一个函数指针,指向用户自己提供的方法。

如下面的这段代码:

#include <stdio.h>    
#include <unistd.h>    
#include <signal.h>    
void mysignal(int signo)    
{    
  printf("catch a signal! %d\n", signo);    
}    
    
int main()    
{    
  signal(2, mysignal);    
  while(1)    
  {    
    printf("hello world!\n");                       
    sleep(1);    
  }    
  return 0;    
}    

这段代码中,使用signal函数注册了一个新的方法,当按下ctrl + C时,不再默认终止进程,而是输出catch a signal! 2。效果如下:

image-20211204153007188

也就是说,系统提供接口用到了回调函数,用户定义该函数,实现了用户所需要的功能。这也是回调函数的主要应用方式。