囘調函數進階一:瞭解囘調函數
調用函數:
如果參數是一個函數指針,調用者可以傳遞一個函數的地址給實現者,
即調用者提供一個函數但自己不去調用,而是讓實現者去調用它,這稱之為囘調函數。
囘調函數示例:
void func(void (*f)(void *),void *p);
實現過程:
調用者提供一個囘調函數,再提供一個準備傳遞給囘調函數的參數;
把囘調函數傳給參數f,把準備傳給囘調函數的參數按照void * 類型傳給參數p。
實現者呢,在適當的時候根據調用者傳來的函數指針f調用囘調函數,
將調用者傳來的參數p轉交給囘調函數,即調用f(p)。記住,此時f是一個函數指針,
真正的函數地址則是看調用者的傳遞了。
應用實例:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> typedef void (*callback_t)(void *); void my_func(callback_t,void *); /* * 當然,正規的定義應該是這樣的: * void my_func(void (*callback_t)(void *),void *p); * 使用 typedef 會更清晰一些。 */ void my_func(callback_t f,void *para) { f(para); } static void say_hello(void *str) { printf("%s\n",(const char *)str); } static void count_num(void *num) { printf("%d\n",(int)num); } int main(int argc, char *argv[]) { my_func(say_hello,"Hello World!"); my_func(count_num,2); return 0; }
通俗的講,囘調函數實現了程序處理的自由性,靈活性。同樣的都是void *p 參數,
但是另一個函數,函數指針的地址(也就是囘調函數)不同,
因此可以通過對同一個參數傳遞不同的函數來處理不同的操作。
再比如下面這個示例:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> typedef int (*cmp_t)(const void *,const void *); void *max(const void *base,size_t nmemb,size_t size,cmp_t cmp); void *max(const void *base,size_t nmemb,size_t size,cmp_t cmp) { size_t i; const char *_base = base; const char *temp = _base; for(i=0;i<nmemb;i++){ if(cmp(temp,_base+size*i)<0) temp = _base + size*i; } return (void *)temp; } typedef struct { const char *name; int score; }student_t; static int cmp_student(const void *a,const 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(int argc,char **argv) { student_t list[4] = {{"Tom",68},{"Jerry",72},{"Moby",60},{"Kirby",89}}; student_t *pmax = max(list,sizeof(list)/sizeof(student_t),sizeof(student_t),cmp_student); printf("%s gets the highest score %d\n",pmax->name,pmax->score); exit(0); }
囘調函數進階二:深入囘調函數
同步囘調:
在上面的例子中,囘調函數是被同步調用的。my_func()調用say_hello(),相當於調用者直接調用了自己提供的囘調函數。
比如,將main函數中的第一行替換成:sya_hello("Hello World!");也是完全可以的。
異步調用:
比起同步調用,異步調用也是很典型的。調用者首先將囘調函數傳給實現者,實現者記住這個函數,這稱為“註冊”一個囘調函數,然後當某個事件發生時實現者再調用先前註冊的函數。
比如信號處理函數sigaction(),首先註冊一個信號處理函數,當此信號發生的時候由操作系統調用該函數進行處理。
再比如創建線程函數pthread_create()註冊一個線程函數,當發生調度時操作系統切換到新註冊的線程函數中運行。
“從這個角度來看,凡是系統api參數中有函數指針,大多(or全部?)是這種囘調函數的形式”。
囘調函數進階三:提升囘調函數
總結:
簡單來說,在層次化程序設計中,一般都是上層函數調用下層函數,逐級調用。
而如果把函數的參數變為函數地址(也就是函數指針),那麼就可以實現函數的囘調,
也就是不必一直向下層調用。這樣,我們就稱之為囘調函數,這樣實現了層次化編程
中的函數自由、靈活的調用。
如果自己提供了一個囘調函數并實現了囘調函數,將之編譯成lib庫或是其他的形式。
那麼在應用中,一旦需要這個囘調函數,系統就會在lib庫中按照名字尋找。
1.可以實現動態綁定。通過運行時傳遞不同的函數地址,執行不同的操作。可以理解為多态。
2.可以實現消息通知和事件驅動。比如程序中設置一個計時器,到達指定時間則通知程序。
3.提供了一種代碼動態嵌入的方式,實現函數內部中斷的一種方式。比如程序出錯,程序需要額外處理,
可以嵌入一個囘調函數,啟動一個單獨線程去執行任務。而程序則繼續執行自己的後續代碼。
實際上,創建線程函數就是一個典型應用。
pthread_create(),其中有一個參數就是函數執行的地址,還有一個參數是參數。
ps:囘調函數都是全局函數、或者是靜態函數。想想這是爲什麽?
http://hi.baidu.com/gtfcugb/blog/item/57b836dd6e11f5e976c638f1.html