一.lambda表达式
关于lambda表达式,我之前是详细讲过的,现在我们只来做重点讲解(如果存在疑问可以回看我之前的作品)。
固定格式:
[]()->返回值{};([capture-list] (parameters) mutable -> return-type { statement}
capture-list:可以自写[],表示不传递任何变量到该作用域,当然也可以如下:[capture-list]:不能省流(最基础为[]) ---------------------------------------------------------------- [var]:表示值传递方式捕捉变量var [=]:表示值传递方式捕获所有父作用域中的变量(包括this) [&var]:表示引用传递捕捉变量var [&]:表示引用传递捕捉所有父作用域中的变量(包括this) [this]:表示值传递方式捕捉当前的this指针 ----------------------------------------------------------------- 例子如下: //1.[]不传递任何变量 //2.[=]表示以传值的方式传递作用域外面的所有变量 //3.[&}表示以传引用的方式传递作用域外面的所有变量 //4.[=,&a]表示a变量以传引用传递,其余变量都以传值传递 //5.[&,b]表示a变量以传值传递,其余都以传引用传递 //6.[a,&b]表示a以传值传递,b以传引用传递 //7.[this]传递父作用域中的this指针
(parameters):表示参数,如果不传参可以省略(模仿函数参数写即可)
mutable :这个不常用,相信大家了解不多,其是一个关键字,表示可变的,与const恰好相反,mutable是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。例子如下:
class Test { public: Test(int count=0):_count(count){}; //析构默认 vois print() const { _count++; std::cout<<"hello world! this is "<< _count<< " times" << std::endl; } private: mutable int _count;//记录输出次数 }; //对于该类对象,print函数是被const修饰的,但是我们想要改变_count的值,所以这种情况下就可以使用_mutable 关键字来使被修饰的变量可以改变
-> return-type:->后面加上返回值类型,如果是void,可以直接全部省略
{ statement}:类似函数体,如果不需要写内容,可以直接写{}
总结:
最简单的lambda表达式: []{];//当然没有任何实际意义,大家可以用其理解那些部分不可省略 例子: int main() { int a=10,b=20; auto func1=[=](int c)->int{return a+b-c;};//注意两个“;”,不要忘了函数体中的分号 std::cout<<func1(15)<<std::endl; return 0; }
补充知识:lambda表达式之间不能赋值,即使看起来类型相似(底层学习):
lambda表达式在底层中会被当成函数对象处理(仿函数),以VS为例,在vs底层会将灭一个lambda表达式 生成一个固定的uuid,每一个都不同,uuid就可以看做类名,而lambda表达式的类型在VS中会被认为是 lambda+uuid,因为uuid不同,每一个lambda表达式的类型都是唯一,因此不能相互赋值,即使如下: auto f1 = []{cout << "hello world" << endl; }; auto f2 = []{cout << "hello world" << endl; }; 这两个看起来类型相同的lambda表达式 实际上类型都是唯一的
二.function包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器
我们还是先知道如何使用再来讲为什么需要它?//首先:头文件为: #include <functional> 模版: function<返回值(参数)> 包装器名(函数名等); function<返回值(参数)> 包装器名=函数名等; //主要使用情况如下4种: //1.函数名(函数指针)形式 int sum(int x,int y){return x+y;}; void Test1() { std::function<int(int,int)> func1=sum; std::cout<<func1(1,2)<<std::endl;;//通过func1调用sum函数 //注意点:区分函数指针如何写:返回值类型(*函数指针名)(参数); } //2.函数对象(仿函数) class Functor { public: int operator()(int a,int b) { return a+b; } }; void Test2() { std::function<int(int,int)> func2(&Functor); //等价于:std::function<int(int,int)> func2=Functor(); std::cout<<func2(2,3)<<std::endl; } //3.lambda表达式 void Test3() { std::function<void(const string)> func3=[](const string str){ std::cout<<str<<std::endl;}; func3("hello world"); } //4.类成员函数 class Count { public: double del(double x,double y) { return x/y; } } void Test4() { std::function<double(double,double)> func4=&Count::del; std::cout<<func4(3.2,2.0)<<std::endl; }
当然,对于例四也可以将类名传递,但是要注意类要取地址!!!
下面我们再来讲下为什么要在C++11中加入function包装器呢?
目的是为了解决模版效率低下问题,模版为什么说效率低下,看下面这个语句:
ans=Func1(x);对于该语句Func1可能是函数指针,也可能是仿函数对象,更可能是lambda对象、函数名等等,如此多的类型,模版推演的效率自然就低下了,因此在C++11中引入了包装器function,这样我们通过function包装可以清晰的调用该调用的函数、lambda等,自然效率就高了。
包装器在实际运用时作用非常广泛,大家可以自行了解。
补充知识:
在该部分开始时,我们放出了包含如下语句
template <class Ret, class... Args>
class function<Ret(Args...)>;的照片,现在补充解释下"..."的作用:
1.在class中"...”位于模版名前面,表示可变参数列表
2.在运用Args时在其后加“...”表示对参数的可变化
三.bind1st/bind2nd/bind
上面三个都是绑定器,第一个和第二个是STL中提供的,bind则是C++11引入boost中的内容提出的,下面我们下来学习前两个:
bind1st简单来说就是将第一个参数绑定,这样就可以解决需要两个参数,但是函数形参只有一个的问题,具体例子:
#include <iostream> #include <functional> #include <algorithm> #include <vector> int main() { std::vector<int> arr = { 1,25,7,21,66,10 }; sort(arr.begin(), arr.end(), std::greater<int>());//从大到小排序 for (auto a : arr) { std::cout << a << " "; } std::cout << std::endl; //现在想在插入27 auto iter = find_if(arr.begin(), arr.end(), std::bind1st(std::greater<int>(),27)); std::cout << *iter << std::endl;//找到从大到小的25 //插入27 arr.insert(iter, 27); for (auto a : arr) { std::cout << a << " "; } std::cout << std::endl; return 0; }
在该例子中我们需要注意:
1.bind1st第一个参数通常是函数对象,第二个参数则是T val,通过绑定我们就可以讲greater仿函数所需要的两个参数变成一个,第一个参数是bind1st绑定的值,第二个为arr中值,从而变成找第一个小于27的位置迭代器
2.find_if函数的理解:前两个参数传迭代器,第三个参数可以是仿函数,也可以是bind,这也揭示了其本质上也是一个函数模版
该大体与bind1st相似,主要区别在于greater中第二个参数才是bind绑定的值,第一个是arr中的值,在绑定时写法和上面一样
bind1st(greater<int>(),27); bind2nd(greater<int>(),27); //这里位置一样的,别记错了!!!
大家应该会发现,我们绑定器无论是bind1st,还是bind2nd,都只能处理二元函数(两个参数),对于多元函数是没法处理的,这也是bind提出的原因,下面我们就来进入本文的最后一个话题:bind绑定器
大家一看这张图片就会明白我们之前的补充,可变参数,即bind的参数可变的,对于bind我们可以直接来学习内容,就没必要讲解其他部分(原因等):
首先,bind有一个placeholders,有1-20左右,可以先通过bind绑定placeholders_1等,然后传递实际参数,例子如下:
//注意:头文件件还是<functional> #include <functional> //1.bind绑定函数: int sum(int a,int b) { return a+b; } void Test1() { std::function<int(int,int)> func1=std::bind(sum,std::placeholders::_1,std::placeholders::_2); std::cout<<func1(1,2)<<std::endl;//输出:3 } //2.bind绑定类成员函数: class Functor { public: int sub(int x, int y) { return x - y; } }; void Test2() { std::function<int(int, int)> func2 = std::bind(&Functor::sub, Functor(),std::placeholders::_1, std::placeholders::_2); std::cout << func2(1, 2) << std::endl;//结果是:-1 std::function<int(int, int)> func3 = std::bind(&Functor::sub, Functor(), std::placeholders::_2, std::placeholders::_1); std::cout << func3(1, 2) << std::endl;//交换_1和_2的顺序,结果是:1 } //绑定类成员函数时,第一个参数为&类名::函数,第二个参数为类对象,后面为可变参数
最后,祝大家国庆快乐!!!