1. 程式人生 > >回撥函式,函式指標與函式物件

回撥函式,函式指標與函式物件

1.  什麼是回撥函式

    回撥函式(callback Function),顧名思義,用於回撥的函式。  回撥函式只是一個功能片段,由使用者按照回撥函式呼叫約定來實現的一個函式。回撥函式是一個工作流的一部分,由工作流來決定函式的呼叫(回撥)時機。回撥函 數包含下面幾個特性:
  •     屬於工作流的一個部分;
  •     必須按照工作流指定的呼叫約定來申明(定義);
  •     他的呼叫時機由工作流決定,回撥函式的實現者不能直接呼叫回撥函式來實現工作流的功能;
   

2. 回撥機制

    回撥機制是一種常見的設計模型,他把工作流內的某個功能,按照約定的介面暴露給外部使用者,為外部使用者提供資料,或要求外部使用者提供資料。

     如上圖所示,工作流提供了兩個對外介面(獲取引數、顯示結果),以回撥函式的形式實現。
  •  “獲取引數”回撥函式,需要工作流使用者設定工作流計算需要的引數。
  •  “顯示結果”回撥函式,提供計算結果給工作流使用者。


2.  回撥機制應用

   使用回撥機制,可以為工作流實現擴充套件。 可以把工作流中需要使用者干預的,或需要提供給使用者的資料以回撥的模式提供給使用者。而使用者不需要知道整個工作的流程,只需知道回撥函式的說明就可以使用工作 流模組提供的功能,這對資訊的隱藏也是有作用的。

3. 回撥機制的實現形式

  •     回撥函式
  •     虛擬函式
  •     事件

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  對於回撥函式的編寫始終是寫特殊處理功能程式時用到的技巧之一。先介紹一下回調的使用基本方法與原理。

  在這裡設:回撥函式為A()(這是最簡單的情況,不帶引數,但我們應用的實際情況常常很會複雜),使用回撥函式的操作函式為B(), 但B函式是需要引數的,這個引數就是指向函式A的地址變數,這個變數一般就是函式指標。使用方法為:

  int A(char *p); // 回撥函式

  typedef int(*CallBack)(char *p) ; // 宣告CallBack 型別的函式指標

  CallBack myCallBack ; // 宣告函式指標變數

  myCallBack = A; // 得到了函式A的地址  

B函式一般會寫為 B(CallBack lpCall,char * P,........); // 此處省略了p後的引數形式 。

所以回撥機制可解為,函式B要完成一定功能,但他自己是無法實現全部功能的。 需要藉助於函式A來完成,也就是回撥函式。B的實現為:
B(CallBack lpCall,char *pProvide)
{
........... // B 的自己實現功能語句
lpCall(PpProvide); // 藉助回撥完成的功能 ,也就是A函式來處理的。
........... // B 的自己實現功能語句
}

(1)基於函式指標的回撥函式:

#include < iostream > using namespace  std;

typedef 
int  ( * CallBack)( char * );//定義函式指標,該指標指向引數為 char *返回 int的函式
int  A( char *  str)
{
    cout
<< " function A starts " << endl;
    cout
<< str << endl;
    cout
<< " function A ends " << endl;
    
return 0 ;
}
void  B(CallBack call, char *  str)
{
    cout
<< " function B starts " << endl;
    call(str);
    cout
<< " function B ends " << endl;
}

int  main()
{
    
char *  str = " hello,world! " ;
    B(A,str);
    
return 0 ;
}

結果:

function B starts

function A starts

hello,world!

function A ends

function B ends

(2)回撥函式還有另外一種方式:函式物件。

函式物件(也稱“算符”)是過載了“()”操作符的普通類物件。因此從語法上講,函式物件與普通的函式行為類似。

用函式物件代替函式指標有幾個優點:

首先,因為物件可以在內部修改而不用改動外部介面,因此設計更靈活,更富有彈性。函式物件也具備有儲存先前呼叫結果的資料成員。在使用普通函式時需 要將先前呼叫的結果儲存在全程或者本地靜態變數中,但是全程或者本地靜態變數有某些我們不願意看到的缺陷。

其次,在函式物件中編譯器能實現內聯呼叫,從而更進一步增強了效能。這在函式指標中幾乎是不可能實現的。

下面的例子說明使用函式指標和函式物件實現整數求負數的的方法。

#include < iostream > using namespace  std;

// 使用函式物件 class  CallBack
{
public :
    
int operator ()( int );
};
int  CallBack:: operator ()( int  arg) //第一個圓括弧總是空的,因為它代表 過載的操作符名;第二個圓括弧是引數列表。
{
    
return  ( - arg);
}

int  fun(CallBack call, int  arg) //注意call是物件,而不是函式。
{
    
return  call(arg); //編譯器將語句call(arg)轉化為call.operator()(arg);
}

// 使用函式指標 typedef  int  ( * callback)( int );
int  callfun( int  arg)
{
    
return  ( - arg);
}

int  fun2(callback call, int  arg)
{
    
return  call(arg);
}

int  main()
{
    cout
<< fun(CallBack(), 3 ) << endl;
    cout
<< fun2(callfun, 3 ) << endl;
}

結果:

-3

-3

從上面的例子中可以看出,函式物件資料型別被限制在int,而通用性是函式物件的優勢之一,如何建立具有通用性的函式物件呢?方法是使用模板,也就 是將過載的操作符“()”定義為類成員模板,以便函式物件適用於任何資料型別:如double,_int64或char:

#include < iostream > using namespace  std;

// 使用函式物件類模板 template < class  T > class  CallBack2
{
public :
    T 
operator ()(T);
};
template
< class  T >
T CallBack2
< T > :: operator ()(T arg)
{
    
return  ( - arg);
}

int  main()
{
    
// 使用函式物件類模板     cout << CallBack2 < int > ()( 3 ) << endl;
    cout
<< CallBack2 < double > ()( 3.333 ) << endl;    
}

結果:

-3

-3.333

標準庫中函式物件 
C++標準庫定義了幾個有用的函式物件,它們可以被放到STL演算法中。例如,sort()演算法以判斷物件(predicate object)作為其第三個引數。判斷物件是一個返回Boolean型結果的模板化的函式物件。