一、簡介
我們可以向一個算法傳遞任何類别的可調用對象。對于一個對象或一個表達式,如果可以對其使用調用運算符,則稱它為可調用的。即,如果e是一個可調用的表達式,則我們可以編寫代碼e(args),其中args是一個逗号分隔的一個或多個參數的清單。
可調用對象分别有:1、函數和函數指針;2、重載了函數調用運算符的類;3、lambda表達式。
一個lambda表達式表示一個可調用的代碼單元。我們可以将其了解為一個未命名的内聯函數。與任何函數類似,一個lambda具有一個傳回類型、一個參數清單和一個函數體。但與函數不同,lambda可能定義在函數内部。一個lambda表達式具有如下形式:
[capture list](parameter list) -> return type {functiom body }
其中,capture list(捕獲清單)是一個lambda所在函數中定義的局部變量的清單(通常為空);return type、parameter list和function body與任何普通函數一樣,分别表示傳回類型、參數清單和函數體。但是,與普通函數不同,lambda必須使用尾置傳回來指定傳回類型。
我們可以忽略參數清單和傳回類型,但必須永遠包含捕獲清單和函數體
auto f = [] { return 42; };
此例中,我們定義了一個可調用對象f,它不接受參數,傳回42.
Lambda的調用方式與普通函數的調用方式相同,都是使用調用運算符:
cout << f() << endl; // 列印42
在lambda中忽略括号和參數清單等價于指定一個空參數清單。在此例中,當調用f時,參數清單是空的。如果忽略傳回類型,lambda根據函數體中的代碼推斷出傳回類型。如果函數體隻是一個return語句,則傳回類型從傳回的表達式的類型推斷而來。否則,傳回類型為void。
二、向lambda傳遞參數
與普通函數不同,lambda不能有預設參數。是以,一個lambda調用的實參數目永遠與形參數目相等。
例如:
[](const string &a, const string &b)
{ return a.size() < b.size(); }
空捕獲清單表明此lambda不使用它所在函數中的任何局部變量。如下所示,可以使用此lambda來調用stable_sort:
// 按長度排序,長度相同的單詞維持字典序
Stable_sort(words.begin(), words.end(),
[](const string &a, const string &b)
{ return a.size() < b.size();});
當stable_sort需要比較兩個元素時,它就會調用給定的這個表達式。
三、使用捕獲清單
接下來我們需要編寫一個可傳遞給find_if的可調用表達式。我們希望這個表達式能将輸入序列中每個string的長度與biggies函數中的sz參數的值進行比較。
在本例中,我們的lambda會捕獲sz,并隻有單一的string參數。其函數體會将string的大小與捕獲的sz的值進行比較:
[sz](const string &a)
{ return a.size() >= sz; };
PS:捕獲清單隻用于局部非static變量,lambda可以直接使用局部static變量和在它所在函數之外聲明的名字。
調用範例:
四、lambda捕獲和傳回
當定義一個lambda時,編譯器生成一個與lambda對應的新的(未命名的)類類型。
值捕獲
類似參數傳遞,變量的捕獲方式也可以是值或引用。當lambda采用值捕獲的時候,前提條件為變量可以拷貝,被捕獲的變量的值是在lambda建立時拷貝,而不是調用時拷貝:
void fcn1()
{
size_t v1 = 42; // 局部變量
// 将v1拷貝到名為f的可調用對象
auto f = [v1] { return v1; };
v1 = 0;
auto j = f(); //j為42,f儲存了我們建立它時v1的拷貝
}
引用捕獲
我們定義lambda時可以采用引用方式捕獲變量。例如:
void fcn2()
{
size_t v1 = 42;
// 對象f2包含v1的引用
auto f2 = [&v1] { return v1; };
v1 = 0;
auto j = f2(); // j為0,f2儲存v1的引用,而非拷貝
}
隐式捕獲
除了顯示列出我們希望使用的來自所在函數的變量之外,還可以讓編譯器根據lambda體重的代碼來推斷我們要使用哪些變量。為了訓示編譯器推斷捕獲清單,應在捕獲清單中寫一個&或=。&告訴編譯器采用捕獲引用方式,=則表示采用值捕獲方式。例如:
// sz為隐式捕獲,值捕獲方式
Wc = find_if(words.begin(), words.end(),
[=](const string &s)
{ return s.size() >= sz; });
如果我們希望對一部分變量采用值捕獲,對其他變量采用引用捕獲,可以混合使用隐式捕獲和顯式捕獲:
1 void biggies(vertor<string> &words,
2
3 vertor<string>::size_type sz,
4
5 ostream &os = cout, char c = ‘ ‘)
6
7 {
8
9 //os 隐式捕獲,引用捕獲方式:c顯式捕獲,值捕獲方式
10
11 for_each(words.begin(), words.end(),
12
13 [&, c](const string &s) { os << s << c; });
14
15 //os顯式捕獲,引用捕獲方式;c隐式捕獲,值捕獲方式
16
17 For_each(words.begin(), words.end(),
18
19 [=, &os](const string &s) { os << s << c; });
20
21 }
當我們混合使用隐式捕獲和顯式捕獲時,捕獲清單中的第一個元素必須是一個&或=.
可變lambda
預設情況下,對于一個值被拷貝的變量,lambda不會改變其值。如果我們希望能改變一個被捕獲的變量的值,就必須在參數清單首加上關鍵字mutable。
1 void fcn3()
2
3 {
4
5 size_t v1 = 42;
6
7 auto f = [v1] () mutable { return ++ v1; };
8
9 v1 = 0;
10
11 auto j = f(); // j為42
12
13 }
指定lambda傳回類型
當我們需要為一個lambda定義傳回類型時,必須使用尾置傳回類型:
1 Transform(vi.begin(), vi.end(), vi.begin(),
2
3 [](int i) - > int
4
5 { if (i < 0) return –i; else return i; });
五、參數綁定
對于那種隻在一兩個地方使用的簡單操作,lambda表達式是最有用的。如果我們需要在很多地方使用相同的操作,通常應該定義一個函數,而不是多次編寫相同的lambda表達式。
但是,對于捕獲局部變量的lambda,用函數來替代它就不是那麼容易了。例如我們用在find_if調用中的lambda比較一個string和一個給定大小。我們可以很容易地編寫一個完成同樣工作的函數:
1 bool check_size(const string &s, string::size_type sz)
2
3 {
4
5 return s.size() >= sz;
6
7 }
标準庫bind函數
我們可以額解決向check_size傳遞一個長度參數的問題,方法是使用一個新的名為bind的标準庫函數,它定義在頭檔案functional中。可以将bind函數看作一個通用的函數擴充卡,它接受一個可調用對象,生成一個新的可調用對象來“适應”原對象的參數清單。
調用bind的一般形式為:
auto newCallable = bind(callable, arg_list);
其中,newCallable本身是一個可調用對象,arg_list是一個逗号分隔的參數清單,對應給定的callable的參數。即,當我們調用newCallable時,newCallable會調用callable,并傳遞給它arg_list中的參數。
綁定chenck_size的sz參數
作為一個簡單的例子,我們将使用bind生成一個調用check_size的對象,如下所示,它用一個定值作為其大小參數來調用check_size:
1 // chenck6是一個可調用對象,接受一個string類型的參數
2
3 // 并用此string和值6來調用check_size
4
5 auto check6 = bind(check_size, _1, 6);
調用check6必須傳遞給它一個string類型的參數,check6會将此參數傳遞給check_size。
string s = “hello”;
bool b1 = check6(s); //check6(s)會調用check_size(s,6)
使用bind,我們可以将原來基于lambda的find_if調用:
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
替換為如下使用check_size的版本:
auto wc = find_if(words.begin(), words.end(), bind(check_size, _1, sz));
參考:《C++ Primer》