C++11 lambda表达式、function类模板、bind函数适配器
文章目錄
- lambda表達式
- lambda表達式的語法
- lambda表達式的原理
- function模板
- function與重載函數
- bind函數適配器
lambda表達式
當我們在寫代碼的時候如果經常需要用到一個公用的功能,為了方便維護,通常就會通過重載類的operator ()來將其寫成仿函數(具有函數特性的類)來使用。
從使用的角度來講,仿函數雖然好用,但是如果需要根據多種不同的情況設計多個函數的時候,這時候使用仿函數無疑是一種折磨,不僅僅代碼量增加,而且還有大量的代碼冗余。
例如下面這種情況
在我們使用購物網站的時候,通常都可以選擇以不同的屬性例如名字、價格、好評度、銷量等來為商品進行排序
這樣的代碼不僅有上面的缺點,他的可讀性也非常的差,簡單的邏輯還好,倘若復雜的話接手代碼的人一定心態爆炸
所以C++11引入了lambda表達式來解決這個問題
同樣的代碼,在引入了lambda表達式后代碼的可讀性就大大的提升了。
sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._price > g2._price; });sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._price < g2._price; });sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._num > g2._num; });sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._num < g2._num; });lambda表達式的語法
lambda表達式書寫格式:
[capture-list] (parameters) mutable -> returntype { statement }lambda表達式各部分說明
[capture-list] : 捕捉列表,該列表總是出現在lambda函數的開始位置,編譯器根據[]來判斷接下來
的代碼是否為lambda函數,捕捉列表能夠捕捉上下文中的變量供lambda函數使用,捕捉列表描述了上下文中那些數據可以被lambda使用,以及使用的方式傳值還是傳引用。
捕獲列表:
- [var]:表示值傳遞方式捕捉變量var
- [=]:表示值傳遞方式捕獲所有父作用域中的變量(包括this)
- [&var]:表示引用傳遞捕捉變量var
- [&]:表示引用傳遞捕捉所有父作用域中的變量(包括this)
- [this]:表示值傳遞方式捕捉當前的this指針
注意事項:
- a. 父作用域指包含lambda函數的語句塊
- b. 語法上捕捉列表可由多個捕捉項組成,并以逗號分割。 用方式捕捉其他變量 c. 捕捉列表不允許變量重復傳遞,否則就會導致編 譯錯誤。 比如:[=, a]:=已經以值傳遞方式捕捉了所有變量,捕捉a重復
- d. 在塊作用域以外的lambda函數捕捉列表必須為空。
- e. 在塊作用域中的lambda函數僅能捕捉父作用域中局部變量,捕捉任何非此作用域或者非局部變量都會導致編譯報錯。
- f. lambda表達式之間不能相互賦值,即使看起來類型相同
(parameters):參數列表。與普通函數的參數列表一致,如果不需要參數傳遞,則可以連同()一起
省略
mutable:默認情況下,lambda函數總是一個const函數,mutable可以取消其常量性。使用該修
飾符時,參數列表不可省略(即使參數為空)。
->returntype:返回值類型。用追蹤返回類型形式聲明函數的返回值類型,沒有返回值時此部分
可省略。返回值類型明確情況下,也可省略,由編譯器對返回類型進行推導。
{statement}:函數體。在該函數體內,除了可以使用其參數外,還可以使用所有捕獲到的變量。
在lambda函數定義中,參數列表和返回值類型都是可選部分,而捕捉列表和函數體可以為空。
因此C++11中最簡單的lambda函數為:[]{}; 該lambda函數不能做任何事情。
例如使用lambda表達式來實現一個reverse
//因為lambda表達式本質是一個匿名函數,是他自己唯一的未命名類型,所以需要使用auto來進行類型推導 auto reverse = [](string& str){int begin = 0, end = str.size() - 1;while (begin < end){std::swap(str[begin++], str[end--]);} };int main() {//使用變量名調用即可string str = "HelloWorld";reverse(str);return 0; }lambda表達式的原理
下面來看看lambda表達式在匯編下的調用過程
轉入反匯編進行查看,當我們調用lambda表達式時,可以看到編譯器在底層調用了lamber_uuid類(uuid為中間的編號)的operator(),這個做法是不是有點眼熟,這就是仿函數的做法。
下面與仿函數進行一個對比
無論是從使用上看,還是從底層的匯編代碼來看,lambda表達式都與仿函數幾乎一模一樣,所以由此可以推斷出lambda表達式的原理,當定義了lambda表達式之后,編譯器按照仿函數的形式自動生成一個lamber_uuid類,并重載其中的operator(),當用戶進行調用的時候會自動通過該類的匿名對象來調用,使得其看起來和普通的函數一樣。
function模板
C++中可調用對象(如函數指針,仿函數,lambda表達式等)的雖然都有一個比較統一的操作形式,但是定義方法五花八門,這樣就導致使用統一的方式保存可調用對象或者傳遞可調用對象時,會十分繁瑣。C++11中提供了std::function和std::bind統一了可調用對象的各種操作。
例如
// 普通函數 int add(int a, int b){return a+b;} // lambda表達式 auto mod = [](int a, int b){ return a % b;}// 仿函數 struct divide{int operator()(int denominator, int divisor){return denominator/divisor;} };上面的幾種不同的可調用對象雖然類型不同,但是根據參數和返回值可以共享同一種調用形式int(int ,int)
通過function就可以統一其調用形式
定義格式:std::function<返回值(參數列表)> 名字
- std::function 是一個可調用對象包裝器,是一個類模板,可以容納除了類成員函數指針之外的所有可調用對象,它可以用統一的方式處理函數、函數對象、函數指針,并允許保存和延遲它們的執行。
- std::function可以取代函數指針的作用,因為它可以延遲函數的執行,特別適合作為回調函數使用。它比普通函數指針更加的靈活和便利。
function與重載函數
在使用function的時候還有一個需要注意的點,就是我們不能將重載過的函數直接放入function對象中,因為會有二義性的問題
例如
bind函數適配器
對于那些只在少量地方使用的簡單操作,使用lambda表達式時非常好的,但是如果使用的地方多了,就應該定義一個函數,而不是多次編寫相同的lambda表達式。而庫函數提供的模板參數又大多是以一個可調用的對象來進行接收,而如果需要將函數寫出仿函數的形式,又增添了不少麻煩,所以C++11提供了一個通用的函數適配器bind,它接受一個可調用對象,生成一個新的可調用對象來“適應”原對象的參數列表。(其實就是創建一個對象,將提供的函數作為其operator()的重載)
std::bind將可調用對象與其參數一起進行綁定,綁定后的結果可以使用std::function保存。std::bind主要有以下兩個作用:
- 將可調用對象和其參數綁定成一個仿函數;
- 只綁定部分參數,減少可調用對象傳入的參數。
bind的語法如下
bind (callable, arg_list); /*返回值為綁定完成的可調用對象參數callable為需要綁定的函數arg_list為參數列表 */下面以reverse舉個例子
void reverse(string& str) {int begin = 0, end = str.size() - 1;while (begin < end){std::swap(str[begin++], str[end--]);} }int main() {string str = "HelloWorld";auto func = bind(reverse,std::placeholders::_1);func(str);cout << str << endl;return 0; }在arg_list中通常還會包含形如_n的名字,這是位于中,std::placeholders::_n占位符,表示它們占據了傳遞給callable的第n個參數,_1為第一個,_2為第二個,以此類推。
例如我們可以利用占位符來固定住第2個參數,只讓用戶傳第一個參數
int add(int x, int y) {return x + y; }int main() {auto func = bind(add, std::placeholders::_1, 5);cout << func(4) << endl;//9return 0; }如果需要綁定成員函數的時候,因為成員函數不會被隱式轉換為函數指針,還需要取地址,還需要其指明其來源的對象。
例如
當需要傳遞的參數為引用時
bind的那些不是占位符的參數會被拷貝到bind返回的可調用對象中。但是,與lambda類似,有時對有些綁定的參數希望以引用的方式傳遞,或是要綁定參數的類型無法拷貝。這時候就需要借助到庫函數中的ref函數
ostream & print(ostream &os, const string& s, char c) {os << s << c;return os; }int main() {ostringstream os1;//ostringstream沒有拷貝構造,所以不能傳拷貝,只能傳引用bind(print, ref(os1), _1, c) }總結
以上是生活随笔為你收集整理的C++11 lambda表达式、function类模板、bind函数适配器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高级数据结构与算法 | B树、B+树、B
- 下一篇: s3c2440移植MQTT