网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
{
auto print = [](int s) {std::cout << "value is " << s << std::endl;};
auto lambAdd = [](int a, int b) ->int { return a + b;};
int iSum = lambAdd(10, 11);
print(iSum);
return 0;
}
lambAdd有两个入参a和b,然后它的返回类型是int,我们可以试一下把`->int`去掉,结果是一样的。
###### 2 lambda捕获块
###### 2.1 捕获的简单使用
在第1节中,我们展示了lambda的语法形式,后面的形参和函数体之类都好理解,那么方括号里面捕获是啥意思呢?
其实这里涉及到lambda表达式一个重要的概念,就是闭包。
这里我们需要先对lambda表达式的实现原理做一下说明:当我们定义一个lambda表达式后,编译器会自动生成一个匿名类,这个类里面会默认实现一个public类型的operator()函数,我们称为闭包类型。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,它是一个右值。
所以,我们上面的lambda表达式的结果就是一个一个的闭包。闭包的一个强大之处是可以通过传值或者引用的方式捕获其封装作用域内的变量,前面的方括号就是用来定义捕获模式以及变量,所以我们把方括号[]括起来的部分称为捕获块。
看这个例子:
#include
int main()
{
int x = 10;
auto print = [](int s) {std::cout << "value is " << s << std::endl;};
auto lambAdd = [x](int a) { return a + x;};
auto lambAdd2 = [&x](int a, int b) { return a + b + x;};
auto iSum = lambAdd(10);
auto iSum2 = lambAdd2(10, 11);
print(iSum);
print(iSum2);
return 0;
}
当lambda块为空时,表示没有捕获任何变量,不为空时,比如上面的lambAdd是以复制的形式捕获变量x,而lambAdd2是以引用的方式捕获x。那么这个复制或者引用到底是怎么体现的呢,我们使用gdb看一下lambAdd和lambAdd2的具体类型,如下:
(gdb) ptype lambAdd
type = struct <lambda(int)> {
int __x;
}
(gdb) ptype lambAdd2
type = struct <lambda(int, int)> {
int &__x;
}
(gdb)
前面我们说过lambda实际上是一个类,这里得到了证明,在c++中struct和class除了有少许区别,其他都是一样的,所以我们可以看到复制形式捕获实际上是一个包含int类型成员变量的struct,引用形式捕获实际上是一个包含int&类型成员变量的struct,然后在运行的时候,会使用我们捕获的数据来初始化成员变量。
既然有初始化,那么必然有构造函数啊,然后捕获生成的成员变量,有operator()函数,暂时来讲,一个比较立体的闭包类型就存在于我们脑海中啦,对于lambda表达式类型具体组成,我们暂时放一放,接着说捕获。
###### 2.2 捕获的类型
捕获的方式可以是引用也可以是复制,但是到底有哪些类型的捕获呢?
捕获类型如下:
* []:默认不捕获任何变量;
* [=]:默认以复制捕获所有变量;
* [&]:默认以引用捕获所有变量;
* [x]:仅以复制捕获x,其它变量不捕获;
* [x…]:以包展开方式复制捕获参数包变量;
* [&x]:仅以引用捕获x,其它变量不捕获;
* [&x…]:以包展开方式引用捕获参数包变量;
* [=, &x]:默认以复制捕获所有变量,但是x是例外,通过引用捕获;
* [&, x]:默认以引用捕获所有变量,但是x是例外,通过复制捕获;
* [this]:通过引用捕获当前对象(其实是复制指针);
* [\*this]:通过复制方式捕获当前对象;
可以看到,lambda是可以有多个捕获的,每个捕获之间以逗号分隔,另外呢,不管多少种捕获类型,万变不离其宗,要么以复制方式捕获,要么以引用方式捕获。
那么复制捕获和引用捕获到底有什么区别呢?
标准c++规定,默认情况下,在lambda表达式中,对于operator()的重载是const属性的,也就意味着如果以复制形式捕获的变量,是不允许修改的,看这段代码:
#include
int main()
{
int x = 10;
int y = 20;
auto print = [](int s) {std::cout << "value is " << s << std::endl;};
auto lambAdd = [x](int a) {
// x++; 此处x是只读,不允许自增,编译会报错
return a + x;
};
auto lambAdd2 = [&x](int a, int b) {
x = x+5;
return a + b + x;
};
auto iSum = lambAdd(10);
auto iSum2 = lambAdd2(10, 11);
print(iSum);
print(iSum2);
return 0;
}
从代码可以看出,复制捕获不允许修改变量值,而引用捕获则允许修改变量值,为什么呢,这里我理解,&x实际上是一个int\*类型的指针,所以我们可以修改x的值,因为我们只是对这个指针所指向的内容进行修改,并没有对指针本身进行修改,且与我们常规声明的引用类型入参一样,修改的值在lambda表达式外也是有效的。
那么如果我想使用复制捕获,又想修改变量的值呢,这时我们就想起来有个关键字,叫做mutable,它允许在常成员函数中修改成员变量的值,所以我们可以给lambda表达式指定mutable关键字,如下:
#include
int main()
{
int x = 10;
int y = 20;
auto print = [](int s) {std::cout << "value is " << s << std::endl;};
auto lambAdd = [x](int a) mutable {
x++;
return a + x;
};
auto iSum = lambAdd(10);
print(iSum);
print(x);
return 0;
}
执行结果如下:
value is 21
value is 10
所以加上mutable以后就可以对复制捕获进行修改,但有一点,它的修改出了lambda表达式以后就无效了。
###### 2.3 包展开方式捕获
仔细看2.2节中捕获类型,会发现有[x…]这样的类型,它实际上是以复制方式捕获了一个可变参数,在c++中其实涉及到了模板形参包,也就是变参模板,看下面例子:
#include
void tprintf()
{
return;
}
template<typename U, typename …Ts>
void tprintf(U u, Ts… ts)
{
auto t = [ts…]{
tprintf(ts…);
};
std::cout << "value is " << u << std::endl;
t();
return;
}
int main()
{
tprintf(1,‘c’,3, 8);
return 0;
}
它捕获了一组可变的参数,不过这里实际上是为了演示对可变参数的捕获,强行使用了lambda表达式,不使用的话,代码可能更加简洁,我们只需要通过这个演示知道怎么使用即可,另外对于变参模板的使用,这里就不展开来讲了。
###### 2.4 捕获的作用
我再看lambda的捕获的时候一直很奇怪,初看的话,这个捕获跟传参数有什么区别呢,都是把一个变量值传入lambda表达式体供使用,但仔细思考的话,它是有作用的,假设有这么一个案例,一个公司有999名员工,每个员工的工号是从1~999,我们现在想找出工号是8的整数倍的所有员工,一个可行的代码如下:
#include
#include
int main()
{
int x = 8;
auto t = [x](int i){
if ( i % x == 0 )
{
std::cout << "value is " << i << std::endl;
}
};
auto t2 = [](int i, int x){
if ( i % x == 0 )
{
std::cout << "value is " << i << std::endl;
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
-1715811903366)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!