C函數指針與回調函數

一、函數指針

    簡單聲明一個函數指針並不意味着它馬上就可以使用,和其它指針一樣,對函數指針執行簡接訪問之前必須把它初始化爲指向某一個函數。

int f(int);

int (*pf)(int)=&f;

    第二個聲明創建了函數指針pf,並把它初始化爲指向函數f。函數指針的初始化也可以通過一條賦值語句完成。在函數指針的初始化之前具有f的原型是很重要的,否則編譯器就無法檢查f的類型是否與pf所指向的類型一致。

   初始化表達式中的&操作符是可選的,因爲函數名被使用時總是由編譯器把它轉換爲函數指針。&操作符只是顯示地說明了編譯器將隱式執行的任務。

    在函數指針被聲明並且初始化之後,我們就可以使用三種方式調用函數:

    int ret;


    ret=f(30);

    ret=(*pf)(30);

    ret=pf(30);

第一條語句簡單的使用名字調用函數f,但它的執行過程可能和我們想的不太一樣。函數名f首先被轉換爲一個函數指針,該指針指定函數在內存中的位置。然後,函數調用操作符調用該函數,執行開始於這個地址的代碼。

第二條語句對pf執行簡接訪問操作,它把函數指針轉換爲一個函數名。這個轉換並不是真正需要的,因爲編譯器在執行函數調用操作符之前又會把它轉換回去。不過,這條語句的效果和第一條語句是完全一樣的。

第三條語句和前兩條語句的效果是一樣的。簡接訪問並非必需,因爲編譯器需要的是一個函數指針。這個例子顯示了函數指針通常是如何使用的。

    那麼什麼時候我們應該使用函數指針呢。兩個最常見的用途是把函數指針作爲參數傳遞給函數以及用於轉換表。

二、回調函數

    回調函數,顧名思義,就是使用者自己定義一個函數,使用者自己實現這個函數的程序內容,然後把這個函數作爲參數傳入別人(或系統)的函數中,由別人(或系統)的函數在運行時來調用的函數。函數是你實現的,但由別人(或系統)的函數在運行時通過參數傳遞的方式調用,這就是所謂的回調函數。簡單來說,就是由別人的函數運行期間來回調你實現的函數。

    在系統編程的角度:當程序跑起來時,一般情況下,應用程序(application program)會時常通過API調用庫裏所預先備好的函數。但是有些庫函數(library function)卻要求應用先傳給它一個函數,好在合適的時候調用,以完成目標任務。這個被傳入的、後又被調用的函數就稱爲回調函數(callback function)。

    使用函數指針的一個例子就是回調函數。

例如如下函數:

Node* search_ist(Node *node,void const *value,int (*compare)(void const *,void const *))

{

    while(node(!=NULL){

        if(compare(&node->value,value) ==0)

            {

                break;

            }

        node=node->link;

    }

    return node;

}

    這是一個類型無關的鏈表查找,其中參數函數指針所指向的函數用於比較存儲於鏈表中類型的值。

在上邊的函數中我們把一個函數指針作爲參數傳遞給其它函數,後者將回調用戶的函數。任何時候,如果我們所編寫的函數必須能夠在不同的時刻執行不同類型的工作或者執行只能由函數調用者的工作,都可以使用這個技巧。許多窗口系統使用回調函數鏈接多個動作,如拖曳鼠標和點擊按鈕來指定用戶程序中某個特定函數。

同樣也可以這樣使用,在一個特定的鏈表中進行查找:

int compare_ints(void const *a,void const *b)

{

    if(*(int)a == *(int *)b)

        return 0;

    else 

        return 1;

}

    這個函數可以像下面這樣使用:

    desired_node=search_list(root,&desired_value,compare_ints);

    如果想在一個字符串鏈表中進行查找,也可以這樣:

    desired_node=search_list(root,"desired_value",strcmp);

    不過有的編譯器可能會發出警告,因爲strcmp的參數被聲明爲char*而不是void*;

三、轉換表

如果有如下代碼:

switch(oper){

case ADD:

    result=add(op1,op2);

    break;

case SUB:

    result=sub(op1,op2);

    break;

case MUL:

    result=mul(op1,op2);

    break;

case DIV:

    result=div(op1,op2);

    break;

...

    對於一個具有上百個操作符計算器而言,這條switch語句將會非常之長。

爲了使用switch語句,表示操作符的代碼必須是整數。如果它們是從零開始的整數,我們可以使用轉換表來完成相同的任務。轉換表就是一個函數指針數組。

    創建一個轉換表需要兩個步驟。首先,聲明並初始化一個函數指針數組。函數的原型必須出現在這個數組的聲明之前。

double add{double,double};

double sub{double,double};

double mul{double,double};

double div{double,double};

...

double (*oper_func[])(double,double)={

    add,sub,mul,div,...

};

初始化列表中各個函數的正確順序取決於程序中用於表示每個操作符的整形代碼。這個例子中假定ADD是0,SUB是1,MUL是2,DIV是3,以此類推。

    第二個步驟是用下面這條語句替換前面整條switch語句。

    result=oper_func[oper](op1,op2);

oper從數組中選擇正確的函數指針,而函數調用操作符將執行這個函數。


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