天天看點

Linux 回調函數

如果參數是一個函數指針,調用者可以傳遞一個函數的位址給實作者,讓實作者去調用它,這稱為回調函數(Callback Function)。例如

qsort(3)

bsearch(3)

表 24.7. 回調函數示例:

void func(void (*f)(void *), void *p);  //f是函數名 p是函數f的(void*)類型的參數

調用者 實作者
  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 *); //定義函數指針 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)。

繼續閱讀