lambda表达式是C++11最重要也是最常用的特性之一,这是现代编程语言的一个特点,lambda表达式由如下一些特点:
1. 声明式的编程风格:就地匿名定义目标函数或者函数对象,不需要额外写一个命名函数或者函数对象
2. 简洁:避免了代码膨胀和功能分散,让开发更加高效
3. 在需要的时间和地点实现功能闭包,使程序更加灵活
Lambda表达式语法:[capture ] ( params ) mutable exception attribute -> return-type { body }
其中cature是捕获列表,params是参数列表,opt(mutable,exception)是函数选项,ret是返回值类型,body是函数体。
其中capture为定义外部变量是否可见(捕获),若为空,则表示不捕获所有外部变量,即所有外部变量均不可访问,
= 表示所有外部变量均以值的形式捕获,在body中访问外部变量时,访问的是外部变量的一个副本,类似函数的值传递,因此在body中对外部变量的修改均不影响外部变量原来的值。
& 表示以引用的形式捕获,后面加上需要捕获的变量名,没有变量名,则表示以引用形式捕获所有变量,类似函数的引用传递,body操作的是外部变量的引用,因此body中修改外部变量的值会影响原来的值。
params就是函数的形参,和普通函数类似,不过若没有形参,这个部分可以省略。
mutalbe 可以修改按值传递进来的拷贝(注意是修改拷贝,而不是值本身)
exception声明可能抛出的异常 throw
return-type表示返回类型,如果能够根据返回语句自动推导,则可以省略,
body即函数体。
除了capture和body是必需的,其他均可以省略。
Lambda表达式是用于创建匿名函数的。Lambda 表达式使用一对方括号作为开始的标识,类似于声明一个函数,只不过这个函数没有名字,也就是一个匿名函数。Lambda 表达式的返回值类型是语言自动推断的。如果不想让Lambda表达式自动推断类型,或者是Lambda表达式的内容很复杂,不能自动推断,则这时必须显示指定Lambda表达式返回值类型,通过”->”。
引入Lambda表达式的前导符是一对方括号,称为Lambda引入符(lambda-introducer):
(1)、[] // 不捕获任何外部变量
(2)、[=] //以值的形式捕获所有外部变量
(3)、[&] //以引用形式捕获所有外部变量
(4)、[x, &y] // x 以传值形式捕获,y 以引用形式捕获
(5)、[=, &z] // z 以引用形式捕获,其余变量以传值形式捕获
(6)、[&,x] // x 以值的形式捕获,其余变量以引用形式捕获
(7)、[this] 捕获当前类中的this指针
(7)、对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入,如:[this]() { this->someFunc(); }();
#include "Lambda.hpp"
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
#include <string>
///
// reference: http://en.cppreference.com/w/cpp/language/lambda
int test_lambda1()
{
/*
[] Capture nothing (or, a scorched earth strategy?)
[&] Capture any referenced variable by reference
[=] Capture any referenced variable by making a copy
[=, &foo] Capture any referenced variable by making a copy, but capture variable foo by reference
[bar] Capture bar by making a copy; don't copy anything else
[this] Capture the this pointer of the enclosing class
*/
int a = 1, b = 1, c = 1;
auto m1 = [a, &b, &c]() mutable {
auto m2 = [a, b, &c]() mutable {
std::cout << a << b << c << '\n';
a = 4; b = 4; c = 4;
};
a = 3; b = 3; c = 3;
m2();
};
a = 2; b = 2; c = 2;
m1(); // calls m2() and prints 123
std::cout << a << b << c << '\n'; // prints 234
return 0;
}
///
// reference: http://en.cppreference.com/w/cpp/language/lambda
int test_lambda2()
{
std::vector<int> c = { 1, 2, 3, 4, 5, 6, 7 };
int x = 5;
c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end());
std::cout << "c: ";
std::for_each(c.begin(), c.end(), [](int i){ std::cout << i << ' '; });
std::cout << '\n';
// the type of a closure cannot be named, but can be inferred with auto
auto func1 = [](int i) { return i + 4; };
std::cout << "func1: " << func1(6) << '\n';
// like all callable objects, closures can be captured in std::function
// (this may incur unnecessary overhead)
std::function<int(int)> func2 = [](int i) { return i + 4; };
std::cout << "func2: " << func2(6) << '\n';
return 0;
}
///
// reference: https://msdn.microsoft.com/zh-cn/library/dd293608.aspx
int test_lambda3()
{
// The following example contains a lambda expression that explicitly captures the variable n by value
// and implicitly captures the variable m by reference:
int m = 0;
int n = 0;
[&, n](int a) mutable { m = ++n + a; }(4);
// Because the variable n is captured by value, its value remains 0 after the call to the lambda expression.
// The mutable specification allows n to be modified within the lambda.
std::cout << m << std::endl << n << std::endl;
return 0;
}
//
// reference: https://msdn.microsoft.com/zh-cn/library/dd293608.aspx
template <typename C>
void print(const std::string& s, const C& c)
{
std::cout << s;
for (const auto& e : c) {
std::cout << e << " ";
}
std::cout << std::endl;
}
void fillVector(std::vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this is not thread-safe and is shown for illustration only
}
int test_lambda4()
{
// The number of elements in the vector.
const int elementCount = 9;
// Create a vector object with each element set to 1.
std::vector<int> v(elementCount, 1);
// These variables hold the previous two elements of the vector.
int x = 1;
int y = 1;
// Sets each element in the vector to the sum of the
// previous two elements.
generate_n(v.begin() + 2,
elementCount - 2,
[=]() mutable throw() -> int { // lambda is the 3rd parameter
// Generate current value.
int n = x + y;
// Update previous two values.
x = y;
y = n;
return n;
});
print("vector v after call to generate_n() with lambda: ", v);
// Print the local variables x and y.
// The values of x and y hold their initial values because
// they are captured by value.
std::cout << "x: " << x << " y: " << y << std::endl;
// Fill the vector with a sequence of numbers
fillVector(v);
print("vector v after 1st call to fillVector(): ", v);
// Fill the vector with the next sequence of numbers
fillVector(v);
print("vector v after 2nd call to fillVector(): ", v);
return 0;
}
/
// reference: http://blogorama.nerdworks.in/somenotesonc11lambdafunctions/
template<typename T>
std::function<T()> makeAccumulator(T& val, T by) {
return [=, &val]() {
return (val += by);
};
}
int test_lambda5()
{
int val = 10;
auto add5 = makeAccumulator(val, 5);
std::cout << add5() << std::endl;
std::cout << add5() << std::endl;
std::cout << add5() << std::endl;
std::cout << std::endl;
val = 100;
auto add10 = makeAccumulator(val, 10);
std::cout << add10() << std::endl;
std::cout << add10() << std::endl;
std::cout << add10() << std::endl;
return 0;
}
// reference: http://blogorama.nerdworks.in/somenotesonc11lambdafunctions/
class Foo_lambda {
public:
Foo_lambda() {
std::cout << "Foo_lambda::Foo_lambda()" << std::endl;
}
Foo_lambda(const Foo_lambda& f) {
std::cout << "Foo_lambda::Foo_lambda(const Foo_lambda&)" << std::endl;
}
~Foo_lambda() {
std::cout << "Foo_lambda~Foo_lambda()" << std::endl;
}
};
int test_lambda6()
{
Foo_lambda f;
auto fn = [f]() { std::cout << "lambda" << std::endl; };
std::cout << "Quitting." << std::endl;
return 0;
}
Lambda函数本质
使用 lambda 表达式捕获列表捕获外部变量,如果希望去修改按值捕获的外部变量,那么应该如何处理呢?这就需要使用 mutable 选项,被mutable修改是lambda表达式就算没有参数也要写明参数列表,并且可以去掉按值捕获的外部变量的只读(const)属性。
int a = 0;
auto f1 = [=] {return a++; }; // error, 按值捕获外部变量, a是只读的
auto f2 = [=]()mutable {return a++; }; // ok
最后再剖析一下为什么通过值拷贝的方式捕获的外部变量是只读的:
1. lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
2. 按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。
mutable 选项的作用就在于取消 operator () 的 const 属性。
因为 lambda 表达式在 C++ 中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操作lambda表达式:
#include <iostream>
#include <functional>
using namespace std;
int main(void)
{
// 包装可调用函数
std::function<int(int)> f1 = [](int a) {return a; };
// 绑定可调用函数
std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);
// 函数调用
cout << f1(100) << endl;
cout << f2(200) << endl;
return 0;
}
对于没有捕获任何变量的 lambda 表达式,还可以转换成一个普通的函数指针:
using func_ptr = int(*)(int);
// 没有捕获任何外部变量的匿名函数
func_ptr f = [](int a)
{
return a;
};
// 函数调用
f(1314);