c++primer——第十章泛型演算法lambda

TinnCHEN發表於2019-03-25

一、謂詞

謂詞是一個呼叫表示式,其返回結果是一個能用做條件的值。
標準庫演算法為此分為兩類:
1、一元謂詞(意味著只能接受單一引數)
2、二元謂詞(意味著他們有兩個引數)
接受謂詞的演算法對輸入序列中的元素呼叫謂詞。因此元素型別必須能轉換為謂詞的引數型別。
以sort和isShorter舉例
eg:

bool isShorter(const string &s1, const string &s2){
     return s1.size() < s2.size();
}
sort(word.begin(), words.end(), isShorter);

二、lambda表示式

問題背景:根據演算法接受一元謂詞還是二元謂詞,我們傳遞給演算法的為此必須嚴格接受一個或兩個引數,當我們想用find_if判斷一個string長度是否大於一個給定長度,我們需要傳入一個string::size_type sz的引數,以及string,但是find_if只接受一元謂詞,為了解決這個問題,我們引入lambda表示式。

介紹lambda:一個lambda表示式表示一個可呼叫的程式碼單元。我們可以理解為一個未命名的行內函數。其包括一個返回型別,一個引數列表和一個函式體。

[capture list](parameter list) -> return type{function body} //基本形式
//可以忽略引數列表和返回型別,但當我們想制定返回型別時,必須使用該形式中所用的尾置返回

1、捕獲列表:

使用函式中定義的區域性變數,且必須是那些明確指定的變數。
捕獲方式分為三種:值捕獲、引用捕獲、隱式捕獲。
首先是值捕獲
採用值捕獲的前提是變數可以拷貝。與引數不同,被捕獲的變數的值實在lambda建立時拷貝,而不是呼叫時拷貝:

void fcn1(){
     size_t v1 = 42; //區域性變數
     //將v1拷貝到名為f的可呼叫物件
     auto f = [v1]{return v1;};
     v1 = 0;
     auto j = f(); //j為42,f儲存了我們建立時的拷貝
}

接著介紹引用捕獲
一個以引用方式捕獲的變數與其他任何引用行為類似。這裡只談問題和限制。如果我們引用捕獲了一個變數,就必須確保被引用的物件在lambda執行的時候是存在的。
由於lambda捕獲的都是區域性變數,加入lambda在函式結束後執行,則其捕獲的變數已經不存在了。
引用捕獲有時候是必要的,比如在接受一個流引數時候,因為流無法拷貝,只能採取引用的策略。

void fcn2(){
     size_t v1 = 42; //區域性變數
     auto f = [&v1]{return v1;};
     v1 = 0;
     auto j = f(); //j為0,f儲存的是引用而不是拷貝
}

使用注意:
儘量保持lambda的變數捕獲簡單化。確保lambda每次執行的時候這些資訊都有與其意義是程式設計師的責任。
一般來說我們應該儘量減少捕獲的資料量,來避免捕獲導致的潛在的問題。同時,如果可能的話我們應該儘量避免捕獲指標和引用。

最後介紹隱式捕獲
除了顯示的列出我們希望使用的變數外,我們還可以讓編譯器根據lambda體中的程式碼來推斷我們要使用的哪些變數。為了指示編譯器推斷捕獲列表,應在捕獲列表中寫一個&或=。&告訴編譯器採用引用捕獲的方式,=表示採用值捕獲方式。

wc = find_if(words.begin(), words.end(), [=](const string &s){return s.szie() >= sz;}); 

也可以混用隱式捕獲和顯示捕獲,當我們混用時候捕獲列表的第一個元素必須是一個&或=,同時第二個元素必須與第一個元素型別不同。

for_each(words.begin(), words.end(), [=,&os](const string &s){os << s << c;});

2、可變lambda

預設情況下對於一個值拷貝的變數lambda不改變其值,但當我們想改變時候需要在引數列表首加上關鍵字mutable。因此可便lambda能省略引數列表。

void fcn3(){
     size_t v1 = 42; //區域性變數
     //f可以改變所捕獲變數的值
     auto f = [v1] () mutable {return ++v1;};
     v1 = 0;
     auto j = f(); //j為43
}

引用捕獲變數是否可以修改依賴於此引用指向的是一個const型別,還是一個非const型別:

void fcn4(){
     size_t v1 = 42; //區域性變數
     //v1為非const變數引用
     auto f = [&v1]{return ++v1;};
     v1 = 0;
     auto j = f(); //j為1
}

3、指定lambda返回型別

預設情況下,如果一個lambda體包含return之外的任何語句,則編譯器假定此lambda返回void。
因此當我們寫如下語句時會報錯

transform(vi.begin(), vi.end(), vi.begin(), [](int i) {if (i < 0) return -i;else return i; });

所以此時我們需要使用尾置返回型別

transform(vi.begin(), vi.end(), vi.begin(), [](int i) ->int {if (i < 0) return -i;else return i; });

4、向lambda傳遞引數

與普通函式不同,lambda不能有預設引數,因此lambda呼叫的實引數目與形引數目相等。

//isShorter
[](const string &a, const string &b){return a.size() < b.size();}

相關文章