C++中一共有5種調(diào)用對(duì)象:函數(shù),函數(shù)指針,重載了函數(shù)調(diào)用運(yùn)算符的類(仿函數(shù)),bind創(chuàng)建的對(duì)象 和 lambda表達(dá)式。
函數(shù)指針
仿函數(shù)
lambda表達(dá)式
沒(méi)有l(wèi)ambda的話,函數(shù)對(duì)象的定義太麻煩了,你得定義一個(gè)類,重載operator(),然后再創(chuàng)建這個(gè)類的實(shí)例。所以lambda表達(dá)式可以看成是函數(shù)對(duì)象的語(yǔ)法糖,在你需要的時(shí)候,它可以很簡(jiǎn)潔地給你生成一個(gè)函數(shù)對(duì)象。
語(yǔ)法格式
[capture list] (param list) -> return type { function body }
[capture list]是一個(gè)所在函數(shù)中定義的局部變量(非static)的列表,param list, return type,function body和普通的函數(shù)一樣表示返回類型(尾置返回),參數(shù)列表,和函數(shù)體。
我們可以忽略參數(shù)列表和返回類型,但必須包含捕獲列表和函數(shù)體。
auto f = [] { return 42; } // 必須包含捕獲列表和函數(shù)體。
當(dāng)定義一個(gè)lambda時(shí),編譯器生成一個(gè)與lambda對(duì)應(yīng)的新的(未命名的)類類型。默認(rèn)情況下,lambda生成的類都包含一個(gè)對(duì)應(yīng)該lambda所捕獲的變量的數(shù)據(jù)成員,在lambda創(chuàng)建時(shí)被初始化。
向lambda傳遞參數(shù)
與一個(gè)普通函數(shù)類似,調(diào)用一個(gè)lambda時(shí)給定的實(shí)參被用來(lái)初始化lambda的形參。通常,實(shí)參和形參類型必須匹配。但與普通函數(shù)不同,lambda不能有默認(rèn)函數(shù)參數(shù)。
使用捕獲列表
- 值捕獲
采用值捕獲的前提是 變量可以被拷貝,另外被捕獲的變量的值是在lambda創(chuàng)建時(shí)拷貝,而不是調(diào)用時(shí)拷貝void func { int v1 = 42; auto f = [v1] { return v1; } v1 = 0; auto j = f(); // j = 42, v1在lambda創(chuàng)建時(shí)拷貝,而不是調(diào)用時(shí)拷貝 } - 引用捕獲
采用引用捕獲時(shí),必須確保被引用的對(duì)象在lambda執(zhí)行的時(shí)候是存在的(lambda捕獲的都是局部變量,這些變量在函數(shù)結(jié)束后就不存在了)void func() { int val = 42; auto f = [&val]() { return val; }; val = 0; auto j = f(); // j = 0,引用捕獲。 } - 隱式捕獲
除了顯示列出我們希望使用來(lái)自函數(shù)的變量之外,我們可以讓編譯器隱式推斷l(xiāng)ambda體中的代碼來(lái)推斷我們使用了哪些變量。為了指示編譯器推斷捕獲列表,我們應(yīng)在捕獲列表寫(xiě)&和=,- &告訴編譯器采用引用捕獲方式
- =表示采用值捕獲的方式。
wc = find_if(words.begin(), words.end(), [=](const string &s) { return s.size() > sz; })
可變lambda
- mutable
對(duì)于lambda表達(dá)式。默認(rèn)情況下,lambda不會(huì)改變其值,如果我們希望能改變一個(gè)被捕獲變量的值,就必須在參數(shù)列表首加上關(guān)鍵字mutable。void func() { int val = 42; // auto f = [val]() mutable { return ++val; }; // 編譯錯(cuò)誤,v1只讀。 auto f = [val]() mutable { return ++val; }; // 加上mutable關(guān)鍵字,編譯正確。 int j = f(); // j = 43 cout << j << " " << val << endl; // 輸出 43 和 42 }
加上mutable關(guān)鍵字后,值捕獲也會(huì)改變被捕獲變量的值。
返回類型
默認(rèn)情況下,如果一個(gè)lambda體包含return之外的任何語(yǔ)句,編譯器假定此lambda返回void。所以此時(shí)我們就要顯示指定尾指返回類型。
transfrom(v.begin(), v.end(),
[](int i) {
if (i < 0)
return -i;
else
return i;
}) // 錯(cuò)誤
transfrom(v.begin(), v.end(),
[](int i) -> int {
if (i < 0)
return -i;
else
return i;
})
參數(shù)綁定bind函數(shù)
我們需要在一個(gè)std::vector<std::string>中尋找大于某長(zhǎng)度單詞。那么我們可以這樣寫(xiě):
auto it = find_if(vec.begin(), vec.end(),
[](const std::string& s) {
return (s.size() > 5);
});
同樣,我們可以用函數(shù)去實(shí)現(xiàn)。
bool check_size(const std::string& s)
{
return s.size() > 5;
}
auto it = find_if(vec.begin(), vec.end(), check_size);
但假設(shè),我們想要指定長(zhǎng)度來(lái)篩選。那用函數(shù)是不能實(shí)現(xiàn)的。
std::string::size_type sz = 5;
auto it = find_if(vec.begin(), vec.end(),
[sz](const std::string& s) {
return (s.size() > sz);
});
bool check_size(const std::string& s, std::string::size_type sz)
{
return s.size() > sz;
}
但是這個(gè)函數(shù)不用你管作為find_if的參數(shù),因?yàn)?code>find_if接受的是一元謂詞。
// find_if 可能實(shí)現(xiàn)
template<class InputIt, class UnaryPredicate>
InputIt find_if(InputIt first, InputIt last, UnaryPredicate p)
{
for (; first != last; ++first) {
if (p(*first)) { // p只接受一個(gè)參數(shù)
return first;
}
}
return last;
}
但我們可以標(biāo)準(zhǔn)庫(kù)的bind函數(shù),它定義在頭文件functional中,可以將它看成一個(gè)通用的函數(shù)適配器,它接受一個(gè)可調(diào)用的對(duì)象,生成一個(gè)新的可調(diào)用對(duì)象來(lái)適應(yīng)原對(duì)象的參數(shù)列表
auto newCallable = bind(callable, arg_list);
那么現(xiàn)在可以這樣寫(xiě):
auto it = find_if(vec.begin(), vec.end(), bind(check_size, std::placegolders::_1, sz));
此處的bind調(diào)用生成一個(gè)可調(diào)用對(duì)象,將check_size的第二個(gè)參數(shù)綁定到sz的值,當(dāng)find_if對(duì)vec中的std::string調(diào)用這個(gè)對(duì)象時(shí)。它會(huì)將給定的參數(shù)std::string和sz傳遞給check_size函數(shù)。
-
使用placegolders名字
名字_n都定義在名為placegolders的命名空間中,而這個(gè)命名空間本身定義在std命名空間中。它表示占位符,意味著將自己第n個(gè)參數(shù)按照順序傳遞給原調(diào)用對(duì)象。auto g = bind(f, a, b, std::placegolders::_2, c, std::placegolders::_1); g(x, y) == f(a, b, y, c, z);在上面的例子中,
g表示一個(gè)有兩個(gè)參數(shù)的新的調(diào)用對(duì)象,原調(diào)用對(duì)象f有5個(gè)參數(shù)。g的第一個(gè)參數(shù)是f的第5個(gè)參數(shù),g的第2個(gè)參數(shù)是f的第3個(gè)參數(shù)。 -
綁定引用參數(shù)
默認(rèn)情況下,bind那些不是占位符的參數(shù)被拷貝到bind返回的可調(diào)用對(duì)象中。但是和lambda一樣,有時(shí)對(duì)綁定的參數(shù)我們希望以引用的方式傳遞,或是要綁定的參數(shù)類型無(wú)法拷貝。例如,我們希望在打印每個(gè)
vec中的單詞后輸出一個(gè)換行。for_each(vec.begin(), vec.end(), [&os, c](const std::string& s){ os << s << c; }); // 很容易編寫(xiě)一個(gè)對(duì)應(yīng)的函數(shù)版本: ostream& print(ostream& os, const std::string& s, char c) { return os << s << c; } // 錯(cuò)誤:不能拷貝os for_each(vec.begin(), vec.end(), bind(print, os, _1, ' '));那么,這時(shí)我們希望傳遞給
bind的是一個(gè)對(duì)象而又不拷貝它,那么就必須使用ref和cref函數(shù)。它返回一個(gè)對(duì)象的引用。for_each(vec.begin(), vec.end(), bind(print, std::ref(os), _1, ' '));