貫穿 C-- 11 與 C-- 17 的 Lambda 到底是個什麼?
鏈接 | https://hackernoon.com/all-about-Lambda-functions-in-cfrom-c11-to-c17-2t1j32qw
翻譯 | CSDN
本文將詳解 Lambda 函數從定義到學習和使用,涉及一些不爲人知的事情,如 LIFE - 立即調用的函數表達式,Lambda 的類型。相信你已經起了興趣,那就開始閱讀吧。
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,它們可以很方便。不幸的是,它們的使用有時也很麻煩,特別是如果你想應用的函子是特定函數的唯一函子的話。以下面的代碼爲例:
struct print
{
void operator()(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());
return 0;
}
如果你只是在特定的地方使用一次 print,那麼僅僅爲了做一些瑣碎的和一次性的事情而編寫一個完整的類,就顯得有些過猶不及了。
對於上面的這種情形,使用內聯代碼會更合適,這可以通過 Lambda 函數來實現,如下所示:
std:for_each(v.begin(), v.end(), [](int element) { cout << element << endl; });
Lambda 函數內部是如何工作的?
[&i] ( ) { std::cout << i; }
// is equivalent to
struct anonymous
{
int &m_i;
anonymous(int &i) : m_i(i) {}
inline auto operator()() const
{
std::cout << i;
}
};
編譯器爲每個 Lambda 函數生成如上所述的唯一閉包。注意,這是 Lambda 函數的核心所在。
捕獲列表將成爲閉包中的構造函數的參數,如果將參數按值捕獲,那麼相應類型的數據成員將在閉包中創建。
此外,可以在 Lambda 函數的參數中聲明變量 / 對象,它們將成爲調用 operator() 函數的參數。
使用 Lambda 函數的好處
零成本抽象。對!你沒有看錯。Lambda 函數不會降低性能,它的性能和普通的函數一樣好。
此外,Lambda 函數使代碼變得更加緊湊、更加結構化和更富有表現力。
學習 Lambda 表達式
按引用 / 值來捕獲,代碼如下:
int main()
{
int x = 100, y = 200;
auto print = [&] { // Capturing object by reference
std::cout << __PRETTY_FUNCTION__ << " : " << x << " , " << y << std::endl;
};
print();
return 0;
}
上面代碼的輸出如下:
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 this
void f(std::function<int(int)> functor)
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
*/
int g() { static int i = 0; return i++; }
int main()
{
auto Lambda_func = [i = 0]() mutable { return i++; };
f(Lambda_func); // Pass Lambda
f(g); // Pass function
}
上面代碼的輸出如下:
Function Type : void f(Functor) [with Functor = main()::<Lambda(int)>]
Function Type : void f(Functor) [with Functor = int (*)(int)]
你還可以將 Lambda 函數作爲參數傳遞給其他函數,就像我在上面編寫的普通函數一樣。
如果你注意到了,這裏我在捕獲列表中聲明瞭變量 i,它將成爲數據成員。因此,每次調用 Lambda_func 時,它都將返回並遞增。
捕獲 Lambda 函數中的成員變量或 this 指針,代碼如下:
class Example
{
public:
Example() : m_var(10) {}
void func()
{
[=]() { 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,代碼如下:
const auto l = [](auto a, auto b, auto c) {};
// is equivalent to
struct anonymous
{
template <class T0, class T1, class T2>
auto operator()(T0 a, T1 b, T2 c) const
{
}
};
在 C++ 14 中引入的泛型 Lambda,它可以使用 auto 標識符捕獲參數。
可變泛型 Lambda,代碼如下:
void print() {}
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 to
struct anonymous
{
auto operator()() // call operator
{
}
};
我們已經在上面看到了上述情況的一個例子。希望你注意到了。
Lambda 作爲函數指針,代碼如下:
#include <iostream>
#include <type_traits>
int main()
{
auto funcPtr = +[] {};
static_assert(std::is_same<decltype(funcPtr), void (*)()>::value);
}
你可以強制編譯器生成 Lambda 作爲函數指針,而不是像上面那樣在它前面添加 + 來使之成爲閉包。
高階函數:Lambda 可以作爲參數和返回值,代碼如下:
const auto 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;
return 0;
}
再進一步,Lambda 函數還可以返回另一個 Lambda 函數。這將爲代碼的定製、代碼表示性和緊湊性(順便說一句,沒有這樣的詞)打開無限可能的大門。
constexpr Lambda 表達式
從 C++ 17 開始,Lambda 表達式可以被聲明爲 constexpr(常量表達式)。
constexpr auto sum = [](const auto &a, const auto &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;
}
};
*/
constexpr int answer = sum(10, 10);
即使你沒有指定 constexpr 關鍵字,如果函數調用運算符恰好滿足所有 constexpr 函數的要求,那麼它也將是 constexpr。
結束語
希望你喜歡這篇文章。我試着用幾個簡單的小例子來涵蓋使用 Lambda 的大多數複雜情況。考慮到代碼的表達性和易維護性,你應該在滿足 Lambda 使用條件的所有地方都優先使用它,就像你可以將其和大多數 STL 算法一起用於智能指針的自定義刪除器中一樣。
關注公衆號**「高效程序員」**👇,一起優秀!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/5yWbbGmmKvlvEkuAURpsqw