从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(上):https://developer.aliyun.com/article/1522407
2.2 lambda表达式语法
lambda表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement } 即: [捕捉列表](参数列表)mutable->(返回值类型){函数体}
[捕捉列表]
捕捉列表是编译器判断lambda表达式的依据,所以必须写[ ],[ ]内可以有参数,后面会讲解。
(参数列表)
参数列表和普通函数的参数列表一样,如果不需要参数传递,可以连同( )一起省略。
mutable
默认情况下,lambda表达式的形参都是const类型,形参不可以被修改,使用mutable可以取消形参的常量属性。使用mutable时,参数列表不可以省略(即使参数为空)。一般情况下mutable都是省略的。
->返回值类型
->和返回值类型是一体的,如->int表示lambda的返回值是int类型。一般情况下省略->返回值类型,因为编译器可以根据函数体中的return推导出返回值类型。为了提高程序的可读性可以写上。
{函数体}
和普通函数一样,{ }里的是lambda的具体实现逻辑。
注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,可写可不写。
而捕捉列表和函数体必须写,但是内容可以为空。
因此C++11中最简单的lambda函数为:[ ]{ }; 该lambda函数不能做任何事情。
写两个简单的lambda:
int main() { // 两个数相加的lambda auto add1 = [](int a, int b)->int {return a + b;}; auto add2 = [](int a, int b) {return a + b;}; cout << add1(1, 6) << endl; cout << add2(1, 6) << endl; return 0; }
C++11之前函数的缺陷例子用lambda表达式解决:
struct Goods { string _name; // 名字 double _price; // 价格 int _evaluate; // 评价 Goods(const char* str, double price, int evaluate) :_name(str) , _price(price) , _evaluate(evaluate) {} }; int main() { vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 7 }, { "西瓜", 5.5,3 }, { "橘子", 7.7, 4 } }; sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price; }); sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price; }); sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate < g2._evaluate; }); sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate; }); return 0; }
交换变量的lambda:
int main() { // 交换变量的lambda int x = 0, y = 7; auto swap1 = [](int& x1, int& x2)->void{int tmp = x1; x1 = x2; x2 = tmp; }; swap1(x, y); cout << x << "-------" << y << endl; auto swap2 = [](int& x1, int& x2) { int tmp = x1; x1 = x2; x2 = tmp; }; swap2(x, y); cout << x << "-------" << y << endl; return 0; }
能不能不传参数交换x和y呢?用上捕捉列表就行:
int main() { // 交换变量的lambda int x = 0, y = 7; auto swap1 = [](int& x1, int& x2)->void{int tmp = x1; x1 = x2; x2 = tmp; }; swap1(x, y); cout << x << "-------" << y << endl; auto swap2 = [](int& x1, int& x2) { int tmp = x1; x1 = x2; x2 = tmp; }; swap2(x, y); cout << x << "-------" << y << endl; auto swap3 = [x, y](int& x1, int& x2) { int tmp = x1; x1 = x2; x2 = tmp; }; swap3(x, y); cout << x << "-------" << y << endl; swap3(x, y); cout << x << "-------" << y << endl; return 0; }
捕获列表说明:
捕捉列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式是传值还是传引用。
- [var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var,捕捉,不是传参,捕捉中就不存在取地址这一语法。
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
注意:
① 父作用域指包含lambda函数的语句块。
② 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
③ 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。
④ 在块作用域以外的lambda函数捕捉列表必须为空。
⑤ 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
⑥ lambda表达式之间不能相互赋值,即使看起来类型相同。
简单使用:
int main() { int a, b, c, d, e; a = b = c = d = e = 1; auto f1 = [=]() // 全部传值捕捉 { cout << a << b << c << d << e << endl; }; f1(); auto f2 = [=, &a]() // 混合捕捉 { a++; cout << a << b << c << d << e << endl; }; f2(); return 0; }
2.3 函数对象与lambda表达式
函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象。
class Rate { public: Rate(double rate) : _rate(rate) {} double operator()(double money, int year) { return money * year * _rate; } private: double _rate; }; int main() { // 函数对象 double rate = 0.5; Rate r1(rate); cout << r1(518, 3) << endl; // lambda auto r2 = [=](double money, int year)->double {return money * year * rate;}; cout << r2(518, 3) << endl; return 0; }
使用函数对象和lambda两种方式进行利率计算,执行的函数体内容相同。
- 从使用方式上来看,函数对象和lambda表达式完全一样,如中r1和r2所示。
- rate是函数对象的成员变量,通过构造函数初始化,lambda通过捕获列表来捕获该变量。
调试起来后,查看汇编代码,如上图所示是调用函数对象部分的汇编代码。
- 创建函数对象时,调用了Rate类域中的构造函数。
- 调用函数对象时,调用了Rate类域中的operator()成员函数。
上图所示是lambda表达式部分的汇编代码。
创建lambda表达式时,也是调用了某个类中的构造函数。该类不像函数对象那样明确,而是有很长一串,如上图所示的lambda_0d841c589991fabbf3e571d463f613ab。
调用lambda表达式时,调用的是该类中的operator()成员函数。函数对象和lambda在汇编代码上是一样的,只是类不同而已。函数对象的类名是我们自己定义的。
lambda的类名是编译器自己生成的。
编译器在遇到lambda表达式的时候,会执行一个算法,生成长串数字,而且几乎每次生成的数字都不同,也就意味着每次创建的类名都不同。
lambda表达式的类型只有编译器自己知道,用户是无法知道的。所以要通过auto来推演它的类型,才能接收这个匿名的函数对象。lambda表达式和函数对象其实是一回事,只是lambda表达式的类是由编译器自动生成的。
此时应该就理解了为什么说lambda其实就是一个匿名的函数对象了吧。所以:lambda表达式相互不可以赋值,因为编译器生成的类不一样,也就意味着不是一个类型。
3. 包装器
3.1 function包装器
function包装器:也叫作适配器。
C++中的function本质是一个类模板,也是一个包装器。
为什么需要function呢?:
ret = func(x);
上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lambda表达式对象?所以这些都是可调用的类型,如此丰富的类型,可能会导致模板的效率低下。为什么呢?往下看:
template<class F, class T> T useF(F f, T x) { static int count = 0; cout << "count:" << ++count << endl; cout << "count:" << &count << endl; return f(x); } double f(double i) { return i / 2; } struct Functor { double operator()(double d) { return d / 3; } }; int main() { // 函数名 cout << useF(f, 11.11) << endl; // 函数对象 cout << useF(Functor(), 11.11) << endl; // lambda表达式对象 cout << useF([](double d)->double { return d / 4; }, 11.11) << endl; return 0; }
通过上面的程序验证,发现useF函数模板实例化了三份,这会导致效率低下。
3.1.1 function包装器使用
包装器可以很好的解决上面的问题:
std::function在头文件
类模板原型如下
template function; // undefined
template
class function;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
function包装器使用:
int f(int a, int b) { return a + b; } struct Functor { public: int operator() (int a, int b) { return a + b; } }; class Plus { public: static int plusi(int a, int b) { return a + b; } double plusd(double a, double b) { return a + b; } }; int main() { std::function<int(int, int)> func1 = f; // 函数名(函数指针) cout << func1(1, 2) << endl; std::function<int(int, int)> func2 = Functor(); // 函数对象 cout << func2(1, 2) << endl; std::function<int(int, int)> func3 = [](const int a, const int b) {return a + b; }; // lambda表达式 cout << func3(1, 2) << endl; std::function<int(int, int)> func4 = &Plus::plusi; // 类的静态成员函数 cout << func4(1, 2) << endl; std::function<double(Plus, double, double)> func5 = &Plus::plusd; cout << func5(Plus(), 1.1, 2.2) << endl; // 类的成员函数需要用对象调用 return 0; }
第二份使用代码:
int f(int a, int b) { return a + b; } struct Functor { public: int operator() (int a, int b) { return a + b; } }; int Plus(int a, int b) { return a + b; } int main() { function<int(int, int)> f1 = Plus; function<int(int, int)> f2 = Functor(); function<int(int, int)> f3 = [](int a, int b) { cout << "[](int a, int b) {return a + b;}" << endl; return a + b; }; cout << f1(1, 2) << endl; cout << f2(10, 20) << endl; cout << f3(100, 200) << endl << endl;; map<string, function<int(int, int)>> opFuncMap; // 类模板又做map第二个模板参数,此时就可以声明可调用对象类型 opFuncMap["函数指针"] = f; opFuncMap["仿函数"] = Functor(); opFuncMap["lambda"] = [](int a, int b) { cout << "[](int a, int b) {return a + b;}" << endl; return a + b; }; cout << opFuncMap["函数指针"](1, 2) << endl; cout << opFuncMap["仿函数"](10, 20) << endl; cout << opFuncMap["lambda"](100, 200) << endl; return 0; }
function包装器解决模板的效率低下问题:
template<class F, class T> T useF(F f, T x) { static int count = 0; cout << "count:" << ++count << endl; cout << "count:" << &count << endl; return f(x); } double f(double i) { return i / 2; } struct Functor { double operator()(double d) { return d / 3; } }; int main() { // 函数名 function<double(double)> f1 = f; cout << useF(f1, 11.11) << endl; // 函数对象 function<double(double)> f2 = Functor(); cout << useF(f2, 11.11) << endl; // lambda表达式对象 function<double(double)> f3 = [](double d)->double { return d / 4; }; cout << useF(f3, 11.11) << endl; return 0; }
useF函数模板只实例化了一份,这就提高了效率。
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(下):https://developer.aliyun.com/article/1522416