概述
文章目录
- 概述
- 语法
- Lambda表达式优点
- 使用方式
- Lambda表达式的类型
- Lambda表达式实例
概述
C++的Lambda是C++11中引入的新特性,用于创建匿名函数,简化编程。一般用在需要传入函数的位置,而该函数在整个程序中只使用一次,定义它则失去了函数重用的价值,此时Lambda就派上用场了。
语法
[ capture ] ( params ) opt -> ret { body; };
参数解释如下:
- capture 捕获列表,用于捕获表达式之外的参数
lambda 表达式通过捕获列表,捕获一定范围内的变量。
[] 不捕获任何变量。
[&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
[=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
[=,&task] 按值捕获外部作用域中所有变量,并按引用捕获 task 变量。
[flv] 按值捕获 flv 变量,同时不捕获其他变量。
[this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。
eg:
socket_.async_read_some(boost::asio::buffer(buffer_),
[this, self](boost::system::error_code ec, std::size_t bytes_transferred)
{
//body
});
- params 参数列表,类似函数的形参
参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种方式进行传递。
- opt 函数选项,比如mutable,可以省略
mutable 或 exception 声明,按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是值本身)。exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。
- -> ret 返回值类型,如果没有返回值,此处可以省略。
- body 函数体。
Lambda表达式优点
- 声明式编程风格:匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。
- 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。
- 在需要的时间和地点实现功能闭包,使程序更灵活。
使用方式
- 未使用捕获器,无法使用闭包之外的变量。如下示例,变量a在闭包外面定义,而x1未捕获任何变量, 因此使用是错误的。
- 捕获外部所有变量【值传递】,使用 = 捕获列表
void func(int x, int y)
{
auto x1 = [=]()->int {return a; };
}
值传递不能修改本体的值,是以只读模式传入。如下使用会报错误。
- 捕获所有外部变量【引用传值】,使用 & 捕获列表
class LambdaA
{
public:
int a = 0;
void func(int x, int y)
{
auto x1 = [&]() {return ++a; };
}
}
引用传值可以修改本体的值,与C++引用传递相似。
- 捕获整个类对象,使用 this 捕获列表
class LambdaA
{
public:
int a = 0;
void func(int x, int y)
{
auto x1 = [this]() { return ++this->a; };
}
}
this只捕获当前类对象中的所有变量,其他变量仍然无法使用,比如上例中的形参 x 、 y。this捕获的对象内部也是可以直接修改的。
- 如果需要获取 this 之外的变量,需要逐个捕获。
int a = 0;
void func(int x, int y)
{
auto x1 = [this, x, y]() { return this->a + x + y; };
}
那我单独获取函数形参后,能否直接修改其值呢?如下图错误提示是什么原因呢?
是因为捕获的值,默认是只读的,即值传递,如果需要修改本体,需要改为引用传值:
int a = 0;
void func(int x, int y)
{
auto x1 = [this, x, &y]()
{
++y;
return this->a + x + y;
};
}
- 特别注意的是,Lambda是延后调用的,因此在一些需要涉及到拷贝和引用传值的位置,需要特别注意。如下代码段:
int main()
{
int intA = 0;
auto x1 = [=]() { return intA;};
++intA;
std::cout << x1() << std::endl;
return 0;
}
运行结果:
那么为什么不是1呢?因为后面intA执行了+1操作。分析这个问题其实很简单,因为 x1 中调用的intA是通过 拷贝 的方式传值的,在intA执行+1之前,副本已经被拷贝了,因此后续的任何操作都不会影响x1的结果。
改为引用传值,即可实现同步传值。如下代码段所示:
int main()
{
int intA = 0;
auto x1 = [&]() { return intA;};
++intA;
std::cout << x1() << std::endl;
return 0;
}
运行结果:
但是 ,通过值捕获的变量, 也可以当做左值参与运算,前提是通过使用关键字 mutable 修饰,将常量转换成可以参与计算的左值。如下代码段。
int main()
{
int intA = 0;
auto x1 = [=]() mutable { return ++intA;};
std::cout << "x1的值为:" << x1() << std::endl;
std::cout << "intA的值为:" << intA << std::endl;
return 0;
}
运行结果:
可以看出,用 mutable 修饰的变量,可以当做左值参与运算,但是并没有修改本体,只是将副本参与运算。
另外需要注意,被 mutable修改的Lambda表达式,不管有没有参数,都必须加上参数列表 (),否则会报错,如图所示:
Lambda表达式的类型
lambda 表达式的类型在 C++11 中被称为“闭包类型(Closure Type)”。它是一个特殊的,匿名的非 nunion 的类类型。可以认为它是一个带有 operator() 的类,即仿函数。因此,我们可以使用 std::function 和 std::bind 来存储和操作 lambda 表达式:
std::function<int(int)> f1 = [=](int a) {return a + intA; };
std::function<int(void)> f2 = std::bind(f1, 123);
std::function<int(void)> f3 = std::bind([](int a) {return a;; }, 123);
lambda 表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终均会变为闭包类型的成员变量。而一个使用了成员变量的类的 operator(),如果能直接被转换为普通的函数指针,那么 lambda 表达式本身的 this 指针就丢失掉了。而没有捕获任何外部变量的 lambda 表达式则不存在这个问题。
这里也可以很自然地解释为何按值捕获无法修改捕获的外部变量。因为按照 C++ 标准,lambda 表达式的 operator() 默认是 const 的。一个 const 成员函数是无法修改成员变量的值的。而 mutable 的作用,就在于取消 operator() 的 const。
需要注意的是,没有捕获变量的 lambda 表达式可以直接转换为函数指针,而捕获变量的 lambda 表达式则不能转换为函数指针。看看下面的代码:
typedef void(*Ptr)(int*);
Ptr p = [](int* p){delete p;}; // 正确,没有状态的lambda(没有捕获)的lambda表达式可以直接转换为函数指针
Ptr p1 = [&](int* p){delete p;}; // 错误,有状态的lambda不能直接转换为函数指针
Lambda表达式实例
在STL中,遍历或者需要提供仿函数的位置,可以直接用Lambda代替,非常简洁方便,如下示例代码段:
int main()
{
vector<int> v;
v.push_back(2);
v.push_back(1);
v.push_back(3);
v.push_back(5);
v.push_back(4);
// 排序
sort(v.begin(), v.end(), [](int a, int b) {return a > b; });
// 打印
for_each(v.begin(), v.end(), [](int a) {std::cout << a<<" "; });
return 0;
}
运行结果
以上排序和打印都用到了Lambda表达式,在未接触它之前,都是使用的仿函数支持排序和打印的,很繁琐。可以通过如下代码段体验一下仿函数的繁琐程度。
#include <iostream>
#include <functional>
#include<vector>
#include <algorithm>
// 提供Sort函数的仿函数,实现自定义排序
class MySort
{
public:
bool operator()(int a,int b)
{
return a > b;
}
};
// 提供for_each的打印函数支持
class MyForEach
{
public:
void operator()(int a)
{
std::cout << a << " ";
}
};
int main()
{
vector<int> v;
v.push_back(2);
v.push_back(1);
v.push_back(3);
v.push_back(5);
v.push_back(4);
sort(v.begin(), v.end(), MySort());
for_each(v.begin(), v.end(), MyForEach());
return 0;
}
运行结果
从以上连个版本可以看出,Lambda表达式确实比较方便,不用重新定义类、重载运算符。另外在C++11很多地方都用到了Lamdba表达式,如果要看懂C++11的代码,必须对Lambda 的规则等做一个比较深入的了解。
最后
以上就是美好纸鹤为你收集整理的C++11学习之Lambda表达式的全部内容,希望文章能够帮你解决C++11学习之Lambda表达式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复