1.Lambad
1.1 语法及其用法
lambad表达式:[捕捉列表](参数列表)mutable->返回值类型{函数体}
捕捉列表:该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
参数列表:与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
返回值类型:用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。(连同->一起省略)
函数体:在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
lambad最具特点的地方当属捕捉列表。
int a = 0;
int b = 1;
//最简单的函数
auto fun1 = [] {};
auto fun2 = [](int x, int y) {return x + y; };
auto fun3 = [](int x, int y)->int {return x + y; };
auto fun4 = [a, b] (int& x){ x = a + b; };
// //mutable 只是让传值捕捉变量const属性去掉了
auto Swap2 = [a, b]()mutable{
int tmp = a;
a = b;
b = tmp;
};
// 用引用的方式捕捉
auto Swap2 = [&a, &b]{
int tmp = a;
a = b;
b = tmp;
};
//通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调
//用,如果想要直接调用,可借助auto将其赋值给一个变量
1.2 lambad捕获列表
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
父作用域指的是lambad函数的语句块,捕捉列表在语法上支持多个捕捉项(例下,标识符=&要写在最前面,不能写在后面),不允许变量重复传递。
int c =2, d=3, e=4, f=5, g=6, ret;
// 传值捕捉全部对象
auto Func1 = [=]{
return c + d*e / f + g;
};
cout << Func1() << endl;
// 传引用捕捉全部对象
auto Func2 = [&]{
ret = c + d*e / f + g;
};
Func2();
cout << ret << endl;
// 混着捕捉
auto Func3 = [c, d, &ret]{
ret = c + d;
};
Func3();
cout << ret << endl;
// ret传引用捕捉 其他全部传值捕捉
auto Func4 = [=, &ret]{
ret = c + d*e / f + g;
//c = 1;
};
1.3 lambad底层
lambad不支持相互赋值,即使看起来类型相同
这是因为lambad的实现底层是仿函数,每个lambad函数都是不一样的。仿函数名称是lambad+uuid,uuid的生成是唯一的!
可以通过反汇编来查看。
1.4 lambad的好处
C++11之前,每次为了实现一个algorithm算法, 都要重新去写一个类(仿函数),如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名, 这些都给编程者带来了极大的不便。而lambad表达式的出现极大地方便了方法的编写。auto fun实际上就是一个对象。
2. 包装器
2.1 包装器的引入
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;
}
};
auto fun2 = [] (double a){return 6.0 / 1.2; };
int main()
{
useF(f, 1.0);
useF(Functor(), 1.0);
useF(fun2, 1.0);
}
通过上面的程序验证,我们会发现useF函数模板实例化了三份。
由此引入包装器,function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
std::function在头文件 类模板原型如下:
template <class Ret,class ... Args>
class function<Ret(Args...)>;
模板参数说明: Ret: 被调用函数的返回类型, Args…:被调用函数的形参
2.2 使用方法
template<class F, class T>
T useF(F f, T x, T y)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x,y);
}
int f(int x, int y)
{
return x + y;
}
struct Functor
{
int operator()(int x, int y)
{
return x + y;
}
};
class wzz
{
public:
static int pluss(int x, int y)
{
return x + y;
}
int plusi(int x, int y)
{
return x + y;
}
};
int main()
{
int a = 0;
int b = 1;
//普通函数包装
function<int(int, int)> f1 = f;
cout << f1(1, 2)<<endl;
//仿函数包装
function<int(int, int)> f2 = Functor();
cout << f2(1, 2) << endl;
//成员函数 静态包装,&可加可不加
function<int(int, int)> f3 = &wzz::pluss;
cout << f3(1, 2) << endl;
//成员函数 非静态包装,有this指针!而且包装plusi时必须取地址加&
function<int(wzz, int, int)> f4 = &wzz::plusi;
cout << f4(wzz(),1, 2) << endl;
//lambad函数对象静态包装
function<int(int, int)> f5 = [](int x, int y) {return x + y; };//必须要有足够
//的参数
cout << f5(1, 2) << endl;
}
//用以下代码验证使用fuction后,useF函数模板只实例化了一份
int main()
{
int a = 0;
int b = 1;
//普通函数包装
function<int(int, int)> f1 = f;
useF(f1, 2, 2);
//仿函数包装
function<int(int, int)> f2 = Functor();
useF(f2, 2, 2);
//成员函数 静态包装,&可加可不加
function<int(int, int)> f3 = &wzz::pluss;
useF(f3, 2, 2);
//成员函数 非静态包装,有this指针!而且包装plusi时必须取地址加&
function<int(wzz, int, int)> f4 = &wzz::plusi;
//lambad函数对象静态包装
function<int(int, int)> f5 = [](int x, int y) {return x + y; };//必须要有足够的参数
useF(f5, 2, 2);
}
但是我们发现如果对非静态成员函数包装时,它的参数是会多出一个对象的,和其他的函数参数不一致,该怎么变一致?接下来介绍bind。
3.bind
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可 调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而 言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M 可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺 序调整等操作。
// 原型如下:
template <class Fn, class... Args>
bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
bind (Fn&& fn, Args&&... args);
可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对
象来“适应”原对象的参数列表。
调用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为第二个参数,以此类推。
#include <functional>
int Plus(int a, int b)
{
return a + b;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1,
placeholders::_2);
//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
//func2的类型为 function<void(int, int, int)> 与func1类型一样
//表示绑定函数 plus 的第一,二为: 1, 2
auto func2 = std::bind(Plus, 1, 2);
cout << func1(1, 2) << endl;
cout << func2() << endl;
Sub s;
// 绑定成员函数
std::function<int(int, int)> func3 = std::bind(&Sub::sub, s,
placeholders::_1, placeholders::_2);
// 参数调换顺序
std::function<int(int, int)> func4 = std::bind(&Sub::sub, s,
placeholders::_2, placeholders::_1);
cout << func3(1, 2) << endl;
cout << func4(1, 2) << endl;
return 0;
}