lambda表达式
基本语法特性
[capture](params) opt -> retureType {
body;
};
- capture 捕获列表
- params 参数列表
- opt 函数选项
- retureType 返回值类型
- body 函数体
捕获列表capture
不能省略。捕获一定范围内的变量。
[]
不捕捉任何变量[&]
捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)[=]
捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获),拷贝的副本在匿名函数 体内部是只读的[=, &bar]
按值捕获外部作用域中所有变量, 并按照引用捕获外部变量bar[bar]
按值捕获bar变量, 同时不捕获其他变量[&bar]
按引用捕获bar变量,同时不捕获其他变量[&,bar]
按引用捕获其他变量,同时按值捕获bar变量[this]
捕获当前类中的this指针。让lambda表达式拥有和当前类成员函数同样的访问权限;可以 修改类的成员变量,使用类的成员函数。如果已经使用了 & 或者 =, 默认添加此选项
注意
如果是全局变量,可以不用捕获,直接使用。
下面看两个例子,看看lambda的使用方法。
class Example {
public:
void print(int x, int y) {
auto x1 = [] { return _number; }; // error
auto x2 = [this] { return _number; }; // ok
auto x3 = [this] { return _number + x + y; }; // error
auto x4 = [this, x, y] { return _number + x + y; }; // ok
auto x5 = [this] { return _number++; }; // ok
auto x6 = [=] { return _number + x + y; }; // ok
auto x7 = [&] { return _number + x + y; }; // ok
}
int _number = 100;
};
void test() {
int a = 10, b = 20;
auto f1 = [] { return a; }; // error
auto f2 = [&] { return a++; }; // ok
auto f3 = [=] { return a; }; // ok
auto f4 = [=] { return a++; }; // error
auto f5 = [a] { return a + b; }; // error
auto f6 = [a, &b] { return a + (b++); }; // ok
auto f7 = [=, &b] { return a + (b++); }; // ok
}
在匿名函数内部,需要通过lambda表达式的捕获列表控制如何捕获外部变量,以及访问哪些变量。默 认状态下lambda表达式无法修改通过复制方式捕获外部变量,如果希望修改这些外部变量,需要通过 引用的方式进行捕获。
参数列表params
和普通函数的参数列表一样,如果没有参数,参数列表可以省略不写。
auto f = []() { return 10; }; // 没有参数, 参数列表为空
auto f = [] { return 10; }; // 没有参数, 参数列表省略不写
选项opt
可以省略。
- mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
- exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();
返回类型retureType
可以省略。通过返回值后置语法来定义的。一般情况下,不指定lambda表达式的返回值,编译器会根 据return语句自动推导返回值的类型,但需要注意的是labmda表达式不能通过列表初始化自动推导出 返回值类型。
// ok,可以自动推导出返回值类型
auto f = [](int i) { return i; };
// error,不能推导出返回值类型
auto f1 = []() {
return {1, 2}; // 基于列表初始化推导返回值,错误
};
函数体body
不能省略。但函数体可以为空
函数本质
使用lambda表达式捕获列表捕获外部变量,如果希望去按值捕获的外部变量,那么应该如何处理呢? 这就需要使用mutable选项,被mutable修改是lambda表达式就算没有参数也要写明参数列表,并且可 以去掉按值捕获的外部变量的只读(const)属性。
int a = 0;
auto f1 = [=] { return a++; }; // error, 按值捕获外部变量, a是只读的
auto f2 = [=]() mutable { return a++; }; // ok
最后再剖析一下为什么通过值拷贝的方式捕获的外部变量是只读的。
lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。按照C++标准, lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。mutable选 项的作用就在于取消operator()的const属性。
因为lambda表达式在C++中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操 作lambda表达式。
void test() {
// 包装可调用函数
function<int(int)> f1 = [](int a) { return a; };
// 绑定可调用函数
function<int(int)> f2 = bind([](int a) { return a; }, placeholders::_1);
// 函数调用
cout << f1(100) << endl;
cout << f2(200) << endl;
}
基本使用形式
void test() {
// 匿名函数
[](const std::string &name) { std::cout << "[] name = " << name << std::endl; }("lili");
auto func2 = [](const std::string &name) -> std::string {
std::cout << "func2 name = " << name << std::endl;
return name;
};
func2("lucy");
auto func3 = [](const std::string &name) { std::cout << "func3 name = " << name << std::endl; };
func3("welcome");
std::function<void(const std::string &)> func4 = [](const std::string &name) { std::cout << "func4 name = " << name << std::endl; };
func4("wangdao");
}
因为Lambda表达式是一个匿名函数, 因此是没有函数声明的, 直接在程序中进行代码的定义即可, 但是如果只定义匿名函数在程序执行过程中是不会被调用的。
// 匿名函数的定义, 程序执行这个匿名函数是不会被调用的
[](){
std::cout << "hello, 我是一个lambda表达式..." << std::endl;
};
// 匿名函数的定义+调用:
int ret = [](int a) -> int
{
return a+1;
}(100); // 100是传递给匿名函数的参数
在Lambda
表达式的捕获列表中也就是 []
内部添加不同的关键字, 就可以在函数体中使用外部变量了。
// 在匿名函数外部定义变量
int a=100, b=200, c=300;
// 调用匿名函数
[](){
// 打印外部变量的值
std::cout << "a:" << a << ", b: " << b << ", c:" << c << std::endl; // error, 不能使用任何外部变量
}
[&](){
std::cout << "hello, 我是一个lambda表达式..." << std::endl;
std::cout << "使用引用的方式传递数据: " << std::endl;
std::cout << "a+1:" << a++ << ", b+c= " << b+c << std::endl;
}();
// 值拷贝的方式使用外部数据
[=](int m, int n)mutable{
std::cout << "hello, 我是一个lambda表达式..." << std::endl;
std::cout << "使用拷贝的方式传递数据: " << std::endl;
// 拷贝的外部数据在函数体内部是只读的, 如果不添加 mutable 关键字是不能修改这些只读数据的值的
// 添加 mutable 允许修改的数据是拷贝到函数内部的副本, 对外部数据没有影响
std::cout << "a+1:" << a++ << ", b+c= " << b+c << std::endl;
std::cout << "m+1: " << ++m << ", n: " << n << std::endl;
}(1, 2);
捕获列表细究
void test() {
int num = 10;
int age = 100;
std::string name("wangdao");
// 匿名函数
[](std::string value) {
/* cout << "num = " << num << endl; */
/* cout << "name = " << name << endl; */
std::cout << "value = " << value << std::endl;
}("lili");
// 值捕获
[num, name](std::string value) {
std::cout << "num = " << num << std::endl;
std::cout << "name = " << name << std::endl;
std::cout << "value = " << value << std::endl;
}("lili");
std::cout << std::endl;
// 引用捕获
[&num, &name](std::string value) {
num = 100;
name = "nice";
std::cout << "num = " << num << std::endl;
std::cout << "name = " << name << std::endl;
std::cout << "value = " << value << std::endl;
}("wangdao");
std::cout << "num = " << num << ", name = " << name << std::endl;
std::cout << std::endl;
// 全部用值捕获,name用引用捕获
[=, &name]() {
// num = 100; // error
name = "good";
std::cout << "num = " << num << std::endl;
std::cout << "age = " << age << std::endl;
std::cout << "name = " << name << std::endl;
}();
std::cout << std::endl;
// 全部用引用捕获,name用值捕获
[&, name]() {
num = 1000; // ok
age = 200; // ok
// name = "good"; // error
std::cout << "num = " << num << std::endl;
std::cout << "age = " << age << std::endl;
std::cout << "name = " << name << std::endl;
}();
}
使用误区
不要捕获局部变量的引用
std::vector<std::function<void(const std::string &)>> vec;
void test() {
int num = 100;
std::string name("wangdao");
vec.push_back([&num, &name](const std::string &value) {
std::cout << "num = " << num << std::endl;
std::cout << "name = " << name << std::endl;
std::cout << "value = " << value << std::endl;
});
}
void test2() {
for (auto func : vec) {
func("wuhan");
}
}
异步体系中的使用
现有以下代码片段
void function1() {
int data = 10;
function2([data]() {
std::cout << data << "\n";
});
}
function2 函数需要一个回调函数,我们提供了一个 lamada 表达式,此处的捕获列表尽量用值捕获,因为离开 function1 函数的作用域后 再访问data的引用或它指向的地址就是非法访问了,是一种不安全的做法。