当前位置:   article > 正文

C++技能系列( 1 ) - 使用Lambda表达式【详解】_c++ lambda捕获

c++ lambda捕获

系列文章目录

C++高性能优化编程系列
深入理解软件架构设计系列
高级C++并发线程编程
C++技能系列

期待你的关注哦!!!
在这里插入图片描述

现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
Now everything is for the future of dream weaving wings, let the dream fly in reality.


lambda表达式是C++11引入的一个很重要的的特性, lambda表达式也是 一个可调用对象,它定义了一个 匿名函数,并且可以 捕获一定范围内的变量

一、lambda表达式 - 定义

lambda表达式一般形式:

[捕获列表](参数列表)-> 返回类型 { 函数体; };

auto f = [](int a) -> int{
	return a + 1;
}
std::cout << f(1) << std::endl;

  • 1
  • 2
  • 3
  • 4
  • 5

(1) 参数列表也可以有默认值:

auto f = [](int a = 6) -> int{
	return a + 1;
}
std::cout << f(1) << std::endl;
  • 1
  • 2
  • 3
  • 4

(2) 没有参数的时候,参数列表可以省略,甚至"()"也可以省略,所以如下代码是合法的:

auto f1 = ()[]{return 1;};
auto f2 = []{return 2;};
std::cout << f(1) << std::endl;
std::cout << f(2) << std::endl;
  • 1
  • 2
  • 3
  • 4

(3) 捕获列表[ ]和函数体不能省略,必须时刻包含。
(4) lambda表达式的调用方法和普通函数相同,都是使用"( )"这种函数调用运算符。
(5) lambda表达式可以不返回任何类型,返回任何类型就是返回void。
(6) 函数体末尾的分号不能省。

二、lambda表达式 - 捕获列表

lambda表达式通过捕获列表捕获一定范围内的变量,那么,这个范围究竟是什么意思呢?

(1) [ ]: 不捕获任何变量

看如下范例:

int i = 9;
auto f1 = []{
	//报错(无法捕获外部变量),不认识这个i在哪里定义,
	//看来lambda表达式毕竟是匿名函数,按常规理解是不行。
	return i;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

⚠️ 但不包括静态局部变量,lambda可以直接使用静态局部变量。例如,上面的int i = 9;修改为static int i = 9; 是可以在lambda表达式中使用的。

(2) [&]: 捕获外部作用域中所有变量,并作为引用在函数体内使用

看如下范例:

int i = 9;
auto f1 = [&]{
	//因为&的存在,允许给i赋值,从而也就改变了i的值
	i = 5;
	return i;
};
//5,调用了lambda表达式,所以i的发生改变
std::cout << f1() << std::endl;
//5,i值发生改变,现在i=5
std::cout << i < std::endl; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

⚠️ 既然引用,那么在调用这个lambda表达式的时候,就必须确保该lambda表达式里的引用的变量没有超过这个变量的作用域(保证有效性)。

(3) [=]: 捕获外部作用域中所有变量,并作为副本(按值)在函数中使用,也就可以用它的值,但不能给它赋值

看如下范例:

int i = 9;
auto f1 = [=]{
	//这就非法了,不可以给它赋值,因为是以值方式捕获
	//使用该值(返回该值),就可以
	//i = 5;
	return i;
}
//9, 调用了lambda表达式
std::cout << f1() << std::endl;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

⚠️ 不可以给它赋值,因为是以值方式捕获,使用该值(返回该值),就可以。

(4) [this]:一般用于类中,捕获当前类中的this指针,让lambda拥有和当前类成员函数同样的访问权限。如果已经使用了"&“或者”=",则默认添加了此项(this项)。也就是说,捕获this的目的就是在lambda表达式中使用当前类的成员函数和成员变量。

看如下范例:

class CT{
	public:
		int m_i = 5;
		void myfuncpt(int x, int y){
			//无论用this还是&,=都可以读取成员变量的值
			auto mylambda1 = [this]{//是获取不到形参x,y的值的
				//有this,这个访问才合法,有&、=也可以
				return m_i;
			};
			std::cout << mylambda1() << std::endl;
		}
};
//main函数使用如下:
CT ct;
//5
ct.myfuncpt(3, 4);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

⚠️ 针对成员变量,[this] 或者[=]可以读取,但不可以修改,如果想修改,可以使用[&]。

(5) [变量名]:按指捕获 和 [& 变量名]:按引用捕获:

[变量名]:按值捕获(不能修改)变量名所代表的变量,同时不能捕获其他变量。
[& 变量名]:按引用捕获(可以修改)变量名所代表的变量,同时不能捕获其他变量。

在前面CT类的myfuncpt成员函数中,因为没有捕获形参x和y的值,所以无法在lambda表达式中使用形参x和y。
如果lambda表达式使用x和y的值,可以如下修改:

//不能在lambda表达式中修改x,y值
auto mylambda1 = [this, x, y]{...};
  • 1
  • 2

也可以修改如下这样:

//不能在lambda表达式中修改x,y值
auto mylambda1 = [=]{...};
//可以在lambda表达式中修改x,y值
auto mylambda1 = [&]{...};
  • 1
  • 2
  • 3
  • 4

对于按引用捕获变量名所代表的变量,看看如下范例:

//只可以使用修改x的值
auto mylambda1 = [&x]{...};
//只可以使用修改x和y的值
auto mylambda1 = [&x, &y]{...};
  • 1
  • 2
  • 3
  • 4

(6) [=, & 变量名]:按值捕获所有外部变量,但按引用&中所指的变量,这里的=必须写在开头的位置,开头的位置表示默认捕获的方式

看如下CT类的myfuncpt成员函数中的lambda表达式:

auto mylambda1 = [this, &x, y]{
	x = 8;
	...
	return m_i;
}
  • 1
  • 2
  • 3
  • 4
  • 5

⚠️ auto mylambda1 = [this, &x, y] 也可以写成 auto mylambda1 = [=, &x]也可以。

(7) [&, 变量名]:按引用捕获所有外部变量,但按值捕获变量名所代表的变量。这里的&必须写在开头的位置,开头的位置表示默认捕获的方式

下面这样是不行的:

//这样不行,开始制定了默认捕获,后来又指定引用捕获,编译器汇报错
auto f = [&, &x]{...}
  • 1
  • 2

修改为正确如下:

auto f = [&, x]{...}
  • 1

三、lambda表达式 - 延时调用易出错细节分析

看如下范例:

int x = 5;
auto f = [=]{ //此时已将外部局部变量值复制一份在lambda表达式中了
	return x;
};
x = 10;
//5, return的x是5而不是10
std::cout << f() << std::endl;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

⚠️ auto f = [=]{ //此时已将外部局部变量值复制一份在lambda表达式中了,所以打印的是5而不是10。

那怎么办呢?
办法是按引用方式捕获:

auto f = [&]{...}
  • 1

四、lambda表达式 - 如何使用mutable

mutable(易变的)并不陌生,mutable关键字,它的作用就是不管是不是一个常量属性的变量,只要mutable在,就能修改其值。

int x = 5;
auto f = [=]() mutable {
	//没有mutable,这个x是不允许修改的
	x = 6;
	return x;
};
x = 10;
//6, return 的x是6而不是10
std::cout << f() << std::endl;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

⚠️ 正常lambda表达式没有参数时()是可以省略的,但是如果要是使用mutable,lambda表达式中就算没有参数时()是也不可以省略()的,必须写出来。

下面是不合法的:

auto f = [=] mutable {...}; //即便没有参数,也不可以把mutable前面的()省略
  • 1

五、lambda表达式 - 作为匿名的类类型对象

lambda表达式的类型被称为闭包类型。闭包先理解成:函数内的函数(可调用对象)。

这个lambda表达式是一种比较特殊的、匿名的、类型(闭包类)的对象,也就是说有定义了一个类类型,有生成一个匿名的该类的对象(闭包)。可以认为它是一个带有operator()的类类型对象,也就是仿函数(函数对象)或者说是可调用对象。

所以,也可以使用std::function和std::bind来保存和调用lambda表达式。每个lambda都会出发编译器生成一个独一无二的类类型(及所返回的该类类型对象)。

(1)lambda表达式在std::function的使用

看如下两个范例:

范例1:

std::function<int(int)> fc1 = [](int tv){return tv;}
std::cout << fc1(15) << std::endl; //15
  • 1
  • 2

范例2:

std::vector<std::function<bool(int)>> gv;
void func(){
	srand((unsigned)time(NULL));
	int tmpvalue =  rand % 6
	gv.push_back([=](int tv){  //如果是引用[&],会不会造成未定义行为?思考一下。
		if (tv % tmpvalue == 0)
			return true;
		return false;
	});
}
int main(){
	func();
	std::cout << gv[0](10) << std::endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(2)lambda表达式在std::bind的使用

看如下范例:

//bind第一个参数是函数指针,第二个参数开始就是真正的函数参数
std::function<int(int)> fc2 = std::bind([](int tv){return tv;}, std::placeholders::_1);
std::cout << fc2(15) << std::endl; //15
  • 1
  • 2
  • 3

在不捕获任何变量,也就是捕获列表为空(因为类是有this的概念,普通函数是没有这个概念的),lambda表达式可以转换成一个普通的函数指针,看如下范例:

using functype = int (*)(int); //定义一个函数指针类型
functype fp = [](int tv){return tv;};
std::cout << fp(17) << std::endl;  //17
  • 1
  • 2
  • 3

六、lambda表达式 - 在for_each和find_if中使用

(1)for_each中lambda表达式

for_each 其实是一个函数模版,一般是用来配合函数对象使用的,第三个参数就是一个函数对象(可以给进去一个 lambda 表达式)。

看如下范例:

    std::vector<int> myvector = { 10, 20, 30, 40, 50};
    int isum = 0;
    std::for_each(myvector.begin(), myvector.end(), [&isum](int value){
        isum += value;
        std::cout << value << std::endl;
    });
    std::cout << "sum = " << isum << std::endl;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

输出结果:

10
20
30
40
50
sum = 150
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(2)find_if中lambda表达式

find_if 其实也是一个函数模版,一般用来查找一个什么东西,要查什么取决于他的第三个参数,第三个参数也是一个函数对象(也可以给进去一个 lambda 表达式)。

看如下范例:
只要返回false, find_if 就不停地遍历myvector,一直返回true为止。

    auto result = std::find_if(myvector.begin(), myvector.end(), [](int value){
        std::cout << value << std::endl;
        //只要返回false, find_if就不停地遍历myvector,一直返回true为止
        return false;
    });
  • 1
  • 2
  • 3
  • 4
  • 5

利用 find_if返回 true 停止这个特性,就可以寻找myvector中第一个值”>15“的元素。
⚠️ find_if的调用返回一个迭代器,只向第一个满足条件的元素。如果这样的元素不存在,则这个迭代器会指向myvector.end()。
修改后代码如下:

    auto result = std::find_if(myvector.begin(), myvector.end(), [](int value){
        if (value > 15)
            return true;
        return false;
    });
    if (result == myvector.end())
        std::cout << "没找到" << std::endl;
    else 
        std::cout << "没找到了, 结果为:" << *result<< std::endl; //找到了,结果为20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

七、小结

lambda的优点:
善用lambda,让代码更简洁、更灵活、更强大、提高开发效率、可维护性等。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/388398
推荐阅读
相关标签
  

闽ICP备14008679号