函數指針及應用

我們先來看一下以下的聲明:

int f(int);

int  (*pf)(int)=&f;//&操作符可選;因爲函數名被使用時總是由編譯器把它

                           //轉換爲函數指針;

int ans;

ans=f(25);

ans=(*pf)(25);

ans=pf(25);//間接訪問操作並非必需,因爲編譯器需要的是一個函數指針;

**********************************************************************************

兩個最常見的用途是把函數指針作爲參數傳遞給函數以及用於轉換表!

1.回調函數

這裏有一個簡單的函數,它用於在一個單鏈表中查找一個值,它的參數是一個指向

鏈表第一個節點的指針以及那個需要查找的值.

Node* search_list(Node* node,int const value)

{   while(node!=NULL)

  {    if(node->value==value)

                break;

       node=node->link;

   }

    return node;

}

       這個函數看上去相當簡單,但它只適用於值爲整數的鏈表,如果你需要在一個

字符串鏈表中查找,你不得不另外編寫一個函數,這個函數和上面那個函數的絕大

部分代碼相同,只是第二個參數的類型以及節點值的比較方法不同.

       一種更爲通用的方法是查找函數與類型無關,這樣它就能用於任何類型的值

的鏈表,我們必須對函數的兩個方面進行修改,使它與類型無關.首先我們必須改變

比較的執行方式,這樣函數就可以對任何類型的值進行比較.這個目標聽上去好象

不可能,如果你編寫語句用於比較整型值,它怎麼還可能用於其他類型如字符串

的比較呢?解決方案就是使用函數指針,調用者編寫一個函數,用於比較兩個值,然後

把一個指向這個函數的指針作爲參數傳遞給查找函數.然後查找函數調用這

個函數來執行值的比較,使用這種方法,任何類型的值都可以進行比較.

       我們必須修改的第二個方面是向函數傳遞一個指向值的指針而不是本身.

函數由一個void *形參,用於接收這個參數,然後指向這個值的指針便傳遞給比較

函數,這個修改使字符串和數組對象也可以被使用,字符串和數組無法作爲參數傳

遞給函數,但指向它們的指針可以.

      使用這種技巧的函數叫"回調函數"(callback function);因爲用戶把一個函數指

作爲參數傳遞給其他函數,後者將"回調"用戶的函數.任何時候,如果你所編寫的

函數必須能夠在不同的時刻執行不同類型的工作或執行只能由函數調用者定義

的工作,你都可以使用這個技巧.許多窗口系統使用回調函數連接多個動作,

如拖拽鼠標和點擊按鈕來指定用戶程序中的某個特定函數.

      我們無法在這個上下文環境中爲回調函數編寫一個準確的原型,因爲我們並

知道進行比較的值的類型.事實上,我們需要查找函數能作用於任何類型的值,

解決這個難題的方法是把參數類型聲明爲"void *",表示"一個指向未知類型

的指針".

    /*

   **在一個單鏈表中查找一個指定值的函數,它的參數是一個指向鏈表第一個節點

   **的指針,一個指向我們需要查找的值的指針和一個函數指針,它所指向的函數

   **用於比較存儲於此鏈表中的類型的值.

    */

#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;

}

      同時注意雖然函數不會修改參數node所指向的任何節點,但node並未聲明

爲const。如果node被聲明爲const,函數不得不返回一個const結果,這將限制

調用程序,它便無法修改查找函數所找到的節點。

      在一個特定的鏈表中進行查找時,用戶需要編寫一個適當的比較函數,並

把指向該函數的指針和指向需要查找的值的指針傳遞給查找函數。例如,下面

是一個比較函數,它用於在一個整數鏈表中進行查找。

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);

2.轉換表(jump table)

    轉移表最好用個例子來解釋。下面的代碼段取自一個程序,它用於實現

一個袖珍式計算器。程序的其他部分已經讀入兩個數(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語句,表示操作符的代碼必須是整數。如果它們是從零開始

連續的整數,我們可以使用轉換表來實現相同的任務。轉換表就是一個函數指針

數組。

      創建一個轉換表需要兩個步驟。首先,聲明並初始化一個函數指針數組。唯一

需要留心之處就是確保這些函數的原型出現在這個數組的聲明之前。

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,接下去以此類推。

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

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

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

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