想要搶先看後面的章節?打賞本文10元,即可獲得帶插圖全本下載地址!

打賞完成記得私信我哦 :p

第11章

函數的三件馬甲:函數指針、函數對象與Lambda表達式

通過前面的學習我們可以發現,STL中的演算法大多只是提供一個演算法框架,而演算法的核心邏輯往往是通過一個函數的形式來表達的。在一些通用演算法中,通常需要配合一些函數使用,以達到對通用演算法進行自定義的效果。比如,一個通用的sort()排序演算法,可以通過向它提供不同的排序函數來自定義排序的規則;一個通用的find_if()查找演算法,也可以通過不同函數對其匹配規則進行自定義。函數極大地增強了通用演算法的表達能力,使得通用函數做到了真正通吃天下。

這些能夠在通用演算法中使用的函數,雖然它們的本質都是函數,所表達的都是對數據的處理。但是因為應用場景的不同,它們都愛穿上馬甲,在不同的場景下表現出不同的形式:有簡單的普通函數指針,也有複雜的函數對象,更有靈活而優雅的Lambda表達式。下面就來看看函數的這三件馬甲,以免它換個馬甲就不認識了。

11.1 函數指針

所謂函數指針,顧名思義,也就是指向函數的指針。跟指向數據的普通指針一樣,函數指針本質上也是一個變數,它記錄的也是表示某個內存地址的整數數值。只不過這個內存地址上保存的不是普通數據而是某個函數的代碼,所以這個指針就成了指向這個函數的函數指針。

在C++世界中,每個函數都有自己的入口地址,而程序在執行某個函數調用的時候,也總是從這個入口地址開始執行。因此,如果某個函數指針的值是某個函數的入口地址,則說這個函數指針指向這個函數。進而,我們可以通過這個函數指針調用它所指向的函數,就如同我們通過指向普通變數的指針訪問它所指向的變數一樣。

11.1.1 函數指針的定義、賦值與使用

當我們定義普通指針來指向某個變數時,我們需要根據它所指向的這個變數的數據類型來確定指針的類型。比如,我們的指針要指向一個int類型的變數,那麼這個指針的類型就是int*。同樣的,如果我們需要定義函數指針來指向某個函數,也同樣需要根據它所指向的函數來確定這個函數指針的類型。但跟普通指針的類型只是在它所指向變數的類型後面加上一個「*」不同,函數指針類型的確定要稍微複雜一些。在C++中,我們定義一個函數指針的語法形式如下:

函數返回值類型標誌符 (*指針變數名)(形式參數列表);

其中,函數返回值類型標誌符就是這個指針所要指向的函數的返回值類型。指針變數名就是這個函數指針的名字,由於「( )」的優先順序高於「*」符號,所以指針變數名外的括弧必不可少。形式參數列表跟指針所指函數的形式參數列表相同。概括起來,函數指針的定義跟它所指函數的聲明相似(返回值類型和形式參數列表相同),只不過是將函數名換成了「*」加上一個指針變數名。而這也隱含了一個信息,一個函數的函數名, 實際上就是指向這個函數的指針。例如,有這樣一個函數:

// 一個普通的函數
void PrintPass( int nScore )
{
cout<<nScore<<endl;
}

如果要定義一個函數指針指向這個函數,則可以使用下面的代碼:

// 定義函數指針
void (*pPrintFunc)( int nScore );

這樣,就定義了一個可以指向PrintPass()函數的函數指針pPrintFunc,當然,它也可以指向任何返回值類型為void,同時擁有一個int類型參數的普通函數。另外需要注意的是,當定義函數指針時,參數列表中的形式參數名可有可無,以上代碼也可以寫成下面這種簡化的形式:

// 省略形式參數名的函數指針定義
void (*pPrintFunc)( int );

當函數的形式參數比較多時,通常省略形式參數名,讓函數指針的定義更加簡潔。

函數指針的定義比較繁瑣,如果要定義多個同一類型的函數指針,還可以使用typedef關鍵字將這種函數指針類型定義成一種新的數據類型,用這種新的數據類型來定義函數指針。例如:

// 定義一種新的函數指針的數據類型
// 這種類型的函數指針可以指向的函數的返回值類型是void,
// 同時擁有一個int類型的參數
typedef void (* PRINTFUNC )(int);
// 使用新的數據類型定義多個同類型的函數指針
PRINTFUNC pFuncFailed;
PRINTFUNC pFuncPass;

這裡,就定義了一種新的函數指針類型PRINTFUNC,它表示這種類型的函數指針可以指向一個返回值類型為void同時擁有一個int類型參數的函數。

完成函數指針的定義後,就可以用函數名給函數指針賦值,讓它指向這個函數。

// 用函數名給函數指針賦值
pPrintFunc = PrintPass;

這種使用typedef關鍵字定義函數指針類型的方式,雖然可以連續定義多個同類型的函數指針,在一定程度上簡化了函數指針的定義,但是我們依舊要定義函數指針類型本身,在定義一些複雜函數指針類型時,這個過程不僅繁瑣,而且很容易出錯。C++真是程序員們的貼心小棉襖。它早就想到了這一點,於是提供了一個auto關鍵字來解決這個問題。我們可以直接使用auto關鍵字作為函數指針的數據類型來定義一個函數指針並同時給它賦值,至於這種類型的具體定義就留給編譯器根據這個指針的初始值自己去推斷好了。因為它最擅長干這些活,而程序員的腦子就用來思考問題吧。利用auto關鍵字,可以這樣定義函數指針:

// 利用auto關鍵字定義函數指針
// 編譯器會在變數賦值的時候,自動推斷函數指針的具體說明
auto pPrintFunc = PrintPass;

就像我們定義指向數據的普通數據指針,是為了通過它靈活地訪問它所指向的數據, 而我們定義指向函數的函數指針的目的,也同樣是為了通過它靈活地訪問它所指向的函數。與利用函數名直接調用函數相比,兩者的效果完全相同,同時在形式上也十分相近。例如:

// 通過函數名直接調用PrintPass()函數
PrintPass( 75 );
// 通過函數指針pPrintFunc
// 調用它所指向的PrintPass()函數
(*pPrintFunc)( 75 );

這裡,在函數指針前加上一個「*」符號,就相當於得到了它所指函數的函數名,就如同在普通數據指針前加上「*」可以得到它所指向的數據一樣。然後跟普通函數調用一樣,在函數名後的「()」中加上相應的參數,就可以實現對它所指向函數的調用。因為函數指針pPrintFunc所指向的是PrintPass()函數,所以以上這兩行代碼是等效的,都是以75作為實際參數調用PrintPass()函數。


推薦閱讀:
相关文章