C++ 98/03
C++ 98/03標準並不原生支持匿名函數。不過可以利用Boost庫的Boost.Lambda來實現一個匿名函數。
C++11
初步瞭解:
很多語言都提供了 lambda 表達式,如 Python,Java 8。lambda 表達式可以方便地構造匿名函數,如果你的代碼裏面存在大量的小函數,而這些函數一般只被調用一次,那麼不妨將他們重構成 lambda 表達式。
C++11 的 lambda 表達式規範如下:
[ capture ] ( params
) mutableexception
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 指針。[&]
以引用的方式捕獲所有的外部自動變量。[=]
以值的方式捕獲所有的外部自動變量。[]
不捕獲外部的任何變量。
1、空。沒有使用任何函數對象參數。
2、=。函數體內可以使用Lambda所在作用範圍內所有可見的局部變量(包括Lambda所在類的this),並且是值傳遞方式(相當於編譯器自動爲我們按值傳遞了所有局部變量)。
3、&。函數體內可以使用Lambda所在作用範圍內所有可見的局部變量(包括Lambda所在類的this),並且是引用傳遞方式(相當於編譯器自動爲我們按引用傳遞了所有局部變量)。
4、this。函數體內可以使用Lambda所在類中的成員變量。
5、a。將a按值進行傳遞。按值進行傳遞時,函數體內不能修改傳遞進來的a的拷貝,因爲默認情況下函數是const的。要修改傳遞進來的a的拷貝,可以添加mutable修飾符。
6、&a。將a按引用進行傳遞。
7、a, &b。將a按值進行傳遞,b按引用進行傳遞。
8、=,&a, &b。除a和b按引用進行傳遞外,其他參數都按值進行傳遞。
9、&, a, b。除a和b按值進行傳遞外,其他參數都按引用進行傳遞。
此外,params 指定 lambda 表達式的參數。
詳解:
C++11標準提供了匿名函數的支持,在《ISO/IEC 14882:2011》(C++11標準文檔)中叫做lambda表達式[10]。一個lambda表達式有如下的形式:
[capture] (parameters) mutable exception attribute -> return_type { body }
必須用方括號括起來的capture列表來開始一個lambda表達式的定義。
lambda函數的形參表比普通函數的形參表多了3條限制:
- 參數不能有缺省值
- 不能有可變長參數列表
- 不能有無名參數
如果lambda函數沒有形參且沒有mutable、exception或attribute聲明,那麼參數的空圓括號可以省略。但如果需要給出mutable、exception或attribute聲明,那麼參數即使爲空,圓括號也不能省略。
如果函數體只有一個return語句,或者返回值類型爲void,那麼返回值類型聲明可以被省略:
[capture](parameters){body}
一個lambda函數的例子如下:
[](int x, int y) { return x + y; } // 從return語句中隱式獲得的返回值類型 [](int& x) { ++x; } // 沒有return語句 -> lambda函數的返回值為void []() { ++global_x; } // 沒有參數,僅僅是訪問一個全局變量 []{ ++global_x; } // 與前者相同,()可以被省略
這個無名函數的返回值是decltype(x+y)
(在上面的第一個例子中)。如果lambda函數體的形式是returnexpression
,或者什麼也每返回,或者所有返回語句用decltype
都能檢測到同一類型,那麼返回值類型可以被省略。
返回值類型可以顯式指定,如下所示:
[](int x, int y) -> int { int z = x + y; return z; }
在這個例子中,一個臨時變量,z
,被創建來儲存中間過程。與一般的函數一樣,中間值在調用的前後並不存在。什麼也沒有返回的lambda表達式無需顯式指定返回值,沒有必要寫-> void
代碼。
lambda函數可以捕獲lambda函數外的具有automatic storage duration的變量。函數體與這些變量的集合合起來稱做閉包。這些外部變量在聲明lambda表達式時列在在方括號[
和]
中。空的方括號表示沒有外界變量被capture。這些變量被傳值捕獲或者引用捕獲。對於傳值捕獲的變量,默認爲只讀。修改這些變量將導致編譯報錯。但在lambda表達式的參數表的圓括號後面使用mutable關鍵字,就允許lambda函數體內的語句修改傳值引用的變量,這些修改與lambda表達式(實際上是用函數對象實現)有相同的生命期,但不影響被傳值捕獲的外部變量的值。lambda函數可以直接使用具有static存儲期的變量。如果在lambda函數的捕獲列表中給出了static存儲期的變量,編譯時會給出警告,仍然按照lambda函數直接使用這些外部變量來處理。因此具有static存儲期的變量即使被聲明爲傳值捕獲,修改該變量實際上直接修改了這些外部變量。編譯器生成lambda函數對應的函數對象時,不會用函數對象的數據成員來保持被“捕獲”的static存儲期的變量。示例:
[] // 沒有定義任何變量,但必須列出空的方括號。在Lambda表達式中嘗試使用任何外部變量都會導致編譯錯誤。 [x, &y] // x是按值傳遞,y是按引用傳遞 [&] // 任何被使用到的外部變量都按引用傳入。 [=] // 任何被使用到的外部變量都按值傳入。 [&, x] // x按值傳入。其它變量按引用傳入。 [=, &z] // z按引用傳入。其它變量按值傳入。
下面這個例子展示了lambda表達式的使用:
std::vector<int> some_list{ 1, 2, 3, 4, 5 }; int total = 0; std::for_each(begin(some_list), end(some_list), [&total](int x) { total += x; } );
在類的非靜態成員函數中定義的lambda表達式可以顯式或隱式捕捉this
指針,從而可以引用所在類對象的數據成員與函數成員。
lambda函數的函數體中,可以訪問下述變量:
- 函數參數
- 局部聲明的變量
- 類數據成員(當函數聲明在類中)
- 具有靜態存儲期的變量(如全局變量)
- 被捕獲的外部變量
- 顯式捕獲的變量
- 隱式捕獲的變量,使用默認捕獲模式(傳值或引用)來訪問。
lambda函數的數據類型是函數對象,保存時必須用std::function
模板類型或auto
關鍵字,或用auto
關鍵字。例如:
#include <functional> #include <vector> #include <iostream> double eval(std::function <double(double)> f, double x = 2.0) { return f(x); } int main() { std::function<double(double)> f0 = [](double x){return 1;}; auto f1 = [](double x){return x;}; decltype(f0) fa[3] = {f0,f1,[](double x){return x*x;}}; std::vector<decltype(f0)> fv = {f0,f1}; fv.push_back ([](double x){return x*x;}); for(int i=0;i<fv.size();i++) std::cout << fv[i](2.0) << std::endl; for(int i=0;i<3;i++) std::cout << fa[i](2.0) << std::endl; for(auto &f : fv) std::cout << f(2.0) << std::endl; for(auto &f : fa) std::cout << f(2.0) << std::endl; std::cout << eval(f0) << std::endl; std::cout << eval(f1) << std::endl; std::cout << eval([](double x){return x*x;}) << std::endl; return 0; }
一個lambda函數的捕捉表達式爲空,則可以用普通函數指針存儲或調用。例如:
auto a_lambda_func = [](int x) { /*...*/ }; void (* func_ptr)(int) = a_lambda_func; func_ptr(4); //calls the lambda.