天天看點

STL之lambda表達式

一、簡介

我們可以向一個算法傳遞任何類别的可調用對象。對于一個對象或一個表達式,如果可以對其使用調用運算符,則稱它為可調用的。即,如果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》

繼續閱讀