函數指針與回調函數

一.函數指針與指針函數的區別

    1. 指針函數是指帶指針的函數,本質上是一個函數,函數返回類型是某一類型的指針,其形式一般如下:

          類型標識符 *函數名(參數列表)

    例如:int *f(x,y),它的意思是聲明一個函數f(x,y),該函數返回類型爲int型指針。

    2.函數指針是指向函數的指針變量,即本質上是一個指針變量,表示的是一個指針,它指向的是一個函數。其形式一般如下:

        類型說明符 (*函數名)(參數)

        例如,int (*pf)(int x),它的意思是聲明一個函數指針,而pf=func則是將func函數的首地址賦值給指針。

二.函數指針

    簡單聲明一個函數指針並不意味着它馬上就可以使用。和其他指針一樣,對函數指針執行間接訪問之前必須把它初始化爲指向某個函數。下面的代碼說明了一種初始化函數指針的方法:

        int  f(int );

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

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

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

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

    int  ans;

    ans=f(25);

    ans=(*pf)(25);

    ans=pf(25);

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

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

    第3條語句和前兩句的效果是一樣的。間接訪問操作並非必須,因爲編譯器需要的是一個函數指針。

三.函數指針的用途

    函數指針最常見的用途是作爲參數傳遞給另一個函數和轉換表。下面使用回調函數和轉移表舉例。

四.回調函數

    回調函數就是被調用者回頭調用的函數,它是一個通過函數指針調用的函數。如果把函數的指針(地址)作爲參數傳遞給另一個函數,當這個指針被用爲調用它所指向的函數時,此時就稱之爲回調函數。

    使用回調函數實際上就是在調用某個函數時,將自己的一個函數(這個函數爲回調函數)的地址作爲參數傳遞給那個被調用函數。而該被調用函數在需要的時候,利用傳遞的地址調用回調函數。

    下面是一個在單鏈表查找一個值的程序(適用於值爲任意類型)。

//與類型無關的鏈表查找

    #include<stdio.h>

    #include"node.h"

    Node *search_list(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;

    }

    這個函數將向下面這樣使用:

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

    注意強制類型轉換:比較函數的參數必須聲明爲void *以匹配查找函數的原型,然後再強制轉換爲int *類型,用於比較整型值。

    如果你希望在一個字符串鏈表中進行查找,下面的代碼可以完成任務:

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

    碰巧,庫函數strcmp所執行的比較和我們需要的完全一樣,不過有些編譯器會發出警告信息,因爲它的參數被聲明爲char *而不是void *。

五.轉移表

    下面的代碼取自於一個用於實現袖珍版計算器的程序,程序的其他部分已經讀入兩個數(op1和op2)和一個操作符(oper)。下面的代碼對操作符進行測試,然後決定調用哪個函數。

    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語句,表示操作符的代碼必須是整數。如果它們是從0開始連續的整數,我們可以使用轉換表來實現相同的任務。轉換表就是一個函數指針數組。

    創建一個轉換表需要兩個步驟。首先,聲明並初始化一個函數指針數組。唯一需要留心之處就是確保這些函數的原型出現在這個數組的聲明之前。

    double add(double,doule);

    double sub(double,doule);

    double mul(double,doule);

    double div(double,doule);

    ...

    double (*oper_func[])(double,double)={add,sub,mul,div,...};

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

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

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

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

    

    

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