索引
- lambda表达式
- (1).什么是lambda
- (2).lambda基本规则
- (3).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没有太大区别。