程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++ 理解函數對象與lambda表達式

C++ 理解函數對象與lambda表達式

編輯:關於C++

參考《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

struct DisplayElement

{

void operator () (const elementType& element) const

{

cout<< element << ' ';

}

};

vector vectorElement;

for_each(vectorElement.begin(), vectorElement.end(), DisplayElement ());

這樣就可以使用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

class Multiply

{

public:

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

{ return (elem1 * elem2);}

}

vector vecMultiplicand, vecMultiplier;

vector vecResult;

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

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的表示:

 

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表達式時,都要重新定義它,這無助於提高代碼的重用性。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved