C++ 中的lambda表示式【C++11版本】
一直都在提醒自己,我是搞C++的;但是當C++11出來這麼長時間了,我卻沒有跟著隊伍走,發現很對不起自己的身份,也還好,發現自己也有段時間沒有寫C++程式碼了。今天看到了C++中的Lambda表示式,雖然用過C#的,但是C++的,一直沒有用,也不知道怎麼用,就可憐的連Lambda語法都看不懂。好了,這裡就對C++中的Lambda進行一個簡單的總結,就算是對自己的一個交代,我是搞C++的,我是一個C++ programmer。
一段簡單的Code
我也不是文藝的人,對於Lambda的歷史,以及Lambda與C++的那段淵源,我也不是很熟悉,技術人,講究拿程式碼說事。
#include<iostream>
using namespace std;
int main()
{
int a = 1;
int b = 2;
auto func = [=, &b](int c)->int {return b += a + c;};
return 0;
}
當我第一次看到這段程式碼時,我直接凌亂了,直接看不懂啊。上面這段程式碼,如果你看懂了,下面的內容就當時複習了;如果看不懂了,就接著和我一起總結吧。
基本語法
簡單來說,Lambda函式也就是一個函式,它的語法定義如下:
[capture](parameters) mutable ->return-type{statement}
1.[capture]:捕捉列表。捕捉列表總是出現在Lambda函式的開始處。實際上,[]是Lambda引出符。編譯器根據該引出符判斷接下來的程式碼是否是Lambda函式。捕捉列表能夠捕捉上下文中的變數以供Lambda函式使用;
2.(parameters):引數列表。與普通函式的引數列表一致。如果不需要引數傳遞,則可以連同括號“()”一起省略;
3.mutable:mutable修飾符。預設情況下,Lambda函式總是一個const函式,mutable可以取消其常量性。在使用該修飾符時,引數列表不可省略(即使引數為空);
4.->return-type:返回型別。用追蹤返回型別形式宣告函式的返回型別。我們可以在不需要返回值的時候也可以連同符號”->”一起省略。此外,在返回型別明確的情況下,也可以省略該部分,讓編譯器對返回型別進行推導;
5.{statement}:函式體。內容與普通函式一樣,不過除了可以使用引數之外,還可以使用所有捕獲的變數。
與普通函式最大的區別是,除了可以使用引數以外,Lambda函式還可以通過捕獲列表訪問一些上下文中的資料。具體地,捕捉列表描述了上下文中哪些資料可以被Lambda使用,以及使用方式(以值傳遞的方式或引用傳遞的方式)。語法上,在“[]”包括起來的是捕捉列表,捕捉列表由多個捕捉項組成,並以逗號分隔。捕捉列表有以下幾種形式:
1.[var]表示值傳遞方式捕捉變數var;
2.[=]表示值傳遞方式捕捉所有父作用域的變數(包括this);
3.[&var]表示引用傳遞捕捉變數var;
4.[&]表示引用傳遞方式捕捉所有父作用域的變數(包括this);
5.[this]表示值傳遞方式捕捉當前的this指標。
上面提到了一個父作用域,也就是包含Lambda函式的語句塊,說通俗點就是包含Lambda的“{}”程式碼塊。上面的捕捉列表還可以進行組合,例如:
1.[=,&a,&b]表示以引用傳遞的方式捕捉變數a和b,以值傳遞方式捕捉其它所有變數;
2.[&,a,this]表示以值傳遞的方式捕捉變數a和this,引用傳遞方式捕捉其它所有變數。
不過值得注意的是,捕捉列表不允許變數重複傳遞。下面一些例子就是典型的重複,會導致編譯時期的錯誤。例如:
3.[=,a]這裡已經以值傳遞方式捕捉了所有變數,但是重複捕捉a了,會報錯的;
4.[&,&this]這裡&已經以引用傳遞方式捕捉了所有變數,再捕捉this也是一種重複。
Lambda的使用
對於Lambda的使用,說實話,我沒有什麼多說的,個人理解,在沒有Lambda之前的C++ , 我們也是那樣好好的使用,並沒有對缺少Lambda的C++有什麼抱怨,而現在有了Lambda表示式,只是更多的方便了我們去寫程式碼。不知道大家是否記得C++ STL庫中的仿函式物件,仿函式想對於普通函式來說,仿函式可以擁有初始化狀態,而這些初始化狀態是在宣告仿函式物件時,通過引數指定的,一般都是儲存在仿函式物件的私有變數中;在C++中,對於要求具有狀態的函式,我們一般都是使用仿函式來實現,比如以下程式碼:
#include<iostream>
using namespace std;
typedef enum
{
add = 0,
sub,
mul,
divi
}type;
class Calc
{
public:
Calc(int x, int y):m_x(x), m_y(y){}
int operator()(type i)
{
switch (i)
{
case add:
return m_x + m_y;
case sub:
return m_x - m_y;
case mul:
return m_x * m_y;
case divi:
return m_x / m_y;
}
}
private:
int m_x;
int m_y;
};
int main()
{
Calc addObj(10, 20);
cout<<addObj(add)<<endl; // 發現C++11中,enum型別的使用也變了,更“強”了
return 0;
}
現在我們有了Lambda這個利器,那是不是可以重寫上面的實現呢?看程式碼:
#include<iostream>
using namespace std;
typedef enum
{
add = 0,
sub,
mul,
divi
}type;
int main()
{
int a = 10;
int b = 20;
auto func = [=](type i)->int {
switch (i)
{
case add:
return a + b;
case sub:
return a - b;
case mul:
return a * b;
case divi:
return a / b;
}
};
cout<<func(add)<<endl;
}
顯而易見的效果,程式碼簡單了,你也少寫了一些程式碼,也去試一試C++中的Lambda表示式吧。
關於Lambda那些奇葩的東西
看以下一段程式碼:
#include<iostream>
using namespace std;
int main()
{
int j = 10;
auto by_val_lambda = [=]{ return j + 1; };
auto by_ref_lambda = [&]{ return j + 1; };
cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
++j;
cout<<"by_val_lambda: "<<by_val_lambda()<<endl;
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
return 0;
}
程式輸出結果如下:
by_val_lambda: 11
by_ref_lambda: 11
by_val_lambda: 11
by_ref_lambda: 12
你想到了麼???那這又是為什麼呢?為什麼第三個輸出不是12呢?
在by_val_lambda中,j被視為一個常量,一旦初始化後不會再改變(可以認為之後只是一個跟父作用域中j同名的常量),而在by_ref_lambda中,j仍然在使用父作用域中的值。所以,在使用Lambda函式的時候,如果需要捕捉的值成為Lambda函式的常量,我們通常會使用按值傳遞的方式捕捉;相反的,如果需要捕捉的值成成為Lambda函式執行時的變數,則應該採用按引用方式進行捕捉。
再來一段更暈的程式碼:
#include<iostream>
using namespace std;
int main()
{
int val = 0;
// auto const_val_lambda = [=](){ val = 3; }; wrong!!!
auto mutable_val_lambda = [=]() mutable{ val = 3; };
mutable_val_lambda();
cout<<val<<endl; // 0
auto const_ref_lambda = [&]() { val = 4; };
const_ref_lambda();
cout<<val<<endl; // 4
auto mutable_ref_lambda = [&]() mutable{ val = 5; };
mutable_ref_lambda();
cout<<val<<endl; // 5
return 0;
}
這段程式碼主要是用來理解Lambda表示式中的mutable關鍵字的。預設情況下,Lambda函式總是一個const函式,mutable可以取消其常量性。按照規定,一個const的成員函式是不能在函式體內修改非靜態成員變數的值。例如上面的Lambda表示式可以看成以下仿函式程式碼:
class const_val_lambda
{
public:
const_val_lambda(int v) : val(v) {}
void operator()() const { val = 3; } // 常量成員函式
private:
int val;
};
對於const的成員函式,修改非靜態的成員變數,所以就出錯了。而對於引用的傳遞方式,並不會改變引用本身,而只會改變引用的值,因此就不會報錯了。都是一些糾結的規則。慢慢理解吧。
總結
對於Lambda這種東西,有的人用的非常爽,而有的人看著都不爽。仁者見仁,智者見智。不管怎麼樣,作為程式設計師的你,都要會的。這篇文章就是用來彌補自己對C++ Lambda表示式的認知不足的過錯,以免以後在別人的程式碼中看到了Lambda,還看不懂這種東西,那就丟大人了。
相關文章
- 「C++11」Lambda 表示式C++
- C++11 lambda表示式 原理C++
- C++11新特性之Lambda表示式C++
- c++之lambda表示式C++
- 【C++】C++之Lambda表示式C++
- C++ lambda表示式的通用格式C++
- Java 中的 Lambda 表示式Java
- C++ Lambda 表示式使用詳解C++
- C#中的Lambda表示式和表示式樹C#
- Java中Lambda表示式的使用Java
- Java 8 中的 lambda 表示式Java
- C++ lambda 表示式與「函式物件」(functor)C++函式物件
- Java8中的Lambda表示式Java
- Python中lambda表示式的用法Python
- Java中Lambda表示式的應用Java
- [轉]Java 8 的 lambda 表示式 Java 8 的 lambda 表示式Java
- C++ 接受狀態變數的lambda表示式C++變數
- lambda 表示式
- lambda表示式
- Java中lambda表示式詳解Java
- Java的Lambda表示式Java
- cpp的lambda表示式
- Java中Lambda表示式的進化之路Java
- 如何利用 C++ 的 Lambda 表示式提升 Qt 程式碼C++QT
- C# Lambda表示式詳解,及Lambda表示式樹的建立C#
- Java | Lambda表示式Java
- Lambda表示式(Java)Java
- java lambda 表示式Java
- 八,Lambda表示式
- Java Lambda表示式Java
- JDK1.8中Lambda表示式的應用JDK
- Java 8中的Lambda表示式最佳實踐Java
- 關於C++中在模板引數中使用Lambda表示式的問題C++
- C++ 一元謂詞對應的lambda表示式C++
- Lambda 表示式的應用
- 中綴表示式的計算,C++版本,Linux環境C++Linux
- 利用 Lambda 表示式實現 Java 中的惰性求值Java
- 利用Lambda表示式進行Java中的惰性求值Java