泛型2(lambda表達式/參數綁定)


lambda 表達式:

Lambda表達式完整的聲明格式如下:

[capture list] (params list) mutable exception-> return type { function body }

各項具體含義如下:

  1. capture list:捕獲外部變量列表
  2. params list:形參列表
  3. mutable 指示符:用來說用是否可以修改捕獲的變量
  4. exception:異常設定
  5. return type:返回類型
  6. 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 }
View Code

注意:在 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 }
View Code

其中,空捕獲列表表名此 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 }
View Code

注意:這里的 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 }
View Code

注意:由於被捕獲的變量的值是在 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 }
View Code

注意:使用捕獲一個引用(迭代器,指針)變量時,必須確保被引用的對象在 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 }
View Code

注意:當我們混合使用隱式捕獲和顯示捕獲時,捕獲列表中的第一個元素必須是 & 或 =。此符號指定了默認捕獲方式為引用或值

當混合使用隱式捕獲和顯示捕獲時,顯示捕獲的變量必須使用與隱式捕獲不同的方式。即,如果隱式捕獲是引用方式,則顯示捕獲必須采用值方式。反之亦然~

捕獲列表中只能有一個元素采用隱式捕獲,且該變量必須是捕獲列表中的第一個元素

 

可變 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 }
View Code

注意:對於值捕獲,若沒加 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 }
View Code

注意:對於不能正確推斷出返回類型的情況,必須使用尾置返回類型指明返回值的類型

 

在 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 }
View Code

參考: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 }
View Code

對於 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 }
View Code

注意:此處 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 }
View Code

注意:使用這種方式需要注意命名沖突問題

 

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 }
View Code

注意:在 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 }
View Code

注意:對於不能拷貝的對象,需要通過 ref 函數才能用作 bind 的非占位符參數

函數 ref() 返回一個對象,包含給定的引用,此對象是可以拷貝的。標准庫中還有一個 cref 函數,生成一個保存 const 引用的類。ref 和 cref 定義在頭文件 functional 中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM