回撥函式,函式指標與函式物件
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型結果的模板化的函式物件。