C++11:lambda表达式,包装器
一、lambda表达式
1.lambda表达式语法
• lambda表达式本质是一个匿名函数对象,和普通函数不同的是他可以定义在函数内部。
lambda 表达式语法使用层而言没有类型,所以我们一般是用 auto 或者模板参数定义的对象去接 收 lambda对象。
• lambda表达式的格式:
[ capture-list ] (parameters)-> return type { function boby }• [ capture-list] :捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,具体细节下面细讲。捕捉列表为空也不能省略。
• ( parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连 同()一起省略
• ->return type :返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
• { function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。
int main()
{auto add1 = [](int x, int y)->int {return x + y;};cout << add1(1, 2) << endl;// 1.捕捉为空也不能省略// 2.参数为空可以省略// 3.返回值可以省略,可以通过返回对象自动推导// 4.函数体不能省略auto func1 = []{cout << "hello word" << endl;return 0;};func1();return 0;
}

2.捕捉列表
• lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用外层作用域中的变量就 需要进行捕捉
• 第一种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x, y,&z]表示 x 和 y 传值捕捉,z 引捕捉。
• 第二种捕捉方式是在捕捉列表中隐式捕捉,我们在捕捉列表写一个 = 表示隐式值捕捉,在捕捉列表写一个 & 表示隐式引用捕捉,这样我们 lambda 表达式中用了哪些变量,编译器就会自动捕捉哪些变量。
• 第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。[=,&x]表示其他变量隐式值捕捉, x引用捕捉;[&,x,y]表示其他变量引用捕捉,x和y 传值捕捉。当使用混合捕捉时,第一个元素必须是 & 或 =,并且 & 混合捕捉时,后面的捕捉变量必须是值捕捉,同理 = 混合捕捉时,后面的捕捉变量必须是引用捕捉。
• lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态 局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使用。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
• 默认情况下, lambda 捕捉列表是被 const 修饰的,也就是说传值捕捉的过来的对象不能修改, mutable 加在参数列表的后面可以取消其常量性,也就是说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。
int x = 0;int main()
{// 只能用当前lambda局部域和捕捉的对象和全局对象int a = 0, b = 1, c = 2, d = 3;auto func1 = [a, &b]() {// 值捕捉的变量不能修改,引用捕捉的变量可以修改// a++;b++;x++;int ret = a + b;return ret;};cout << func1() << endl;// 隐式值捕捉// 用了哪些变量就捕捉哪些变量auto func2 = [=]{int ret = a + b + c;return ret;};cout << func2() << endl;// 隐式引用捕捉// 用了哪些变量就捕捉哪些变量auto func3 = [&]{a++;c++;d++;};func3();cout << a << " " << b << " " << c << " " << d << endl;// 混合捕捉auto func4 = [&, a, b]{//a++;//b++;c++;d++;return a + b + c + d;};func4();cout << a << " " << b << " " << c << " " << d << endl;// 混合捕捉auto func5 = [=, &a, &b]{a++;b++;/*c++;d++;*/return a + b + c + d;};func5();cout << a << " " << b << " " << c << " " << d << endl;return 0;
}
3.lambda的应用
• 在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的 类型定义起来比较麻烦,仿函数要定义一个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既方便又简单
• lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定 制删除器等, lambda 的应用还是很广泛的。
#include<vector>
#include<algorithm>
struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价// ...Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};struct Compare1
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}/*bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}*/
};struct Compare2
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};struct Compare3
{bool operator()(const Goods& gl, const Goods& gr){return gl._evaluate > gr._evaluate;}
};int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3}, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), Compare1());sort(v.begin(), v.end(), Compare2());sort(v.begin(), v.end(), Compare3());auto priceLess = [](const Goods& gl, const Goods& gr){return gl._price < gr._price;};sort(v.begin(), v.end(), priceLess);sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){return gl._price > gr._price;});sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {return gl._evaluate > gr._evaluate;});sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {return gl._evaluate < gr._evaluate;});
}
4.lambda的原理
• lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,而 lambda 底层是仿函数对象,也就说我们写了一个lambda 以后,编译器会生成一个对应的仿函数的类。
• 仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返 回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些对象就传哪些对象。
• 上面的原理,我们可以透过汇编层了解一下,下面汇编层代码印证了上面的原理。
class Rate
{
public:Rate(double rate): _rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};int main()
{double rate = 0.49;// lambdaauto r2 = [rate](double money, int year) {return money * rate * year;};// 函数对象Rate r1(rate);r1(10000, 2);r2(10000, 2);
}

// 汇编代码:// lambdaauto r2 = [rate](double money, int year) {return money * rate * year;};
00007FF70CE51A2B lea rdx,[rate]
00007FF70CE51A2F lea rcx,[r2]
00007FF70CE51A33 call `main'::`2'::<lambda_1>::<lambda_1> (07FF70CE517B0h)
00007FF70CE51A38 nop // 函数对象Rate r1(rate);
00007FF70CE51A39 movsd xmm1,mmword ptr [rate]
00007FF70CE51A3E lea rcx,[r1]
00007FF70CE51A42 call Rate::Rate (07FF70CE510C8h)
00007FF70CE51A47 nop r1(10000, 2);
00007FF70CE51A48 mov r8d,2
00007FF70CE51A4E movsd xmm1,mmword ptr [__real@40c3880000000000 (07FF70CE5ACD0h)]
00007FF70CE51A56 lea rcx,[r1]
00007FF70CE51A5A call Rate::operator() (07FF70CE51136h)
00007FF70CE51A5F nop r2(10000, 2);
00007FF70CE51A60 mov r8d,2
00007FF70CE51A66 movsd xmm1,mmword ptr [__real@40c3880000000000 (07FF70CE5ACD0h)]
00007FF70CE51A6E lea rcx,[r2]
00007FF70CE51A72 call `main'::`2'::<lambda_1>::operator() (07FF70CE51870h)
00007FF70CE51A77 nop
}
汇编层可以看到 r2 lambda 对象调用本质还是调用 operator() ,类型是 lambda_1, 这个类型名的规则是编译器自己定制的,保证不同的 lambda 不冲突
二、包装器
1.function
// 官方文档: template <class T> class function; //undefinedtemplate <class Ret, class... Args> class function<Ret(Args...)>;• std::function 是一个类模板,也是一个包装器。 std::function 的实例对象可以包装存储其他的可以调用的对象,包括函数指针、仿函数、 lambda 、bind 表达式等,;存储的可调用对象被称为 std::function 的目标。若 std::function不含目标,则称它为空。调用空 std::function 的目标导致抛出 std::bad_function_call(std::bad_function_call - cppreference.com) 异常。
• 以上是 function 的原型,他被定义<functional>头件中。std::function - cppreference.com是function的官方文件链接。
• 函数指针、仿函数、 lambda 等可调用对象的类型各不相同, std::function 的优势就是统一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型,下面的第二个代 码样例展示了 std::function 作为map的参数,实现字符串和可调用对象的映射表功能。
#include <functional>
#include <vector>
using namespace std;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 n = 10):_n(n){}static int plusi(int a, int b){return a + b;}double plusd(double a, double b) const{return (a + b) * _n;}
private:int _n;
};int main()
{// void(*pf)(int) = nullptr;// // 包装各种可调用对象function<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b) {return a + b; };cout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;vector<function<int(int, int)>> vf = { f , Functor(),[](int a, int b) {return a + b; } };for (auto& f : vf){cout << f(1, 1) << endl;}// 包装静态成员函数// 成员函数要指定类域并且前面加&才能获取地址,一般取地址function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus ps;cout << f5(&ps, 1.1, 1.1) << endl;//function<double(Plus, double, double)> f6 = &Plus::plusd;function<double(const Plus&, double, double)> f6 = &Plus::plusd;cout << f6(Plus(), 1.1, 1.1) << endl;cout << f6(ps, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(Plus(), 1.1, 1.1) << endl;cout << f7(move(ps), 1.1, 1.1) << endl;return 0;
}
150. 逆波兰表达式求值 - 力扣(LeetCode)
// 常规代码:
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for(int i = 0; i < tokens.size(); ++i){const string& s = tokens[i];if(s != "+" && s != "-" && s != "*" && s != "/"){st.push(stoi(s));}else{int right = st.top();st.pop();int left = st.top();st.pop();switch(s[0]){case '+':st.push(left + right);break;case '-':st.push(left - right);break;case '*':st.push(left * right);break;case '/':st.push(left / right);break;}}}return st.top();}
};
下面进行优化:
使用 map 映射 string 和 function 的方式实现,这种方式的最大优势之一是方便扩展,假设还有其他运算,我们增加 map 中的映射即可
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;unordered_map<string, function<int(int, int)>> opFuncMap = {{"+",[](int x, int y){return x + y;}},{"-",[](int x, int y){return x - y;}},{"*",[](int x, int y){return x * y;}},{"/",[](int x, int y){return x / y;}}};for(int i = 0; i < tokens.size(); ++i){const string& s = tokens[i];if(opFuncMap.count(s)){int right = st.top(); st.pop();int left = st.top(); st.pop();int ret = opFuncMap[s](left, right);st.push(ret);}else{st.push(stoi(s));}}return st.top();}
};
2.bind
• bind 是一个函数模板,它也是一个可调用对象的包装器,可以把他看做一个函数适配器,对接收 的 fn 可调用对象进行处理后返回一个可调用对象。 bind 可以用来调整参数个数和参数顺序。bind 也在<function>这个头文件中。
• 调用 bind 的一般形式:
auto newCallable = bind(callable,arg_list);其中 newCallable本身是一个可调用对象,arg_list 是一个逗号分隔的参数列表,对应给定的 callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
• arg_list中的参数可能包含形如 _n 的名字,其中n是一个整数,这些参数是占位符,表示 newCallable 的参数,它们占据了传递给 newCallable 的参数的位置。数值 n 表示生成的可调用对象中参数的位置:_1 为newCallable的第一个参数,_2为第二个参数,以此类推。_1 / _ 2 / _3 ....这些占位符放到placeholders的一个命名空间中。
看下面代码来理解吧,其实用起来是很简单的。
#include<functional>using placeholders::_1;
using placeholders::_2;
using placeholders::_3;int Sub(int a, int b)
{return (a - b) * 10;
}int SubX(int a, int b, int c)
{return (a - b - c) * 10;
}class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};int main()
{cout << Sub(10, 5) << endl;// bind 本质返回的一个仿函数对象// 调整参数顺序(不常用)// _1代表第一个实参// _2代表第二个实参// ...auto newSub1 = bind(Sub, _1, _2);auto newSub1 = bind(Sub, _2, _1);cout << newSub1(10, 5) << endl;// 调整参数个数auto newSub2 = bind(Sub, _1, 10);cout << newSub2(20) << endl;auto newSub3 = bind(Sub, 5, _1);cout << newSub3(20) << endl;auto newSub4 = bind(SubX, 5, _1, _2);cout << newSub4(10, 20) << endl;auto newSub5 = bind(SubX, _1, 5, _2);cout << newSub5(10, 20) << endl;auto newSub6 = bind(SubX, _1, _2, 5);cout << newSub6(10, 20) << endl;// 成员函数对象进行绑死,就不需要每次都传递了//function<double(Plus&&, double, double)> f6 = &Plus::plusd;//Plus pd;//cout << f6(move(pd), 1.1, 1.1) << endl;//cout << f6(Plus(), 1.1, 1.1) << endl;//// auto f7 = bind(&Plus::plusd, Plus(), _1, _2);//function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);//cout << f7(1.1, 1.1) << endl;function<double(Plus&&, double, double)> f6 = &Plus::plusd;Plus pd;cout << f6(move(pd), 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;//auto f7 = bind(&Plus::plusd, _1, _2);function<double(double, double)> f7 = bind(&Plus::plusd,Plus(), _1, _2);cout << f7(1.1, 1.1) << endl;// 100w// 计算复利的lambdaauto func1 = [](double rate, double money, int year)->double {double ret = money;for (int i = 0; i < year; i++){ret += ret * rate;}return ret - money;};//cout << func1(0.2, 1000000, 3) << endl;//cout << func1(0.2, 1000000, 10) << endl;//cout << func1(0.2, 1000000, 30) << endl;// 绑死一些参数,实现出支持不同年华利率,不同金额和不同年份计算出复利的结算利息function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);function<double(double)> func5_2_0 = bind(func1, 0.02, _1, 5);function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);cout << func3_1_5(1000000) << endl;cout << func5_2_0(1000000) << endl;cout << func10_2_5(1000000) << endl;cout << func20_3_5(1000000) << endl;return 0;
}
