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;
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 函数对象
Rate r1(rate);
r1(10000, 2);
r2(10000, 2);
}

// 汇编代码:
// lambda
auto 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; //undefined template <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;
}
// 常规代码:
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
// 计算复利的lambda
auto 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;
}
更多推荐


所有评论(0)