函式指標的重要用途——回撥函式

烏有先生ii 發表於 2021-12-04

什麼是回撥函式?

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

也就是說,系統提供介面用到了回撥函式,使用者定義該函式,實現了使用者所需要的功能。這也是回撥函式的主要應用方式。