前言
Lambda函数是 Modern C++在C++11中的一个体现,虽然在网络上已经有很多关于lambda函数的讨论或者教学,但是仍然有一部分内容(例如IIFE,lambda类型等)没人谈论。因此在这里,我不仅要向您展示C++中的lambda函数,而且还将介绍Lambda的工作原理以及发展。
什么是lambda函数
lambda并不总是函数指针,它一个表达式。但是为了简单起见,我一直都称它为函数。那么在本文中,我将会交替使用lambda函数和表达式来称呼。
Lambda函数是一段简短的代码片段,它具有以下特点:
(1)不值得命名(无名,匿名,可处理等,无论您如何称呼)
(2)不会重复使用
换句话说,它只是语法糖。lambda函数的语法定义为:
1 2 3 4
| [ capture list ] (parameters) -> return-type { method definition }
|
通常, 编译器会评估lambda函数本身的返回类型。因此,我们不需要显式指定返回类型,即->return-type,但是在某些复杂的情况下,编译器无法推断出返回类型,此时我们需要指定返回类型。
为什么要使用lambda函数
C++包括许多有用的通用函数,例如 std::for_each,通常情况下方便了我们的使用。但是,当需要考虑特殊需求的时候,就比较头疼了。如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct print { void operator()(int element) { cout <<element << endl; } }; int main() { std::vector<int> v = {1, 2, 3, 4, 5}; std::for_each(v.begin(),v.end(), print()); return 0; }
|
如果您只在某个特定位置使用一次print,却写了一个类。这样的操作代价似乎太大了。
但是,对于这种情况,内联代码将更合适,并且可以通过如下的lambda函数来实现:
1
| std::for_each(v.begin(), v.end(), [](int element) { cout <<element << endl; });
|
lambda函数工作原理
1 2 3 4 5 6 7 8 9 10 11
| [&i] ( ) { std::cout << i; } struct anonymous { int &m_i; anonymous(int &i) :m_i(i) {} inline autooperator()()const { std::cout << i; } };
|
如代码所示,编译器为每一个lambda函数生成独特的闭合。捕获列表将成为闭包中的构造函数参数,如果按值捕获,则会在闭包中创建相应类型的数据成员。此外,您可以在lambda函数参数中声明变量/对象,它将成为调用运算符的参数,即operator()。
使用Lambda函数的好处
(1)零成本抽象。是的!你没看错。lambda不会牺牲性能,运行速度和普通函数一样快。
(2)此外,代码会变得更加紧凑,结构层次更加明显和代码可读性更佳。
学习lambda表达式
1.通过引用/值捕获
1 2 3 4 5 6 7 8 9
| int main() { int x = 100, y = 200; auto print = [&]{ std::cout <<__PRETTY_FUNCTION__ << " : " << x<< " , " << y << std::endl; }; print(); return 0; }
|
输出:
1
| main()::<lambda()> : 100 , 200
|
在上面的示例中,我在捕获列表中用到了 &。通过引用的方式捕获变量 x和 y。同样,= 表示按值捕获,这将在闭包内创建相同类型的数据成员,同时赋上相同的值。需要注意的是,参数列表是可选的, 如果不将参数传递给lambda表达式,则可以省略空括号。
2.Lambda捕获列表
2.1 将lambda作为参数传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| template <typename Functor> void f(Functor functor) { std::cout <<__PRETTY_FUNCTION__ << std::endl; } 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); f(g); }
|
输出:
1 2
| Function Type : void f(Functor) [with Functor = main()::<lambda(int)>] Function Type : void f(Functor) [with Functor = int (*)(int)]
|
您也可以将lambda函数作为参数传递给其他函数,就像我上面编写的普通函数一样。相信您注意到了,我在捕获列表中声明了变量i,它将成为数据成员。所以,每次调用lambda_func时,它将被返回并递增。
2.2lambda捕获this指针或成员变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Example { public: Example() : m_var(10) {} void func() { [=]() { std::cout << m_var<< std::endl; }(); } private: int m_var; }; int main() { Example e; e.func(); }
|
捕获this指针也可以使用 [this], [=]或者 [&]。在上述任何情况下,类内数据成员(包括 private)的访问方式与常规方法一样。
可以看到lambda表达式的末尾,我多写了一个 ( ),该函数通常在声明之后立刻对其进行调用。它称为IIFE(立即调用函数表达式)。
C++ lambda函数类型
1.通用lambda
1 2 3 4 5 6 7 8 9
| const auto l = [](auto a, auto b, auto c) {}; struct anonymous { template <class T0, class T1, class T2> auto operator()(T0 a, T1 b, T2 c) const { } };
|
C++ 14中引入的通用lambda可以使用auto说明符。
2.可变参数通用λ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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在许多情况下非常有用,例如调试,使用不同的数据输入重复操作等。
3.mutable lambda函数通常,lambda的函数调用运算符是const-by-value,这意味着lambda需要捕获可变值的关键字时,需要使用mutable关键字。
通常,lambda的函数调用运算符是const-by-value,这意味着lambda需要捕获可变值的关键字时,需要使用mutable关键字。
1 2 3 4 5 6 7 8 9 10
| []() mutable {} struct anonymous { auto operator()() { } };
|
4.Lambda作为函数指针
1 2 3 4 5 6 7 8
| #include<iostream> #include<type_traits> int main() { auto funcPtr = +[] {}; static_assert(std::is_same<decltype(funcPtr), void (*)()>::value); }
|
您可以通过添加命令“+”来强制编译器将lambda生成为函数指针而不是闭包。
5.lambda函数作为返回值
1 2 3 4 5 6 7 8 9 10 11 12 13
| const auto less_than = [](auto x) { return [x](auto y) { return y < x; }; }; int main() { 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函数。这也为代码的自定义性,代码可读性和紧凑性带来无限可能。
6.constexpr lambda表达式
从C ++ 17开始,可以将lambda表达式声明为 constexpr。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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算法一样。