走進C++11(二十二) 之lambda(匿名函式)
提示這篇文章可能較長,分為如下幾部分
-
什麼是lambda函式
-
Lambda函式的用處
-
Lambda函式中的變數擷取
-
Lambda函式和STL
1. 什麼是lambda函式
lambda表示式無疑是C++11最激動人心的特性之一!它會使你編寫的程式碼變得更優雅、更快速! 它實現了C++11對於支援閉包的支援。首先我們先看一下什麼叫做閉包
維基百科上,對於閉包的解釋是:
In programming languages, a closure (also lexical closure orfunction closure) is a
The concept of closures was developed in the 1960s and was first fully implemented in 1975[citation needed] as a language feature in the Scheme programming language to support lexically scoped first-class functions. The use of closures is associated with functional programming languages
簡單來說,閉包(Closure)是詞法閉包(Lexical Closure)或者函式閉包的簡稱,是引用了自由變數的函式。這個被引用的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函式和與其相關的引用環境組合而成的實體。
在程式設計語言中,變數可以分為自由變數(free variable)與約束變數(bound variable)兩種。簡單來說,一個函式裡區域性變數和引數都被認為是約束變數;而不是約束變數的則是自由變數。
百度百科的解釋可能更加通俗易懂:
閉包是可以包含自由(未繫結到特定物件)變數的程式碼塊;這些變數不是在這個程式碼塊內或者任何全域性上下文中定義的,而是在定義程式碼塊的環境中定義(區域性變數)。“閉包” 一詞來源於以下兩者的結合:要執行的程式碼塊(由於自由變數被包含在程式碼塊中,這些自由變數以及它們引用的物件沒有被釋放)和為自由變數提供繫結的計算環境(作用域)。在 Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python和Lua,objective c 等語言中都能找到對閉包不同程度的支援。
在程式設計領域我們可以通俗的說:子函式可以使用父函式中的區域性變數,這種行為就叫做閉包!
有人說lambda是C++的語法糖。在C++11中labmda可以通過捕獲實現閉包。
2. Lambda函式的用處
lambda 表示式可以方便地構造匿名函式,如果你的程式碼裡面存在大量的小函式,而這些函式一般只被呼叫一次,那麼不妨將他們重構成 lambda 表示式。
C++11 的 lambda 表示式規範如下:
[ capture ] ( params ) mutable exception attribute -> ret { body } | (1) |
[ capture ] ( params ) -> ret { body } | (2) |
[ capture ] ( params ) { body } | (3) |
[ capture ] { body } | (4) |
其中
-
(1) 是完整的 lambda 表示式形式,
-
(2) const 型別的 lambda 表示式,該型別的表示式不能改捕獲("capture")列表中的值。
-
(3)省略了返回值型別的 lambda 表示式,但是該 lambda 表示式的返回型別可以按照下列規則推演出來:
-
如果 lambda 程式碼塊中包含了 return 語句,則該 lambda 表示式的返回型別由 return 語句的返回型別確定。
-
如果沒有 return 語句,則類似 void f(...) 函式。
-
-
省略了引數列表,類似於無參函式 f()。
mutable 修飾符說明 lambda 表示式體內的程式碼可以修改被捕獲的變數,並且可以訪問被捕獲物件的 non-const 方法。
exception 說明 lambda 表示式是否丟擲異常(noexcept),以及丟擲何種異常,類似於void f() throw(X, Y)。
attribute 用來宣告屬性。
另外,capture 指定了在可見域範圍內 lambda 表示式的程式碼內可見得外部變數的列表(詳見第三節),具體解釋如下:
-
[a,&b] a變數以值的方式唄捕獲,b以引用的方式被捕獲。
-
[this] 以值的方式捕獲 this 指標。
-
[&] 以引用的方式捕獲所有的外部自動變數。
-
[=] 以值的方式捕獲所有的外部自動變數。
-
[] 不捕獲外部的任何變數。
此外,params 指定 lambda 表示式的引數。
3. Lambda函式中的變數擷取
捕獲:
(1)值捕獲
與引數傳值類似,值捕獲的前提是變數可以拷貝,不同之處則在於,被捕獲的變數在lambda 表示式被建立時拷貝,而非呼叫時才拷貝
void f(const int*);
void g()
{
const int N = 10;
[=]{
int arr[N]; // not an odr-use: refers to g's const int N
f(&N); // odr-use: causes N to be captured (by copy)
// &N is the address of the closure object's member N, not g's N
}();
}
(2)引用捕獲
與引用傳參類似,引用捕獲儲存的是引用,值會發生變化。
#include <iostream>
auto make_function(int& x) {
return [&]{ std::cout << x << '\n'; };
}
int main() {
int i = 3;
auto f = make_function(i); // the use of x in f binds directly to i
i = 5;
f(); // OK; prints 5
}
(3)隱式捕獲
[] 空捕獲列表
[name1, name2, ...] 捕獲一系列變數
[&] 引用捕獲, 讓編譯器自行推導捕獲列表
[=] 值捕獲, 讓編譯器執行推導應用列表
void f3() {
float x, &r = x;
[=]
{ // x and r are not captured (appearance in a decltype operand is not an odr-use)
decltype(x) y1; // y1 has type float
decltype((x)) y2 = y1; // y2 has type float const& because this lambda
// is not mutable and x is an lvalue
decltype(r) r1 = y1; // r1 has type float& (transformation not considered)
decltype((r)) r2 = y2; // r2 has type float const&
};
}
當然C++14,C++17,C++20對lambda做了更多的更新,這裡就不多講了。
4. Lambda函式和STL
lambda函式的引入為STL的使用提供了極大的方便。比如下面這個例子,當你想便利一個vector的時候,原來你得這麼寫:
vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ )
{
cout << *itr;
}
現在有了lambda函式你就可以這麼寫:
vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for_each( v.begin(), v.end(), [] (int val)
{
cout << val;
} );
而且這麼寫了之後執行效率反而提高了。因為編譯器有可能使用”迴圈展開“來加速執行過程。
引用:
https://blog.csdn.net/anzhsoft/article/details/17414665