C++11中的lambda表达式与包装器
目录
- lambda语法
- 捕捉列表
- lambda的应用
- lambda的原理
- 包装器
- function
- bind
lambda语法
lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。
lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambd编程客栈a 对象lambda表达式的格式: [capture-list] (parameters)-> return type {function boby }- [capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上⽂中的变量供 lambda 函数使用,捕捉列表可以传python值和传引⽤捕捉。捕捉列表为空也不能省略。(捕捉列表只能是已经定义的)
- (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() {js 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
的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型。
.*
操作符对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;
到此这篇关于C++11之lambda表达式与包装器的文章就介绍到这了,更多相关C++ lambda表达式内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论