歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> 關於Unix

復雜指針的使用

你不會每天都使用函數指針,但是,它們確有用武之地,兩個最常見的用途是把函數指針作為參數傳遞給另一個函數以及用於轉換表(jump table) 【警告】簡單聲明一個函數指針並不意味著它馬上就可以使用。和其它指針一樣,對函數指針執行間接訪問之前必須把它初 cc" size="4">      你不會每天都使用函數指針,但是,它們確有用武之地,兩個最常見的用途是把函數指針作為參數傳遞給另一個函數以及用於轉換表(jump table)

     【警告】簡單聲明一個函數指針並不意味著它馬上就可以使用。和其它指針一樣,對函數指針執行間接訪問之前必須把它初始化為指向某個函數。下面的代碼段說明了一種初始化函數指針的方法。
      int  f(int);
      int  (*pf)(int)=&f;
      第 2 個聲明創建了函數指針 pf ,並把它初始化為指向函數 f 。 函數指針的初始化也可以通過一條賦值語句來完成。 在函數指針的初
始化之前具有 f 的原型是很重要的,否則編譯器就無法檢查 f 的類型是否與 pf 所指向的類型一致。

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

      在函數指針被聲明並且初始化之後,我們就可以使用三種方式調用函數:
      int  ans;
   
      ans=f(25);
      ans=(*pf)(25);
      ans=pf(25);
      第 1 條語句簡單地使用名字調用函數 f ,但它的執行過程可能和你想象的不太一樣。 函數名 f 首先被轉換為一個函數指針,該指針指

定函數在內存中的位置。然後, 函數調用操作符調用該函數,執行開始於這個地址的代碼。
      第 2 條語句對 pf 執行間接訪問操作,它把函數指針轉換為一個函數名。這個轉換並不是真正需要的,因為編譯器在執行函數調用操作符

之前又會把它轉換回去。不過,這條語句的效果和第1條是完全一樣的。
      第 3 條語句和前兩條的效果是一樣的。間接訪問並非必需,因為編譯器需要的是一個函數指針。

      (一)回調函數
      這裡有一個簡單的函數,它用於在單鏈表中查找一個值。它的參數是一個指向鏈表第 1 個節點的指針以及那個需要查找的值。
      Node *
      search_list(Node  *node, int  const  value)
      {
          while(node!=NULL){
              if( node->value == value )
                  break;
              node = node->link;
          }
          return node;
      }

      這個函數看上去相當簡單,但它只適用於值為整數的鏈表。如果你需要在一個字符串鏈表中查找,你不得不另外編寫一個函數。這個函數和上面那個函數的絕大部分代碼相同,只是第 2 個參數的類型以及節點值的比較方法不同。

      一種更為通用的方法是使查找函數與類型無關,這樣它就能用於任何類型的值的鏈表。我們必須對函數的兩個方面進行修改,使它與類型無關。

      首先,我們必須改變比較的執行方式,這樣函數就可以對任何類型的值進行比較。這個目標聽上去好像不可能,如果你編寫語句用於比較整型值,它怎麼還可能用於其它類型如字符串的比較呢? 解決方案就是使用函數指針。 調用者編寫一個比較函數,用於比較兩個值,然後把一個指向此函數的指針作為參數傳遞給查找函數。 而後查找函數來執行比較。使用這種方法,任何類型的值都可以進行比較。

      我們必須修改的第 2 個方面是向比較函數傳遞一個指向值的指針而不是值本身。比較函數有一個 void  * 形參,用於接收這個參數。然後指向這個值的指針便傳遞給比較函數。(這個修改使字符串和數組對象也可以被使用。字符串和數組無法作為參數傳遞給函數,但指向它們的指針卻可以。)

      使用這種技巧的函數被稱為回調函數(callback  function),因為用戶把一個函數指針作為參數傳遞其它函數,後者將”回調“用戶的函數。任何時候,如果你所編寫的函數必須能夠在不同的時刻執行不同類型的工作或者執行只能由函數調用者定義的工作,你都可以使用這個技巧。
     
      【提示】
      在使用比較函數的指針之前,它們必須被強制轉換為正確的類型。因為強制類型轉換能夠躲開一般的類型檢查,所以你在使用時必須格外小心,確保函數參數類型是正確的。

      在這個例子裡,回調函數比較兩個值。查找函數向比較函數傳遞兩個指向需要進行比較的值的指針,並檢查比較函數的返回值。例如:零表示相等的值,現在查找函數就與類型無關,因為它本身並不執行實際的比較。確實,調用者必須編寫必需的比較函數,但這樣做是很容易的,因為調用者知道鏈表中所包含的值的類型。 如果使用幾個分別包含不同類型值的鏈表,為每種類型編寫一個比較函數就允許單個查找函數作用於所有類型的鏈表。

      程序段01 是類型無關的查找函數的一種實現方法。 注意函數的第 3 個參數是一個函數指針。這個參數用一個完整的原型進行聲明。同時注意雖然函數絕不會修改參數 node 所指向的任何節點,但 node 並未被聲明為 const 。如果 node 被聲明為 const, 函數將不得不返回一個const結果,這將限制調用程序,它便無法修改查找函數所找到的節點。
      /*
      **程序 01 ——類型無關的鏈表查找函數
      **在一個單鏈表中查找一個指定值的函數。它的參數是一個指向鏈表第 1 個節點的指針、一個指向我們需要  查找的值的指針和一個函數指針。
      **它所指向的函數用於比較存儲於鏈表中的類型的值。
      */
      #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->value 被傳遞給比較函數。後者是我們當前所檢查的節點值。
     
      在一個特定的鏈表中進行查找時,用戶需要編寫一個適當的比較函數,並把指向該函數的指針和指向需要查找的值的指針傳遞給查找函數下面是一個比較函數,它用於在一個整數鏈表中進行查找。
      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 );
      注意強制類型轉換:比較函數的參數必須聲明為 void * 以匹配查找函數的原型,然後它們再強制轉換為 int * 類型,用於比較整型值。
      如果你希望在一個字符串鏈表中進行查找,下面的代碼可以完成這項任務:
      #include  <string.h>
      ...
      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 語句,表示操作符的代碼必須是整數。如果它們是從零開始連續的整數,我們可以使用轉換表來實現相同的任務。
轉換表就是一個函數指針數組。

      創建一個轉換表需要兩個步驟。首先,聲明並初始化一個函數指針數組。唯一需要留心之處就是確保這些函數的原型出現在這個數組的
聲明之前。
      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,依次類推。
      第 2 個步驟是用下面這條語句替換前面整條 switch 語句!
      result = oper_func[ oper ]( op1,op2 );
      oper從數組中選擇正確的函數指針,而函數調用操作符執行這個函數。


Copyright © Linux教程網 All Rights Reserved