C++学习笔记【泛型算法——lambda表达式】

在介绍lambda之前,先介绍下C++中的谓词。谓词是一个可调用的表达式,返回结果是一个能用做条件的值。标准库算法使用的谓词有两类:一元谓词(只接受单一参数)和二元谓词(有两个参数)。接受二元谓词的sort算法用该谓词来代替<符号比较元素。

例如以下代码,它会将words向量排序,按照元素长度从小到大排序。

bool isShoter(const string &s1,const string &s2)
{
	return s1.size()<s2.size();
}
vector<string> words;
sort(words.begin(),words.end(),isShorter);
//isShorter就是谓词,而且是一个二元谓词

1、lambda表达式

根据算法接受一元谓词还是二元谓词,传递给算法的谓词必须严格接受一个参数或两个参数。有时候程序员用到的谓词需要更多参数。为了解决这一问题,可以使用lambda表达式。

可调用对象是指能被调用运算符“()”直接调用的对象,lambda表达式是可调用对象,其他三个可调用对象分别是:函数,函数指针和重载了函数调用运算符的类。

一个lambda表达式表示一个可调用的代码单元。可以将其理解为一个未命名的内联函数。每个lambda有一个返回类型、一个参数列表和一个函数体。lambda可以定义在函数内部,函数不能定义在函数内部。

//lambda表达式的形式
[capture list](parameter list) -> return type { function body }
//capture list是一个lambda所在函数中定义的局部变量的列表,通常为空
//return type、parameter list和function body与普通函数一样
//表示返回类型、参数列表以及函数体
//lambda必须使用尾置返回来指定返回类型

可以忽略参数类别和返回类型,但必须包含捕获列表和函数体

auto f=[]{return 42;};	//定义了一个不接受参数的可调用对象f
cout<<f()<<endl;		//调用f,打印42

以上lambda忽略了参数列表,等价于指定空参数列表。如果忽略返回类型,lambda将根据函数体推断返回类型。如果函数体不只有return语句,且未指定返回类型,则返回void。

与普通函数调用类似,调用lambda时给定的实参用来初始化形参。且lambda不能有默认参数,实参与形参的数目相等,类型也要匹配。

用lambda表达式来实现与之前isShorter函数相同的功能。该lambda的捕获列表为空,说明它不适用所在函数中的任何局部变量。

sort(words.begin(),words.end(),
	[](const string &s1,const string &s2)
		{ return s1.size()<s2.size(); } );

如何使用捕获列表呢?捕获列表中可以使用lambda所在函数的局部变量。lambda通过把局部变量写在捕获列表中来指明会使用哪些变量。考虑以下代码

void biggerThan(vector<string> &words,vector<string>::size_type sz)
{
	//将words中字符串按长度从小到大排序
	sort(words.begin(),words.end(),
	[](const string &s1,const string &s2)
		{ return s1.size()<s2.size(); } );
	//找到words中第一个长度大于sz的元素,返回一个迭代器
	auto it=find_if(words.begin(),words.end(),
			[sz](const string &a)
				{return a.size()>sz;});
	//输出words长度比sz大的元素
	for(;it<words.end();++it)
		cout<<*it<<endl;
}

2、lambda捕获和返回

可以理解为,当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数是编译器生成的类类型的未命名对象。

值捕获

变量的捕获方式可以是值。被捕获的变量值是在lambda创建时拷贝,而不是调用时拷贝。所以之后对所捕获变量的值进行修改不会影响到lambda内的值。

int testFun()
{
    int a=5;
    auto f=[a](int b){return a+b;};		//将b与所捕获的值a进行相加
    a=0;
    auto j=f(3);		//j为8,f保存了创建时a的值
}

引用捕获

变量的捕获方式也可是引用。当在lambda函数体内使用该变量时,实际使用的是引用所绑定的对象。引用捕获和返回引用有一样的问题和限制。当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。

int testFun()
{
    int a=5;
    auto f=[&a](int b){return a+b;};		//将b与所捕获的值a进行相加
    a=0;
    auto j=f(3);		//j为3,f保存了创建时a的值
}

隐式捕获

除了显示列出我们希望使用的变量以外,还可以让编译器根据lambda中的代码来推断要使用的变量。为了指示编译器推断捕获列表,应该在捕获列表中写一个&或=。其中&表示采用捕获引用方式,=表示采用值捕获方式。
在这里插入图片描述

3、参数绑定

对于只在一两个地方使用的简单操作,lambda表达式是最有效的。如果需要在许多地方进行相同的操作,那么还是使用函数更好。有些标准库函数的可调用对象只支持单一参数,但有时候我们希望能够多传几个参数,那么如何解决这一问题呢。

bind函数

bind函数是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象以适应原对象的参数列表。

auto newCallable=bind(callable,arg_list);
//newCallable是一个可调用对象,arg_list是用逗号分隔的参数列表,callable是原来的可调用对象
//arg_list参数包含形如_n的形式,其中n是整数,表示newCallable中参数的位置,_1表示newCallable的第一个参数,_2表示newCallable的第二个参数
bool check_size(const string &s,string::size_type sz)
{
	return s.size()>=sz;
}
auto check6=bind(check_size,_1,6);//只接受一个string类型的参数
string s="hello";
bool b1=check6(s);//调用check_size(s,6)

_n 都定义在placeholders命名空间中,而这个命名空间定义在std命名空间中。_1对应的using声明为

#include<functional>
using std::placeholders::_1;

当然这样一一声明非常繁琐,其实可以直接声明std就行。

#include<functional>
using namespace std;

此外,我们可以用bind函数绑定可调用对象的参数或重新安排顺序。例如,假设f是一个可调用对象,有5个参数

auto g=bind(f,a,b,_2,c,_1);//g是有两个参数可调用对象
//g将自己的参数作为f的第三个和第五个参数
//g的第一个参数绑定到_1,第二个参数绑定到_2,
//而后将g的第一个参数传递给f作为最后一个参数
//将g的第二个参数传递给f作为第三个参数
//实际上bind调用
g(_1,_2)=f(a,b,_2,c,_1)

下面是用bind重排参数顺序的具体例子,isShorter在文章最开头有写。

sort(words.begin(),words.end(),isShorter);
sort(words.begin(),words.end(),bind(isShorter,_2,_1));
//第一个sort调用isShorter(A,B),第二个调用isShorter(B,A)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值