先看一组案例,假设现在有一个要根据运动员的得分数量和得分效率分别进行排序的场景:
struct player
{
string _name;//姓名
int _score;//得分
int _hit_rate;//命中率
player(const char* name, int socre, int hit)
:_name(name)
, _score(socre)
, _hit_rate(hit)
{}
};
void _print(const vector& vv)
{
for (size_t i = 0; i < vv.size(); i++)
{
cout << vv[i]._name <<":"<< vv[i]._score <<":" << vv[i]._hit_rate << endl;
}
}
struct Comper1 { bool operator()(const player p1, const player p2) { //得分高的在前面 return p1._score > p2._score; } }; struct Comper2 { bool operator()(const player p1, const player p2) { //效率高的在前面 return p1._hit_rate > p2._hit_rate; } };
根据上述场景,要进程排序的对象是自定义类型,这时我们需要根据不同的需求编写不同的排序规则!
测试代码:
void test1()
{
vector vv = { {"张继科",11,80},{"马龙",12,88},{"许欣",10,82} };
_print(vv);
//按照得分排序
sort(vv.begin(), vv.end(), Comper1());
cout << "----------按照得分排序---------------" << endl;
_print(vv);
//按照效率排序
cout << "----------按照效率排序---------------" << endl;
sort(vv.begin(), vv.end(), Comper2());
_print(vv);
}
如上图测试结果所示,分别根据运动员的得分和效率进行了排序,并达到了我们的预期。但是在上述的代码实现中,好像略微有一点点麻烦。同样是对数据进行排序,每当有新的比较逻辑时就要实现一个类,而且在代码的可读性上也差了一点点。
先来看看最简单的lambda:
[]{};
哈哈哈,上述表达式没有任何意义,但是语法上是支持的。
[捕捉列表](参数列表)mutable->return_type{函数体};
●[]捕捉列表:听名字就很霸气,它能捕捉上下文变量供函数体使用,已及使用的方式是传值还是传引用。
[var]:表示值传递方式捕捉变量var
[&var]:表示引用传递捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针●()参数列表:和函数传参差不多。
●mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
●return_type:返回值类型,可以省略不写。●{}函数体:函数体内可以使用传进来的参数和捕获到的变量。
小应用:
void test11() { //比较完整的lambda函数 auto fun1 = [](int x, int y)->int{ return x + y; }; cout <<"fun1():" << fun1(10, 20) << endl; //传值捕获 int a = 11, b = 22; auto fun2 = [=]()->int { return a + b;}; cout <<"fun2():"<< fun2() << endl; }
void test3() { int a = 10, b = 20; //引用传递捕获父作用域中的变量 auto it = [&]{ a = b+10; }; it(); cout <<"调用it()后a=" << a << endl; cout <<"交换前a=" << a << " b=" << b << endl; //引用传递捕获父作用域中的变量 auto Swap = [&] { a = a ^ b; b = a ^ b; a = a ^ b; }; //交换 Swap(); cout << "交换后a=" << a << " b=" << b << endl; }
lambda表达式就像是一个无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
在回到文章开头的情景中,使用lambda表达式:
void test2() { vector
vv = { {"李梦",36,60},{"刘思雨",23,65},{"张萌",15,70}}; _print(vv); //按照得分排序 sort(vv.begin(), vv.end(), [](const player p1, const player p2) {return p1._score > p2._score; }); cout << "----------按照得分排序---------------" << endl; _print(vv); //按照效率排序 cout << "----------按照效率排序---------------" << endl; sort(vv.begin(), vv.end(), [](const player p1, const player p2) {return p1._hit_rate> p2._hit_rate; }); _print(vv); } 通过lambda表达式的使用,也能完成上述情景中的任务。而且不用在专门写两个类,读代码的时候也比较方便。
ret = Func(a);
观察上述的伪代码,Fun可能是一个函数名,也有可能是一个仿函数对象,或者是一个lambda表达式对象,包装器的作用就是把它们的类型“包装”起来,这样一来就提高了模板的效率。
template
T useF(F f, T x) { static int count = 0; cout << "count:" << ++count << endl; cout << "&count:" <<&count << endl; return f(x); } int Fun(int x) { return x + 1; } struct Func { int operator()(int x) { return x + 2; } }; void test5() { int a = 10; //函数指针 cout << useF(Fun, a) << endl; //函数对象(仿函数) cout << useF(Func(), a) << endl; //lambad表达式 cout << useF([](int x)->int {return x + 3; }, a) << endl; //测试结果:类模板实例化了三次 }
包装器的使用:
//函数 int AddFun(int a, int b) { return a + b; } //仿函数 struct AddFunc { int operator()(int a, int b) { return a + b; } }; //类 class Add { public: static int add1(int a, int b) { return a + b; } int add2(int a, int b) { return a + b; } }; void test12() { //函数名(函数指针) function
fun1 = AddFun; cout <<"函数名(函数指针):"<< fun1(10, 20) << endl; //仿函数 function fun4 = AddFunc(); cout <<"仿函数:" << fun4(20, 40) << endl; //lambda表达式 function fun5 = [](int a, int b) ->int {return a + b; }; cout <<"lambda表达式:" << fun5(50, 60) << endl; //类静态成员函数 function fun2 = &Add::add1; cout <<"类静态成员函数:" << fun2(10, 11) << endl; //类成员函数 function fun3 = &Add::add2; cout <<"类成员函数:"<< fun3(Add(), 10, 11) << endl; } 需要注意的是:当包装器包装的对象是类的成员函数时,应该子函数名前指明该函数所在的类域,并在前加上&符号(静态类成员函数可加可不加,普通类成员函数必须加)。
使用包装器解决上述模板效率低的问题:
//包装器 void test9() { int a = 10; function
fun1 = Fun; cout << useF(fun1,a) << endl; function fun2 = Func(); cout << useF(fun2, a) << endl; function fun3 = [&](int a) {return a + 4; }; cout << useF(fun3, a) << endl; } 练习包装器的使用:
链接:力扣
思路分析:将四种运算符和表示不同运算方法的lambda表达式存入到map中建立映射关系,lambda表达式的类型给成包装器。这样一来,在后序的工作就变得十分轻松,当容器中的运算是运算符时,从栈中取左右操作数,在根据运算符直接从map中找到与之对应的lambda表达式进行运算。
class Solution { public: int evalRPN(vector
& tokens) { map > mm = {{"+",[](int a,int b){return a+b;}}, {"-",[](int a,int b){return a-b;}}, {"*",[](int a,int b){return a*b;}}, {"/",[](int a,int b){return a/b;}} }; stack st; for(auto& str : tokens) { if(mm.find(str) != mm.end()) { //找到了 int right = st.top(); st.pop(); int left = st.top(); st.pop(); st.push(mm[str](left,right)); } else { st.push(stoi(str)); } } return st.top(); } };
我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M
可以大于N,但这么做没什么意义)参数的新函数。bind函数还可以实现参数顺序调整等操作。
int add(int x, int y)
{
return x + y;
}
class Sub
{
public:
int sub(int x,int y)
{
return x - y;
}
};
void test13()
{
function f3 = bind(add, placeholders::_1, placeholders::_2);
cout << f3(1, 2) << endl;
Sub s;
function f1 = bind(&Sub::sub,Sub(),placeholders::_1, placeholders::_2);
cout << f1(10, 8) << endl;
function f2 = bind(&Sub::sub,s, placeholders::_2, placeholders::_1);
cout << f1(10, 8) << endl;
}