貫穿C++ 11 與 C++ 17 的 Lambda 到底是個什麼?

2020-12-03 CSDN

本文將詳解Lambda函數從定義到學習和使用,涉及一些不為人知的事情,如LIFE-立即調用的函數表達式,Lambda的類型。相信你已經起了興趣,那就開始閱讀吧。

作者 | Vishal Chovatiya

譯者 | 蘇本如,責編 | maozz

以下為譯文:

Lambda函數是C++ 11中引入的現代C++的一個直觀概念,因此在網際網路上可以找到大量的關於Lambda函數的文章。但是仍然有一些不為人知的事情(如LIFE-立即調用的函數表達式,Lambda的類型等等)鮮有人談論。因此,在這篇文章裡,我不僅要向你展示C++中的Lambda函數,同時還要介紹它的內部工作機制,以及Lambda函數的其他方方面面。

這篇文章的標題有點誤導人。因為Lambda並不總是轉化為函數指針。實際上它是一個表達式(確切地說是唯一的閉包)。為了簡單起見,在這篇文章中,我會一直互換使用Lambda函數和Lambda表達式。

什麼是Lambda函數?

Lambda函數是簡短的代碼片段,它:

不值得命名(匿名的、未被命名的、一次性的,等等,無論你怎麼稱呼它),

也不能重複使用。

換句話說,它只是一種糖衣語法(syntactic sugar)。Lambda函數的語法定義如下:

[ capture list ] (parameters) -> return-type { method definition}

編譯器通常會計算Lambda函數本身的返回類型。因此,我們不需要顯式地給它指定一個尾置返回類型,如-> return-type。

但在一些複雜的情況下,編譯器無法推斷返回類型,這時候我們就需要給它指定一個返回類型。

為什麼我們要使用Lambda函數?

C++包含許多有用的通用函數,如std::for_each,它們可以很方便。不幸的是,它們的使用有時也很麻煩,特別是如果你想應用的函子是特定函數的唯一函子的話。以下面的代碼為例:

structprint{voidoperator()(int element){cout << element << endl; }};int main(void){std::vector<int> v = {1, 2, 3, 4, 5};std::for_each(v.begin(), v.end(), print());return0;}

如果你只是在特定的地方使用一次print,那麼僅僅為了做一些瑣碎的和一次性的事情而編寫一個完整的類,就顯得有些過猶不及了。

對於上面的這種情形,使用內聯代碼會更合適,這可以通過Lambda函數來實現,如下所示:

std:for_each(v.begin(), v.end(), [](int element) { cout << element << endl; });

Lambda函數內部是如何工作的?

[&i] ( ) { std::cout << i; }// is equivalent tostructanonymous{int &m_i; anonymous(int &i) : m_i(i) {}inlineautooperator()()const{std::cout << i; }};

編譯器為每個Lambda函數生成如上所述的唯一閉包。注意,這是Lambda函數的核心所在。

捕獲列表將成為閉包中的構造函數的參數,如果將參數按值捕獲,那麼相應類型的數據成員將在閉包中創建。

此外,可以在Lambda函數的參數中聲明變量/對象,它們將成為調用operator()函數的參數。

使用Lambda函數的好處

零成本抽象。對!你沒有看錯。Lambda函數不會降低性能,它的性能和普通的函數一樣好。

此外,Lambda函數使代碼變得更加緊湊、更加結構化和更富有表現力。

學習Lambda表達式

按引用/值來捕獲,代碼如下:

int main(){int x = 100, y = 200;auto print = [&] { // Capturing object by referencestd::cout << __PRETTY_FUNCTION__ << " : " << x << " , " << y << std::endl; }; print();return0;}

上面代碼的輸出如下:

main()::<Lambda()> : 100 , 200

在上面的例子中,我在捕獲列表中對「&」符號作了注釋。它表示按引用來捕獲變量x和y。類似地,「=」符號表示按值捕獲,它將在閉包中創建相同類型的數據成員,並且將執行copy-assignment操作。

請注意,參數列表是可選的,如果你不向Lambda表達式傳遞任何參數,則可以省略空括號。

Lambda函數的捕獲列表

下表顯示了捕獲列表中不同用法的含義:

將Lambda函數作為參數傳遞,代碼如下:

template <typename Functor>void f(Functor functor){std::cout << __PRETTY_FUNCTION__ << std::endl;}/* Or alternatively you can use thisvoid f(std::function<int(int)> functor){ std::cout << __PRETTY_FUNCTION__ << std::endl;} */intg(){ staticint i = 0; return i++; }int main(){auto Lambda_func = [i = 0]() mutable { return i++; }; f(Lambda_func); // Pass Lambda f(g); // Pass function}

上面代碼的輸出如下:

FunctionType : void f(Functor) [with Functor = main()::<Lambda(int)>]FunctionType : void f(Functor) [with Functor = int (*)(int)]

你還可以將Lambda函數作為參數傳遞給其他函數,就像我在上面編寫的普通函數一樣。

如果你注意到了,這裡我在捕獲列表中聲明了變量i,它將成為數據成員。因此,每次調用Lambda_func時,它都將返回並遞增。

捕獲Lambda函數中的成員變量或this指針,代碼如下:

classExample{public: Example() : m_var(10) {}voidfunc(){ [=]() { std::cout << m_var << std::endl; }(); // IIFE }private:int m_var;};int main(){ Example e; e.func();}

也可以使用[this], [=] 或者 [&]來捕獲This指針。在任何這些情況下,類中的數據成員(包括private類型的數據成員)都可以像在普通方法中那樣被訪問。

如果你看到Lambda表達式行,我在Lambda函數聲明的末尾使用了額外的 (),這個額外的 ()用來表示在聲明之後立即調用它,它被稱為IIFE(立即調用的函數表達式)。

C++的Lambda函數類型

泛型Lambda,代碼如下:

constauto l = [](auto a, auto b, auto c) {};// is equivalent tostructanonymous{template <classT0, classT1, classT2>autooperator()(T0a, T1b, T2c) const { }};

在C++ 14中引入的泛型Lambda,它可以使用auto標識符捕獲參數。

可變泛型Lambda,代碼如下:

voidprint(){}template <typename First, typename... Rest>void print(const First &first, Rest &&... args){std::cout << first << std::endl; print(args...);}int main(){auto variadic_generic_Lambda = [](auto... param) { print(param...); }; variadic_generic_Lambda(1, "lol", 1.1);}

帶可變參數包的Lambda在許多情況下都很有用,如代碼調試、不同數據輸入的重複操作等。

可變(Mutable)Lambda函數

通常,Lambda函數的call-operator(調用運算符)隱式為const-by-value(常量,按值捕獲),這意味著它是不可變的。如果要按值捕獲任何內容,需要在Lambda函數體前使用mutable關鍵字。代碼如下所示:

[]() mutable {}// is equivalent tostructanonymous{autooperator()()// call operator{ }};

我們已經在上面看到了上述情況的一個例子。希望你注意到了。

Lambda作為函數指針,代碼如下:

#include<iostream>#include<type_traits>int main(){auto funcPtr = +[] {};static_assert(std::is_same<decltype(funcPtr), void (*)()>::value);}

你可以強制編譯器生成Lambda作為函數指針,而不是像上面那樣在它前面添加+來使之成為閉包。

高階函數:Lambda可以作為參數和返回值,代碼如下:

constauto less_than = [](auto x) {return [x](auto y) {return y < x; };};int main(void){auto less_than_five = less_than(5);std::cout << less_than_five(3) << std::endl;std::cout << less_than_five(10) << std::endl;return0;}

再進一步,Lambda函數還可以返回另一個Lambda函數。這將為代碼的定製、代碼表示性和緊湊性(順便說一句,沒有這樣的詞)打開無限可能的大門。

constexpr Lambda表達式

從C++ 17開始,Lambda表達式可以被聲明為constexpr(常量表達式)。

constexprauto sum = [](constauto &a, constauto &b) { return a + b; };/* is equivalent to constexpr struct anonymous { template <class T1, class T2> constexpr auto operator()(T1 a, T2 b) const { return a + b; } };*/constexprint answer = sum(10, 10);

即使你沒有指定constexpr關鍵字,如果函數調用運算符恰好滿足所有constexpr函數的要求,那麼它也將是constexpr。

結束語

希望你喜歡這篇文章。我試著用幾個簡單的小例子來涵蓋使用Lambda的大多數複雜情況。考慮到代碼的表達性和易維護性,你應該在滿足Lambda使用條件的所有地方都優先使用它,就像你可以將其和大多數STL算法一起用於智能指針的自定義刪除器中一樣。

原文:https://hackernoon.com/all-about-Lambda-functions-in-cfrom-c11-to-c17-2t1j32qw

本文為 CSDN 翻譯,轉載請註明來源出處。

相關焦點

  • 三種基本用法、五種應用場景,理清C++11新特性:Lambda表達式
    另外,由於lambda表達式默認返回是const類型,如果想要取消const屬性,那麼需要加上修飾符mutable。基本用法現在通過三個簡單的例子來介紹lambda表達式的用法,加深對其的理解。第一個例子是函數沒有返回值的形式,首先定義四個變量,然後創建lambda表達式ret, 內部直接列印父作用域四個變量的值,注意[=] 表示值傳遞方式捕捉所有父作用域的對象。運行程序輸出的結果如下,調用lambda表示式ret()之後,正確的輸出了父作用域的所有的值。第二個例子是增加函數的返回值,先計算父作用域所有變量的和,作為返回值。
  • 【C++】C++獲取系統日期時間
    tm_sec; /* 秒 – 取值區間為[0,59] */int tm_min; /* 分 - 取值區間為[0,59] */int tm_hour; /* 時 - 取值區間為[0,23] */int tm_mday; /* 一個月中的日期 - 取值區間為[1,31] */int tm_mon; /* 月份(從一月開始,0代表一月) - 取值區間為[0,11
  • C++的轉換手段並與explicit關鍵詞配合使用
    C++除了隱式轉換和顯示轉化,顯示轉化是我們熟知,有四個顯示轉化函數:static_cast、dynamic_cast、const_cast、reinterpret_cast,主要運用於繼承關係類間的強制轉化。下面我就給大家說道說道。
  • 基於Matlab和Visual C++的數字濾波器設計方法
    電子電路往往是由若干個動態環節連在一起構成一個複雜電路。對於每個具體環節來說,都有它的輸入量和輸出量,而一定輸入量的變化都會引起輸出量的變化。根據一個環節中所進行的物理過程可以寫出微分方程,它表示了該環節輸出量和輸入量的關係。例如RLC振蕩迴路的微分方程為
  • C++後端開發面試題與知識點匯總(附答案)
    以下匯總C++後臺開發面試題與知識點,還有其他崗位的相關題庫和資料,想要什麼崗位的可以留言哦~附面試題目:一、基礎知識1、基本語言說一下C++和C的區別說一下C++中static關鍵字的作用說一說c++中四種cast轉換請說一下C/C++ 中指針和引用的區別?
  • C++程式設計師的職業生涯規劃
    ); 4、深刻理解網際網路視頻播放原理,對ffmpeg等框架有實際的使用經驗; 5、有視頻直播、點播、視頻會議、監控安防等方面經驗優先 6、熟悉Nginx/Squid模塊開發優先考慮,有FMS等流媒體伺服器搭建經驗、P2P系統研發經驗、知名優秀的視頻產品服務端設計和研發經驗優先考慮 二、應用開發工程師windows /linuxc++
  • 圖解C++、CoffeeScript、Ruby複雜度
    它總共有8大範疇共68個概念。注意:我把元編程也當作一個概念,因為它很大程度上基於可執行類體(executable class body)這一理念。Ruby明顯要更加複雜,總共11大範疇96個概念。它比CoffeeScript有更加複雜的類模型,比如常量、塊、操作符重載之類。
  • 基於C++的電力潮流計算牛-拉法與P-Q法的分析比較
    vector Matrix;for(i=0;i MatrixSize;i++)for(j=0;j MatrixSize;j++)Matrix[i* MatrixSize+j]=X[i][j];其中,一維向量的大小即MatrixSize*MatrixSize,它等於二維數組X[i][j]中所有元素的個數
  • c++ 內存,虛函數,運算函數,三角函數
    看例子:#define XNAME(n) x ## n則會被展開成這樣:x8也就是說,##就是個粘合劑,將前後兩部分粘合起來const const 類型的對象在程序執行期間不能被修改改變a=[01101001]表示{0,3,5,6}b=[01010101]表示{0,2,4,6}最終a&b={01000001}={0,6}以題目的例子來講,a=[01101001],從右邊數起,第0、3、5、6位是1,所以就表示了0、3、5、6這4個數
  • 大家都在說的「Lambda 表達式」到底是什麼?
    自從java8中引入了Lambda表達式之後,使用的人也越來越多了,這篇文章我們就來看看什麼是Lambda表達式。廢話少說,先看定義!2、簡單了解一下lambda的語法格式(參數) ->{語句; }上面就是lambda的語法格式,「->」是它的一個標誌,當你看到「->」你就知道這裡使用了Lambda表達式。3、再來看一下Lambda的特徵可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。
  • Python中的Lambda表達式
    因此,從現在開始,我們可以square像調用任何傳統函數一樣調用對象square(10)Lambda函數的示例初級lambda_func = lambda x: x**2 # 需要一個整數並返回其平方的函數lambda_func
  • 17 個 Linux 下用於 C/C++ 的最好的 IDE
    讓我們來看看關於它的特性:C/C++ 編輯器很好的整合了多線程的 GNU GDB 調試工具支持代碼協助支持 C++11 標準主頁: https://www.visualstudio.com11主頁: https://www.gnu.org/software/emacs/17
  • Python面試題推薦:什麼是lambda函數?
    定義lambda函數的形式如下:labmda 參數:表達式lambda函數默認返回表達式的值。你也可以將其賦值給一個變量。lambda函數可以接受任意個參數,包括可選參數,但是表達式只有一個:>>> g = lambda x, y: x*y>>> g(3,4)12>>> g = lambda x, y=0,