lambda与包装器
- lambda语法
- 捕捉列表
- lambda的应用
- lambda的原理
- 包装器
- function
- bind
lambda语法
lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。
lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象
lambda表达式的格式: [capture-list] (parameters)-> return type {function boby }
- [capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上⽂中的变量供 lambda 函数使用,捕捉列表可以传值和传引⽤捕捉。捕捉列表为空也不能省略。(捕捉列表只能是已经定义的)
- (parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略
- ->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。
- {function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使⽤其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1, 2) << endl;
// 1、捕捉为空也不能省略
// 2、参数为空可以省略
// 3、返回值可以省略,可以通过返回对象⾃动推导
// 4、函数题不能省略
auto func1 = []
{cout << "hello bit" << endl;return 0;
};
捕捉列表
- lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想用外层作⽤域中的变量就需要进⾏捕捉
- 第⼀种捕捉⽅式是在捕捉列表中显⽰的传值捕捉和传引⽤捕捉,捕捉的多个变量⽤逗号分割。[x,y, &z] 表⽰x和y值捕捉,z引⽤捕捉。
- 第⼆种捕捉⽅式是在捕捉列表中隐式捕捉,我们在捕捉列表写⼀个=表⽰隐式值捕捉,在捕捉列表写⼀个&表⽰隐式引⽤捕捉,这样我们 lambda 表达式中⽤了那些变量,编译器就会⾃动捕捉那些变量
- 第三种捕捉⽅式是在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=, &x]表⽰其他变量隐式值捕捉,x引⽤捕捉;[&, x, y]表⽰其他变量引⽤捕捉,x和y值捕捉。当使用混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。
- lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。(但是一般都是将lambda定义在局部区域)
- 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。
auto func1 = [a, &b]
{// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改//a++;b++;int ret = a + b;return ret;
};
auto func2 = [=]
{int ret = a + b + c;return ret;
};
auto func6 = []
{int ret = x + m;return ret;
};
auto func4 = [&, a, b]
{//a++;//b++;c++;d++;return a + b + c + d;
};
auto func5 = [=, &a, &b]
{a++;b++;/*c++;d++;*/return a + b + c + d;
};
auto func7 = [=]()mutable
{a++;b++;c++;
};
但如果lambda是写在类里面的,那么lambda是不能够捕获私有成员变量的,也是不能直接使用的,如果想使用需要捕获this指针
class A
{
public:void f(){auto add1 = [this](int a,int b){return a + b + _a1 + _a2};}
private:
int _a1 = 1;
int _a2 = 2;
};
lambda的应用
在学习 lambda 表达式之前,可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义⼀个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
});
因为sort
会将迭代器区间传给后面的对象,所以需要两个参数。
lambda的原理
- lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有lambda 和范围for这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
- 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。
包装器
function
std::function
是⼀个类模板,也是⼀个包装器。 std::function
的实例对象可以包装存储其他的可以调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调用对象被称为 std::function
的⽬标。若 std::function
不含⽬标,则称它为空。调⽤空std::function
的⽬标导致抛出 std::bad_function_call
异常
int f(int a, int b)
{return a + b;
}struct Fun
{
public:int operator()(int a,int b){return (a + b) * 10;}
};int main()
{auto lf = [](int a, int b) {return (a + b) * 30; };function<int(int, int)>f1(f);//function<int(int, int)>f2(Fun());编译器会将其解释为函数声明而不是变量定义,这被称为"最令人烦恼的解析"function<int(int, int)>f2 = Fun();function<int(int, int)>f3(lf);vector<function<int(int, int)>>Vf = { f,Fun(),lf };cout << f1(2, 3) << endl;cout << f2(1, 2) << endl;cout << lf(4, 5) << endl;for (auto p : Vf){cout << p(1, 2) << endl;}return 0;
}
函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function
的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型。
可以把function当做把可调用对象进行统一为一个类型,这样就可以将其储存到容器中。
如果包的是成员函数的话需要注意,由于类的成员函数的第一个参数是默认为this指针,所以function实例化的类型需要有类的对象的地址或者是对象,即使传的是对象,对象会使用.*
操作符对this指针进行调用。
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){return (a + b) * _n;}private:int _n;
};
int main()
{// 包装静态成员函数// 成员函数要指定类域并且前⾯加&才能获取地址//静态成员函数是可以不加&function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;// 包装普通成员函数// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(pd, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pd), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;
}
bind
- bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。bind 也在
<functional>
这个头⽂件中。 - 调⽤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
的⼀个命名空间中。
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{return (a - b) * 10;
}
int main()
{auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;// bind 本质返回的⼀个仿函数对象// 调整参数顺序(不常⽤)// _1代表第⼀个实参// _2代表第⼆个实参// ...auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;// 调整参数个数 (常⽤)auto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);cout << sub4(5) << endl;// 分别绑死第123个参数auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 1) << endl;cout << bind(SubX, 100, _1, _2)(5, 1) << endl;auto sub6 = bind(SubX, _1, 100, _2);cout << sub6(5, 1) << endl;auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 1) << endl;return 0;
}
bind对于我们之前用function来包装成员函数时第一个参数必须传对象的情况做出了改变,可以用bind来绑定第一个参数,这样在后续调用该可调用对象的时候就可以不用传对象了
// 成员函数对象进⾏绑死,就不需要每次都传递了
function<double(Plus&&, double, double)> f6 = &Plus::plusd;
Plus pd;
cout << pd.plusd(1.1, 1.1) << endl;
cout << f6(move(pd), 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;auto f = bind(&Plus::plusd, &pd, _1, _2);
function<double(double, double)> f2 = bind(&Plus::plusd, &pd, _1, _2);
cout << f(1.1,1.1) << endl;
cout << f2(1.1, 1.1) << endl;