【C++进阶】之C++11的简单介绍(三)
📃博客主页: 小镇敲码人
💚代码仓库,欢迎访问
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞
【C++进阶】之C++11的简单介绍(三)
- lambda表达式
- 为什么要有lambda表达式
- lambda表达式解决上述问题
- lambda表达式的语法
- lambda表达式中值捕获与引用捕获的区别
- lambda表达式在类函数作用域中捕获this对象(非静态函数)
- 深入理解值捕获lambda的常性与类里面的常量成员函数的区别
- 再谈类和对象中的this指针
- lambda表达式的底层原理
- 不同的lambda表达式不能互相赋值
- 包装器
- 包装器引入的初衷
- 包装器的语法
- 包装器介绍
- 包装器包装类成员函数的不同方式
- 使用包装器解决上述问题
- 包装器的其它应用
- 绑定
- 绑定修改可调用对象的参数个数
- 绑定修改可调用对象的参数的调用顺序
lambda表达式
为什么要有lambda表达式
库里面有一个排序函数sort
,它的接口如下:
前两个参数是要排序的容器的起始迭代器,最后面一个是比较规则,它是一个实例化后的对象,可以是类对象也可以是函数指针。
如果我们要排的是内置类型:
#include<algorithm>
#include<vector>
#include<iostream>
#include<time.h>
using namespace std;
template<class T>
void print(T& v)
{
for (auto& num : v)
cout << num << " ";
cout << endl;
}
int main()
{
srand(time(NULL));
vector<int> v;
for (int i = 0; i < 10; ++i)
v.emplace_back(i);
sort(v.begin(), v.end(), greater<int>());
print(v);
return 0;
}
运行结果:
对内置类型排序可以使用类里面的比较函数,传greater<T>
对象就是降序,传less<T>
对象就是升序,但是加入我们要对日期类排序呢?就必须自己写一个仿函数或者函数指针对象传进去了。
#include<algorithm>
#include<vector>
#include<iostream>
#include<time.h>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
//cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
//cout << "Date(const Date& d)" << endl;
}
friend ostream& operator<<(ostream& is, const Date& date);
friend int compare(const Date& a, const Date& b);
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
ostream& operator<<(ostream& os, const Date& date)
{
os << date._year << " " << date._month << " " << date._day;
return os;
}
template<class T>
void print(T& v)
{
for (auto& num : v)
cout << num << endl;
}
int compare(const Date& a, const Date& b)
{
return a._year > b._year || (a._year == b._year && a._month > b._month) || (a._year == b._year && a._month == b._month && a._day > b._day);
}
int main()
{
srand(time(NULL));
vector<Date> v;
for (int i = 0; i < 10; ++i)
v.emplace_back(i+rand(),rand()%13,rand()%31);
print(v);
cout << "-----------------------------------------" << endl;
sort(v.begin(), v.end(), compare);
print(v);
return 0;
}
运行结果:
也可以创建一个仿函数对象传进去。
这样做太复杂了,我们只需要实现一个排序的功能,就要去写一个函数或者是一个类,如果每次排序逻辑不一样,岂不是每次都要多写一个类,这给程序员们带来了极大的不便,所以lambda
表达式出现了。
lambda表达式解决上述问题
#include<algorithm>
#include<vector>
#include<iostream>
#include<time.h>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
//cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
//cout << "Date(const Date& d)" << endl;
}
friend ostream& operator<<(ostream& is, const Date& date);
friend int compare(const Date& a, const Date& b);
public:
int _year = 1;
int _month = 1;
int _day = 1;
};
ostream& operator<<(ostream& os, const Date& date)
{
os << date._year << " " << date._month << " " << date._day;
return os;
}
template<class T>
void print(T& v)
{
for (auto& num : v)
cout << num << endl;
}
int main()
{
srand(time(NULL));
vector<Date> v;
for (int i = 0; i < 10; ++i)
v.emplace_back(i+rand(),rand()%13,rand()%31);
print(v);
cout << "-----------------------------------------" << endl;
sort(v.begin(), v.end(),[](const Date& a,const Date& b)
{
return a._year > b._year || (a._year == b._year && a._month > b._month) || (a._year == b._year && a._month == b._month && a._day > b._day);
}
);
print(v);
return 0;
}
运行结果:
lambda表达式的语法
从上面的代码可以看出
lambda
表达式是一个匿名函数,下面让我们具体学习它的语法:
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
[capture-list]
:捕捉列表,该表达式总是出现在开始位置,编译器通过捕捉列表判断接下来的代码是否是lambda
表达式,捕捉列表能够捕捉上下文的变量供lambda
函数使用。(同一作用域函数类的变量或者是全局的变量或类型)。- (
parameters
):参数列表,该表达式内需想一般函数一样写出lambda函数
的形参列表。如果是无参函数,可以和()
一起省略。mutable
:在lambda
表达式中,如果捕获列表中的变量是值捕获的,并且你希望在lambda
函数体内修改这些变量的值,你需要在lambda
定义时加上mutable
关键字。这允许lambda
函数修改其捕获的变量。->returntyp
没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
导。{statement}
:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
lambda表达式中值捕获与引用捕获的区别
- 值捕获
当你在
lambda
表达式中值捕获了一个变量,这个函数是默认具有常性的,也就是你值捕获的变量都不可修改,带有const
修饰符,这样的设计的初衷可能是认为既然你值捕获了一个变量,肯定是只希望准确的使用它,所以修改是无意义的(而且修改后可能会导致我们的结果出现错误),所以lambda
表达式默认给它带const
属性。
1. 值捕获是对原变量的拷贝(地址不同):
#include<iostream>
using namespace std;
int main()
{
int b = 0;
cout << &b << endl;
auto Func = [b]
{
cout << &b << endl;
const int& b_ = b;
};
Func();
return 0;
}
运行结果:
const int
是它的类型,如果你把引用变量的类型的const
属性丢掉,就无法对其引用:
- 添加
mutable
修饰符,去掉值捕获变量的const
属性,添加mutable
后参数列表的()
不能省略,即使它的参数为空:
#include<iostream>
using namespace std;
int main()
{
int b = 0;
cout << &b << endl;
auto Func = [b] () mutable
{
b = 3;
cout << &b << endl;
};
Func();
cout << b << endl;
return 0;
}
这里添加了mutable
修饰符后我们对lambda
函数中捕获的b
可以修改了,但是由于是值捕获,并无法影响外面。
lambda
值捕获一个变量后,类型和外面的被捕获变量保持一致,即使是const
修饰符也会一致(&
不会)。
2.引用捕获
引用捕获的变量,在
lambda
表达式中,不会给它加上const
属性,所以mutable
对引用捕获的变量无影响,因为我们本意就是想修改它们,如果默认加上const
属性就无意义了。
#include<iostream>
using namespace std;
int main()
{
int b = 0;
auto Func = [&b] ()
{
b = 3;
cout << &b << endl;
};
Func();
cout << b << endl;
return 0;
}
运行结果:
捕获列表的语法:
[]
:空捕获,不捕获任何变量。[var]
:值捕获var变量。[&var]
:引用捕获var变量。[=]
:值捕获所有变量,包括this
类中的指针。(在父作用域内或者全局中可访问的变量)[&]
:引用捕获所有变量。包括this
指针。(在父作用域内或者全局中可访问的变量)[=,&var]
:值捕获其它变量,引用捕获var
变量。[&,var]
:引用捕获其它变量,引用捕获var
变量。
注意:
- 捕捉列表不能捕获重复的变量否则会报错。
- 父作用域指包括
lambda
表达式的语句块。
lambda表达式在类函数作用域中捕获this对象(非静态函数)
#include <iostream>
#include <functional>
using namespace std;
class MyClass {
public:
int value;
void doSomething() {
// 使用lambda表达式,并在内部直接使用this指针
auto lambda = [this]() {
std::cout << "Accessing member via this: " << this->value << std::endl;
// 也可以直接访问,无需this->,如:std::cout << value << std::endl;
};
// 调用lambda
lambda();
std::cout << value << std::endl;
// 修改成员变量,并通过lambda再次访问以验证修改
value = 42;
lambda();
}
};
int main() {
MyClass obj;
obj.value = 10;
obj.doSomething();
return 0;
}
运行结果:
深入理解值捕获lambda的常性与类里面的常量成员函数的区别
#include <iostream>
#include <functional>
using namespace std;
class MyClass {
public:
int value;
void doSomething() {
// 使用lambda表达式,并在内部直接使用this指针
auto lambda = [this]() {
std::cout << "Accessing member via this: " << this->value << std::endl;
this->value = 2;
// 也可以直接访问,无需this->,如:std::cout << value << std::endl;
};
// 调用lambda
lambda();
std::cout << value << std::endl;
}
void Func() const//const MyClass * const this
{
//Func为常量函数,不可修改
//value = 3;
}
};
int main() {
MyClass obj;
obj.value = 10;
obj.doSomething();
return 0;
}
运行结果:
为什么lambda
表达式使用的是值捕获,还是可以修改this指针指向的内容呢?但是常量成员函数却不能修改:
lambda
表达式的const
修饰的是变量本身,而不是变量指向的内容,在这个例子中也就是:MyClass * const this
,const
修饰的是this
变量本身,它本身保存的地址不能修改,但是它指向的内容可以修改,这就是我们可以修改它的成员的原因,值捕获是拷贝了和this
指针一样的地址。const
修饰成员函数则不同,它修饰的是this
指针指向的内容,这点我们在类和对象部分讲过,在本例子中也就是:MyClass const * const this
,但是由于this
指针变量本身就具有常量属性(它是一个右值,我们稍后会证明)。
再谈类和对象中的this指针
this
指针是一个右值,具有常性。我们无法对右值取地址。
class MyClass {
public:
int value;
void Func() //const MyClass * const this
{
cout << &this << endl; //this指针是一个右值无法取地址
}
};
报错截图:
当我们使用
const
修饰成员函数时,即使使用右值引用引用this
指针,也只能做到修改右值引用变量本身,无法修改它指向的内容。这是因为const
修饰的是*this
,也就是this
指针指向的内容。我们上面在右值引用引用指针部分已经谈到了。
class MyClass {
public:
int value;
void Func() const//const MyClass * const this
{
MyClass const* const &lptr = this;//少一个const都无法使用左值引用
MyClass const* ptr = this;//不是引用,指针变量可以不使用const修饰,但是它指向的内容必须用const修饰
MyClass const*&& rptr = this;//const不修饰*rptr就会报错
}
};
只要在成员函数中就可以访问本类的
this
指针,都可以省略->
或者.
访问符号。(特指lambada
表达式在类成员函数时)
#include <iostream>
#include <functional>
using namespace std;
class MyClass {
public:
int value;
void doSomething() {
// 使用lambda表达式,并在内部直接使用this指针
auto lambda = [this]() {//MyClass * const this
std::cout << "Accessing member via this: " << value << std::endl;
value = 2;
// 也可以直接访问,无需this->,如:std::cout << value << std::endl;
};
// 调用lambda
lambda();
std::cout << value << std::endl;
}
};
int main() {
MyClass obj;
obj.value = 10;
obj.doSomething();
return 0;
}
但是有重名的情况还是不能省略,不然可能出现未定义行为。友元函数和友元类中不能这样做,因为它们只是拥有访问我们类私有成员的权利,并不直接属于类域。
运行结果:
引用捕获
this
指针和值捕获区别不大(由于this
指针是右值,无法修改),我们这里不再谈到。
lambda表达式的底层原理
lambda
表达式是借助仿函数实现的。
#include<iostream>
using namespace std;
class Rate
{
public:
Rate(double rate) : _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
// lambda
auto r2 = [=](double monty, int year)->double {return monty * rate * year;};
r2(10000, 2);
auto f1 = [] {cout << "hello world" << endl; };
auto f2 = [] {cout << "hello world" << endl; };
f1();
f2();
return 0;
}
反汇编查看其底层调用:
- 从反汇编的底层调用中可以看出,
lambda
表达式其实就是一个匿名的仿函数类,只能创建一次,因为再次创建的时候你无法知道其类名称,也就无法再创建对象了。我们通过auto
自动推导类型的功能,创建了一个仿函数对象,调用的时候是调用其底层的operator()
函数。
这里虽然f1
和f2
的函数体一样,但是它们仍然是两个不同类型的lambda
表达式。
其实我们通过
decltype
关键字也可以实现再次创建相同类型的lambda
表达式。
#include<iostream>
using namespace std;
int main()
{
double rate = 0.49;
// lambda
auto r1 = [=](double monty, int year)->double {return monty * rate * year; };
decltype(r1) r2 = r1;//支持赋值运算符重载
decltype(r1) r3(r2);//支持拷贝构造
//decltype(r1) r4;禁掉了 默认构造
cout << typeid(r1).name() << endl;
cout << typeid(r2).name() << endl;
cout << typeid(r3).name() << endl;
return 0;
}
运行结果:
不同的lambda表达式不能互相赋值
在介绍完
lambda
表达式的原理之后想必就很好理解了,两个仿函数对象连类型都不同,怎么能互相赋值呢。
#include<iostream>
using namespace std;
int main()
{
auto f1 = []()
{
cout << "我是lambada表达式" << endl;
};
auto f2 = [=]()
{
cout << "我是lambada表达式" << endl;
};
//f2 = f1;这是不正确的,会报错
return 0;
}
但是可以将
lambda
对象赋值给同类型的函数指针(参数和返回值一样)。前提是这个lambda
对象没有捕获外部变量(如果它捕获了外部变量,则单纯的函数无法表示这种状态)。此时我们可以认为lambda
表达式的行为和函数没有区别,但是底层还是一个类。
- 我们可以看到,即使
f1
没有捕获任何外部变量,打印出它的类型还是一个自定义的类,至于它是如何赋值给符合要求的函数指针的,我们就不得而知了。
#include<iostream>
using namespace std;
typedef void (*pf) ();
class MyClass
{
public:
void operator()()
{
cout << "我是仿函数对象" << endl;
}
};
int main()
{
int a = 0;
auto f1 = []()
{
cout << "我是lambada表达式" << endl;
};
f1();
auto f2 = [=]()
{
cout << a << endl;
cout << "我是lambada表达式" << endl;
};
f2();
MyClass f3;
pf Pf1 = f1;//可以赋值
//pf Pf2 = f2;
return 0;
}
如果把
f2
赋值给同类型的函数指针变量就会报错,因为它依赖外部变量,函数无法表示这种状态,此时可以把lambda
表达式看作一个仿函数对象。
包装器
包装器引入的初衷
我们已经学习了仿函数、
lambda
表达式和函数指针,它们三个都可以去像函数一样使用。但是传参上有各有不同,特别是函数指针,在类里面还不能通过传类型来定义,必须传函数指针给类方法,很不方便;lambda
表达式更是连类型我们都不能直观的看见。这不是最主要的,最主要的是,它们三个的存在会让模板的效率变得低下。
看下面的代码:
#include<iostream>
using namespace std;
template<class F,class T>
T Functor(F f,T data)
{
static int count = 0;
count++;
cout << count << endl;
cout << &count << endl;
return f(data);
}
class MyClass
{
public:
double operator()(double data)
{
return data / 2;
}
};
double func1(double x)
{
return x * 3;
}
int main()
{
MyClass fun;
Functor(fun, 3.4);
Functor(func1, 10.9);
Functor([](double x)-> double {return x / 4; }, 2.2);
return 0;
}
运行结果:
如果调用的是同一份模板函数,count
是静态成员,应该只会定义一次。但运行结果显示count
定义了三次,所以函数模板实例化了三份。
能否存在一种机制,可以让我们不区分它们三个,让它们的使用方式一样呢?于是C++11引入了包装器的语法,也叫function
。
包装器的语法
包装器介绍
包装器是
functional
头文件中封装的一个类模板。
template <class Ret, class... Args> class function<Ret(Args...)>;
Ret
是函数的返回值,Args
是参数包,也就是函数的一系列参数。
我们只需要了解包装器的使用即可,对于其底层不用过多研究。
下面介绍function
的各种使用场景:
#include<functional>
#include<iostream>
using namespace std;
//函数
int f1(int a, int b)
{
return a + b;
}
//仿函数对象
class f2
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
//普通类
class f3
{
public:
static int f1(int a, int b)
{
return a + b;
}
int f2(int a, int b)
{
return a + b;
}
};
int main()
{
function<int(int, int)> func1 = f1;//包装函数
cout << func1(1, 2) << endl;
//包装仿函数对象两种方式
function<int(int, int)> func2 = f2();//方式1
function<int(f2, int, int)> func3 = &f2::operator();//它的底层我们不关心如何实现
cout << func2(1, 2) << endl;
cout << func3(f2(),1, 2) << endl;
//包装类中的静态函数
function<int(int, int)> func4 = &f3::f1;
//包装类中的普通成员方法
function<int(f3, int, int)> func5 = &f3::f2;
cout << func4(1, 2) << endl;
cout << func5(f3(), 1, 2) << endl;
return 0;
}
运行结果:
包装器包装类成员函数的不同方式
对于其第一个参数(类对象)支持不同传参方式
#include<iostream>
#include<functional>
using namespace std;
class MyClass
{
public:
int Add(int a, int b)
{
return a + b;
}
};
int main()
{
包装类中的方法的几种方式
//1.第一个参数是类指针
function<int(MyClass*, int, int)> func1 = &MyClass::Add;//但是这里必须带取地址
MyClass object;
cout << func1(&object, 3, 2) << endl;
//2.第一个参数是类的右值引用
function<int(MyClass&&, int, int)> func2 = &MyClass::Add;
cout << func2(MyClass(), 3, 2) << endl;
int num = 3;
//3.第一个参数是普通类对象
function<int(const MyClass,int,int)> func3 = &MyClass::Add;
cout << func3(MyClass(),3, 2) << endl;
//4.第一个参数是左值引用
function<int(MyClass&, int, int)> func4 = &MyClass::Add;
cout << func4(object, 3, 2) << endl;
return 0;
}
运行结果:
在使用
function
的时候,我们还发现function
中的类型应该尽量和原调用对象的类型保持一致,(特别是const
的有无)。不然就很可能出现一些问题:
这里我们将
MyClass
对象的左值引用加上const
修饰就报错了,也能理解因为函数方法不具有常性(没有加const
修饰),我们使用function
封装,最终肯定也要回调原可调用对象(这里就是类中的成员方法),会涉及强制类型转化。这个成员方法没有加const
修饰,它的this
指针指向的内容是可修改的,const &
->&
会报错我们可以理解。
但是下面这种情况呢?
我们给非引用的
MyClass
对象加上const
修饰传进去,它没有报错。
再看下面的代码,你就会发现这并不是偶然:
#include<iostream>
#include<functional>
using namespace std;
class MyClass
{
public:
int Add(int& a, int b)
{
return a + b;
}
};
int main()
{
int num1 = 2;
function<int(MyClass&&, int&, const int)> func2 = &MyClass::Add;
cout << func2(MyClass(),num1, 2) << endl;
function<int(const MyClass, const int&,int)> func3 = &MyClass::Add;
cout << func3(MyClass(), 4, 2) << endl;
return 0;
}
报错截图:
MyClass
对象的传参太特殊了,不能说明问题。那现在普通的参数呢?当我们的函数方法中的第一个参数是int&
是const int&
->int &
是无法强制类型转换成功的,但是const int
转int
只是值传递可以转成功,这和我们之前学的知识是逻辑自洽的。至于类中的第一个参数this
指针具体在function
中是如何处理的,我们就不得而知了。
- 总结:在使用
function
包装调用对象时,尽可能的不要加const
修饰符,如果你对这方面的语法不是很熟悉的话(int &
可以->const int&
,int*
可以->const int*
。但是反过来就不行。==(等价于) 权限可以缩小,但是不能放大)。也可以选择和原调用对象的参数保持严格的一致。
通过这里对
function
类的学习,我们对this
指针又产生了一个疑惑,到底是function
内部如此处理,还是类和对象中这样去处理的呢?为什么对于类中的方法的第一个参数,能有如此多的传参方式。但是不管其底层如何,我们只要明确一点,在function
这里就不会出错,函数方法没有const
修饰的,不要给引用变量本身或者指针指向的内容加上const
修饰符。
使用包装器解决上述问题
代码如下:
#include<iostream>
#include<functional>
using namespace std;
template<class F,class T>
T Functor(F f,T data)
{
static int count = 0;
count++;
cout << count << endl;
cout << &count << endl;
return f(data);
}
class MyClass
{
public:
double operator()(double data)
{
return data / 2;
}
};
double func1(double x)
{
return x * 3;
}
int main()
{
function<double(double)> func1_ = MyClass();
Functor(func1_, 2.2);
function<double(double)> func2_ = func1;
Functor(func2_, 2.2);
function<double(double)> func3_ = [](double x)->double {return x / 4; };
Functor(func3_, 10.9);
return 0;
}
运行结果:
function
模板类的模板参数一样,所以它们三个是一样的类型(对于类模板来说,实例化后的模板类的类型是类模板带上模板参数)。仿函数的function
类型比较特殊(相比于类中的普通方法),是为了便于与函数和lambda
表达式调用方式统一。lambda
表达式和仿函数的function
封装方式是最相似的,都是把一个创建好的对象赋值给function
。这与它们的底层有一定的关系。
包装器的其它应用
看下面一到OJ题,思考如何使用包装器来做。这里是题目链接
题干如下:
ak代码:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st1;
map<string,function<int(int,int)>> mp1 = {{"+",[](int a,int b)->int{return a+b;}},
{"*",[](int a,int b)->int{return a*b;}},
{"-",[](int a,int b)->int{return a-b;}},
{"/",[](int a,int b)->int{return a/b;}}
};
for(auto& str:tokens)
{
if(str == "+" || str == "-" || str == "*" || str == "/")
{
int b = st1.top();
st1.pop();
int a = st1.top();
st1.pop();
st1.push(mp1[str](a,b));
}
else
{
st1.push(stoi(str));
}
}
return st1.top();
}
};
ak截图:
- 这题如果不使用
function
,就要用if
或者switch
表达式代码看着很冗余,不够优雅,但是使用map
容器建立op-method
的映射关系,就看起来十分优雅。(这题的具体算法思路,参考这篇博客)
绑定
绑定是C++11中新出来的语法,它是一个函数模板,下面是它的函数模板参数及其函数形参。
bind
的形参使用万能引用,既可传左值也可传右值,它的返回值类型比较复杂,没有给出,但是我们可以把它的返回值认为是function
类型。它重载了两个版本,我们只介绍第一个版本,第二个和第一个区别不大,就是多了一个返回值类型让我们传。
Fn
是可调用对象的类型,args
是可调用对象的参数包。
下面我们将介绍bind
的使用,它有如下两种用途:
绑定修改可调用对象的参数个数
下面是代码:
#include<iostream>
#include<functional>
using namespace std;
class Sub//仿函数对象
{
public:
int operator()(int x, int y)
{
return x - y;
}
int Mul(int x, int y)//仿函数中的普通函数方法
{
return x * y;
}
};
int Add(int x, int y)//函数
{
return x + y;
}
int main()
{
//绑定普通函数
int num1 = 3;
function<int(int)> func1 = bind(Add,num1,placeholders::_1);//placeholders::_1代表我们要传的第一个参数对应Add中的第二个参数
cout << typeid(func1).name() << endl;
cout << func1(3) << endl;
//绑定仿函数对象
int num2 = 4;
auto func2 = bind(Sub(), num2, placeholders::_1);
cout << typeid(func2).name() << endl;
cout << func2(3) << endl;
//绑定仿函数对象中的普通方法
int num3 = 4;
auto func3 = bind(&Sub::Mul,Sub(), placeholders::_1, placeholders::_2);
cout << typeid(func3).name() << endl;
cout << func3(num3, 4) << endl;
//绑定lambda表达式
auto func4 = bind([](int x, int y)->int {return x + 2 * y; }, 5, placeholders::_1);
cout << typeid(func4).name() << endl;
cout << func4(5) << endl;
return 0;
}
运行结果:
可以看到,使用
bind
我们确实可以绑死可调用对象的一些参数,特别当我们需要在其它文件中调用类中的方法时,把第一个类对象给绑死,可以让我们使用起来像普通函数一样很方便。另外从运行结果中还可以看出,虽然
auto
关键字推导出的bind
返回值的类型很复杂,但是我们的第一个普通函数使用function
类模板作为其类型也是可以的,前提是其返回值类型和参数类型和原调用对象以及bind
中相应的处理保持一致。(特指使用bind
绑死了一个参数,或者调换了某些参数的调用顺序)
绑定修改可调用对象的参数的调用顺序
下面是代码:
#include<iostream>
#include<algorithm>
#include<functional>
#include<vector>
using namespace std;
int main()
{
//我们可以使用bind调整类中方法的调用顺序
auto func1 = bind(sort<vector<int>::iterator,less<int>>, placeholders::_2, placeholders::_1,placeholders::_3);
vector<int> v1;
for (int i = 0; i < 10; ++i)
v1.emplace_back(10 - i);
for (auto& i : v1)
cout << i << " ";
cout << endl;
func1(v1.end(), v1.begin(),less<int>());
for (auto& i : v1)
cout << i << " ";
cout << endl;
return 0;
}
-
上述代码中我们修改了
algorithm
库中sort
函数模板的前两个参数的调用顺序。对于函数模板来说,绑定可调用对象必须传参数编译器才会帮助我们实例化出具体的函数。下面是sort
函数模板的参数及其模板类型,它重载了两个函数模板:
如果我们不需要传比较器(用于比较的仿函数对象),就可以使用第一个函数模板,不显式的传它的比较器类型;否则必须传。
运行结果:
对于
bind
的底层实现我们不用过多关注,我们可以发现它和function
的传参(特别是在传类中的函数方法时)很相似。它的返回值类型也肯定和function
类型有一定联系,如果你感兴趣可以自行阅读源码,本篇博客只讲使用。
- 本人知识、能力有限,若有错漏,烦请指正,非常非常感谢!!!
- 转发或者引用需标明来源。