1. 前言
C++11新增了lambda表达式来解决
特定场景下使用仿函数很麻烦的问题
而function包装器则将C语言中复杂的
函数指针问题给简单化了!
本章重点:
本篇文章着重讲解lambda表达式
的语法使用方法和实用场景以及
function包装器的语法使用以及如何
用包装器一次性搞定函数指针,仿函数
和lambda表达式,最后简单讲解关键字
decltype的使用方法和可变模板参数
2. lambda表达式的提出
在C++98中,对自定义类型进行排序时,
需要自己写仿函数,并传递给sort库函数
但是如果每次要按照自定义类型的不同
成员变量进行排序的话,就要写很多个仿
函数,十分的不方便,C++11给出了一个新玩法:
struct Goods { string _name; // 名字 double _price; // 价格 int _evaluate; // 评价 Goods(const char* str, double price, int evaluate) :_name(str) , _price(price) , _evaluate(evaluate) {} }; vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } }; sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool {return g1._price < g2._price; });//按照价格升序 sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool {return g1._price > g2._price; });//按照价格降序 sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool {return g1._evaluate < g2._evaluate; });//按照评价升序 sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool {return g1._evaluate > g2._evaluate; });//按照评价降序
看不懂没关系,现在你只需要知道这种
写法可以代替完美去写仿函数即可.
它的大概意思请看下图:
3. lambda表达式的语法
书写格式:
A=捕捉列表, B=参数列表, C=返回值
`[A] (B)-> C {函数体}
lambda表达式各部分说明:
- 捕捉列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda表达式,
捕捉列表能够捕捉上下文中的变量供lambda函数使用
- 参数列表,与普通函数的参数列表一致,如果不需要传递参数,则可以连同()一起省略
- 返回值类型,没有返回值时此部分可省略,返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
- 函数体,在该函数体内,除了可以使用其参数外,还能使用捕捉列表中的变量
- 参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空,
因此C++11中最简单的lambda表达式为:[]{}
;,该lambda表达式不能做任何事情。
事实上,可以把lambda表达式看作一个
class类,被捕捉或定义的变量可以看作
是类中的成员变量,但是lambda表达式
有一个特性是它默认有const属性,相当于
这个类的所有成员变量都是const修饰的
,无法被修改,要想修改它,要在返回值前加
上一个关键字:mutable
利用lambda表达式实现一个swap函数:
int x = 3; int y = 5; auto myswap = [](int& x,int&y)mutable->void { int tmp = x; x = y; y = tmp; }; myswap(x,y);
实际上lambda表达式的返回值是一个函数对象
在sort中传参正是要一个函数对象,而在这里需要
的是用函数对象来充当一个函数,也就是使用(x,y)
4. lambda表达式的捕捉列表
lambda表达式的捕捉列表[ ]可以
捕捉父作用域的变量供自己使用
下面请看它的捕捉规则:
首先捕捉分为值捕捉和引用捕捉
int a = 10; char* b = "xxxxxxxxxxx" vector<double> v{1.11,2.22}; auto it = [a,&b,c]()->bool{return b+="abcd";}; //以值传递的方式捕捉a和c,引用捕捉b
其实可以发现,lambda表达式的使用
方法和仿函数及其相似实际在底层
编译器对于lambda表达式的处理方式
完全就是按照函数对象的方式处理的
即:如果定义了一个lambda表达式,
编译器会自动生成一个类,
在该类中重载了operator()
5. function包装器
C++中的function本质是一个类模板
也是一个包装器,请看下面的代码:
ret = func(x); // 上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)? //也有可能是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型 //可能会导致模板的效率低下! 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; // lamber表达式 cout << useF([](double d)->double{ return d/4; }, 11.11) << endl; return 0; }
我们会发现useF函数模板实例化了三份
但是不管func是什么东西,都可以用
function来定义,这也就提高了效率
function包装器的使用方法:
第一个int代表返回值类型
括号里面用逗号分割的是参数类型
6. function包装器使用场景
function的使用场景非常多,博主
结合了自己学习操作系统的编码
经验来给大家做几个分享:
创建线程时用function:
在Linux下创建线程时,我们使用
pthread_create函数时要传入此
线程要调用的线程函数对象,这里
配合function使用起来非常方便
pthread_t tid; pthread_create(&tid,nullptr,[](void* args)->void* { //函数体 },nullptr);
线程池内部的处理方法用function
在编写线程池时,每一个线程被创建
出来可能会执行不同的任务,也就是
执行不同的函数,但所有函数的参数
与返回值都一样,这是就可以使用一个
数组保存函数方法,而数组中的元素
类型就是function定义的对象类型!
//func_t是一种函数类型,此类型的函数的返回值和参数都是int typedef function<int(int,int)> func_t; //将不同的函数方法插入到数组中,使用时去数组找! vector<func_t> Task;
这里旨在告诉大家,function的使用场景
很多,即使你现在还没有接触过它,你也
应该掌握它!!!
7. decltype关键字用法
关键字decltype可以将变量的
类型声明为表达式指定的类型
使用场景以及用法:
// decltype的一些使用使用场景 template<class T1, class T2> void F(T1 t1, T2 t2) { decltype(t1 * t2) ret; cout << typeid(ret).name() << endl; } int main() { const int x = 1; double y = 2.2; decltype(x * y) ret; // ret的类型是double decltype(&x) p; // p的类型是int* cout << typeid(ret).name() << endl; cout << typeid(p).name() << endl; F(1, 'a'); return 0; }
你可能会觉得decltype关键字很鸡肋
因为有auto可以自动推导类型了,还要
decltype干啥?不错!auto固然好用,但是
有些场景下你想要一个具体的类型时,
比如vector的元素类型时,你不能用auto
decltype([](int x)->int{return 2*x+10;}) it;
8. 可变参数模板讲解
下面是一个基本可变参数的函数模板
// Args是一个模板参数包,args是一个函数形参参数包 // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。 template <class ...Args> void ShowList(Args... args) {}
上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含0~N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值
递归函数的方式展开参数包:
// 递归终止函数 template <class T> void ShowList(const T& t) { cout << t << endl; } // 展开函数 template <class T, class ...Args> void ShowList(T value, Args... args) { cout << value <<" "; ShowList(args...); } int main() { ShowList(1); ShowList(1, 'A'); ShowList(1, 'A', std::string("sort")); return 0; }
对于可变模板参数的认知到这儿就差不多了
这属于是了解认知的范畴,下次看见了不会懵逼
9. 总结
本篇文章介绍了两个C++11十分
常用的内容,lambda表达式和包装器
function是需要同学们掌握并且能熟练
编写的,后面的decltype和可变模板参数
属于了解内容,保证你下次看见这个的
时候不会懵逼
🔎 下期预告:智能指针详解🔍