C++基础(九)再谈lambda 值捕获、引用捕获、隐式捕获以及避坑

lambda的基础知识及用法,已在另一篇文章中阐明,此处不再赘述。

下面,介绍一些lambda表达式的另一些主要注意的点。

修改值捕获的非静态局部变量

前一篇文章中已说明,想要在lambda表达式的函数体中修改非静态局部变量的值,需要引用捕获。

如果非要修改值捕获的变量,有方法吗?有!加上关键字mutable

但是!修改对原变量无任何影响!

代码如下:

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	string strTemp = "非静态局部变量";
	auto fun = [strTemp]()mutable{	
		strTemp = "local";
	};
	fun();
	cout << strTemp << endl;
	system("pause");
	return 0;
}

执行结果如下:

结果是不是有些出乎意料?fun函数体中的修改,居然对strTemp本身无任何影响。为何如此?请看下面的点。

值捕获,在lambda定义时变量的值就固定不变;引用捕捉,为lambda调用时变量的值

什么意思呢?先上代码:

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	string strTemp = "非静态局部变量";
	int iTemp = 10;
	
	//定义lambda
	auto fun = [strTemp, &iTemp]()
	{	
		cout << strTemp << endl;
		cout << iTemp << endl;
	};

	//改变两个局部变量的值
	strTemp = "local";
	iTemp = 20;

	fun();//执行lambda

	system("pause");
	return 0;
}

执行结果如下:

以上代码,fun中,strTemp为值捕获,iTemp为引用捕获。

在fun定义的时候,值捕获的变量的值就固定不变。可以理解为,值捕获创建了一个临时对象,拷贝了原变量的值,之后,这个临时变量与原来的变量再无任何瓜葛,lambda中操作的是这个临时变量

引用捕获,创建的是一个引用/指针,指向了原来的变量,lambda函数体中操作的就是变量本身

在fun定义后,对strTemp的改变,是原变量,不会影响到定义时创建的临时对象。这也就能解释为什么另一篇的代码,在函数体中修改值捕获的变量对原变量没影响,因为这本身就是两个变量。

隐式捕获

前面介绍的,将变量名写入捕获列表,为显式捕获。如果lambda中需要用到的非静态局部变量非常多,全部写入捕获列表,就会显得非常臃肿,有简便写法,即为隐式捕获。

1、捕获列表的形式为"[=]"(值捕获)或者"[&]"(引用捕获);
2、捕获列表里不能同时写"="和"&",即"[=, &]"是非法的;
3、隐式捕获可以和显式捕获搭配使用,但不能和同类型的显示捕获一起使用。即隐式
值捕获只能搭配显式引用捕获,隐式引用捕获只能搭配显式值捕获。表现形式为:
"[=,&变量1,&变量2]"或者"[&,变量1,,变量2]"。

示例代码如下:

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	string strTemp = "非静态局部变量";
	int iTemp = 10;
	double dValue = 13.14;
	bool bOK = true;
	
	auto fun_ref = [&, strTemp, iTemp]() mutable //strTemp和iTemp为值捕获,其他局部变量为引用捕获
	{	
		strTemp = "local";  //显式值捕获
		iTemp = 15; //显式值捕获
		dValue = 5.20;  //隐式引用捕获
		bOK = false;  //隐式引用捕获
	};
	fun_ref();
	cout << strTemp << endl;
	cout << iTemp << endl;
	cout << dValue << endl;
	cout << boolalpha << bOK << endl;

	auto fun_value = [=, &strTemp, &iTemp]() mutable //strTemp和iTemp为引用捕获,其他局部变量为值捕获
	{
		strTemp = "local";  //显式引用捕获
		iTemp = 15; //显式引用捕获
		dValue = 12.12;  //隐式值捕获
		bOK = true;  //隐式值捕获
	};	
	cout << "------------------------------------------" << endl;
	fun_value();
	cout << strTemp << endl;
	cout << iTemp << endl;
	cout << dValue << endl;
	cout << boolalpha << bOK << endl;

	//auto fun_ = [=, strTemp, iTemp](){}; //这种定义是非法的,=表示隐式值捕获,显式捕获只能采用引用捕获的方式


	system("pause");
	return 0;
}

执行结果如下:

这段代码,fun_ref中显式“值捕获”了strTemp、iTemp两个变量,隐式“引用捕获”了其他变量。在fun_ref中修改值捕获的变量strTemp和iTemp,不会改变原变量的值;而对dValue和bOK的修改,会影响到原变量。

fun_value中则恰好相反,变量strTemp和iTemp为引用捕获,函数体中的修改会作用于原变量。其他变量为值捕获,修改不会导致原变量修改。

至此,lambda的绝大多数知识点已结束,但还留了一个东西:返回值。

返回值

在《C++ Primer》第五版中,有这么一句话:

翻译过来就是:如果lambda的函数体包含任何多于单一return语句的内容,且未指定返回类型,则返回void。

这句话很让人费解,再结合后面给出的示例:

从这些描述中,不难看出,作者想表达的意思是,lambda的函数体中只能有单一的return语句,否则就应该显式指定返回类型。如果函数体中不是单一的return语句,又没有显式指定返回类型,则会返回void。

但是,实际测试下来,并不是这么回事。

上述代码在visual studio2013中可以正常编译通过并运行:


int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	
	vector<int> vi = { -10, 2, -3, 14, -5 };
	transform(vi.begin(), vi.end(), vi.begin(),
		[](int i) { 
		if (i < 0)
			return -i;
		else 
			return i;
	});
	
	for (int i : vi)
		cout << i << " ";
	cout << endl;

	system("pause");
	return 0;
}

执行结果如下:

难道是编译器的问题?在“https://en.cppreference.com/”提供的在线编译器中选择gcc,编译运行,也是可以的:

至于C++的文档,在https://en.cppreference.com里面有关lambda的介绍中:https://en.cppreference.com/w/cpp/language/lambda ,也没有看到相关的描述。

相当困惑,《C++ Primer》的这句话,究竟是他使用的编译器的限制,还是C++标准的限制。

如果哪位同仁找到依据,请不吝赐教!

在visual studio2013编译器中,如果指定了lambda的返回值类型,则只需要函数体中return的类型可以隐式转换成指定的返回值类型,编译就可以通过编译,类型不匹配,可能会给出警告,代码如下:

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;	
	auto fun = [](int i)->int
	{
		if (i % 2 == 0)
			return true;
		else
			return 11.14;
	};
	cout << boolalpha << fun(3) << endl;;
	system("pause");
	return 0;
}

编译后会有一句警告:

这里是从11.14转换成int会丢失精度的警告,至于reurn true,会直接将bool类型转换成int类型,执行结果如下:

如果没有显式指定返回值,编译器会以第一个return语句的类型作为返回值类型,之后的所有return语句,必须是此类型,否则会直接报错。

这一条,比显式指定返回值更严格,将上面的代码去掉返回类型,如下:

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;	
	auto fun = [](int i)  //不再指定返回值类型
	{
		if (i % 2 == 0)
			return true;
		else
			return 11.14;
	};
	cout << boolalpha << fun(3) << endl;;
	system("pause");
	return 0;
}

编译结果:

 

有关lambda表达式,需要在实践中不断尝试。有关《C++ Primer》中返回值的部分,如果有哪位同仁知道出处,麻烦一定要告诉我一下!!!感激涕零!!!

  • 42
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Lambda 表达式中,捕获列表用于指定要捕获的外部变量。捕获列表可以包含以下 3 种方式: 1. 捕获 捕获可以通过在捕获列表中指定变量的方式来完成。在捕获时,Lambda 表达式会在创建时将指定的变量复制一份,并在函数体中使用这份副本。例如: ```c++ int x = 1; auto func = [x]() { std::cout << x << std::endl; }; ``` 在上面的代码中,Lambda 表达式通过捕获的方式来捕获变量 x。在 Lambda 表达式中使用的变量 x 是一个拷贝,对其进行修改不会影响原有的变量 x。 2. 捕获引用 捕获引用可以通过在捕获列表中指定变量的方式来完成。在捕获引用时,Lambda 表达式会在创建时将指定的变量的引用传递给函数体。例如: ```c++ int x = 1; auto func = [&x]() { std::cout << x << std::endl; }; ``` 在上面的代码中,Lambda 表达式通过捕获引用的方式来捕获变量 x。在 Lambda 表达式中使用的变量 x 是原有变量的引用,对其进行修改会影响原有的变量 x。 3. 捕获列表 捕获列表可以通过在捕获列表中指定变量的方式来完成。在捕获列表中,可以同时指定多个变量,并且可以使用引用的方式进行捕获。例如: ```c++ int x = 1, y = 2; auto func = [x, &y]() { std::cout << x << " " << y << std::endl; }; ``` 在上面的代码中,Lambda 表达式通过捕获列表的方式来捕获变量 x 和 y。变量 x 是以的方式进行捕获的,变量 y 是以引用的方式进行捕获的。 需要注意的是,在 Lambda 表达式中捕获变量时,要考虑变量的生命周期。如果捕获了一个局部变量,而且这个局部变量已经超出了作用域,那么在 Lambda 表达式执行时可能会引发未定义的行为。为了避免这种情况,可以使用捕获捕获引用的方式来避免变量超出作用域的问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值