如果參數是一個函數指針,調用者可以傳遞一個函數的位址給實作者,讓實作者去調用它,這稱為回調函數(Callback Function)。例如
qsort(3)
和
bsearch(3)
。
表 24.7. 回調函數示例:
void func(void (*f)(void *), void *p); //f是函數名 p是函數f的(void*)類型的參數
調用者 | 實作者 |
---|---|
|
|
以下是一個簡單的例子。實作了一個
repeat_three_times
函數,可以把調用者傳來的任何回調函數連續執行三次。
例 24.7. 回調函數
/* para_callback.h */
#ifndef PARA_CALLBACK_H
#define PARA_CALLBACK_H
typedef void (*callback_t)(void *); //定義函數指針 callback_t是void*類型,其中包含一個void*的參數.格式: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) //callback_t是void*,也就是之前定義的類型(函數指針)
{
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)。