C++ 的 lambda表達(dá)式

C++中一共有5種調(diào)用對(duì)象:函數(shù),函數(shù)指針重載了函數(shù)調(diào)用運(yùn)算符的類(仿函數(shù)),bind創(chuàng)建的對(duì)象lambda表達(dá)式

函數(shù)指針

仿函數(shù)

lambda表達(dá)式

沒(méi)有l(wèi)ambda的話,函數(shù)對(duì)象的定義太麻煩了,你得定義一個(gè)類,重載operator(),然后再創(chuàng)建這個(gè)類的實(shí)例。所以lambda表達(dá)式可以看成是函數(shù)對(duì)象的語(yǔ)法糖,在你需要的時(shí)候,它可以很簡(jiǎn)潔地給你生成一個(gè)函數(shù)對(duì)象。

語(yǔ)法格式
[capture list] (param list) -> return type  { function body  }

[capture list]是一個(gè)所在函數(shù)中定義的局部變量(非static)的列表,param listreturn typefunction body和普通的函數(shù)一樣表示返回類型(尾置返回),參數(shù)列表,和函數(shù)體。

我們可以忽略參數(shù)列表和返回類型,但必須包含捕獲列表和函數(shù)體。

auto f = [] {  return 42;  }    // 必須包含捕獲列表和函數(shù)體。

當(dāng)定義一個(gè)lambda時(shí),編譯器生成一個(gè)與lambda對(duì)應(yīng)的新的(未命名的)類類型。默認(rèn)情況下,lambda生成的類都包含一個(gè)對(duì)應(yīng)該lambda所捕獲的變量的數(shù)據(jù)成員,在lambda創(chuàng)建時(shí)被初始化。

向lambda傳遞參數(shù)

與一個(gè)普通函數(shù)類似,調(diào)用一個(gè)lambda時(shí)給定的實(shí)參被用來(lái)初始化lambda的形參。通常,實(shí)參和形參類型必須匹配。但與普通函數(shù)不同,lambda不能有默認(rèn)函數(shù)參數(shù)。

使用捕獲列表
  • 值捕獲
    采用值捕獲的前提是 變量可以被拷貝,另外被捕獲的變量的值是在lambda創(chuàng)建時(shí)拷貝,而不是調(diào)用時(shí)拷貝
    void func
    {
        int v1 = 42;
        auto f = [v1] {  return v1;  }
        v1 = 0;
        auto j = f();      // j = 42,  v1在lambda創(chuàng)建時(shí)拷貝,而不是調(diào)用時(shí)拷貝
    }
    
  • 引用捕獲
    采用引用捕獲時(shí),必須確保被引用的對(duì)象在lambda執(zhí)行的時(shí)候是存在的(lambda捕獲的都是局部變量,這些變量在函數(shù)結(jié)束后就不存在了)
    void func()
    {
        int val = 42;
        auto f = [&val]() {   return val; };
        val = 0;
        auto j = f();      // j = 0,引用捕獲。
    }
    
  • 隱式捕獲
    除了顯示列出我們希望使用來(lái)自函數(shù)的變量之外,我們可以讓編譯器隱式推斷l(xiāng)ambda體中的代碼來(lái)推斷我們使用了哪些變量。為了指示編譯器推斷捕獲列表,我們應(yīng)在捕獲列表寫(xiě)&和=,
    • &告訴編譯器采用引用捕獲方式
    • =表示采用值捕獲的方式。
    wc = find_if(words.begin(),  words.end(),  [=](const string &s) {  return s.size() > sz;  })
    
可變lambda
  • mutable
    對(duì)于lambda表達(dá)式。默認(rèn)情況下,lambda不會(huì)改變其值,如果我們希望能改變一個(gè)被捕獲變量的值,就必須在參數(shù)列表首加上關(guān)鍵字mutable。
    void func()
    {
        int val = 42;
        // auto f = [val]() mutable { return ++val;   };    // 編譯錯(cuò)誤,v1只讀。
        auto f = [val]() mutable {    return ++val;   };    // 加上mutable關(guān)鍵字,編譯正確。
        int j = f();    // j = 43
        cout << j << " " << val << endl;    // 輸出 43 和 42
    }
    

加上mutable關(guān)鍵字后,值捕獲也會(huì)改變被捕獲變量的值。

返回類型

默認(rèn)情況下,如果一個(gè)lambda體包含return之外的任何語(yǔ)句,編譯器假定此lambda返回void。所以此時(shí)我們就要顯示指定尾指返回類型。

transfrom(v.begin(), v.end(), 
          [](int i) {  
              if (i < 0) 
                  return -i; 
              else 
                  return i;  
          })  // 錯(cuò)誤
transfrom(v.begin(), v.end(), 
          [](int i) -> int {  
              if (i < 0) 
                  return -i; 
              else 
                  return i;  
          })

參數(shù)綁定bind函數(shù)

我們需要在一個(gè)std::vector<std::string>中尋找大于某長(zhǎng)度單詞。那么我們可以這樣寫(xiě):

auto it = find_if(vec.begin(), vec.end(), 
                  [](const std::string& s) {    
                      return (s.size() > 5);    
                  });

同樣,我們可以用函數(shù)去實(shí)現(xiàn)。

bool check_size(const std::string& s)
{
    return s.size() > 5;
}
auto it = find_if(vec.begin(), vec.end(), check_size);

但假設(shè),我們想要指定長(zhǎng)度來(lái)篩選。那用函數(shù)是不能實(shí)現(xiàn)的。

std::string::size_type sz = 5;
auto it = find_if(vec.begin(), vec.end(), 
                  [sz](const std::string& s) {  
                      return (s.size() > sz);   
                  });

bool check_size(const std::string& s, std::string::size_type sz)
{
    return s.size() > sz;
}

但是這個(gè)函數(shù)不用你管作為find_if的參數(shù),因?yàn)?code>find_if接受的是一元謂詞。

// find_if 可能實(shí)現(xiàn)
template<class InputIt, class UnaryPredicate>
InputIt find_if(InputIt first, InputIt last, UnaryPredicate p)
{
    for (; first != last; ++first) {
        if (p(*first)) {    // p只接受一個(gè)參數(shù)
            return first;
        }
    }
    return last;
}

但我們可以標(biāo)準(zhǔn)庫(kù)的bind函數(shù),它定義在頭文件functional中,可以將它看成一個(gè)通用的函數(shù)適配器,它接受一個(gè)可調(diào)用的對(duì)象,生成一個(gè)新的可調(diào)用對(duì)象來(lái)適應(yīng)原對(duì)象的參數(shù)列表

auto newCallable = bind(callable, arg_list);

那么現(xiàn)在可以這樣寫(xiě):

auto it = find_if(vec.begin(), vec.end(), bind(check_size, std::placegolders::_1, sz));

此處的bind調(diào)用生成一個(gè)可調(diào)用對(duì)象,將check_size的第二個(gè)參數(shù)綁定到sz的值,當(dāng)find_if對(duì)vec中的std::string調(diào)用這個(gè)對(duì)象時(shí)。它會(huì)將給定的參數(shù)std::stringsz傳遞給check_size函數(shù)。

  • 使用placegolders名字
    名字_n都定義在名為placegolders的命名空間中,而這個(gè)命名空間本身定義在std命名空間中。它表示占位符,意味著將自己第n個(gè)參數(shù)按照順序傳遞給原調(diào)用對(duì)象。

    auto g = bind(f, a, b, std::placegolders::_2, c, std::placegolders::_1);
    g(x, y) == f(a, b, y, c, z);
    

    在上面的例子中,g表示一個(gè)有兩個(gè)參數(shù)的新的調(diào)用對(duì)象,原調(diào)用對(duì)象f有5個(gè)參數(shù)。g的第一個(gè)參數(shù)是f的第5個(gè)參數(shù),g的第2個(gè)參數(shù)是f的第3個(gè)參數(shù)。

  • 綁定引用參數(shù)
    默認(rèn)情況下,bind那些不是占位符的參數(shù)被拷貝到bind返回的可調(diào)用對(duì)象中。但是和lambda一樣,有時(shí)對(duì)綁定的參數(shù)我們希望以引用的方式傳遞,或是要綁定的參數(shù)類型無(wú)法拷貝。

    例如,我們希望在打印每個(gè)vec中的單詞后輸出一個(gè)換行。

    for_each(vec.begin(), vec.end(), [&os, c](const std::string& s){  os << s << c;  });
    
    // 很容易編寫(xiě)一個(gè)對(duì)應(yīng)的函數(shù)版本:
    ostream& print(ostream& os, const std::string& s, char c)  {  return os << s << c;  } 
    // 錯(cuò)誤:不能拷貝os
    for_each(vec.begin(), vec.end(), bind(print, os, _1, ' '));
    

    那么,這時(shí)我們希望傳遞給bind的是一個(gè)對(duì)象而又不拷貝它,那么就必須使用refcref函數(shù)。它返回一個(gè)對(duì)象的引用。

    for_each(vec.begin(), vec.end(), bind(print, std::ref(os), _1, ' '));
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容