C++11 lambda+包装器+可变参数模板

lambda表达式

(1).什么是lambda

假设有这样一个类

struct Goods
{
string _name;  // 名字
double _price; // 价格
int _evaluate; // 评价
}

现在要将商品分别按照名字,价格三种方式排序,为此我们必须写三个仿函数,但这个有点麻烦,因为一旦比较的逻辑不一样,就得多实现一个类,所以c++11出现了与局部深度绑定的lambda表达式,其本质上是一个匿名函数。
eg:

vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price < g2._price; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price > g2._price; });

(2).lambda基本规则

格式[capture-list](parameters) mutable->return-type-{statement}

[capture-list] :捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文的变量供lambda函数使用
(parameters):参数列表,与普通函数的参数列表一致,如果不需要传参,则可以与()一起省略
mutable:默认情况下,lambda函数总是一个const函数,即捕捉过来的参数自动加了const,如果需要改变参数const’属性,需在()后加mutable
->returntype:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略,返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{statement}:函数体。除可以使用参数外,还可以使用所有捕获到的变量
小结:参数和返回值可以省略,捕捉列表和函数体不能省略。

[capture-list] (parameters) mutable -> return-type { statement }
    	 捕捉列表  参数  返回值  函数体
	最简单的lambda表达式,该表达式无意义
	[] {};
	auto Add1 = [](int x, int y)->double {return (x + y) / 3.0; };
	cout << Add1(2, 3) << endl;//Add1其实就是一个局部匿名函数
	int x = 3;

	int a = 4, b = 5;
	auto Add2 = [a, b,x] ()mutable//无参数可以直接省略
	{
		a = a + 3;
		b = b + 3;//加了mutable只能在函数体内部改变值,但是出了作用域还是无法改变的
		return a + b+x;
	};
	cout << Add2() << endl;
	cout << a << " " << b << endl;//4 5
	auto Swap = [](int& x, int& y)
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	Swap(a, b);
	cout << a << " " << b << endl;//5  4
	通过上述的例子可以看出,lambda表达式实际上可以理解成一个匿名函数
	该函数无法直接调用,若想直接调用,可借助auto将其赋值给一个变量
	
	捕捉列表描述了上下文哪些数据可以被lambda使用,以及使用的方式是传值还是传引用

	/*  [var]:表示值传递方式捕捉变量var
		[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
		[&var]:表示引用传递捕捉变量var
		[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
		[this]:表示值传递方式捕捉当前的this指针*/
	
	/*a.父作用域指包含lambda函数的语句块 即{}中的语句块
		b.语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
		比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
		[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
		c.捕捉列表不允许变量重复传递,否则就会导致编译错误。
		比如:[=, a]: = 已经以值传递方式捕捉了所有变量,捕捉a重复*/
	捕捉列表只能捕捉局部变量,不能捕捉全局变量
	int c = 100, d = 200;
	static int g = 20;
	auto Addn = [c, d,20] {return c + d; };//此时20是静态变量 不能捕捉
	cout << Addn() << endl;
	auto Swap2 = [&c, &d](int x, int y) {c = 2; d = 3; return c + d + x + y; };//引用捕捉
	 此时并不是传地址而是传引用
	auto Swap2 = [&](int x, int y) {c = 2; d = 3; return c + d + x + y; };//引用捕捉与上述作用一样,捕捉变量都是引用传递
	auto Swap2 = [=](int x, int y) {c = 2; d = 3; return c + d + x + y; };//报错,值传递,此时不加mutabl,捕捉的变量具有const
	auto Swap2 = [=](int x, int y) mutable{c = 2; d = 3; return c + d + x + y; };
	报错,值传递,此时不加mutabl,捕捉的变量具有const
	但此时无法修改c 和d的值
	int t = 500;
	auto Swap2 = [=, &c](int x, int y) mutable {c = 2; d = 3; t = 1000; return c + d +t+ x + y; };
	=表示捕捉的都是值传递,但是c是引用传递
	此时在函数体内部用的是修改后的值
	但因为是值传递,此时只能成功修改c的值,d和t在函数体中修改的都是其临时拷贝

总结: lambda就是定义了一个匿名的可调用的对象,一般定义在局部,特点是跟普通变量相比可以深度绑定局部的数据,比如说参数很多可以直接捕捉,不用传参了,有一些便捷性。
所以lambda可以提到仿函数吗?
不行,仿函数既可以传类型也可以传对象,但是lambda整体是一个对象,他只能用于那些传递对象的场景,eg:sort用lambda非常好,因为sort传的就是对象,但是在模板参数的时候lambda可能就不怎么好用。

(3).lambda实现原理

先补充一个lambda的规则:lambda表达式之间不能相互赋值,即使看起来类型相同
eg:

auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
f1 = f2 会编译失败
void(*PF)()
但是可以将lambda表达式赋值给相同类型的指针
PF = f1;
但是不建议这样做

在这里插入图片描述

UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准,亦为开放软件基金会组织在分布式计算环境领域的一部分。其目的,是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与其它人冲突的UUID。在这样的情况下,就不需考虑数据库创建时的名称重复问题。最广泛应用的UUID,是微软公司的全局唯一标识符(GUID),而其他重要的应用,则有Linux ext2/ext3文件系统、LUKS加密分区、GNOME、KDE、Mac OS X等等。另外我们也可以在e2fsprogs包中的UUID库找到实现。

所以上述的即使f1与f2表面上看上去函数是一样的,但是在其所生成的仿函数名称确实完全不一样的,所以不能赋值。

包装器

function包装器也叫做适配器。c++中的function本质是一个类模板,也是一个包装器,为什么需要它?
eg:ret = func(x)
上面的func可能是什么?
func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lambda表达式对象?这些都是可调用的类型,所以如此丰富的类型可能也会导致模板效率降低。
eg:

template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
通过上面的程序验证,我们会发现useF函数模板实例化了三份。
包装器可以很好的解决上面的问题
// 函数名
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d)->double{ return d/4; }, 11.11) << endl;
return 0;
}

在这里插入图片描述模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
使用方法如下:

	function<int(int, int)>func1 = f;
	cout << func1(1, 2) << endl;
	function<int(int, int)>func2 = Functor();
	cout << func2(2, 4) << endl;
	//静态成员函数的包装跟其他的包装一样
	function<int(int, int)>func3 = &Plus::plusi;//取地址符号可加可不加,但最好加上
	cout << func2(5, 4) << endl;
	//非静态成员函数有点区别
	function<double(Plus, double, double)>func4 = &Plus::plusd;
	cout << func4(Plus(), 5.2, 3.5) << endl;
	//Plus()匿名对象,因为非静态成员函数要用this指针去调用
	//静态成员函数不用,上述需要靠匿名对象调用函数
	function<int(int, int)>func5 = [](int a, int b) {return a + b; };
	cout << func5(100, 200) << endl;

可以看到包装器统一的特点是统一类型
c++中常用命令对应函数

map<string,function>
命令对应函数
eg
cout << opFuncMap["普通函数指针"](1, 2) << endl;
	cout << opFuncMap["函数对象"](1, 2) << endl;
	

在这里插入图片描述
所以可以像下面这样

int f(int a, int b)
{
	return a - b;
}

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};


class Plus
{
public:
	Plus(int x = 2)
		:_x(x)
	{}

	int plusi(int a, int b)
	{
		return (a + b) * _x;
	}
private:
	int _x;
};
void Teste()
{
	map<string, std::function<int(int, int)>> opFuncMap =
	{
		{ "普通函数指针", f },
		{ "函数对象", Functor() },
		{ "成员函数指针", std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2) }
	};

	cout << opFuncMap["普通函数指针"](1, 2) << endl;
	cout << opFuncMap["函数对象"](1, 2) << endl;
	cout << opFuncMap["成员函数指针"](1, 2) << endl;
}

可变参数模板

c++98/03,类模板和函数模板中只能包含固定数量的模板参数,c++11新特性可以接受可变参数的函数模板和类模板,使用起来稍微一点技巧。

//Argss是一个模板参数包,args是一个函数形参参数包
//声明一个参数包Args...args,这个参数包中包含0到任意个模板参数
template<class ...Args>
void ShowList1(Args... args){}

上面的参数args前面有省略号,所以它就是一个可变模板参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模板参数,我们无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包重点每个参数,这是使用可变模板参数的一个主要特点,也是最大的难点,下面我用一张图来演示如果展开。
在这里插入图片描述
可变参数在STL中的运用
在这里插入图片描述
首先看到emplace_back系列的接口,支持模板的可变参数,且是万能引用。

list<pair<string, int>>li;
	li.push_back(make_pair("zjt", 20));
	li.emplace_back("nidie", 18);

emplace_back支持可变参数,拿到pair对象参数后自己去创建对象,
在这看到除了用法上面,和push_back没有太大区别。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天少点debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值