函數指針的重要用途——回調函數

什麼是回調函數?

粗暴的說,如果一個函數作為另一個函數的參數傳入,這種函數就可以稱為回調函數(這句話並不嚴謹,但為了說明問題可以這麼理解)。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

也就是說,系統提供接口用到了回調函數,用戶定義該函數,實現了用戶所需要的功能。這也是回調函數的主要應用方式。