C++11中引入的比较常用的新特性讲解(下)
目录
7、新的类功能
8、可变参数模板
8.1、emplace系列的接口和普通的接口的差别
9、lambda表达式
9.1、lambda表达式语法
10、包装器
10.1、function包装器
10.2、bind(绑定)
7、新的类功能
C++11的新东西:移动构造和移动赋值
注:如果你没有自己去实现这两个函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载这三个函数(三个函数通常都是绑定在一起的,要写其中一个,其他两个也需要写),则编译器会自动生成这两个函数。如果你实现了上述的那三个函数中的任意一个,则编译器则不会自动生成移动构造和移动赋值。如果你提供了移动构造或者移动赋值,编译器就不会自动提供拷贝构造和拷贝赋值。
注:默认生成的移动构造/赋值函数,对于内置类型成员会执行逐成员按字节拷贝(值拷贝),对于自定义类型成员,则需要看这个成员是否实现移动构造/赋值,如果实现了就调用移动构造/赋值,没有实现就调用拷贝构造/赋值。
注:编译器是可以同时生成默认的拷贝构造函数和移动构造函数的。前提是你没有显示定义 析构、拷贝构造、拷贝赋值重载、移动构造、移动赋值函数 中的任意一个。
强制生成默认函数的关键字default。
ex:
Person(Person&& p) = default; // 强制生成一个Person类的移动构造
禁止生成默认函数的关键字delete。
ex:
Person(const Person& p) = delete; // 禁止编译器默认生成一个Person类的拷贝构造
注:如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明不定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上 = delete即可,该语法指示编译器不生成对应函数的默认版本,称 = delete修饰的函数为删除函数。
注:delete适合于当要求一个类不希望被拷贝的场景。-- ex:库里的 istream 和 ostream,你用传值传参会报错,因为它内部把拷贝构造给 delete 掉了,不希望流对象被拷贝。
举个栗子:假设要求一个类只能在堆上生成对象。
class HeapOnly
{
public:
static HeapOnly *CreateObj() // 专门写一个函数用于只在堆上生成该类的对象。
// 不加static的话,需要通过创建这个类的对象才能去调用这个函数,但是构造函数是私有的,又没法直接创建对象,需要去调用这个函数,所以...
{
return new HeapOnly;
}
private:
HeapOnly() // 方法一:把这个类的构造函数弄成私有的,这样就没法随便去生成这个类的对象了
{
}
HeapOnly(const HeapOnly &);
int _a = 1;
};
void test8()
{
HeapOnly *p1 = HeapOnly::CreateObj(); // 静态成员函数可以通过类域直接调用
// 但是方法一有漏洞,如果没有把拷贝构造也私有化,那么下面这句代码就能成功运行,然后就成功创建出该类的对象了。
// HeapOnly obj(*p1); // obj是在栈上创建的
// 此时,C++98的做法是把拷贝构造也私有化,即 私有 + 只声明不实现。
// C++11的做法是加了个关键字delete。
}
注:C++11中还新添加了继承和多态中的final与override关键字。
8、可变参数模板
早在C语言中就有可变参数这个概念。
例如Printf函数,去官网上看该函数的文档时会发现它的参数中有一个参数是 ...
C语言中就是用 ... 来代表可变参数的,其底层大概就是用一个数组去存了各种各样的参数,当你传过来一个参数的时候,它内部会自动去这个数组中匹配看是哪种参数。
举个C++中的可变参数模板的栗子:
下面就是一个基本可变参数的函数模板:
Args是一个模板参数包,args是一个函数形参参数包
声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数(类型)。
template <class... Args>
void Example(Args... args) // args:模板参数包Args 定义的 形参对象包
// 注意:在<>里 ... 是在Args前面的,但是在()里 ... 是在Args后面的,两个地方刚好相反,这里的格式要记一下。
{
}
上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N >= 0)个模版参数。
我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数。
这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。
奇招一:递归函数方式展开函数包(编译时,参数推导的递归)
举个栗子:
众所周知,C语言的printf有个致命的缺陷就是,只能打印内置类型,对于自定义类型的打印是没有办法。
那现在来实现一个C++版本的可打印任意类型对象的含可变参数的函数。
// 正确的递归出口
void _Cpp_Printf() // 写个空函数就好
{
cout << endl;
}
// 递归函数
template <class T, class... Args>
void _Cpp_Printf(const T &val, Args... args)
{
// if (sizeof(...args) == 0) return; // 注意:这样写递归出口是不行的,因为这个递归函数走的是编译时的逻辑,但是这里的sizeof走的是运行时,不匹配。
cout << val << endl;
_Cpp_Printf(args...);
}
// 展开函数
template <class... Args>
void Cpp_Printf(Args... args)
{
// 补充:这个args是可以sizeof一下的,通过sizeof可以知道这个args的参数个数
cout << sizeof...(args) << endl;
// 但是:这个args的sizeof的语法设计的很奇怪
// 补充:不支持这样获取参数包中的每个参数
// error C3520: “args”: 必须在此上下文中扩展参数包
/*for (size_t i = 0; i < sizeof...(args); ++i)
{
cout << args[i] << endl;
}
cout << endl;*/
// 开始递归
_Cpp_Printf(args...);
}
奇招二:逗号表达式展开参数包 -- 有点抽象
这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的。
printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。
也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
template <class T>
int PrintArg(T t)
{
cout << t << " ";
return 0;
}
// 展开函数
template <class... Args>
void Cpp_Printf2(Args... args)
{
// 编译时推导,args...有几个参数,PrintArg就调用几次,就有几个返回值,arr就开多大
int arr[] = {PrintArg(args)...};
cout << endl;
}
// 其底层也就是编译器实现的时候,大概是这样的
void Cpp_Printf2(int x, char y, std::string z)
{
int arr[] = {PrintArg(x), PrintArg(y), PrintArg(z)};
cout << endl;
}
注:这个可变参数可以加万能引用&&
例如:
template <class... Args>
void Func(Args &&...args); // 注意:万能引用加的位置
8.1、emplace系列的接口和普通的接口的差别
先说结论:推荐使用emplace系列的接口
void test9()
{
Cpp_Printf(1);
Cpp_Printf(1, 'A');
Cpp_Printf(1, 'A', std::string("sort"));
// 在只传一个参数的时候,emplace系列的接口和普通的接口没什么区别
kk::string s1("kkkkkk");
list<kk::string> lt;
lt.push_back(s1);
lt.push_back(move(s1));
kk::string s2("kkkkkk");
lt.emplace_back(s2);
lt.emplace_back(move(s2));
// 真正的区别在于传复合类型的参数时的场景
list<pair<kk::string, int>> lt2;
pair<kk::string, int> kv1("lll", 1);
lt2.push_back(kv1);
lt2.push_back(move(kv1));
lt2.emplace_back(kv1);
lt2.emplace_back(move(kv1));
// 正常情况下也不会这样去用emplace系列的接口,不然就还是跟普通接口还是没什么区别
// 而是这样写
lt2.emplace_back("lll", 1); // 这才是emplace系列接口的真正用处
// 对于普通的接口,在这种场景下,只能先去构造一个pair类型的对象,再进行传参
// 但是emplace系列的接口支持可变参数,你可以直接给它传构建pair对象所需的参数,然后它自己去创建对象
// 补充:
// 上面的push_back(move(kv1))需要进行 一次构造 + 一次移动构造
// 而这里的emplace_back("lll", 1)只进行了一次构造
// 但是:移动构造的消耗并不大,所以要记住emplace系列的接口在效率上并不比普通接口高多少。
// 网上有人说:emplace系列的接口的效率比普通接口更高,这种说法并不完全正确,
// 补充:直接 emplace 或者 push/insert (有移动构造的)右值对象 -> 构造 + 移动构造
// 直接 emplace 参数包 -> 构造
// 在有移动构造的深拷贝的对象中,两种接口的效率差别不大。
// 因为移动构造的代价很小。
// 直接 emplace 或者 push/insert 左值对象/浅拷贝的右值对象 -> 构造 + 拷贝构造
// 直接 emplace 参数包 -> 构造
// 这种情况下用普通接口的代价就大了很多。
// 因为拷贝构造的代价很大。
// 注:在没有移动构造的浅拷贝的对象中(只有拷贝构造),两者的效率差别也很大,emplace系列的接口在这种情况下更优。
// 注:可以去看list的模拟实现,在那里模拟实现了一个emplace的接口
// 总结:emplace系列的接口的特点是:它们可以做到直接构造,而普通接口只能做到先构造再拷贝构造。
// 所以:以后在使用容器插入接口(push/insert)的时候,推荐使用emplace系列的,其次emplace能用参数包就用参数包。
// 补充:单参数也可以用参数包
list<string> lt3;
lt3.emplace_back("kkkk"); // 这里是直接构造
lt3.emplace_back(string("kkkk")); // 这里是 构造 + 移动构造
}
总结:可变参数模板是个伟大的设计,比如当你在定义一个函数接口时,如果想要这个函数可以接收任意多个参数,就可以选择把该接口写成一个可变参数的函数模板。
9、lambda表达式
在实际生活中,大部分情况下,只进行 int 和 int 之间的比较是没有什么意义的,一般都是进行 自定义类型 和 自定义类型 之间的比较。
但是一般实际中,两个 自定义类型 的比较可能需要考虑很多因素。
比如下面的自定义类型商品,可能需要你按价格比较,也可能需要你按评价去比较。
这时候,在C++11出来之前你只能去写很多个仿函数,给每个比较因素都专门写一个仿函数去实现比较逻辑。
后来觉得这样写太麻烦了,于是就有了lambda表达式。
补充:lambda表达式就像一个匿名函数对象(就是仿函数),本质是一个特殊的,匿名的类类型。它是一个带有operator()的类,即仿函数。
9.1、lambda表达式语法
lambda表达式书写格式:[capture - list](parameters) mutable -> return-type{ statement }
1. lambda表达式各部分说明
[capture - list]:捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
注:捕捉列表不能被省略。
(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
注:默认情况下,mutable 可以省略
->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{ statement }:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。
因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
// ...
Goods(const char *str, double price, int evaluate)
: _name(str), _price(price), _evaluate(evaluate)
{
}
};
struct ComparePriceLess
{
bool operator()(const Goods &gl, const Goods &gr)
{
return gl._price < gr._price;
}
};
struct ComparePriceGreater
{
bool operator()(const Goods &gl, const Goods &gr)
{
return gl._price > gr._price;
}
};
void test10()
{
vector<Goods> v = {{"苹果", 2.1, 5}, {"香蕉", 3, 4}, {"橙子", 2.2, 3}, {"菠萝", 1.5, 4}};
// 没有 lambda 之前,对 v 的排序方法:
// sort(v.begin(), v.end(), ComparePriceLess());
// sort(v.begin(), v.end(), ComparePriceGreater());
// lambda 使用举例
// 一个 Add 的 lambda 函数
[](int a, int b) -> int
{ return a + b; };
// 那么该如何使用这个函数呢?(这个函数是匿名的,没有名字)
auto add1 = [](int a, int b) -> int
{ return a + b; }; // 方法一:可以用auto
// 因为严格来说lanmda表达式的类型,你不知,我不知,只有编译器知道
cout << add1(1, 2) << endl;
cout << typeid(add1).name() << endl; // 想看lambda表达式到底是什么类型,也可以用typeid看,然后会发现看不懂
auto add2 = [](int a, int b) -> int
{ return a + b; };
cout << typeid(add2).name() << endl;
// 注:即使两个lambda表达式写的一模一样,它们两个的类型也不一样。
// 原因:lambda表达式的原理类似于范围for,lambda编译时,编译器会生成对应的仿函数,上面的typeid的结果就是这个仿函数的名称,
// 编译器为了避免生成重复的名称(uuid),所以才会导致,即使两个lambda表达式写的一模一样,你去typeid出来的结果也不一样。
// 注:通过汇编层面可以发现使用lambda表达式的汇编指令,跟使用仿函数的汇编指令是一样的。
// 返回值明确的情况下,返回值可以省略,编译器会自动推导
auto add3 = [](int a, int b)
{ return a + b; };
// 没有参数,参数列表也可以省略
auto func1 = []
{ cout << "hello world" << endl; };
func1();
// 有了lambda表达式之后,上面的商品的sort就可以这样写
auto priceLess = [](const Goods &gl, const Goods &gr) -> bool
{ return gl._price < gr._price; }; // 价格的升序
sort(v.begin(), v.end(), priceLess);
// 更简洁一点的写法
sort(v.begin(), v.end(), [](const Goods &gl, const Goods &gr) -> bool // 价格的降序
{ return gl._price > gr._price; });
sort(v.begin(), v.end(), [](const Goods &gl, const Goods &gr) -> bool // 评价的升序
{ return gl._evaluate < gr._evaluate; });
sort(v.begin(), v.end(), [](const Goods &gl, const Goods &gr) -> bool // 评价的降序
{ return gl._evaluate > gr._evaluate; });
// 捕获列表说明 -- 注:被捕捉过来的对象就相当于被当成了一个类的成员变量使用
int a = 1, b = 2;
auto swap1 = [](int &x, int &y)
{
int tmp = x;
x = y;
y = tmp;
};
swap1(a, b);
// 捕捉列表可以捕捉当前局部域的对象
auto swap2 = [a, b]() mutable // 可以不传参数,捕捉a,b对象给lambda用
// 注意:这里是传值捕捉,所以这里的a、b的修改并不影响上面的a、b
// 传值捕捉过来的对象具有const属性
// 要想修改传值捕捉过来的对象,就需要加个mutable去除掉其const属性
// 因为这里的a、b是拷贝过来的,即使修改也不改变外面的a、b,所以使用mutable的场景不多
// 有没有传值捕捉,都可以加mutable,只是说在没有传值捕捉的时候,mutable就不起作用了。
{
int tmp = a;
a = b;
b = tmp;
};
swap2();
// 引用方式捕捉(这里有点坑,让人不好区分这里的 & 到底是 取地址 还是 引用)
auto swap3 = [&a, &b]()
{
int tmp = a;
a = b;
b = tmp;
};
swap3();
// 注意:不能直接捕捉a、b的地址,但是可以间接捕捉
int *pa = &a, *pb = &b;
auto swap4 = [pa, pb]()
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
};
swap4();
// 不过,这没有什么意义,有引用方式的捕捉何必多此一举,简直神经
// 以值传递方式捕捉当前作用域中的所有变量(包括this)
int q = 1, w = 2, e = 3, r = 4, t = 5;
auto func2 = [=]()
{
return q + w + e + r * t;
};
cout << func2() << endl;
// 以引用传递捕捉当前作用域中的所有变量(包括this)
auto func3 = [&]()
{
++a;
++b;
++q;
++w;
};
// 混合捕捉
auto func4 = [&a, &b, q, w]()
{
++a;
++b;
// ++q; // 这里 q 没法++,因为是传值捕捉的q,在不加mutable的情况下,传值捕捉过来的对象具有const属性
};
}
捕捉列表总结:
捕捉列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式传值还是传引用。
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]:表示值传递方式捕捉当前的this指针
注意:
a.父作用域指包含lambda函数的语句块
b.语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他所有变量
c.捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]: = 已经以值传递方式捕捉了所有变量,捕捉a重复。
d.在块作用域以外的lambda函数捕捉列表必须为空。
e.在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
f.lambda表达式之间不能相互赋值,即使看起来类型相同。
补充:函数对象(仿函数)与lambda表达式
- 从使用方式上来看,函数对象(仿函数)与lambda表达式完全一样。
- 实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象(仿函数)的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
总结:lambda表达式适合用于当前需要一个小函数的场景,需要一个大函数的时候就不适用了。
10、包装器
10.1、function包装器
注:目的就是为了做到类型统一。
function包装器,也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
补充一个概念:可调用对象,包括:
- 函数指针(类型定义复杂)
- 仿函数对象(要定义一个类,用的时候有点麻烦,且不适合做类型统一)
- lambda(从用的角度上,没有类型概念)
那么我们来看看,我们为什么需要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;
}
};
void test11()
{
// 函数名
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
}
通过上面的程序验证,我们会发现useF函数模板实例化了三份。(count的地址每次打印出来的都不一样)
包装器可以很好的解决上面的问题。
注:std::function在头文件<functional>
类模板原型如下:
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret : 被调用函数的返回类型
Args…:被调用函数的形参
注:通过查看function的文档可知,function的底层是一个仿函数,因为它是一个重载了operator()的类。
#include <functional>
// 注:function不是用来定义可调用对象的,是用来包装可调用对象的。
int f1(int a, int b)
{
return a + b;
}
struct Functor1
{
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;
}
};
void test12()
{
// 先介绍一下function的用法
function<int(int, int)> fcn; // 注意:这里的格式跟以前定义一个类模板对象的格式不太一样
// 还可以这样写
function<int(int, int)> fcn1(f1);
function<int(int, int)> fcn2 = f1;
function<int(int, int)> fcn3 = Functor1();
function<int(int, int)> fcn4 = [](int x, int y)
{ return x + y; };
cout << fcn1(1, 2) << endl;
cout << fcn1.operator()(1, 2) << endl;
cout << fcn2(1, 2) << endl;
cout << fcn3(1, 2) << endl;
// 包装器就能很好的解决上面的问题(test11)(包装器能够统一类型)
// 函数名
std::function<double(double)> func1 = f;
cout << useF(func1, 11.11) << endl;
// 函数对象
std::function<double(double)> func2 = Functor();
cout << useF(func2, 11.11) << endl;
// lambda表达式
std::function<double(double)> func3 = [](double d) -> double
{ return d / 4; };
cout << useF(func3, 11.11) << endl;
// 此时,count的地址每次打印出来的都是一样的。
// 包装类的成员函数时,格式:&类名::函数名
function<int(int, int)> fc1 = &Plus::plusi; // 静态成员函数这样包装就行了(可以不加&,但建议加上,这样好记一点)
// 静态成员函数是没有this指针的
cout << fc1(1, 2) << endl;
function<double(Plus *, double, double)> fc2 = &Plus::plusd; // 非静态成员函数要这样包装(规定必须要加一个&)
// 注意:参数要匹配,普通的成员函数前面还有一个this指针
// 注意:如果function(fc2)是这样写的,则这里不能用匿名对象,匿名对象是右值,没有办法取地址&
Plus plus;
cout << fc2(&plus, 1.1, 2.2) << endl;
function<double(Plus, double, double)> fc3 = &Plus::plusd;
// 如果function(fc3)这样写的,这里就可以用匿名对象。
cout << fc3(Plus(), 1.1, 2.2) << endl;
// 注:这里并不是直接把这几个参数传给对应的成员函数,首先,使用成员函数时就不能显示传递参数给 this 。
// 原因:这里不论是传匿名对象,还是对象指针,其底层都并不是直接将其传给this指针的,而是用这个匿名对象或者函数指针去调用那个成员函数。
}
10.2、bind(绑定)
std::bind函数定义在头文件中,是一个函数模板。
它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。-- 包装一个可调用对象和它的参数
一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。
同时,使用std::bind函数还可以实现参数顺序调整等操作。
原型如下:
template <class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
注:unspecified -- 不确定的,不明确的
功能:用于调整可调用对象的参数个数和参数顺序的。
注:调用bind的一般形式:auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。
注:arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”。
int sub(int a, int b)
{
return a - b;
}
void test_bind1()
{
auto f1 = sub;
// cout << typeid(f1).name() << endl; // f1的类型是int(*)(int,int)
cout << f1(10, 5) << endl;
// 功能一:调整参数顺序(没啥用,了解即可)
auto f2 = bind(sub, placeholders::_2, placeholders::_1); // _1永远代表你传的第一个参数,_2永远代表你传的的第二个参数
cout << f2(10, 5) << endl; // 调整顺序的原理:10传给了_1,5传给了_2,然后_1传给了b,_2传给了a
cout << typeid(f2).name() << endl; // 尝试查看一些bind的返回值是什么类型,发现看不懂,不过可以知道的是bind的本质也是一个仿函数,因为它也重载了 operator()
}
class Sub
{
public:
Sub(int x)
: _x(x)
{
}
int sub(int a, int b)
{
return (a - b) * _x;
}
private:
int _x;
};
void test_bind2()
{
// 功能二:调整参数个数(实际就是调整你需要传参的个数)
auto f3 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3); // 有个参数对应的是 this 指针
cout << f3(Sub(1), 10, 5) << endl;
Sub sub1(1);
cout << f3(&sub1, 10, 5) << endl;
auto f4 = bind(&Sub::sub, Sub(1), placeholders::_1, placeholders::_2);
cout << f4(10, 5) << endl;
auto f5 = bind(&Sub::sub, &sub1, placeholders::_1, placeholders::_2);
cout << f5(10, 5) << endl;
auto f6 = bind(&Sub::sub, &sub1, placeholders::_1, 5); // 这样写,就相当于把第三个参数写死了,不能再给第三个参数传东西了
cout << f6(10) << endl;
auto f7 = bind(&Sub::sub, &sub1, 10, 5); // 你甚至可以把所有的参数都用bind绑死,这样你传参的时候可以一个都不用传
cout << f7() << endl;
// 注:这里和 function 那里是一样的,不论是传匿名对象,还是对象指针,其底层都并不是直接将其传给this指针的,而是用这个匿名对象或者函数指针去调用那个成员函数。
}
void fx(const string &name, int x, int y)
{
cout << name << " " << "血量:" << x << " " << " 蓝量:" << y << endl;
}
void test_bind3()
{
// 这里来个贴近实际的栗子
// 以前使用fx函数都是这样使用的
fx("卤蛋", 3450, 666);
fx("卤蛋", 3568, 456);
fx("卤蛋", 3456, 334);
fx("卤蛋", 3984, 865);
// 会发现你一个参数实际上都是固定的,没有变
// 所以,可以现在可以这样写
auto f8 = bind(fx, "卤蛋", placeholders::_1, placeholders::_2); // 专门弄一个函数f8给"卤蛋"用
f8(3450, 666);
f8(3568, 456);
f8(3456, 334);
f8(3984, 865);
// 还可以只绑死中间的某一个参数
auto f9 = bind(fx, placeholders::_1, 5000, placeholders::_2); // 开了锁血挂
f9("monkey", 600);
f9("monkey", 624);
f9("monkey", 646);
f9("monkey", 656);
}
void test_bind4()
{
// 补充:
// 只要是可调用对象都可以用 bind 去绑定
auto f10 = bind([](int a, int b)
{ return a - b; }, placeholders::_1, placeholders::_2);
cout << f10(1, 2) << endl;
// bind也可以传给function
function<void(string, int, int)> f11 = bind(fx, placeholders::_1, 5000, placeholders::_2);
// 注意:传给了function之后,typeid出来的就是function类型了
cout << typeid(f11).name() << endl;
// 实际应用中bind还可以用来解决这种问题
map<string, function<int(int, int)>> opFuncMap = {
{"+", [](int a, int b)
{ return a + b; }},
// {"-",&Sub::sub}, // 这里是编译不过去的,因为sub是普通的成员函数,有个this指针,你这样写会导致参数不匹配
// 在这种情况中,你也不能去修改上面的fucntion的参数个数,你改了其他的就不匹配了,所以这时候bind就可以派上用场了
{"-", bind(&Sub::sub, Sub(10), placeholders::_1, placeholders::_2)},
{"*", [](int a, int b)
{ return a * b; }},
{"/", [](int a, int b)
{ return a / b; }} };
}
// 这里最后介绍一下 bind 原型中的 class Ret的作用(了解即可,很少用)
template <class T>
void fy(int n)
{
T *p = new T[n];
}
double my_divide(double a, double b)
{
return a / b;
}
// 请问:这里的fy函数要怎么调用
void test_fy()
{
// fy(10); // 这样调肯定是调不动的
fy<int>(10); // 只能显示实例化
// bind 中的 class Ret 的作用就是让你可以去显示实例化指定 这个绑定的可调用对象的 返回值
auto func = bind<int>(my_divide, placeholders::_1, placeholders::_2);
// my_divide 默认的返回值的类型是 double,但经过现在这么一 bind ,类型就变成了 int 。
cout << func(10, 3) << endl;
}