c++primer——第十章泛型演算法lambda
一、謂詞
謂詞是一個呼叫表示式,其返回結果是一個能用做條件的值。
標準庫演算法為此分為兩類:
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();}
相關文章
- c++primer——第十章泛型演算法所提到的函式總結C++泛型演算法函式
- C++泛型演算法C++泛型演算法
- C# 委託(delegate)、泛型委託和Lambda表示式C#泛型
- Java泛型知識點:泛型類、泛型介面和泛型方法Java泛型
- 泛型類、泛型方法及泛型應用泛型
- 泛型類和泛型方法泛型
- 【java】【泛型】泛型geneticJava泛型
- java泛型之泛型方法。Java泛型
- 泛型--泛型萬用字元和泛型的上下限泛型字元
- TypeScript 泛型介面和泛型類TypeScript泛型
- Go 泛型之泛型約束Go泛型
- java泛型之泛型陣列。Java泛型陣列
- 泛型泛型
- 泛型最佳實踐:Go泛型設計者教你如何用泛型泛型Go
- 五分鐘重溫委託,匿名方法,Lambda,泛型委託,表示式樹泛型
- 第十六篇:泛型演算法結構泛型演算法
- 型別 VS 泛型型別泛型
- TypeScript 泛型型別TypeScript泛型型別
- 泛型(一)泛型
- 泛型(三)泛型
- 泛型(二)泛型
- 泛型(四)泛型
- 泛型(五)泛型
- Go 泛型Go泛型
- va泛型泛型
- Java 泛型Java泛型
- Java泛型Java泛型
- 泛型viewmodle泛型View
- 泛型類、泛型方法、型別萬用字元的使用泛型型別字元
- 【譯】在非泛型類中建立泛型方法泛型
- 五分鐘重溫C#委託,匿名方法,Lambda,泛型委託,表示式樹C#泛型
- 泛型型別(.NET 指南)泛型型別
- Go 官方出品泛型教程:如何開始使用泛型Go泛型
- Java函式泛型List引數,操作泛型元素Java函式泛型
- JAVA泛型使用練習2(自定義泛型使用)Java泛型
- 泛型類及系統中常用的泛型類泛型
- c++primer——過載運算與型別轉換C++型別
- Java 泛型原理Java泛型