c++11新特性匿名函數

c++ lambda表達式詳解

轉載請注明:http://krystism.is-programmer.com/若有錯誤,請多多指正,謝謝!
  lambda表達式是c++11標準新加特性,學過python的一定不會陌生了,或者類似javascript的閉包。cppreference中的定義是:--*Constructs a closure: an unnamed function object capable of capturing variables in scope. *—-簡單地說就是定義一個臨時局部匿名函數。語法為:
[ capture ] ( params ) mutable exception attribute -> ret { body }
其中capture為定義外部變量是否可見(捕獲),(這里的外部變量是指與定義這個lambda處于同一個作用域的變量)。
若為空,則表示不捕獲所有外部變量,即所有外部變量均不可訪問,= 表示所有外部變量均以值的形式捕獲,在body中訪問外部變量時,訪問的是外部變量的一個副本,類似函數的值傳遞,因此在body中對外部變量的修改均不影響外部變量原來的值。& 表示以引用的形式捕獲,后面加上需要捕獲的變量名,沒有變量名,則表示以引用形式捕獲所有變量,類似函數的引用傳遞,body操作的是外部變量的引用,因此body中修改外部變量的值會影響原來的值。例如:[ ]表示不捕獲任何外部變量, [a, b]以值的形式捕獲a,b, [=]以值的形式捕獲所有外部變量,[&a, b]a以引用的形式捕獲,而b以值的形式捕獲,[&, a],除了a以值的形似捕獲,其他均以引用的形式捕獲,[this]以值的形式捕獲this指針!


params就是函數的形參,和普通函數類似,不過若沒有形參,這個部分可以省略。
mutalbe表示運行body修改通過拷貝捕獲的參數,exception聲明可能拋出的異常,attribute 修飾符,參考:http://en.cppreference.com/w/cpp/language/attributes 。->ret ret表示返回類型,如果能夠根據返回語句自動推導,則可以省略,body即函數體。
注意:除了capture和body是必需的,其他均可以省略,即

int int main(int argc, char const *argv[])
{
    []() {};
    return 0;
}

定義了一個空lambda表達式,并執行(實際上它什么都沒有做)。

int main(int argc, char **argv)
{
    int i = 0;
    []{cout << i << endl;}(); /* 'i' is not captured */
}

聲明這段代碼不能編譯通過,因為[ ] 沒有捕獲任何外部變量,因此i是不可見的,lambda不能訪問i。

int main(int argc, char **argv)
{
    int i = 0;
    cout << i << endl;
    [=]()mutable{cout << ++i << endl;}();
    cout << i << endl;

上面的代碼輸出為0, 1, 0,注意mutable是必需的,因為body中修改了捕獲的i,由于i是以值傳遞的,因此并沒有修改i原來的值,而是i的一個副本。

int main(int argc, char **argv)
{
    int i = 0;
    cout << i << endl;
    [&](){cout << ++i << endl;}();
    cout << i << endl;

上面代碼輸出0, 1, 1,因為i是以引用傳遞的,而body中修改了i的值。
lambda表達式有什么用呢? lambda的作用就是創建一個臨時匿名函數,想想有STL算法中很多需要傳遞謂詞函數,比如count_if。假如我有一個字符串容器,我需要統計長度大于3的個數,則可以這樣:

int main(int argc, char **argv)
{
    vector<string> s = {"1", "12", "123", "1234", "12345", "123456", "1234567"};
    cout << count_if(begin(s), end(s), [](string s){return s.size() > 3;});
  }

這樣就不必要再聲明一個函數了,代碼比較簡潔。
還有一個問題就是,我可能需要統計字符串長度大于4,或者5,或者6,顯然不能通過傳遞一個形參來實現,因為謂詞函數限定只能傳遞一個形參,這里我們傳遞的是string,而不能再傳遞一個表示長度的數。 如果定義全局函數實現,則我們有兩種方式:
一是聲明一個全局變量,通過和這個全局變量作比較。二是分別聲明大于4或者5的函數。顯然兩個辦法都不太好。全局變量污染一直是很危險的,而聲明多個函數顯然不科學,如果有更多的需求,則需聲明gt2, gt3, gt4 ...。 這個用lambda表達式似乎更好,如:

int main(int argc, char **argv)
{
    vector<string> s = {"1", "12", "123", "1234", "12345", "123456", "1234567"};
    string::size_type n = 3;
    cout << count_if(begin(s), end(s), [n](string s){return s.size() > n;});
} 

注意這里的n并不是全局變量,所以不存在全局變量污染的問題。當然還有更好的辦法,那就是利用函數對象,如:

using namespace std;
class Gt
{
    public:
        typedef string::size_type size_type;
        Gt(size_type i):n(i){};
        Gt():n(0){};
        void setN(const size_type& i) {
            n = i;
        }
        size_type getN() const{return n;}
        bool operator () (const string &s) const {return s.size() > n;}
        operator int() const { return n;}
    private:
        string::size_type n = 0;
};
inline Gt operator + (const Gt &s1, const Gt &s2)
{
    return Gt(s1.getN() + s2.getN());
}
int main(int argc, char **argv)
{
    vector<string> s = {"1", "12", "123", "1234", "12345", "123456", "1234567"};
    cout << count_if(begin(s), end(s), Gt(1)) << endl; /* > 1 */
    cout << count_if(begin(s), end(s), Gt(2)) << endl; /* > 2 */
    cout << count_if(begin(s), end(s), Gt(2) + Gt(3)) << endl; /* > 5 */
    return 0;
}

上面分別統計長度大于1,大于2,大于5的字符串數量(重載+運算符,純屬娛樂?。?。
lambda表達式另外一個功能是聲明局部函數。我們知道c++中是不允許在函數中嵌套定義函數的,如果要聲明一個局部函數,即只能在函數內部調用,則可以用lambda實現,如聲明一個min函數,返回a,b中較小值。則

#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
    auto min = [](int a, int b) -> int{
        return a < b ? a : b;
    };
    cout << min(1, 2) << endl;
    cout << min(8, 2) << endl;
    return 0;
}```
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容