lambda 表達式:
Lambda表達式完整的聲明格式如下:
[capture list] (params list) mutable exception-> return type { function body }
各項具體含義如下:
- capture list:捕獲外部變量列表
- params list:形參列表
- mutable 指示符:用來說用是否可以修改捕獲的變量
- exception:異常設定
- return type:返回類型
- function body:函數體
我們這里先不討論 exception
我們可以忽略參數列表和返回類型,但必須永遠包含捕獲列表和函數體:
1 #include <iostream> 2 using namespace std; 3 4 int main(void){ 5 auto f = [] { return 42; }; 6 auto g = [] {int a = 1; a++; return a;}; 7 cout << f() << endl;//42 8 cout << g() << endl;//2 9 return 0; 10 }
注意:在 lambda 中忽略括號和參數列表等價於指定一個空參數列表。在此列表中,當調用 f 時,參數列表是空的。如果忽略返回類型,lambda 根據函數體中的代碼推斷出返回類型。在 c++11 標准中,如果 lambda 的函數體包含任何單一 return 語句之外的內容,且未指定返回類型,則返回 void。不過在很多編譯器中好像只要有 return 語句就會返回編譯器推斷的類型~
向 lambda 傳遞參數:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 using namespace std; 5 6 int main(void){ 7 vector<string> words = {"df", "fsl", "feg", "laf", "fsfl", "jfsoe"}; 8 stable_sort(words.begin(), words.end(), 9 [](const string &a, const string &b) 10 {return a.size() < b.size();}); 11 for(const auto &indx : words){ 12 cout << indx << " "; 13 } 14 cout << endl;//df fsl feg laf fsfl jfsoe 15 return 0; 16 }
其中,空捕獲列表表名此 lambda 不使用它所在函數中的任何局部變量。當 stable_sort 需要比較兩個元素時,它就會調用給定的這個 lambda 表達式。
注意:通常,實參和形參類型必須匹配。但與普通函數不同,lambda 不能有默認參數。因此,一個 lambda 調用的實參數目永遠與形參數目相等。一旦形參初始化完畢,就可以執行函數體了。
使用捕獲列表 / find_if / for_each:
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 using namespace std; 5 6 int gg = 2; 7 8 int main(void){ 9 vector<string> words = {"df", "fsl", "feg", "laf", "fsfl", "jfsoe"}; 10 int sz1 = 3, sz2 = 2; 11 auto wc = find_if(words.begin(), words.end(), 12 [sz1, sz2](const string &a){//捕獲sz1,sz2變量 13 return a.size() >= sz1 || a.size() >= sz2;//使用捕獲的變量sz1,sz2 14 });//find_if返回第一個滿足謂詞條件的迭代器 15 16 cout << *wc << endl;//df 17 auto count = words.end() - wc; 18 cout << count << endl;//6 19 20 const int sz = 3; 21 auto indx = find_if(words.begin(), words.end(), 22 [](const string &a){ 23 return a.size() >= sz;//常量可以不捕獲就使用!!! 24 }); 25 26 int len = 3; 27 // auto indx = find_if(words.begin(), words.end(), 28 // [](const string &a){ 29 // return a.size() >= len;//錯誤,len變量沒有被捕獲不能使用 30 // }); 31 32 static int b = 3; 33 auto id = find_if(words.begin(), words.end(), 34 [](const string &a){ 35 return a.size() >= b || a.size() >= gg;//static變量和lambda所在函數之外的變量也可以直接使用 36 }); 37 38 for_each(wc, words.end(), 39 [](const string &s){//對迭代器區間的每個元素都調用一遍謂詞 40 cout << s << " "; 41 });//df fsl feg laf fsfl jfsoe 42 cout << endl; 43 44 return 0; 45 }
注意:這里的 lambda 表達式是作為 find_if 和 for_each 的謂詞的,lambda 的形參列表接受這兩個函數的輸入並對輸入序列中的元素調用謂詞,返回一個能用作條件的值
捕獲列表捕獲其所在函數中的局部變量,使它們能在 lambda 函數體中被使用
形參列表中的參數也能直接在 lambda 函數體中使用
發現函數體中的 const 變量能直接在 lambda 函數體中直接使用
static 變量和 lambda 函數之外定義的變量也可以直接使用
lambda 捕獲和返回:
當定義一個 lambda 時,編譯器生成一個與 lambda 對應的新的(未命名的)類類型。當向一個函數傳遞一個 lambda 時,同時定義了一個新類型和該類型的一個對象:傳遞的參數就是此編譯器生成的類類型的未命名對象。類似的,當使用 auto 定義一個用 lambda 初始化的變量時,定義了一個從 lambda 生成的類型的對象。
默認情況下,從 lambda 生成的類都包含一個對應 lambda 所捕獲的變量的數據成員。類似任何普通類的數據成員,lambda 的數據成員也在 lambda 對象創建時就被初始化。
值捕獲:
類似於參數傳遞,變量的捕獲方式也可以是值或引用。前面的代碼我們用的都是值捕獲。與傳值參數類似,采用值捕獲的前提是變量可拷貝。與參數不同的是被捕獲的變量的值是在 lambda 創建時拷貝的,而不是調用時拷貝:
1 #include <iostream> 2 #include <fstream> 3 using namespace std; 4 5 int main(void){ 6 size_t v1 = 42; 7 auto f = [v1]{return v1;};//將v1拷貝到名為f的可調用對象 8 v1 = 0; 9 auto j = f(); 10 cout << j << endl;//42 j 保存了我們創建它時v1的拷貝 11 12 ofstream cc; 13 // auto g = [os]{};//錯誤,ostream類型的對象是不可拷貝的,不能用值捕獲 14 auto g = []{cout << 1;};//cout定義在iostream頭文件中,可以直接使用 15 16 return 0; 17 }
注意:由於被捕獲的變量的值是在 lambda 創建時拷貝,因此隨后對其修改不會影響到 lambda 內對應的值
不能被拷貝的對象不能用於 lambda 值捕獲
引用捕獲:
引用捕獲和一般的引用傳參行為類似
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 using namespace std; 5 6 void fun(void){ 7 size_t v1 = 42; 8 auto f = [&v1]{return v1;};//對v1引用捕獲 9 v1 = 0; 10 auto j = f(); 11 cout << j << endl;//0 12 } 13 14 void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' '){ 15 for_each(words.begin(), words.end(), 16 [&os, c](const string &s){//IO對象只能引用捕獲 17 os << s << c; 18 });//fjks fsl fjsl fiwo 19 } 20 21 int main(void){ 22 fun(); 23 vector<string> v = {"fjks", "fsl", "fjsl", "fiwo"}; 24 biggies(v, 4); 25 26 return 0; 27 }
注意:使用捕獲一個引用(迭代器,指針)變量時,必須確保被引用的對象在 lambda 執行的時候是存在的
lambda 捕獲的都是局部變量,這些變量在函數接受后就不復存在了
如果 lambda 可能在函數結束后執行,捕獲的引用指向的局部變量已經消失
對於不能拷貝的對象,如 IO 對象,必須采用引用捕獲
隱式捕獲:
除了顯示列出我們希望使用的來自所在函數的變量之外,還可以讓編譯器根據 lambda 體中代碼來判斷我們要使用哪些變量。為了指示編譯器判斷捕獲列表,應該在捕獲列表中寫一個 & 或 =。& 告訴編譯器采用引用的捕獲方式,= 則表示值捕獲方式:
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 using namespace std; 5 6 void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' '){ 7 for_each(words.begin(), words.end(),//如果我們希望對一部分變量采用值捕獲,對其它變量采用引用捕獲,可以混合使用隱式捕獲和顯示捕獲 8 [&, c](const string &s){//os隱式捕獲,引用捕獲方式;c顯示捕獲,值捕獲方式 9 os << s << c; 10 });//df fsl feg laf fsf 11 12 cout << endl; 13 14 for_each(words.begin(), words.end(), 15 [=, &os](const string &s){//os顯示捕獲,引用捕獲方式;c隱式捕獲,值捕獲方式 16 os << s << c; 17 });//df fsl feg laf fsf 18 19 cout << endl; 20 21 int cc = 1; 22 for_each(words.begin(), words.end(), 23 [&, cc, c](const string &s)mutable{//錯誤,兩個都使用隱式捕獲,編譯器不能區分 24 os << cc++ << c << s << endl; 25 }); 26 27 // for_each(words.begin(), words.end(), 28 // [cc, c, &](const string &s)mutable{//只能捕獲列表中第一個元素可以使用隱式捕獲 29 // os << cc++ << c << s << endl; 30 // }); 31 32 // for_each(words.begin(), words.end(), 33 // [=, =](const string &s)mutable{//只能有一個捕獲是隱式的 34 // cout << cc++ << c << s << endl; 35 // }); 36 } 37 38 int main(void){ 39 int sz = 3; 40 vector<string> words = {"df", "fsl", "feg", "laf", "fsfl", "jfsoe"}; 41 auto wc = find_if(words.begin(), words.end(), 42 [=](const string &s){//sz為隱式捕獲,值捕獲方式 43 return s.size() >= sz; 44 }); 45 cout << *wc << endl;//fsl 46 47 biggies(words, words.size()); 48 49 return 0; 50 }
注意:當我們混合使用隱式捕獲和顯示捕獲時,捕獲列表中的第一個元素必須是 & 或 =。此符號指定了默認捕獲方式為引用或值
當混合使用隱式捕獲和顯示捕獲時,顯示捕獲的變量必須使用與隱式捕獲不同的方式。即,如果隱式捕獲是引用方式,則顯示捕獲必須采用值方式。反之亦然~
捕獲列表中只能有一個元素采用隱式捕獲,且該變量必須是捕獲列表中的第一個元素
可變 lambda:
1 #include <iostream> 2 using namespace std; 3 4 int main(void){ 5 int x = 1; 6 // auto gel = [x](){ 7 // x++;//這里的x不能改變 8 // return x; 9 // }; 10 11 // auto gel = [x] mutable{//錯誤,加了mutable關鍵字后不能省略參數列表 12 // x++; 13 // return x; 14 // }; 15 16 auto gel = [x] () mutable{ 17 return ++x; 18 }; 19 cout << gel() << endl;//2 20 21 const int y = 1; 22 // auto cc = [y] () mutable{ 23 // return ++y;//雖然加了mutable關鍵字,但y本身是const變量,所以不能修改 24 // }; 25 //即在顯示值捕獲中,拷貝得到的形參仍然是const的 26 27 auto cc = [&x] { 28 return ++x;//對於引用捕獲,如果引用綁定的是一個非const對象,則不加mutable也是可變的 29 }; 30 31 // auto cb = [&y] { 32 // return ++y;//y是一個const對象,不可變 33 // }; 34 35 auto cd = [&x] () mutable{ 36 return ++x; 37 }; 38 cout << gel() << endl;//3 39 40 // auto ce = [&y] () mutable{ 41 // return ++y;//加了mytable,但y是const的,不可變 42 // }; 43 44 return 0; 45 }
注意:對於值捕獲,若沒加 mutalbe 關鍵字,則其捕獲變量一定是不可變的,加了 mutable 關鍵字則取決於對應變量在函數中聲明是否是 const 的。而引用捕獲變量是否可變與 mutable 無關,只取決於綁定的對象是否是 const 的。
指定 lambda 返回類型:
1 #include <iostream> 2 #include <algorithm> 3 #include <array> 4 using namespace std; 5 6 int main(void){ 7 array<int, 10> a = {1, -1, 2, -3, 4, -459, -94}; 8 //transform接受3個迭代器和一個可調用對象.前兩個迭代器表輸入序列,第三個迭代器表目標位置。 9 //算法對輸入序列中每個可調用元素調用可調用對象,並將結果寫入目的位置 10 transform(a.begin(), a.end(), a.begin(), 11 [] (int i) { 12 return i < 0 ? -i : i; 13 }); 14 15 //如果我們將上面代碼改寫為看起來等價的if語句,按照c++11標准會編譯錯誤,因為lambda函數體中如果含有多個return語句的話編譯器推斷是返回void的 16 //不過我用g++11通過了編譯欸~ 17 transform(a.begin(), a.end(), a.begin(), 18 [] (int i) { 19 if(i < 0) return -i; 20 else return i; 21 }); 22 23 //符合c++11的標准的寫法 24 transform(a.begin(), a.end(), a.begin(), 25 [] (int i) -> int { 26 if(i < 0) return -i; 27 return i; 28 }); 29 30 return 0; 31 }
注意:對於不能正確推斷出返回類型的情況,必須使用尾置返回類型指明返回值的類型
在 lambda 中不能訪問 protected 成員:
1 #include <cstdio> 2 3 class A{ 4 protected: 5 void Somefunc(){ 6 printf("Hello world!"); 7 } 8 }; 9 10 class B{ 11 public: 12 template<class F> 13 void D(F func){ 14 func(); 15 } 16 }; 17 18 class E : public A{ 19 public: 20 void Myfunc(){ 21 A::Somefunc(); // works 22 B C; 23 // C.D([&](){//錯誤,lambda的作用域是單獨的,不能在其中調用受保護的類成員 24 // A::Somefunc(); // not works 25 // }); 26 27 C.D([&](){ 28 this->Somefunc(); //我們可以利用繼承的特性直接使用protected成員嘛~ 29 }); 30 } 31 }; 32 33 int main(){ 34 E F; 35 F.Myfunc(); 36 37 return 0; 38 }
參考:https://segmentfault.com/q/1010000000479642
參數綁定:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 using namespace std; 5 6 int main(void){ 7 int sz = 5; 8 vector<string> v = {"jkfd", "jfkls", "fj", "fjslf", "fjsljfslf"}; 9 auto cnt = find_if(v.begin(), v.end(), 10 [sz] (const string &s) { 11 return s.size() > sz; 12 }); 13 14 cout << *cnt << endl;//fjsljfslf 15 16 return 0; 17 }
對於 sort 中的謂詞通常我們可以將其寫成一個單獨的可調用函數,但是對於上面代碼中 find_if 中的謂詞,如果我們不將 sz 定義成全局變量顯然不能將其寫成一個單獨函數的形式。因為 find_if 接受的是一個一元謂詞,這意味着我們不能給其謂詞函數不能有兩個形參~
標准庫 bind 函數:
我們可以通過使用一個名為 bind 的標准庫函數來解決上面提到的問題,它定義在 functional 頭文件中。可以將 bind 函數看作一個通用的函數適配器,它接受一個可調用對象,生成一個新的可調用對象來 "適應" 原對象的參數列表。調用 bind 的一般形式為:
auto newCallable = bind(callable, arg_list);
其中,newCallable 本身是一個可調用對象,arg_list 是一個逗號分隔的參數列表,對於給定的 calloble 的參數。即,當我們調用 newCallable 時,newCallable 會調用 callable,並傳遞給它 arg_list 中的參數。
arg_list 中的參數可能包含形如 _n 的名字,其中 n 是一個整數。這些參數是 "占位符",表示 newCallable 的參數,它們占據了傳遞給 newCallable 的參數的 "位置"。數值 n 表示 生成的可調用對象中參數的位置:_1 為 newCallable 的第一個參數,_2 為第二個參數,依此類推。
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <functional> 5 using namespace std; 6 7 bool check_size(const string &s, int sz){ 8 return s.size() >= sz; 9 } 10 11 int main(void){ 12 int sz = 5; 13 vector<string> v = {"jkfd", "jfkls", "fj", "fjslf", "fjsljfslf"}; 14 auto check6 = bind(check_size, std::placeholders::_1, sz);//_n都定義在命名空間placeholders中,且此命名空間又定義在命名空間std中 15 //check6是一個可調用對象,接受一個string類型的參數 16 auto cnt = find_if(v.begin(), v.end(), check6); 17 18 cout << *cnt << endl;//fjslf 19 20 return 0; 21 }
注意:此處 bind 調用只有一個占位符,表示 check6 只接受單一參數。占位符出現在 args_list 的第一個位置,表示 check6 的此參數對應 check_size 的第一個參數。此參數是一個 const string&。因此,調用 check6 必須傳遞給他一個 string 類型的參數,check6 會將此參數傳遞給 check_size
_n 都定義在命名空間 placeholders 中,且此命名空間又定義在命名空間 std 中。我們也可以通過 using 來聲明:
using std::placeholders::_1;
不過這種聲明意味着我們對每個占位符名字都必須提供一個單獨的 using 聲明。我們可以使用另外一種更方便一些的聲明方式:
using namespace namespace_name;
則所有來自 namespace_name 中定義的名字我們都可以直接在程序中直接使用:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <functional> 5 using namespace std; 6 7 using namespace std::placeholders; 8 9 bool check_size(const string &s, int sz){ 10 return s.size() >= sz; 11 } 12 13 int main(void){ 14 int sz = 5; 15 vector<string> v = {"jkfd", "jfkls", "fj", "fjslf", "fjsljfslf"}; 16 auto check6 = bind(check_size, _1, sz); 17 auto cnt = find_if(v.begin(), v.end(), check6); 18 19 cout << *cnt << endl;//fjslf 20 21 return 0; 22 }
注意:使用這種方式需要注意命名沖突問題
bind 參數順序問題:
1 #include <iostream> 2 #include <algorithm> 3 #include <array> 4 #include <functional> 5 using namespace std; 6 using namespace std::placeholders; 7 8 bool cmp(const string &s1, const string &s2){ 9 return s1.size() < s2.size(); 10 } 11 12 int main(void){ 13 array<string, 5> a = {"fjls", "jf", "jflsjdf", "jfslk", "jfsljflsfjsjfsl"}; 14 15 stable_sort(a.begin(), a.end(), cmp); 16 for(const auto &indx : a){ 17 cout << indx << " "; 18 } 19 cout << endl;//jf fjls jfslk jflsjdf jfsljflsfjsjfsl 20 21 auto rcmp = bind(cmp, _2, _1);//_2對應cmp的第一個形參,_1對應cmp的第二個形參 22 stable_sort(a.begin(), a.end(), rcmp);//傳入的第一個參數綁定的是_1,第二個參數綁定_2,即實際上調用的是bind(_1, _2) 23 for(const auto &indx : a){ 24 cout << indx << " "; 25 } 26 cout << endl;//jfsljflsfjsjfsl jflsjdf jfslk fjls jf 27 return 0; 28 }
注意:在 stable_sort(a.begin(), a.end(), rcmp);的調用中,傳入 rcmp 中的第一個參數對應 _1,第二個參數對應 _2,即是按照占位符的數字大小順序對應的,而不是按照占位符在 rcmp 參數列表的順序對應的。但是 rcmp 中的參數對應 cmp 中的形參列表是按照參數位置順序一一對應的。因此,在第一個調用中,當 stable_sort 需要比較兩個元素 A 和 B 時,它會調用 cmp(A, B)。在第二個 stable_sort 調用時,傳遞給 cmp 的參數就被交換過來了。即比較 A,B 兩個元素時相當於調用的 cmp(B, A)。因此,在第一次 stable_sort 調用的結果是對 a 中字符串按長度升序排列,而第二次調用 stable_sort 調用的結果是對 a 中的字符串按長度降序排列~
綁定引用參數:
默認情況下,bind 的那些不是占位符的參數被拷貝到 bind 返回的可調用對象中:
1 #include <iostream> 2 #include <algorithm> 3 #include <vector> 4 #include <functional> 5 using namespace std; 6 using namespace std::placeholders; 7 8 ostream &print(ostream &os, const string &s, char c){ 9 os << s << c; 10 } 11 12 int main(void){ 13 vector<string> v = {"fjs", "fjsl", "fjslf", "fjlsjf"}; 14 ostream &os = std::cout; 15 const char c = ' '; 16 17 for_each(v.begin(), v.end(), 18 [&os, c] (const string &s) { 19 os << s << c; 20 }); 21 cout << endl; 22 23 //用bind實現同樣的功能 24 // for_each(v.begin(), v.end(), bind(print, os, _1, c));//錯誤,非占位符默認是值傳遞的,os是IO對象,不能拷貝 25 for_each(v.begin(), v.end(), bind(print, ref(os), _1, c));//函數ref返回一個對象,包含給定的引用,次對象是可以拷貝 26 27 return 0; 28 }
注意:對於不能拷貝的對象,需要通過 ref 函數才能用作 bind 的非占位符參數
函數 ref() 返回一個對象,包含給定的引用,此對象是可以拷貝的。標准庫中還有一個 cref 函數,生成一個保存 const 引用的類。ref 和 cref 定義在頭文件 functional 中。
