1. 程式人生 > >回撥函式,終於有個講的明白的了

回撥函式,終於有個講的明白的了

所以,實現庫函式時,庫函式的一些功能如果想讓使用者自己去定製,那此時就留下一個回撥函式的引數。

呼叫者呼叫庫函式,庫函式又反過來呼叫呼叫者自己寫的函式,這個過程就叫回調。

簡單說,看到一個函式(一般是庫函式,普通函式或者自己寫的某些api也可以)裡的引數有函式指標,這個指標對應的函式就是回撥函式。就是:引數有指標函式就是用了回撥函式。

=====

一直沒有看到一篇能把回撥函式的作用講的比較清楚的,一直都認為,回撥函式不用也可以,用其它方式都可以實現同樣的功能。

看到一篇文章,算是講的比較清楚的:

C語言回撥函式
定義:
被調者回頭呼叫呼叫者的函式,這種由呼叫方自己提供的函式叫回調函式
應用場景舉例:
現有一個快速排序演算法,實現了快排演算法的邏輯,但是快排演算法中必須涉及資料大小的比較,為提高程式的通用性,掉用者提供一個比較函式,這樣排序函數借此呼叫呼叫者的函式來比較大小。


應用詳解:
回撥在C語言中是通過函式指標來實現的,通過將回調函式的地址傳給被掉函式從而實現回撥。因此要實現回撥,必須首先定義函式指標,如:
void Func(char *s);函式原型
void (*pFunc)(char *);//函式指標
可以看出,函式的定義和函式指標的定義非常類似,一般來說,為了簡化函式指標型別變數定義,提高程式的可讀性,我們需要把函式指標型別自定義一下。
typedef void(*pcb)(char *) pcb;
被調函式的例子:
void GetCallBack(pcb callBack)
{
/*do something*/
}
使用者在呼叫上面的函式時,需要自己實現一個pcb型別的回撥函式:
void fCallBack(char *s)
{
/*do something*/
}
然後,就可以直接把fCallBack當作一個變數傳遞給GetCallBack,
GetCallBack(fCallBack)
如果賦了不同的值給該引數,那麼呼叫者將呼叫不同地址的函式。賦值可以發生在執行時,這樣使你能實現動態繫結。
  (2 )引數傳遞規則
到目前為止,我們只討論了函式指標及回撥而沒有去注意ANSI C/C++的編譯器規範。許多編譯器有幾種呼叫規範。如在Visual C++中,可以在函式型別前加_cdecl,_stdcall或者_pascal來表示其呼叫規範(預設為_cdecl)。C++ Builder也支援_fastcall呼叫規範。呼叫規範影響編譯器產生的給定函式名,引數傳遞的順序(從右到左或從左到右),堆疊清理責任(呼叫者或 者被呼叫者)以及引數傳遞機制(堆疊,CPU暫存器等)。
  將呼叫規範看成是函式型別的一部分是很重要的;不能用不相容的呼叫規範將地址賦值給函式指標。例如:
  // 被呼叫函式是以int為引數,以int為返回值
  __stdcall int callee(int);
  // 呼叫函式以函式指標為引數
  void caller( __cdecl int(*ptr)(int));
  // 在p中企圖儲存被呼叫函式地址的非法操作
  __cdecl int(*p)(int) = callee; // 出錯
  指標p和callee()的型別不相容,因為它們有不同的呼叫規範。因此不能將被呼叫者的地址賦值給指標p,儘管兩者有相同的返回值和引數列
  

(3 )應用舉例
  C語言的標準庫函式中很多地方就採用了回撥函式來讓使用者定製處理過程。如常用的快速排序函式、二分搜尋函式等。
  快速排序函式原型:
void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
  二分搜尋函式原型:
void *bsearch(const void *key, const void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
  其中fcmp就是一個回撥函式的變數


下面給出一個具體的例子:
#include<stdio.h>
#include<stdlib.h>


int sort_function( const void *a, const void *b);

int list[5] = { 54, 21, 11, 67, 22 };

int main(void)
{
int x;
qsort((void *)list, 5, sizeof(list[0]), sort_function);
for (x = 0; x<5; x++)
printf("%i\n", list[x]);
return 0;
}


int sort_function( const void *a, const void *b)
{
return *(int*)a-*(int*)b;
}

============

總結:想了解回撥函式的作用是什麼,就得知道回撥函式是在什麼情況下才會採用。

由這篇文章看出,在實現一些庫函式時,比如文中為了提高庫函式的通用性:

現有一個快速排序演算法,實現了快排演算法的邏輯,但是快排演算法中必須涉及資料大小的比較,為提高程式的通用性,掉用者提供一個比較函式,這樣排序函數借此呼叫呼叫者的函式來比較大小。

比較函式讓使用者自己定製,這樣使用者可以根據資料型別自己定義如何比較大小,甚至可以比較字元長,這樣就使排序演算法可以適用各種資料型別,提高了程式的通用性。
所以,實現庫函式時,庫函式的一些功能如果想讓使用者自己去定製,那此時就留下一個回撥函式的引數。

呼叫者呼叫庫函式,庫函式又反過來呼叫呼叫者自己寫的函式,這個過程就叫回調。

當然在庫函式裡直接呼叫比較大小的函式也行,但是你得告訴使用者去實現這個函式,而在庫函式裡的引數裡顯示的指出我需要一個函式會更加明顯。

庫函式只關心回撥函式的返回值,根據返回值來判斷做出什麼操作。不關心回撥函式的具體實現。如果說因為這點把被呼叫者和呼叫者分開,我覺得牽強,你隨便呼叫個函式都是不用關心函式的內部實現,都是隻關心返回值。

有些常用比如來講解回撥函式的,其實沒講到點子上,例如:

精妙比喻:回撥函式還真有點像您隨身帶的BP機:告訴別人號碼,在它有事情時Call您。

其實你不用回撥函式,用其它的普通函式一樣可以實現這樣的功能。我覺得用回撥函式而不用普通函式的唯一區別實際就是因為庫函式的引數可以顯示的告訴使用者你需要實現我引數裡的這個函式。我覺得其實就這麼一個優點,其它的所謂什麼回撥函式的優點其實用普通函式一樣可以實現。