本文將詳解Lambda函數(shù)從定義到學(xué)習(xí)和使用,涉及一些不為人知的事情,如LIFE-立即調(diào)用的函數(shù)表達式,Lambda的類型。相信你已經(jīng)起了興趣,那就開始閱讀吧。
作者 | Vishal Chovatiya
譯者 | 蘇本如,責(zé)編 | maozz
以下為譯文:
Lambda函數(shù)是C++ 11中引入的現(xiàn)代C++的一個直觀概念,因此在互聯(lián)網(wǎng)上可以找到大量的關(guān)于Lambda函數(shù)的文章。但是仍然有一些不為人知的事情(如LIFE-立即調(diào)用的函數(shù)表達式,Lambda的類型等等)鮮有人談?wù)?。因此,在這篇文章里,我不僅要向你展示C++中的Lambda函數(shù),同時還要介紹它的內(nèi)部工作機制,以及Lambda函數(shù)的其他方方面面。
這篇文章的標(biāo)題有點誤導(dǎo)人。因為Lambda并不總是轉(zhuǎn)化為函數(shù)指針。實際上它是一個表達式(確切地說是唯一的閉包)。為了簡單起見,在這篇文章中,我會一直互換使用Lambda函數(shù)和Lambda表達式。
什么是Lambda函數(shù)?
Lambda函數(shù)是簡短的代碼片段,它:
不值得命名(匿名的、未被命名的、一次性的,等等,無論你怎么稱呼它),
也不能重復(fù)使用。
換句話說,它只是一種糖衣語法(syntactic sugar)。Lambda函數(shù)的語法定義如下:
[ capture list] (parameters) -> return-type
{ method definition
}
編譯器通常會計算Lambda函數(shù)本身的返回類型。因此,我們不需要顯式地給它指定一個尾置返回類型,如-> return-type。
但在一些復(fù)雜的情況下,編譯器無法推斷返回類型,這時候我們就需要給它指定一個返回類型。
為什么我們要使用Lambda函數(shù)?
C++包含許多有用的通用函數(shù),如std::for_each,它們可以很方便。不幸的是,它們的使用有時也很麻煩,特別是如果你想應(yīng)用的函子是特定函數(shù)的唯一函子的話。以下面的代碼為例:
structprint
{
voidoperator(intelement)
{
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,那么僅僅為了做一些瑣碎的和一次性的事情而編寫一個完整的類,就顯得有些過猶不及了。
對于上面的這種情形,使用內(nèi)聯(lián)代碼會更合適,這可以通過Lambda函數(shù)來實現(xiàn),如下所示:
std:for_each(v.begin, v.end, [](intelement) { cout<< element << endl; });
Lambda函數(shù)內(nèi)部是如何工作的?
[&i] ( ) { std::cout<< i; }
// is equivalent to
structanonymous
{
int&m_i;
anonymous(int&i) : m_i(i) {}
inlineautooperatorconst
{
std::cout<< i;
}
};
編譯器為每個Lambda函數(shù)生成如上所述的唯一閉包。注意,這是Lambda函數(shù)的核心所在。
捕獲列表將成為閉包中的構(gòu)造函數(shù)的參數(shù),如果將參數(shù)按值捕獲,那么相應(yīng)類型的數(shù)據(jù)成員將在閉包中創(chuàng)建。
此外,可以在Lambda函數(shù)的參數(shù)中聲明變量/對象,它們將成為調(diào)用operator函數(shù)的參數(shù)。
使用Lambda函數(shù)的好處
零成本抽象。對!你沒有看錯。Lambda函數(shù)不會降低性能,它的性能和普通的函數(shù)一樣好。
此外,Lambda函數(shù)使代碼變得更加緊湊、更加結(jié)構(gòu)化和更富有表現(xiàn)力。
學(xué)習(xí)Lambda表達式
按引用/值來捕獲,代碼如下:
int main
{
intx = 100, y = 200;
autoprint = [&] { // Capturing object by reference
std::cout<< __PRETTY_FUNCTION__ << " : "<< x << " , "<< y << std::endl;
};
print;
return0;
}
上面代碼的輸出如下:
main::<Lambda> : 100, 200
在上面的例子中,我在捕獲列表中對“&”符號作了注釋。它表示按引用來捕獲變量x和y。類似地,“=”符號表示按值捕獲,它將在閉包中創(chuàng)建相同類型的數(shù)據(jù)成員,并且將執(zhí)行copy-assignment操作。
請注意,參數(shù)列表是可選的,如果你不向Lambda表達式傳遞任何參數(shù),則可以省略空括號。
Lambda函數(shù)的捕獲列表
下表顯示了捕獲列表中不同用法的含義:
將Lambda函數(shù)作為參數(shù)傳遞,代碼如下:
template<typenameFunctor>
void f(Functor functor)
{
std::cout<< __PRETTY_FUNCTION__ << std::endl;
}
intg{ staticinti = 0; returni++; }
int main
{
autoLambda_func = [i = 0] mutable{ returni++; };
f(Lambda_func); // Pass Lambda
f(g); // Pass function
}
上面代碼的輸出如下:
FunctionType: voidf(Functor) [withFunctor = main::<Lambda(int)>]
FunctionType: voidf(Functor) [withFunctor = int(*)(int)]
你還可以將Lambda函數(shù)作為參數(shù)傳遞給其他函數(shù),就像我在上面編寫的普通函數(shù)一樣。
如果你注意到了,這里我在捕獲列表中聲明了變量i,它將成為數(shù)據(jù)成員。因此,每次調(diào)用Lambda_func時,它都將返回并遞增。
捕獲Lambda函數(shù)中的成員變量或this指針,代碼如下:
classExample
{
public:
Example : m_var(10) {}
voidfunc
{
[=] { std::cout<< m_var << std::endl; }; // IIFE
}
private:
intm_var;
};
int main
{
Example e;
e.func;
}
也可以使用[this], [=] 或者 [&]來捕獲This指針。在任何這些情況下,類中的數(shù)據(jù)成員(包括private類型的數(shù)據(jù)成員)都可以像在普通方法中那樣被訪問。
如果你看到Lambda表達式行,我在Lambda函數(shù)聲明的末尾使用了額外的 ,這個額外的 用來表示在聲明之后立即調(diào)用它,它被稱為IIFE(立即調(diào)用的函數(shù)表達式)。
C++的Lambda函數(shù)類型
泛型Lambda,代碼如下:
constautol = [](autoa, autob, autoc) {};
// is equivalent to
structanonymous
{
template<classT0, classT1, classT2>
autooperator(T0a, T1b, T2c) const
{
}
};
在C++ 14中引入的泛型Lambda,它可以使用auto標(biāo)識符捕獲參數(shù)。
可變泛型Lambda,代碼如下:
voidprint{}
template<typenameFirst, typename... Rest>
void print(const First &first, Rest &&... args)
{
std::cout<< first << std::endl;
print(args...);
}
int main
{
autovariadic_generic_Lambda = [](auto... param) {
print(param...);
};
variadic_generic_Lambda(1, "lol", 1.1);
}
帶可變參數(shù)包的Lambda在許多情況下都很有用,如代碼調(diào)試、不同數(shù)據(jù)輸入的重復(fù)操作等。
可變(Mutable)Lambda函數(shù)
通常,Lambda函數(shù)的call-operator(調(diào)用運算符)隱式為const-by-value(常量,按值捕獲),這意味著它是不可變的。如果要按值捕獲任何內(nèi)容,需要在Lambda函數(shù)體前使用mutable關(guān)鍵字。代碼如下所示:
[] mutable{}
// is equivalent to
structanonymous
{
autooperator// call operator
{
}
};
我們已經(jīng)在上面看到了上述情況的一個例子。希望你注意到了。
Lambda作為函數(shù)指針,代碼如下:
#include<iostream>
#include<type_traits>
int main
{
autofuncPtr = +[] {};
static_assert(std::is_same<decltype(funcPtr), void(*)>::value);
}
你可以強制編譯器生成Lambda作為函數(shù)指針,而不是像上面那樣在它前面添加+來使之成為閉包。
高階函數(shù):Lambda可以作為參數(shù)和返回值,代碼如下:
constautoless_than = [](autox) {
return[x](autoy) {
returny < x;
};
};
int main(void)
{
autoless_than_five = less_than(5);
std::cout<< less_than_five(3) << std::endl;
std::cout<< less_than_five(10) << std::endl;
return0;
}
再進一步,Lambda函數(shù)還可以返回另一個Lambda函數(shù)。這將為代碼的定制、代碼表示性和緊湊性(順便說一句,沒有這樣的詞)打開無限可能的大門。
constexpr Lambda表達式
從C++ 17開始,Lambda表達式可以被聲明為constexpr(常量表達式)。
constexprautosum = [](constauto&a, constauto&b) { returna + b; };
constexprintanswer = sum(10, 10);
即使你沒有指定constexpr關(guān)鍵字,如果函數(shù)調(diào)用運算符恰好滿足所有constexpr函數(shù)的要求,那么它也將是constexpr。
結(jié)束語
希望你喜歡這篇文章。我試著用幾個簡單的小例子來涵蓋使用Lambda的大多數(shù)復(fù)雜情況??紤]到代碼的表達性和易維護性,你應(yīng)該在滿足Lambda使用條件的所有地方都優(yōu)先使用它,就像你可以將其和大多數(shù)STL算法一起用于智能指針的自定義刪除器中一樣。
原文:https://hackernoon/all-about-Lambda-functions-in-cfrom-c11-to-c17-2t1j32qw
本文為 CSDN 翻譯,轉(zhuǎn)載請注明來源出處。
點擊閱讀原文參與開發(fā)者大調(diào)查,好禮送不停!


