函數指標(Function Pointers)
我們可以像下面那樣聲明一個指向特定類型函數的指標:
void (*fp)(int); //指向函數的指標
注意,其中的括弧是必不可少的,它表明fp是一個指向傳回值為void的函數的指標,而不是傳回值為void* 的函數。就像指向普通資料的一樣,指向函數的指標也可以為空白,否則它就應該指向一個具有適當類型的函數。
例如:
extern int f( int );
extern void g( long );
extern void h( int );
//...
fp = f; // error! &f is of type int(*)(int), not void(*)(int)
fp = g; // error! &g is of type void(*)(long), not void(*)(int)
fp = 0; // OK, set to null
fp = h; // OK, point to h
fp = &h; // OK, take address explicitly
注意,將一個函數的地址初始化或賦值給一個指向函數的指標時,無需要顯式地取得函數地址,編譯器知道隱式地擷取函數的地址,因此在這種情況下’&’操作符是可選的,通常省略不用。
類似地,為了調用函數指標所指向的函數而對指標進行解引用操作也是不必要的,因為編譯器可以幫你解引用:
(*fp)(12); // 顯式地解引用(explicit dereference)
fp(12); // 隱式地解引用,結果一樣(implicit dereference, same result)
和void* 指標可以指向任何類型的資料不同,不存在可以指向任何類型函數的通用函數指標。還要注意,非靜態成員函數的地址不是一個指標,因此不可以將一個函數指標指向一個非靜態成員函數。(詳細參見其它介紹文檔)
函數指標的一個傳統用途是實現回調(callback)。一個回調是一個可能的動作,這個動作在初始化階段設定,以便在對將來可能發生的事件做出反應時而被調用。舉個例子,如果我們希望救火,那麼最好事先計劃好該如何做出反應:
extern void stopDropRoll();
inline void jumpIn() { ... }
//...
void (*fireAction)() = 0;
//...
if( !fatalist ) { // if you care that you're on fire...
// then set an appropriate action, just in the event!
if( nearWater )
fireAction = jumpIn;
else
fireAction = stopDropRoll;
}
一旦決定了要執行的動作,代碼中的另一個部分就是可以專註於是否以及何時去執行該動作,而無需關心這個動作到底是做什麼的:
if( ftemp >= 451 ) { // if there's a fire...
if( fireAction ) // ...and an action to execute...
fireAction(); // ...execute it!
}
注意,一個函數指標指向內嵌函式(inline function)是合法的。然而,通過函數指標調用內嵌函式將不會導致內聯式的函數調用,因為編譯器通常無法在編譯期間精確地確定將會調用什麼函數。在上一個例子中,fireAction可能指向兩個函數中的任一個(當然,也可能兩個都不指向),因此在調用點,編譯器別無它法,只好產生間接、非內聯的函數調用代碼。
另外,函數指標持有一個重載函數的地址也是合法的:
void jumpIn();
void jumpIn( bool canSwim );
//...
fireAction = jumpIn;
指標的類型被用於在各種不同的候選函數中挑選首選的函數。在這個例子中,fireAction的類型為void(*)(),因此選擇的是第一個jumpIn函數。
在標準庫中,在好幾個地方使用了函數指標作為回調機制,最為突出的就是被標準函數set_new_handler用於設定回調。當全域operator new函數無法履行一個記憶體配置請求時,該回呼函數即被調用。例如:
void begForgiveness() {
logError( "Sorry!" );
throw std::bad_alloc();
}
//...
std::new_handler oldHandler =
std::set_new_handler(begForgiveness);
標準類型名稱new_handler是一個typedef:
typedef void (*new_handler)();
因此,該回呼函數必須是一個不帶參數且返回void的函數。Set_new_handler函數將回調設定為參數,並且返回前一個回調。不存在什麼單獨的用於獲得和設定回調的函數。獲得當前回調需要採用一種迴旋式的慣用手法:
std::new_handler current
= std::set_new_handler( 0 ); // get...
std::set_new_handler( current ); // ...and restore!
另外,標準函數set_terminate和set_unexpected也使用了這種合二為一的get/set回調習慣用法。