1. 程式人生 > >C++ 理解函式物件與lambda表示式

C++ 理解函式物件與lambda表示式

參考《21天學通C++》第21與第22章節,對函式物件進行介紹,同時通過lambda表示式這一匿名函式物件的簡潔方式加深對函式物件的理解。本篇博文的主要內容是:

(1) 函式物件的概念;

(2) 將函式物件用作謂詞;

(3) 如何使用函式物件實現一元、二元謂詞;

(4) 如何編寫lambda表示式;

(5) 如何將lambda表示式用作謂詞;

(6) 如何編寫可儲存和可操作狀態的lambda表示式。

一、 函式物件

1. 函式物件的概念和種類

函式物件是C++實體,從概念上講,函式物件是用作函式的物件;從實現上說,函式物件是實現了operator()的類的物件。雖然函式和函式指標也可視為函式物件,但是吸納了operator()的類的物件才能儲存狀態,才能用於標準模板庫演算法。C++常用於STL演算法的函式物件可分為下面兩種型別:(1) 一元函式:接受一個引數的函式,如果一元函式返回一個布林值,稱之為謂詞;(2) 二元函式:接受兩個引數的函式,若返回bool,則稱為二元謂詞函式。返回bool值得函式物件常常用來進行判斷的演算法,組合兩個函式物件的函式物件稱之為自適應函式物件。

2. 函式物件的典型用途

(1) 一元函式的用途

對於STL演算法std::for_each()接受三個引數,第一個引數指定範圍的起點,第二個引數指定範圍的終點,第三個引數是要對指定範圍內的每個元素呼叫的函式。這個函式就可以通過一元謂詞實現,舉例如下:

template <typename elementType>

struct DisplayElement

{

void operator () (const elementType& element) const

{

cout<< element << ' ';

}

};

vector<int> vectorElement;

for_each(vectorElement.begin(), vectorElement.end(), DisplayElement<int> ());

這樣就可以使用STL演算法for_each來對指定範圍內的資料執行相同的函式方法。這裡也可以使用匿名函式物件,即lambda表示式,如下所示,將上面的for_each改造為相同的功能:

for_each(vectorElement.begin(), vectorElement.end(), [](int& element) { cout << element << ' ';});

如果能夠使用結構的物件來儲存資訊,則使用在結構中實現的函式物件的優點將顯現出來。

(2) 一元謂詞的用途

返回布林值的一元函式就是謂詞,這種函式可用於STL演算法的判斷。

一元謂詞被大量用於STL演算法中,例如,std::partition演算法使用一元謂詞來劃分範圍,stable_partition演算法也使用一元謂詞劃分範圍,但保持元素的相對順序不變。諸如std::find_if等查詢函式以及std::remove_if等刪除元素的函式也使用一元謂詞,其中std::remove_if刪除指定範圍內滿足謂詞條件的元素。

(3) 二元函式的用途

如果函式f(x,y)根據輸入引數返回一個值,它將很有用。這種二元函式可用於對兩個運算元執行運算,如加減乘除等。同樣實現最重要的operator()。在std::transform等演算法中,可使用二元乘積函式計算兩個容器內容的點乘。

template <typename elementType>

class Multiply

{

public:

elementType operator () (const elmentType& elem1,const elmentType& elem2)

{ return (elem1 * elem2);}

}

vector<int> vecMultiplicand, vecMultiplier;

vector<int> vecResult;

transform(vecMultiplicand.begin(), vecMultiplicand.end(), vecMultiplier.begin(), vecResult.begin(), Multiply <int>());

transform將兩個範圍的內容相乘,並將結果儲存在第三個範圍中。這裡,這三個範圍分別儲存在型別std::vector的vecMultiplicand、vecMultiplier和vecResult中。乘法運算是通過呼叫二元函式Multiply::operator ()執行的,對源範圍和目標範圍內的每個元素都呼叫了該函式。operator()的返回值儲存在vecResult中。

(4) 二元謂詞的用途

接受兩個引數並返回一個布林值的函式是二元謂詞,這種函式用於諸如std::sort等STL函式中。使用排序二元謂詞實現排序,通過實現operator()來動態排序。

很多STL演算法都使用二元謂詞,比如刪除相鄰重複元素的std::unique()、排序演算法sort、排序並保持相對順序的std::stable_sort以及兩個範圍進行操作的std::transform。

(5) 總結

在結構或類中實現函式物件時,它將比簡單函式有用的多,因為它也可用於儲存與狀態相關的資訊。謂詞是一類特殊的函式物件。

二、 C++ lambda表示式

1. lambda表示概念

可將lambda表示式視為包含公有operator()的匿名結構(或類),從這種意義上說,lambda表示式屬於函式物件。從上面所講到的進行分析:

for_each(vectorElement.begin(), vectorElement.end(), [](int& element) { cout << element << ' ';});

編譯器加到下述lambda表示式時:[](int& element) { cout << element << ' ';}自動將其展開為類似結構DisplayElement<int>的表示:

struct DisplayElement

{

void operator () (const int& element) const

{

cout<< element << ' ';

}

};

2. 如何定義lambda表示式

以方括號[]打頭,告訴編譯器,接下來是一個lambda表示式,方括號後面是一個引數列表,該引數列表與不使用lambda表示式時提供給operator()引數列表相同。

3. 一元函式對應的lambda表示式

與一元operator(Type)對應的lambda表示式接收一個引數,定義如下:

[](Type paramName){ // lambda expression code here}也可用引用來傳參:[](Type& paramName){ // lambda expression code here}。

4. 一元謂詞對應的lambda表示式

謂詞可幫助做出決策,一元謂詞是返回bool型別的一元表示式。舉例如下:

[](int& Num){return ((Num % 2) == 0);}

在這裡,返回值的性質是為了讓編譯器知道該lambda表示式的返回型別為bool。在演算法中可將lambda表示式用於一元謂詞,如可在find_if中使用上述lambda表示式找出集合中的偶數。如果謂詞返回true,find_if將返回一個指向相應元素的迭代器,指出找到一個滿足條件的元素。

5. 通過捕獲列表接受狀態變數的lambda表示式

對於上面的一元謂詞實現在整數能被2整除時返回true,如果讓它更通用,在數字能被使用者指定除數整除時返回true,可採用狀態變數:

int Divisor = 2;

[Divisor ](int& Num){return ((Num % Divisor ) == 0);}

一系列以狀態變數的方式傳遞的引數,也被稱為lambda表示式的捕獲列表。

6. lambda表示式的通用語法

[StateVar1,StateVar1](Type& param) { // }

如果要在lambda表示式中修改這些狀態變數,可新增關鍵字multable:

[StateVar1, StateVar1](Type& param)  multable { // }

這樣便可在lambda表示式中修改捕獲列表[]中指定的變數,但離開lambda表示式後,這些修改便無效,要確保在lambda表示式內部對狀態變數的修改在其外部也有效,應按照引用傳遞它們:

[&StateVar1, &StateVar1](Type& param) { // }

lambda表示式還可以接受多個輸入引數,為此可用逗號分隔:

[StateVar1, StateVar1](Type1& param1,Type2& param2) { // }

如果要向編譯器明確指明返回型別,可使用->,如下:

[StateVar1, StateVar1](Type1& param1,Type2& param2)-> ReturnType { return (value or expression); }

最後,複合語句{}可包含多條用分號分割的語句:如下

[StateVar1, StateVar1](Type1& param1,Type2& param2)-> ReturnType { Statement 1; Statement 2;return (value or expression); }

如果lambda表示式包含多行程式碼,必須顯示地指明返回型別。

7. 二元函式對應的lambda表示式

二元函式接受兩個引數,還可返回一個值,與之等價的lambda表示式如下:

[...] (Type1& param1,Type2& param2){ // }

8. 二元謂詞對應的lambda表示式

返回true或false,可幫助決策的二元函式被稱為二元謂詞函式。這種謂詞可用於std::sort等排序演算法。

[...] (Type1& param1,Type2& param2){ return bool expression; }

9. 總結

僅當lambda表示式簡潔、高效時才能使用它;

記住lambda表示式總是以[]打頭;

除非使用關鍵字multable進行指定,否則不能修改捕獲列表中指定的狀態變數;

lambda表示式是實現了operator()的匿名類或結構;

注意使用const對引數進行限定;

lambda表示式的語句塊包含多條語句時,要顯式的指定返回型別;

不要使用很長的包含多條語句的lambda表示式,而應轉而使用函式物件,因為每次使用lambda表示式時,都要重新定義它,這無助於提高程式碼的重用性。

******************************************************************************************************

2015-8-3