文章較長,堅持看完,相信對你一定會有所收獲!
Lambda我們可以將其理解為一個未命名的內聯函數。
與任何函數類似,一個lambda具有一個返回類型,一個參數列表和一個函數體。
但與函數不同,lambda可能定義在函數內部。
一個lambda表達式具有如下形式:
[capture list] (parameter list) ->return type {function body}
capture list: 捕獲列表,是一個lambda所在函數中定義的局部變量列表(通常為空)
parameter list:參數列表
return type:返回類型
function body:函數體
但是與普通函數不同,lambda必須使用尾置返回來指定返回類型
我們可以忽略參數列表和返回類型,但必須永遠包含捕獲列表和函數體
auto f
=[]{return 42;};
此例中,我們定義了一個可調用對象f,它不接受參數,返回42.
lambda的調用方式與普通函數調用方式相同,都是使用調用運算符:
cout
<<f()<<endl
;
在lambda中忽略括號和參數列表等價于指定一個空參數列表。在此例中,當調用f時,參數列表是空的。如果忽略返回類型,lambda根據函數體中的代碼推斷出返回類型。
如果函數體只是一個return語句,則返回類型從返回的表達式的類型推斷而來,否則,返會類型為void.
如果lambda的函數體包含任何一個單一的return語句之外的內容,且未指定返回類型,則返回void
向lambda傳遞參數
與一個普通函數調用類似,調用一個lambda時給定的實參被用來初始化lambda的形參。通常,實參和形參的類型必須匹配。但與普通函數不同,lambda不能有默認參數。
因此,一個lambda調用的實參數目永遠與形參數目相等。
下面舉一個帶參數的lambda的例子:
[](
const string
&a
,const string
& b)
{return a
.size()<b
.size();};
空捕獲列表表面此lambda不使用它所在函數中的任何局部變量。
使用捕獲列表
雖然一個lambda可以出現在一個函數中,使用其局部變量,但它只能使用那些明確指明的變量。一個lambda通過將局部變量包含在其捕獲列表中來指明將會使用這些變量。捕獲列表指引lambda在其內部包含訪問局部變量所需的信息。
下面舉一個例子:
#include<iostream>
using namespace std
;
void test()
{string
sz("abc");string
words("abds");auto ret
=[sz
](const string
& a
){return a
.size() >= sz
.size();};cout
<< ret("efgdasd") << endl
;
}
int main()
{test();system("pause");return 0;
}
lambda以一對[]開始,我們可以在其中提供一個以逗號分隔的名字列表(當前函數中定義的局部變量),這些名字都是它所在函數中定義的。
上面例子中由于lambda只捕獲了test函數中局部變量sz,因此可以在lambda的函數體中使用sz.lambda不捕獲words,因此不能在lambda的函數體中訪問此變量。如果我們給lambda提供一個空捕獲列表,則代碼會編譯出錯:
一個lambda只有在其捕獲列表中捕獲了一個它所在函數中的局部變量,才能在函數體中使用該變量
lambda表達式在泛型算法的應用
與find_if結合使用
舉例:調用find_if算法在字符串s中查找第一個長度大于等于字符串sz的元素
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std
;
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
)
{vector
<string
>::iterator pos
= find_if(words
.begin(), words
.end(), [sz
](const string
& a
) {return a
.size() > sz
; });cout
<< *pos
<< endl
;
}
int main()
{ vector
<string
> svec
{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec
, 4);
}
這里對find_if的調用返回一個迭代器,指向第一個長度不小于sz的元素。如果這樣的元素不存在,則返回words.end()的一個拷貝
我們可以使用find_if返回的迭代器來計算從它開始到words的末尾一共有多少個元素。
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
)
{vector
<string
>::iterator pos
= find_if(words
.begin(), words
.end(), [sz
](const string
& a
) {return a
.size() > sz
; });auto count
= words
.end() - pos
;cout
<< count
<< endl
;
}
與for_each結合使用
舉例:打印words中長度大于等于sz的元素。
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
)
{vector
<string
>::iterator pos
= find_if(words
.begin(), words
.end(), [sz
](const string
& a
) {return a
.size() > sz
; });for_each(pos
, words
.end(), [](const string
& a
) {cout
<< a
<< " "; });
}
int main()
{ vector
<string
> svec
{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec
, 4);
}
此lambda中的捕獲列表為空,但其函數體中還是使用了兩個名字:s和cout,前者是它自己的參數。
捕獲列表為空,是因為我們只對lambda所在的函數中定義的(非static)變量使用了捕獲列表。一個lambda可以直接使用定義在當前函數之外的名字。在本例中,cout不是定義在bigger中的局部名字,而是定義在頭文件iostream中。因此,只要在bigger出現的作用域中包含頭文件iostream,我們的lambda就可以使用cout.
注意:捕獲列表只用于局部非static變量,lambda可以直接使用局部static變量和它所在函數之外聲明的名字
完整的biggerd代碼:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std
;
void elimDups(vector
<string
>& words
)
{sort(words
.begin(), words
.end());auto new_end
= unique(words
.begin(), words
.end());words
.erase(new_end
, words
.end());
}
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
)
{elimDups(words
);stable_sort(words
.begin(), words
.end(), [](const string
& a1
, const string
& a2
) {return a1
.size() > a2
.size(); });vector
<string
>::iterator pos
= find_if(words
.begin(), words
.end(), [sz
](const string
& a
) {return a
.size() > sz
; });auto count
= count_if(words
.begin(), words
.end(), [sz
](const string
& a
) {return a
.size()>sz
; });cout
<< count
<< endl
;for_each(pos
, words
.end(), [](const string
& a
) {cout
<< a
<< " "; });
}
int main()
{ vector
<string
> svec
{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec
, 4);
}
類似參數傳遞,變量的捕獲方式也可以是值或引用。
值捕獲
與傳值參數類似,采用值捕獲的前提是變量可以拷貝。
與參數不同,被捕獲的變量的值實在lambda創建時拷貝,而不是調用時拷貝
舉例:
#include <iostream>
using namespace std
;
void test()
{size_t v1
= 42;auto f
= [v1
] {return v1
; };v1
= 0;auto j
= f();cout
<< j
<< endl
;
}
int main()
{ test();return 0;
}
由于被捕獲的變量的值是在lambda創建時拷貝,因此隨后對其修改不會影響到lambda內對應的值
引用捕獲
舉例:
#include <iostream>
using namespace std
;
void test()
{size_t v1
= 42;auto f
= [&v1
] {return v1
; };v1
= 0;auto j
= f();cout
<< j
<< endl
;
}
int main()
{ test();return 0;
}
當我們在lambda函數體內使用此變量時,實際上使用的時引用所綁定的對象。
引用捕獲和返回引用的注意事項:
如果我們采用引用的方式捕獲了一個變量,就必須確保被引用的對象在lambda執行的時候是存在的。
lambda捕獲的都是局部變量,這些變量在函數結束后就不復存在了。
如果lambda可能在函數結束后執行,捕獲的引用執行的局部變量已經消失。
引用捕獲有時候是必要的,例如:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std
;
void bigger(vector
<string
>& words
,ostream
&os
=cout
,char ch
=' ')
{for_each(words
.begin(), words
.end(), [&os
, ch
](const string
& s
) {os
<< s
<< ch
; });
}
int main()
{ vector
<string
> svec
{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec
);
}
我們不能拷貝ostream對象,因此捕獲os的唯一方法就是捕獲其引用(或指向os的指針)。
注意:當以引用方式捕獲一個變量的時候,必須保證lambda指向時變量是存在的
對lambda變量捕獲部分重點總結:
捕獲一個普通變量,如int,string或其他非指針類型,通常采用簡單的值捕獲方式。
如果我們捕獲一個指針或迭代器,或采用引用捕獲方式,就必須保證對象具有預期的值。
在lambda從創建到它執行這段時間內,可能有代碼改變綁定對象的值。
也就是說,在該指針(或引用)被捕獲的時刻,綁定的對象的值是我們所期望的,但在lambda執行時,該對象的值已經完全不同了。
一般來說,我們應該盡量減少捕獲的數據量,來避免潛在的捕獲導致的問題。而且,如果有可能的話,應該避免捕獲指針或引用。
隱式捕獲
通過在捕獲列表中寫一個&或=,指示編譯器推斷捕獲列表。
&告訴編譯器采用引用方式,=則表示采用值捕獲方式
例如:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std
;
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
)
{auto wc
= find_if(words
.begin(), words
.end(), [=](const string
& s
) {return s
.size() >= sz
; });cout
<< *wc
<< endl
;
}
int main()
{ vector
<string
> svec
{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec
, 4);
}
如果我們希望對一部分變量采用值捕獲,對其他變量采用引用捕獲,可以混合使用隱式捕獲和顯示捕獲:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std
;
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
,ostream
& os
=cout
,char c
=' ')
{for_each(words
.begin(), words
.end(), [&, c
](const string
& s
) {os
<< s
<< c
; });cout
<< endl
;for_each(words
.begin(), words
.end(), [=,&os
](const string
& s
) {os
<< s
<< c
; });
}
int main()
{ vector
<string
> svec
{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec
, 4);
}
當我們混合使用隱式捕獲和顯示捕獲時,捕獲列表中的第一個元素必須是一個&或=,此符號指定了默認捕獲方式為引用或值
當混合使用隱式捕獲和顯示捕獲時,顯示捕獲的變量必須使用與隱式捕獲不同的方式。
即如果隱式不會是引用方式,則顯示捕獲命名變量必須采用值方式,因此不能在其名字前使用&.
類似的,如果隱式捕獲采用的是值方式,則顯示捕獲命名的變量必須采用引用方式,即在名字前使用&。
總結lambda捕獲列表:
1、空。沒有使用任何函數對象參數。
2、=。函數體內可以使用Lambda所在作用范圍內所有可見的局部變量(包括Lambda所在類的this),并且是值傳遞方式(相當于編譯器自動為我們按值傳遞了所有局部變量)。
3、&。函數體內可以使用Lambda所在作用范圍內所有可見的局部變量(包括Lambda所在類的this),并且是引用傳遞方式(相當于編譯器自動為我們按引用傳遞了所有局部變量)。
4、this。函數體內可以使用Lambda所在類中的成員變量。
5、a。將a按值進行傳遞。按值進行傳遞時,函數體內不能修改傳遞進來的a的拷貝,因為默認情況下函數是const的。要修改傳遞進來的a的拷貝,可以添加mutable修飾符。
6、&a。將a按引用進行傳遞。
7、a, &b。將a按值進行傳遞,b按引用進行傳遞。
8、=,&a, &b。除a和b按引用進行傳遞外,其他參數都按值進行傳遞。
9、&, a, b。除a和b按值進行傳遞外,其他參數都按引用進行傳遞。
可變lambda
默認情況下,對于一個值被拷貝的變量,lambda不會改變其值,如果我們希望能改變一個被捕獲的變量的值,就必須在參數列表首加上關鍵字mutable。因此,可變lambda能省略參數列表:
#include <iostream>
using namespace std
;
void test()
{size_t val
= 42;auto f
= [val
]()mutable {return val
++; };val
= 0;cout
<< f() << endl
;cout
<< f() << endl
;cout
<< f() << endl
;cout
<< val
<< endl
;
}
int main()
{test();return 0;
}
多次調用函數f,就可以多次對f保存的val進行累加改變。
但val本身值不變
一個引用捕獲的變量是否可以修改依賴與此引用指向的是一個const類型還是一個非const類型:
#include <iostream>
using namespace std
;
void test()
{size_t val
= 42;auto f
= [&val
]() {return val
++; };val
= 0;cout
<< f() << endl
;cout
<< f() << endl
;cout
<< f() << endl
;cout
<< val
<< endl
;
}
int main()
{test();return 0;
}
指定lambda的返回類型
默認情況下,如果一個lambda體包含return之外的任何語句,則編譯器假定此lambda返回void.
與其他返回void的函數類似,被推斷為返回void的lambda不能有返回值。
舉例:
#include <iostream>
#include<algorithm>
using namespace std
;
void test()
{int arr
[5] = { -1,-2,-3,-4,-5 };transform(arr
, arr
+ 5,arr
,[](int i
) {return i
< 0 ? -i
: i
; });for (int i
= 0; i
< 5; i
++){cout
<< arr
[i
];}cout
<< endl
;
}
int main()
{test();return 0;
}
本例中lambda體中只有單一的return語句。我們無需指定返回類型,因為可以根據條件運算符的類型推斷出來。
但是如果我們將程序改寫成看起來是等價的if語句,就會產生編譯錯誤:
雖然這里沒有發生錯誤,是因為版本問題,有些低版本編譯器會出現問題,原因在于:
編譯器推斷這個版本的lambda返回類型為void,但它返回一個int值。
當我們需要為一個lambda定義一個返回類型時,必須使用尾置返回類型:
#include <iostream>
#include<algorithm>
using namespace std
;
void test()
{int arr
[5] = { -1,-2,-3,-4,-5 };transform(arr
, arr
+ 5, arr
, [](int i
)->int{if (i
< 0) return -i
;else return i
;});for (int i
= 0; i
< 5; i
++){cout
<< arr
[i
];}cout
<< endl
;
}
int main()
{test();return 0;
}
lambda是函數對象----->函數對象—>重載()運算符—>operator()
當我們編寫了一個lambda后,編譯器將該表達式翻譯成一個未命名類的未命名對象。
在lambda表達式產生的類中含有一個重載的函數調用運算符。
舉例:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std
;
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
)
{stable_sort(words
.begin(), words
.end(), [](const string
& a1
, const string
& a2
) {return a1
.size()<a2
.size(); });for_each(words
.begin(), words
.end(), [](const string
& a
) {cout
<< a
<< " "; });
}
int main()
{ vector
<string
> svec
{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec
, 4);
}
該lambda的行為類似于下面這個類的一個未命名對象
函數對象的概念
仿函數做泛型算法的參數又細分為一元謂詞和二元謂詞,不了解的建議去看看:
謂詞的概念
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std
;
class ShortString
{
public:bool operator()(const string
& a1
, const string
& a2
){return a1
.size() < a2
.size();}
};
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
)
{stable_sort(words
.begin(), words
.end(), ShortString());for_each(words
.begin(), words
.end(), [](const string
& a
) {cout
<< a
<< " "; });
}
int main()
{ vector
<string
> svec
{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" };bigger(svec
, 4);
}
默認情況下lambda不能改變他捕獲的變量。是因為由lambda產生的類中的函數調用的運算符是一個const成員函數。如果lambda被聲明為可變的,則調用運算符就不是const的了。
表示lambda及相應捕獲行為的類
當一個lambda表達式通過引用捕獲變量時,將由程序負責確保lambda執行時引用所引的對象確實存在。因此,編譯器可以直接使用該引用而無需在lambda產生的類中將其存儲為數據成員。
相反,通過值捕獲的變量被拷貝到lambda中。因此,這種lambda產生的類必須為每個值捕獲的變量建立對應的數據成員,同時創建構造函數,令其使用捕獲捕獲的變量的值來初始化數據成員。
舉個例子:
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
)
{auto wc
= find_if(words
.begin(), words
.end(), [sz
](const string
& a
) {return a
.size() >= sz
; });
}
該lambda表達式的產生的類的將形如:
class SizeComp
{
private:size_t sz
;
public:SizeComp(size_t n
):sz(n
){}bool operator()(const string
& s
)const{return s
.size() >= sz
;}
};
上面這個類含有一個數據成員以及一個用于初始化該成員的構造函數。
這個合成的類不含有默認構造函數,因此想使用這個類必須提供一個實參:
void bigger(vector
<string
>& words
,vector
<string
>::size_type sz
)
{auto wc
= find_if(words
.begin(), words
.end(), SizeComp(sz
));
}
lambda表達式產生的類不含默認構造函數,賦值運算符及默認析構函數;
它是否含有默認的拷貝/移動構造函數則通常要視捕獲的數據成員類型而定。
完結撒花!!!
總結
以上是生活随笔為你收集整理的Lambda表达式用法超详细整理!!!的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。