回調函數原理

回調函數原理

聲明CALLBACK  

調用(calling)機制從彙編時代起已經大量使用:準備一段現成的代碼,調用者可以隨時跳轉至此段代碼的起始地址,執行完後再返回跳轉時的後續地址。CPU爲此準備了現成的調用指令,調用時可以壓棧保護現場,調用結束後從堆棧中彈出現場地址,以便自動返回。借堆棧保護現場,它使調用者和被調者可以互不相識,於是纔有了後來的函數和構件.  

此調用機制並非完美。回調函數就是一例。例如,寫一個快速排序函數供他人調用,其中必包含比較大小。麻煩來了:此時並不知要比較的是何類數據--整數、浮點數、字符串?於是只好爲每類數據製作一個不同的排序函數。更通行的辦法是在函數參數中列一個回調函數地址,並通知調用者:君需自己準備一個比較函數,其中包含兩個指針類參數,函數要比較此二指針所指數據之大小,並由函數返回值說明比較結果。排序函數借此調用者提供的函數來比較大小,借指針傳遞參數,可以全然不管所比較的數據類型。被調用者回頭調用調用者的函數(夠咬嘴的),故稱其爲回調(callback)。

Windows系統還包含着另一種更爲廣泛的回調機制,即消息機制。消息本是Windows的基本控制手段,乍看與函數調用無關,其實是一種變相的函數調用。發送消息的目的是通知收方

運行一段預先準備好的代碼,相當於調用一個函數。消息所附帶的WParam和LParam相當於函數的參數,只不過比普通參數更通用一些。應用程序可以主動發送消息,更多情況下是坐等 Windows發送消息。一旦消息進入所屬消息隊列,便檢感興趣的那些,跳轉去執行相應的消息處理代碼。操作系統本是爲應用程序服務,由應用程序來調用。而應用程序一旦啓動,卻要反過來等待操作系統的調用。這分明也是一種回調,或者說是一種廣義回調。其實,應用程序之間也可以形成這種回調。假如進程B收到進程A發來的消息,啓動了一段代碼,其中又向進程A發送消息,這就形成了回調。這種回調比較隱蔽,弄不好會搞成遞歸調用,若缺少終止條件,將會循環不已,直至把程序搞垮。若是故意編寫成此遞歸調用,並設好終止條件,倒是很有意思。但這種程序結構太隱蔽,除非十分必要,還是不用爲好。

利用消息也可以構成狹義回調。上面所舉排序函數一例,可以把回調函數地址換成窗口handle。如此,當需要比較數據大小時,不是去調用回調函數,而是借API函數SendMessage   向指定窗口發送消息。收到消息方負責比較數據大小,把比較結果通過消息本身的返回值傳給消息發送方。所實現的功能與回調函數並無不同。當然,此例中改爲消息純屬畫蛇添腳,反倒把程序搞得很慢。但其他情況下並非總是如此,特別是需要異步調用時,發送消息是一種不錯的選擇。假如回調函數中包含文件處理之類的低速處理,調用方等不得,需要把同步調用改爲異步調用,去啓動一個單獨的線程,然後馬上執行後續代碼,其餘的事讓線程慢慢去做。一個替代辦法是借   API函數PostMessage發送一個異步消息,然後立即執行後續代碼。這要比自己搞個線程省事許多,而且更安全。

如今我們是活在一個object時代。只要與編程有關,無論何事都離不開object。但object並未消除回調,反而把它發揚光大,弄得到處都是,只不過大都以事件(event)的身份出現,鑲嵌在某個結構之中,顯得更正統,更容易被人接受。應用程序要使用某個構件,總要先弄清構件的屬性、方法和事件,然後給構件屬性賦值,在適當的時候調用適當的構件方法,還要給事件編寫處理例程,以備構件代碼來調用。何謂事件?它不過是一個指向事件例程的地址,與回調函數地址沒什麼區別。不過,此種回調方式比傳統回調函數要高明許多。首先,它把讓人不太舒服的回調函數變成一種自然而然的處理例程,使編程者頓覺氣順。再者,地址是一個危險的東西,用好了可使程序加速,用不好處處是陷阱,程序隨時都會崩潰。現代編程方式總是想法把地址隱藏起來(隱藏比較徹底的如 VB和Java),其代價是降低了程序效率。事件例程使編程者無需直接操作地址,但並不會使程序減速。

 

自定義回調函數

回調函數是不能顯式調用的函數;通過將回調函數的地址傳給調用者從而實現調用。回調函數使用是必要的,在我們想通過一個統一接口實現不同的內容,這時用回掉函數非常合適。比如,我們爲幾個不同的設備分別寫了不同的顯示函數:void TVshow(); void ComputerShow(); void NoteBookShow()...等等。這是我們想用一個統一的顯示函數,我們這時就可以用回掉函數了。

    void show(void (*ptr)());

使用時根據所傳入的參數不同而調用不同的回調函數。

不同的編程語言可能有不同的語法,下面舉一個c語言中回調函數的例子,其中一個回調函數不帶參數,另一個回調函數帶參數。

例子1:

 

//Test.c

 

#include <stdlib.h>

#include <stdio.h>

 

int Test1()

{

   int i;

   for (i=0; i<30; i++)

   {

     printf("The %d th charactor is: %c/n", i, (char)('a' + i%26));

    }

   return 0;

}

int Test2(int num)

{

   int i;

   for (i=0; i<num; i++)

   {

    printf("The %d th charactor is: %c/n", i, (char)('a' + i%26));

    }

   return 0;

}

 

void Caller1(void (*ptr)())//指向函數的指針作函數參數

{

   (*ptr)();

}

void Caller2(int n, int (*ptr)())//指向函數的指針作函數參數,這裏第一個參數是爲指向函數的指針服務的,

{ //不能寫成void Caller2(int (*ptr)(int n)),這樣的定義語法錯誤。

   (*ptr)(n);

   return;

}

int main()

{

   printf("************************/n");

   Caller1(Test1); //相當於調用Test2();

   printf("&&&&&&************************/n");

   Caller2(30, Test2); //相當於調用Test2(30);

   return 0;

}

 

以上通過將回調函數的地址傳給調用者從而實現調用,但是需要注意的是帶參回調函數的用法。要實現回調,必須首先定義函數指針。函數指針的定義這裏稍微提一下。比如: int (*ptr)(); 這裏ptr是一個函數指針,其中(*ptr)的括號不能省略,因爲括號的優先級高於星號,那樣就成了一個返回類型爲整型的函數聲明瞭。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章