c++中lambda表达式用法
lambda表達式是C++11中引入的一項新技術,利用lambda表達式可以編寫內嵌的匿名函數(shù),用以替換獨立函數(shù)或者函數(shù)對象,并且使代碼更可讀。
所謂函數(shù)對象,其實就是對operator()進行重載進而產(chǎn)生的一種行為,比如,我們可以在類中,重載函數(shù)調用運算符(),此時類對象就可以直接類似函數(shù)一樣,直接使用()來傳遞參數(shù),這種行為就叫做函數(shù)對象,同樣的,它也叫做仿函數(shù)。
如果從廣義上說,lambda表達式產(chǎn)生的是也是一種函數(shù)對象,因為它也是直接使用()來傳遞參數(shù)進行調用的。
1 lambda表達式基本使用
lambda表達式基本語法如下:
[ 捕獲 ]?( 形參 )?->?ret { 函數(shù)體 };lambda表達式一般都是以方括號[]開頭,有參數(shù)就使用(),無參就直接省略()即可,最后結束于{},其中的ret表示返回類型。
我們先看一個簡單的例子,定義一個可以輸出字符串的lambda表達式,完整的代碼如下:
#include <iostream>int main() {auto atLambda = [] {std::cout << "hello world" << std::endl;};atLambda();return 0; }上面定義了一個最簡單的lambda表達式,沒有參數(shù)。如果需要參數(shù),那么就要像函數(shù)那樣,放在圓括號里面,如果有返回值,返回類型則要放在->后面,也就是尾隨返回類型,當然你也可以忽略返回類型,lambda會幫你自動推導出返回類型,下面看一個較為復雜的例子:
#include <iostream>int main() {auto print = [](int s) {std::cout << "value is " << s << std::endl;};auto lambAdd = [](int a, int b) ->int { return a + b;};int iSum = lambAdd(10, 11);print(iSum);return 0; }lambAdd有兩個入?yún)和b,然后它的返回類型是int,我們可以試一下把->int去掉,結果是一樣的。
2 lambda捕獲塊
2.1 捕獲的簡單使用
在第1節(jié)中,我們展示了lambda的語法形式,后面的形參和函數(shù)體之類都好理解,那么方括號里面捕獲是啥意思呢?
其實這里涉及到lambda表達式一個重要的概念,就是閉包。
這里我們需要先對lambda表達式的實現(xiàn)原理做一下說明:當我們定義一個lambda表達式后,編譯器會自動生成一個匿名類,這個類里面會默認實現(xiàn)一個public類型的operator()函數(shù),我們稱為閉包類型。那么在運行時,這個lambda表達式就會返回一個匿名的閉包實例,它是一個右值。
所以,我們上面的lambda表達式的結果就是一個一個的閉包。閉包的一個強大之處是可以通過傳值或者引用的方式捕獲其封裝作用域內的變量,前面的方括號就是用來定義捕獲模式以及變量,所以我們把方括號[]括起來的部分稱為捕獲塊。
看這個例子:
#include <iostream>int main() {int x = 10;auto print = [](int s) {std::cout << "value is " << s << std::endl;};auto lambAdd = [x](int a) { return a + x;};auto lambAdd2 = [&x](int a, int b) { return a + b + x;};auto iSum = lambAdd(10);auto iSum2 = lambAdd2(10, 11);print(iSum);print(iSum2);return 0; }當lambda塊為空時,表示沒有捕獲任何變量,不為空時,比如上面的lambAdd是以復制的形式捕獲變量x,而lambAdd2是以引用的方式捕獲x。那么這個復制或者引用到底是怎么體現(xiàn)的呢,我們使用gdb看一下lambAdd和lambAdd2的具體類型,如下:
(gdb) ptype lambAdd type = struct <lambda(int)> {int __x; } (gdb) ptype lambAdd2 type = struct <lambda(int, int)> {int &__x; } (gdb)前面我們說過lambda實際上是一個類,這里得到了證明,在c++中struct和class除了有少許區(qū)別,其他都是一樣的,所以我們可以看到復制形式捕獲實際上是一個包含int類型成員變量的struct,引用形式捕獲實際上是一個包含int&類型成員變量的struct,然后在運行的時候,會使用我們捕獲的數(shù)據(jù)來初始化成員變量。
既然有初始化,那么必然有構造函數(shù)啊,然后捕獲生成的成員變量,有operator()函數(shù),暫時來講,一個比較立體的閉包類型就存在于我們腦海中啦,對于lambda表達式類型具體組成,我們暫時放一放,接著說捕獲。
2.2 捕獲的類型
捕獲的方式可以是引用也可以是復制,但是到底有哪些類型的捕獲呢?
捕獲類型如下:
- []:默認不捕獲任何變量;
- [=]:默認以復制捕獲所有變量;
- [&]:默認以引用捕獲所有變量;
- [x]:僅以復制捕獲x,其它變量不捕獲;
- [x...]:以包展開方式復制捕獲參數(shù)包變量;
- [&x]:僅以引用捕獲x,其它變量不捕獲;
- [&x...]:以包展開方式引用捕獲參數(shù)包變量;
- [=, &x]:默認以復制捕獲所有變量,但是x是例外,通過引用捕獲;
- [&, x]:默認以引用捕獲所有變量,但是x是例外,通過復制捕獲;
- [this]:通過引用捕獲當前對象(其實是復制指針);
- [*this]:通過復制方式捕獲當前對象;
可以看到,lambda是可以有多個捕獲的,每個捕獲之間以逗號分隔,另外呢,不管多少種捕獲類型,萬變不離其宗,要么以復制方式捕獲,要么以引用方式捕獲。
那么復制捕獲和引用捕獲到底有什么區(qū)別呢?
標準c++規(guī)定,默認情況下,在lambda表達式中,對于operator()的重載是const屬性的,也就意味著如果以復制形式捕獲的變量,是不允許修改的,看這段代碼:
#include <iostream>int main() {int x = 10;int y = 20;auto print = [](int s) {std::cout << "value is " << s << std::endl;};auto lambAdd = [x](int a) { // x++; 此處x是只讀,不允許自增,編譯會報錯return a + x;};auto lambAdd2 = [&x](int a, int b) { x = x+5;return a + b + x;};auto iSum = lambAdd(10);auto iSum2 = lambAdd2(10, 11);print(iSum);print(iSum2);return 0; }從代碼可以看出,復制捕獲不允許修改變量值,而引用捕獲則允許修改變量值,為什么呢,這里我理解,&x實際上是一個int*類型的指針,所以我們可以修改x的值,因為我們只是對這個指針所指向的內容進行修改,并沒有對指針本身進行修改,且與我們常規(guī)聲明的引用類型入?yún)⒁粯?#xff0c;修改的值在lambda表達式外也是有效的。
那么如果我想使用復制捕獲,又想修改變量的值呢,這時我們就想起來有個關鍵字,叫做mutable,它允許在常成員函數(shù)中修改成員變量的值,所以我們可以給lambda表達式指定mutable關鍵字,如下:
#include <iostream>int main() {int x = 10;int y = 20;auto print = [](int s) {std::cout << "value is " << s << std::endl;};auto lambAdd = [x](int a) mutable { x++;return a + x;};auto iSum = lambAdd(10);print(iSum);print(x);return 0; }執(zhí)行結果如下:
value is 21 value is 10所以加上mutable以后就可以對復制捕獲進行修改,但有一點,它的修改出了lambda表達式以后就無效了。
2.3 包展開方式捕獲
仔細看2.2節(jié)中捕獲類型,會發(fā)現(xiàn)有[x...]這樣的類型,它實際上是以復制方式捕獲了一個可變參數(shù),在c++中其實涉及到了模板形參包,也就是變參模板,看下面例子:
#include <iostream>void tprintf() {return; }template<typename U, typename ...Ts> void tprintf(U u, Ts... ts) {auto t = [ts...]{tprintf(ts...);};std::cout << "value is " << u << std::endl;t();return; }int main() {tprintf(1,'c',3, 8);return 0; }它捕獲了一組可變的參數(shù),不過這里實際上是為了演示對可變參數(shù)的捕獲,強行使用了lambda表達式,不使用的話,代碼可能更加簡潔,我們只需要通過這個演示知道怎么使用即可,另外對于變參模板的使用,這里就不展開來講了。
2.4 捕獲的作用
我再看lambda的捕獲的時候一直很奇怪,初看的話,這個捕獲跟傳參數(shù)有什么區(qū)別呢,都是把一個變量值傳入lambda表達式體供使用,但仔細思考的話,它是有作用的,假設有這么一個案例,一個公司有999名員工,每個員工的工號是從1~999,我們現(xiàn)在想找出工號是8的整數(shù)倍的所有員工,一個可行的代碼如下:
#include <iostream> #include <array>int main() {int x = 8;auto t = [x](int i){if ( i % x == 0 ){std::cout << "value is " << i << std::endl;}};auto t2 = [](int i, int x){if ( i % x == 0 ){std::cout << "value is " << i << std::endl;}};for(int j = 1; j< 1000; j++){t(j);t2(j, x);}return 0; }表達式t使用了捕獲,而表達式t2沒有使用捕獲,從代碼作用和量來看,它們其實區(qū)別不大,但有一點,對于表達式t,x的值只復制了一次,而對于t2表達式,每次調用都要生成一個臨時變量來存放x的值,這其實是多了時間和空間的開銷,不過,對于這段代碼而言,這點消耗可以忽略不計呢,但一旦數(shù)據(jù)上了規(guī)模,那就會有比較大的區(qū)別了。
對于捕獲的作用,我暫時只想到了這一點,如果有大佬知道更多的作用,麻煩說一下呀。
對于捕獲,還是盡量不要使用[=]或者[&]這樣全捕獲的形式,因為不可控,你不能確保哪些變量會被捕獲,容易發(fā)生一些不測的行為。
3 lambda表達式作為回調函數(shù)
lambda表達式一個更重要的應用是它可以作為函數(shù)的參數(shù)傳入,通過這種方式可以實現(xiàn)回調函數(shù)。比如在STL算法中,經(jīng)常要給一些模板類或者模板函數(shù)來指定某個模板參數(shù)為lambda表達式,就想上一節(jié)說的,我想統(tǒng)計999個員工中工號是8的整數(shù)倍的員工個數(shù),一個可用的代碼如下:
#include <iostream> #include <array> #include <algorithm>int main() {int x = 8;std::array<int, 999> arr;for (int i =1; i< 1000; i++){arr[i] = i;}int cnt = std::count_if(arr.begin(), arr.end(), [x](int a){ return a%x == 0;});std::cout << "cnt=" << cnt << std::endl;return 0; }這里很明顯,我們指定了一個lambda表達式來作為一個條件,更多時候,是使用排序函數(shù)的時候,指定排序準則,也可以使用lambda表達式。
4 lambda表達式賦值
lambda表達式既然生成了一個類對象,那么它是否可以像普通類對象那樣,進行賦值呢?
我們寫一段代碼試一下:
#include <iostream> using namespace std;int main() {auto a = [] { cout << "A" << endl; };auto b = [] { cout << "B" << endl; };//a = b; // 非法,lambda無法賦值auto c(a); // 合法,生成一個副本return 0; }很顯然賦值不可以,而拷貝則可以,結合編譯器自動生成構造函數(shù)規(guī)則,很明顯,賦值函數(shù)被禁用了,而拷貝構造函數(shù)則沒有被禁用,所以不能用一個lambda表達式給另外一個賦值,但可以進行初始化拷貝。
5 總結
總而言之,根據(jù)lambda表達式的一個定義來看,它其實是用于替代一些功能比較簡單,但又有大量使用的函數(shù),lambda在stl中大量使用,對于大部分STL算法而言,可以非常靈活地搭配lambda表達式來實現(xiàn)想要的效果。
同時這里要說明一下,lambda其實是作為c++11新引入的一種語法規(guī)則,它與STL并沒有什么直接關聯(lián),只是STL里面大量使用了lambda表達式而已,并不能直接就說把它當做是STL的一部分。
總結
以上是生活随笔為你收集整理的c++中lambda表达式用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis 源码阅读
- 下一篇: s3c2440移植MQTT