C++基础(八)初窥lambda、lambda在STL中应用

lambda表达式是C++11新推出来的一个概念,非常好用,但如同C++的其他知识点一样,坑也很多。

想要比较全面的介绍lambda,放到一篇博客中会比较臃肿,故决定用两篇介绍。本篇重点介绍lambda的简单应用,另一篇着重介绍lambda的坑

进入正题!

lambda可以理解成局部函数,即函数中定义一个函数,与STL算法搭配使用,会极其方便。可以这么说,有了lambda,STL如虎添翼。

但是!lambda也可以定义成全局的,即不在函数内定义。但这种做法有违了lambda的设计初衷,直接用普通函数就好。

其语法格式如下:

[捕获列表] (参数列表) -> 返回值类型 { 函数体 }
其中,仅有“[]{}”是必须的,其他全部可以省略不写。lambda的使用,和函数类似,先要定义,然后调用。

简单的示例代码如下:

auto lam_global = []{std::cout << "全局" << std::endl; };  //函数体外定义的lambda,虽然合法,但不建议这么做,用普通函数即可
int _tmain(int argc, _TCHAR* argv[])
{	
	using namespace std;
	auto lam_empty = []{};  //最简单的Lambda表达式,什么都不做
	auto lam_print = []{cout << "简单lambda" << endl; }; 
	lam_empty();
	lam_print();
	lam_global();
	system("pause");
	return 0;
}

执行结果如下:

上面用的是auto关键字定义的变量名来接受lambda表达式,用函数指针也可以:

	using Fun = void(*)(void);
	Fun fun = []{cout << "yes" << endl; };
	fun();

建议统一用auto,交由编译器完成类型推断!

下面一一解释各部分。

参数列表

1、参数列表和普通函数的参数列表一样,可以是值、引用、const引用等,也是作为形参;

2、如果是非const引用,函数体内部可以修改引用的值;

3、与普通函数不同,lambda表达式的参数不能有默认值。

示例代码如下:

int _tmain(int argc, _TCHAR* argv[])
{	
	using namespace std;

	auto lam_printStr = [](const string& str){cout << str << endl; };  //const 引用
	auto lam_printNum = [](int& iValue, double dValue){cout << dValue << endl; ++iValue; };  //既有值传递,也有引用传递
	//auto lam_defualtValue = [](int iValue = 10){};  //非法,无法通过编译,参数不能有默认值
	lam_printStr("lambda"); 
	int iCount = 0;
	lam_printNum(iCount, 12.12);  //执行结束,iCount会被+1
	cout << iCount << endl;  //为1
	
	system("pause");
	return 0;
}

执行结果如下:

函数体

1、可以使用参数列表中传递的参数;

2、可以直接访问静态局部变量,以及全局变量;

3、不能直接访问非静态局部变量;

4、可以使用捕捉列表里捕捉的局部变量;

5、可以在函数体中对全局变量和静态局部变量修改。

第一点上面已经介绍过。

第二点,静态的局部变量、全局变量以及其他的函数,都可以在lambda表达式的函数体中直接访问。

第三点和第四点相辅相成,因为有了第三点,才会有第四点。

请看示例代码:

std::string g_strGlobal("全局变量");  
int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	static string s_strLocal = "静态局部变量";
	string strTemp = "非静态局部变量";

	//函数体中可以直接访问局部静态变量、全局变量以及参数列表中的形参
	auto lam_print = [](char chSperator){
		cout << g_strGlobal << chSperator << s_strLocal << endl;
		g_strGlobal = "global"; //修改全局变量
		s_strLocal = "local";  //修改静态局部变量
	};  
	//auto lam_printLocal = [](){cout << strTemp << endl;};  //编译不过,函数体无法直接访问非静态局部变量
	lam_print('/');
	cout << g_strGlobal << endl; 
	cout << s_strLocal << endl;

	system("pause");
	return 0;
}

执行结果如下:

函数体中不能直接访问非静态局部变量,有没有其他方式访问呢?肯定有,就是接下来要说的捕捉列表,也是lambda表达式中必须的成分之一。

捕捉列表

有关捕捉列表,有很多坑及注意事项,将在另一篇文章中深挖:

https://blog.csdn.net/yedawei_1/article/details/109508875

这里仅介绍捕捉列表的简单使用。

1、捕捉列表中直接写非静态变量的变量名,函数体中便可访问;

2、与参数列表不同,不需要在变量名前加上类型;

3、如果有多个变量,用","隔开;

4、只能捕捉在lambda定义之前已经定义的非静态局部变量;

5、直接写变量名,为值捕获;想要引用方式捕获,则在变量名前加上"&"。

将上述代码略作修改,将局部变量strTemp以值捕获的方式放到捕捉列表,在函数体中便可访问。如下:

std::string g_strGlobal("全局变量");  
int _tmain(int argc, _TCHAR* argv[])
{	
	using namespace std;
	static string s_strLocal = "静态局部变量";  
	string strTemp = "非静态局部变量";

	auto lam_print = [strTemp](char chSperator){cout << g_strGlobal << chSperator << s_strLocal << chSperator << strTemp<< endl; };  
	lam_print('/');
	
	system("pause");
	return 0;
}

执行结果如下:

如果是引用捕获,在函数体中,是可以修改变量的值的;如果是值捕获,直接在函数体中修改,会编译报错(有修改方式,请参见另一篇文章)。

示例代码如下:

std::string g_strGlobal("全局变量");  
int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	static string s_strLocal = "静态局部变量";
	string strTemp1 = "非静态局部变量1";
	string strTemp2 = "非静态局部变量2";

	auto lam_print = [strTemp1, &strTemp2/*, strTemp3*/](char chSperator)  //这里的strTemp3还未定义,无能捕获,会编译报错
	{
		cout << g_strGlobal << chSperator << s_strLocal;
		cout << chSperator << strTemp1 << chSperator << strTemp2 << endl;
		//strTemp1 = "strTemp1";  //会编译不过
		strTemp2 = "local";  //修改引用捕获的非静态局部变量的值
	};

	string strTemp3 = "非静态局部变量3";
	lam_print('/');
	cout << strTemp2 << endl;

	system("pause");
	return 0;
}

执行结果如下:

这里定义了两个非静态的局部变量strTemp1和strTemp2,strTemp1为值捕获,strTemp2为引用捕获。

在函数体中,对strTemp2进行修改,在lambda表达式执行成功后打印strTemp2,值为lambda表达式中的修改的值,修改成功。

至此,lambda表达式的基础内容已差不多。至于返回值,在另一篇文章中讨论。

下面看看lambda表达式在STL中运用。

STL和lambda

1、与for_each搭配使用,改变原始容器中的值

for_each算法在另一篇文章中有介绍:https://blog.csdn.net/yedawei_1/article/details/105843348

改算法会忽视第三个一元谓词的返回值,如果有lambda表达式,很容易做到直接更改原始容器中的元素。

例如,想要将原始容器中所有小于0的元素改为正的,可以这么做:

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;

	list<int> lstInt = { -10, 15, 20, -3, -45 };
	for_each(lstInt.begin(), lstInt.end(), [](int& i){
		if (i < 0)
			i = -i;
	});
	for (int i : lstInt)
		cout << i << " ";
	cout << endl;

	system("pause");
	return 0;
}

执行结果:

2、与sort搭配,实现比较复杂的比较规则

sort函数(std::sort和list.sort),都有接受一个二元谓词的版本。但也有限值,只能接受两个参数,如果想要引入比较因子,有些麻烦,lambda恰恰就能实现要求。

比如,想要对一个list<int>进行排序,要求引入一个比较因子,将比比较因子小的的元素放到容器前端,比比较因子大的元素放到容器后端,且满足条件的那些元素保持原有的相对位置不变。

假如原始元素为{2,5,8,1,3},比较因子为4,则排序后应该为{2,1,3,5,8}。

实现代码如下:

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;

	//定义一个打印函数
	auto fun_print = [](const list<int>& lst){
		for_each(lst.begin(), lst.end(), [](int i){ cout << i << " "; });
		cout << endl;
	};

	
	int iSign = 9;  //最初的比较因子
	auto fun_compare = [&iSign](int iLeft, int iRight){  //按照引用捕获的方式获取比较因子
		if (iLeft <= iSign && iRight > iSign)
			return true;
		else
			return false;
	};

	list<int> lstInt = { 2, 18, 17, 13, 12, 8, 10, 1, 11, 45 };
	lstInt.sort(fun_compare); 
	fun_print(lstInt);
	iSign = 12;  //改变比较因子
	lstInt.sort(fun_compare);
	fun_print(lstInt);

	system("pause");
	return 0;
}

执行结果如下:

这段代码,定义了一个打印容器中所有元素的lambda表达式fun_print,注意,里面嵌套了另一个lambda表达式。

lambda表达式可以嵌套使用!

接着,定义了一个比较因子iSign,以及自定义lambda表达式作为比较谓词。

注意,fun_compare中用的是引用捕获。这里必须用引用捕获,不能用值捕获,否则后面比较因子的改变会无效另一篇文章中有详细解释

后面就是调用和实现。

除了这些外,lambda还有非常多的用途,掌握后会使得代码更加简练、易读。

 

 

 

 

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值