回調函數

若是參數是一個函數指針,調用者能夠傳遞一個函數的地址給實現者,讓實現者去調用它,這稱爲回調函數(Callback Function) 。例如 qsort(3)bsearch(3)
表 24.7. 回調函數示例:void func(void (*f)(void *), void *p);
調用者 實現者
  1. 提供一個回調函數,再提供一個準備傳給回調函數的參數。
  2. 把回調函數傳給參數 f,把準備傳給回調函數的參數按 void *類型傳給參數 p
  1. 在適當的時候根據調用者傳來的函數指針 f調用回調函數,將調用者傳來的參數 p轉交給回調函數,即調用 f(p);

如下是一個簡單的例子。實現了一個 repeat_three_times函數,能夠把調用者傳來的任何回調函數連續執行三次。
例 24.7. 回調函數
/* para_callback.h */
#ifndef PARA_CALLBACK_H
#define PARA_CALLBACK_H
typedef void (*callback_t)(void *);
extern void repeat_three_times(callback_t, void *);
#endif
/* para_callback.c */
#include "para_callback.h"
void repeat_three_times(callback_t f, void *para)
{
     f(para);
     f(para);
     f(para);
}
/* main.c */
#include <stdio.h>
#include "para_callback.h"
void say_hello(void *str)
{
     printf("Hello %s\n", (const char *)str);
}
void count_numbers(void *num)
{
     int i;
     for(i=1; i<=(int)num; i++)
	  printf("%d ", i);
     putchar('\n');
}
int main(void)
{
     repeat_three_times(say_hello, "Guys");
     repeat_three_times(count_numbers, (void *)4);
     return 0;
}

回顧一下前面幾節的例子,參數類型都是由實現者規定的。而本例中回調函數的參數按什麼類型解釋由調用者規定,對於實現者來講就是一個 void *指針,實現者只負責將這個指針轉交給回調函數,而不關心它到底指向什麼數據類型。調用者知道本身傳的參數是 char *型的,那麼在本身提供的回調函數中就應該知道參數要轉換成 char *型來解釋。
回調函數的一個典型應用就是實現相似C++的泛型算法(Generics Algorithm) 。下面實現的 max函數能夠在任意一組對象中找出最大值,能夠是一組 int、一組 char或者一組結構體,可是實現者並不知道怎樣去比較兩個對象的大小,調用者須要提供一個作比較操做的回調函數。
例 24.8. 泛型算法
/* generics.h */
#ifndef GENERICS_H
#define GENERICS_H
typedef int (*cmp_t)(void *, void *);
extern void *max(void *data[], int num, cmp_t cmp);
#endif
/* generics.c */
#include "generics.h"
void *max(void *data[], int num, cmp_t cmp)
{
     int i;
     void *temp = data[0];
     for(i=1; i<num; i++) {
	  if(cmp(temp, data[i])<0)
	       temp = data[i];
     }
     return temp;
}
/* main.c */
#include <stdio.h>
#include "generics.h"
typedef struct {
     const char *name;
     int score;
} student_t;
int cmp_student(void *a, void *b)
{
     if(((student_t *)a)->score > ((student_t *)b)->score)
	  return 1;
     else if(((student_t *)a)->score == ((student_t *)b)->score)
	  return 0;
     else
	  return -1;
}
int main(void)
{
     student_t list[4] = {{"Tom", 68}, {"Jerry", 72},
		       {"Moby", 60}, {"Kirby", 89}};
     student_t *plist[4] = {&list[0], &list[1], &list[2], &list[3]};
     student_t *pmax = max((void **)plist, 4, cmp_student);
     printf("%s gets the highest score %d\n", pmax->name, pmax->score);
     return 0;
}

max函數之因此能對一組任意類型的對象進行操做,關鍵在於傳給 max的是指向對象的指針所構成的數組,而不是對象自己所構成的數組,這樣 max沒必要關心對象究竟是什麼類型,只需轉給比較函數 cmp,而後根據比較結果作相應操做便可, cmp是調用者提供的回調函數,調用者固然知道對象是什麼類型以及如何比較。
以上舉例的回調函數是被同步調用的,調用者調用 max函數, max函數則調用 cmp函數,至關於調用者間接調了本身提供的回調函數。在實際系統中,異步調用也是回調函數的一種典型用法,調用者首先將回調函數傳給實現者,實現者記住這個函數,這稱爲 註冊一個回調函數,而後當某個事件發生時實現者再調用先前註冊的函數,好比 sigaction(2)註冊一個信號處理函數,當信號產生時由系統調用該函數進行處理,再好比 pthread_create(3)註冊一個線程函數,當發生調度時系統切換到新註冊的線程函數中運行,在GUI編程中異步回調函數更是有廣泛的應用,例如爲某個按鈕註冊一個回調函數,當用戶點擊按鈕時調用它。
如下是一個代碼框架。
/* registry.h */
#ifndef REGISTRY_H
#define REGISTRY_H
typedef void (*registry_t)(void);
extern void register_func(registry_t);
#endif
/* registry.c */
#include <unistd.h>
#include "registry.h"
static registry_t func;
void register_func(registry_t f)
{
     func = f;
}
static void on_some_event(void)
{
     ...
     func();
     ...
}
既然參數能夠是函數指針,返回值一樣也能夠是函數指針,所以能夠有 func()();這樣的調用。返回函數的函數在C語言中不多見,在一些函數式編程語言(例如LISP)中則很常見,基本思想是把函數也看成一種數據來操做,輸入、輸出和參與運算,操做函數的函數稱爲高階函數(High-order Function)


相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息