回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
std::function作为回调函数,std::function配合std::bind和lambda表达式能够很方便的指向函数指针。C++中有如下几种可调用对象:函数、函数指针、lambda表达式、bind对象、函数对象。其中,lambda表达式和bind对象是C++11标准中提出的(bind机制并不是新标准中首次提出,而是对旧版本中bind1st和bind2st的合并)。个人认为五种可调用对象中,函数和函数指针本质相同,而lambda表达式、bind对象及函数对象则异曲同工。
函数指针
插播一下函数指针和函数类型的区别:
函数指针指向的是函数而非对象。和其他指针类型一样,函数指针指向某种特定类型;
函数类型由它的返回值和参数类型决定,与函数名无关。
例如:
bool fun(int a, int b)
上述函数的函数类型是:bool(int, int)
上述函数的函数指针pf是:bool (*pf)(int, int)
一般对于函数来说,函数名即为函数指针:
#include<iostream>
//被调用的普通函数
int Func1(int x,int y){
std::cout<<x+y<<std::endl;
return x+y;
}
//有一个形参为函数指针
int Func2(int(*fp)(int,int),int x,int y){
return fp(x,y);
}
//定义一个函数指针类型为FType
typedef int (*FType)(int,int);
int Func3(FType fp,int x,int y){
return fp(x,y);
}
int main(){
Func2(Func1,100,100); //函数Func2调用函数Func1
Func3(Func1,200,200); //函数Func3调用函数Func1
return 0;
}
输出结果:
PS D:\bind和function的混合使用\build\函数指针\Debug> .\main.exe
200
400
PS D:\bind和function的混合使用\build\函数指针\Debug>
可以看出,函数指针作为参数,可以调用函数指针所指向的函数内容。
std::bind和std::function的回调技术
参考自《Linux多线程服务端编程》以及muduo源码,对其中的一些实现细节有着十分深刻的印象,尤其是使用std::bind和std::function的回调技术。可以说,这两个大杀器简直就是现代C++的“任督二脉”,甚至可以解决继承时的虚函数指代不清的问题。在此详细叙述使用std::bind和std::function在C++对象之间的用法,用以配合解决事件驱动的编程模型。
本文组成:
1.std::function
2.std::bind
3.使用std::bind和std::function的回调技术
4.std::bind绑定到虚函数时会表现出多态行为,解决继承时的虚函数指代不清的问题
function模板类和bind模板函数,使用它们可以实现类似函数指针的功能,但却却比函数指针更加灵活,特别是函数指向类 的非静态成员函数时。不过网上有文章简单测试:函数指针要比直接调用慢2s左右;std::function 要比函数指针慢2s左右 。
std::function
std::function位于头文件#include,可将各种可以调用实体进行封装统一。包括:
- 普通函数
- lambda表达式
- 函数指针
- 仿函数(函数对象)类重载了()
- 类成员函数
- 静态成员函数
实例通过上述几种方式实现一个简单的比较两个数的大小的功能(读者可拷贝代码观察结果)代码如下:
#include<iostream>
#include<functional>
#include<unordered_map>
std::unordered_map<int,std::function<void(int,int)>> functions;
std::function<bool(int,int)> func;
//1.普通函数
bool compare_com(int a,int b){
return a>b;
}
//lambda表达式
auto compare_lam=[](int a,int b)->bool{
return a>b;
};
//仿函数(函数对象)
class Compare_class{
public:
bool operator()(int a,int b){
return a>b;
}
};
//类成员函数(静态或者动态)
class Compare{
public:
bool compare_member(int a,int b){
return a>b;
}
static bool compare_static_member(int a,int b){
return a>b;
}
};
void Func1(){
func=compare_com;
bool flag=func(10,1);
std::cout<<"普通函数输出,flag is "<<flag<<std::endl;
}
void Func2(){
func=compare_lam;
bool flag=func(10,1);
std::cout<<"lambda表达式输出,flag is "<<flag<<std::endl;
}
void Func3(){
func=Compare_class();
bool flag=func(10,1);
std::cout<<"仿函数(函数对象输出),flag is "<<flag<<std::endl;
}
void Func4(){
func=Compare::compare_static_member;
bool flag=func(10,1);
std::cout<<"类静态成员函数输出,flag is "<<flag<<std::endl;
}
void Func5(){
Compare tmp;
func=std::bind(&Compare::compare_member,tmp,std::placeholders::_1,std::placeholders::_2);
bool flag=func(10,1);
std::cout<<"类普通成员函数输出,flag is "<<flag<<std::endl;
}
int main(){
Func1();
Func2();
Func3();
Func4();
Func5();
return 0;
}
输出结果:
普通函数输出,flag is 1
lambda表达式输出,flag is 1
仿函数(函数对象输出),flag is 1
类静态成员函数输出,flag is 1
类普通成员函数输出,flag is 1
PS D:\bind和function的混合使用\build\function\Debug>
由上文可以看出:由于可调用对象的定义方式比较多,但是函数的调用方式较为类似,因此需要使用一个统一的方式保存可调用对象或者传递可调用对象。于是,std::function就诞生了。
std::function是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。
定义function的一般形式:
# include <functional>
std::function<函数类型>
std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。
故而,std::function的作用可以归结于:
- std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象,简化调用。
- std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(如:函数指针这类可调用实体,是类型不安全的)。
std::bind
std::bind可以看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:
- 将可调用对象和其参数绑定成一个仿函数;
- 只绑定部分参数,减少可调用对象传入的参数。
- 调用bind的一般形式:
auto newCallable = bind(callable, arg_list);
该形式表达的意思是:当调用newCallable时,会调用callable,并传给它arg_list中的参数。
需要注意的是:arg_list中的参数可能包含形如_n的名字。其中n是一个整数,这些参数是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调用对象中参数的位置:_ _ 1为newCallable的第一个参数,_ 2为第二个参数,以此类推。
std::bind函数将可调用对象(开头所述6类)和可调用对象的参数进行绑定,返回新的可调用对象(std::function类型,参数列表可能改变),返回的新的std::function可调用对象的参数列表根据bind函数实参中std::placeholders::_x从小到大对应的参数确定。
实例详细说明返回的新的std::function可调用对象的参数列表如何确定:
#include<iostream>
#include<functional>
struct Int{
int a;
};
bool compare_com(struct Int a,float b){
return a.a>b;
}
void Func1(){
Int a{3};
//Int a={3};
//placeholders::_1对应float,placeholders::_2对应struct Int所以返回值func返回值得类型为function<bool(float,Int)>
std::function<bool(float,struct Int)> func=std::bind(compare_com,std::placeholders::_2,std::placeholders::_1);
bool flag=func(2.0,a);
std::cout<<"flag is "<<flag<<std::endl;
}
int main(){
Func1();
return 0;
}
输出结果:
flag is 1
PS D:\bind和function的混合使用\build\bind\Debug>
由此例子可以看出:
- 预绑定的参数是以值传递的形式,不预绑定的参数要用std::placeholders(占位符)的形式占位,从_1开始,依次递增,是以引用传递的形式;
- std::placeholders表示新的可调用对象的第几个参数,而且与原函数的该占位符所在位置的进行匹配;
- **bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址,这是因为对象的成员函数需要有this指针。**并且编译器不会将对象的成员函数隐式转换成函数指针,需要通过&手动转换;一看到bind里面绑定的类成员函数时都带着地址,我非常不理解,现在根据这句话查到了原来对于非类成员函数,编译器会把函数名隐式转换成指针的形式,即转换成函数指针,而对于类成员函数无法转换。
- 并且编译器不会将对象的成员函数隐式转换成函数指针,需要通过&手动转换",一看到bind里面绑定的类成员函数时都带着地址,我非常不理解,现在根据这句话查到了原来对于非类成员函数,编译器会把函数名隐式转换成指针的形式,即转换成函数指针,而对于类成员函数无法转换。
std::bind的返回值是可调用实体,可以直接赋给std::function。
使用std::bind和std::function的回调技术
C++的类成员函数不能像普通函数那样用于回调,因为每个成员函数都需要有一个对象实例去调用它。
通常情况下,要实现成员函数作为回调函数:
**一种过去常用的方法就是把该成员函数设计为静态成员函数(因为类的成员函数需要隐含的this指针 而回调函数没有办法提供)。**但这样做有一个缺点,就是会破坏类的结构性,因为静态成员函数只能访问该类的静态成员变量和静态成员函数,不能访问非静态的。
要解决这个问题,可以把对象实例的指针或引用做为参数传给它。后面就可以靠这个对象实例的指针或引用访问非静态成员函数。
下面的所有讨论基于对象。
代码如下:
#include<iostream>
#include<functional>
typedef std::function<void()> func;
class Blas{
public:
//类成员函数
virtual void add(int a,int b){
std::cout<<a+b<<std::endl;
}
//类静态成员函数
static void addStatic(int a,int b){
std::cout<<a+b<<"this Blas"<<std::endl;
}
};
class BBlas:public Blas{
public:
virtual void add(int a,int b){
std::cout<<a+b<<"this BBlas"<<std::endl;
}
};
void Func1(){
//Blas blas
BBlas bblas;
//使用std::bind绑定类静态成员函数
func f1(std::bind(&Blas::addStatic,1,2));
f1();
//使用std::bind绑定类的成员函数 对象加地址与不加地址实现结果一样
//func f2(std::bind(&Blas::add,&bblas,1,2));
func f2(std::bind(&Blas::add,bblas,1,2));
f2();
}
int main(){
Func1();
return 0;
}
输出结果:
3this Blas
3this BBlas
PS D:\bind和function的混合使用\build\bind和function混合\Debug>
上述代码中的区别是:如果不是类的静态成员函数,需要在参数绑定时,往绑定的参数列表中加入使用的对象。
另一种办法就是使用std::bind和std::function结合实现回调技术。
#include<iostream>
#include<functional>
typedef std::function<void()> func;
class Blas{
public:
void SetCallBack(const func& cb){
cb_=cb;
}
void PrintFunctor(){
cb_();
}
private:
func cb_;
};
class Atlas{
public:
Atlas(int x)
:x_(x){
//使用当前类的静态成员函数
blas_.SetCallBack(std::bind(&AddStatic,x,2));
//使用当前类的非静态成员函数
blas_.SetCallBack(std::bind(&Atlas::Add,this,x,2));
}
void Print(){
blas_.PrintFunctor();
}
private:
void Add(int a,int b){
std::cout<<a+b<<std::endl;
}
static void AddStatic(int a,int b){
std::cout<<a+b<<std::endl;
}
private:
Blas blas_;
int x_;
};
void Func1(){
Atlas atlas(5);
atlas.Print();
}
int main(){
Func1();
return 0;
}
输出结果:
7
两个函数在Atlas类中,并且可以自由操作Atlas的数据成员。尽管是将add()系列的函数封装成函数对象传入Blas中,并且在Blas类中调用,但是它们仍然具有操作Atlas数据成员的功能,在两个类之间形成了弱的耦合作用。但是如果要在两个类之间形成弱的耦合作用,必须在使用std::bind()封装时,向其中传入this指针。
可调用对象
在 C++中, 可以像函数一样调用的有: **普通函数、类的静态成员函数、仿函数、 lambda 函数、类的非静态成员函数、 可被转换为函数的类的对象,**统称可调用对象或函数对象。
可调用对象有类型, 可以用指针存储它们的地址, 可以被引用(类的成员函数除外) 。
普通函数
普通函数类型可以声明函数、 定义函数指针和函数引用, 但是, 不能定义函数的实体。
#include<iostream>
#include<string>
using Func=void(int,const std::string&); //普通函数的别名
Func Show; //声明普通函数
//void Show(int,const std::string&);
//定义普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的"<<n<<", "<<str<<std::endl;
}
int main(){
Show(1,"我是一只笨鸟"); //直接调用普通函数
void(*fp1)(int,const std::string&)=Show; //声明函数指针,指向Show这个函数
void(&fr1)(int,const std::string&)=Show; //声明函数引用,引用Show这个函数
fp1(2,"我是一只笨鸟"); //用函数指针调用Show普通函数
fr1(3,"我是一只笨鸟"); //用函数引用调用普通函数
Func* fp2=Show; //声明函数指针,指向Show普通函数
Func& fr2=Show; //声明函数引用,指向Show普通函数
fp2(4,"我是一只笨鸟"); //用声明的函数指针调用普通函数
fr2(5,"我是一只笨鸟"); //用声明的函数引用调用普通函数
return 0;
}
输出结果:
PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的1, 我是一只笨鸟
亲爱的2, 我是一只笨鸟
亲爱的3, 我是一只笨鸟
亲爱的4, 我是一只笨鸟
亲爱的5, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>
类的静态成员函数
类的静态成员函数和普通函数本质上是一样的,把普通函数放在类中而已。
#include<iostream>
#include<string>
using Func=void(int ,const std::string& ); //普通函数起别名
struct AA{
//类中有静态成员函数
static void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
};
int main(){
AA::Show(1,"我是一只笨鸟 "); //直接用类::调用静态成员函数
void(*fp1)(int,const std::string&)=AA::Show; //用函数指针指向静态成员函数
void(&fr1)(int,const std::string&)=AA::Show; //用函数引用引用静态成员函数
fp1(2,"我是一只笨鸟 "); //用函数指针调用静态成员函数
fr1(3,"我是一只笨鸟 "); //用函数引用调用静态成员函数
Func* fp2=AA::Show; //定义一个函数指针指向静态成员函数
Func& fr2=AA::Show; //定义一个函数引用指向静态成员函数
fp2(4,"我是一只笨鸟 "); //用函数指针调用静态成员函数
fr2(5,"我是一只笨鸟 "); //用函数引用调用静态成员函数
return 0;
}
输出结果:
亲爱的 1, 我是一只笨鸟
亲爱的 2, 我是一只笨鸟
亲爱的 3, 我是一只笨鸟
亲爱的 4, 我是一只笨鸟
亲爱的 5, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug> ^C
PS D:\可调用对象、包装器function、绑定器bind\Debug>
仿函数(函数对象)
仿函数的本质是类,调用的代码像函数。仿函数的类型就是类的类型。
#include<iostream>
#include<string>
struct BB{
void operator()(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
};
int main(){
BB bb;
bb(11,"我是一只笨鸟 "); //用对象调用仿函数(函数对象)
BB()(12,"我是一只笨鸟 "); //用匿名函数对象调用仿函数(函数对象)
BB& br=bb; //对象的引用绑定bb对象
br(13,"我是一只笨鸟 "); //用对象的引用调用仿函数
return 0;
}
输出结果:
PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 11, 我是一只笨鸟
亲爱的 12, 我是一只笨鸟
亲爱的 13, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>
lambda表达式
lambda 函数的本质是仿函数, 仿函数的本质是类。
#include<iostream>
#include<string>
int main(){
//创建lambda对象
auto lb=[](int n,const std::string& str)->void{
std::cout<<"亲爱的 "<<n<<" ,"<<str<<std::endl;
};
auto& lr=lb; //引用lambda对象
lb(1,"我是一只笨鸟 "); //直接用lambda对象调用仿函数
lr(2,"我是一只笨鸟 "); //用lambda对象的引用调用仿函数
return 0;
}
输出结果:
PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 1 ,我是一只笨鸟
亲爱的 2 ,我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>
类的非静态成员函数
类的非静态成员函数有地址,但是,只能通过类的对象才能调用它,所以, C++对它做了特别处理。
类的非静态成员函数只有指针类型,没有引用类型,不能引用。
#include<iostream>
#include<string>
struct CC{
//类中有普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
};
int main(){
CC cc;
cc.Show(14,"我是一只笨鸟 "); //直接用类的对象去调用类中方法
void(CC::*fp1)(int,const std::string&)=&CC::Show; //定义类的成员函数指针指向类的成员函数
(cc.*fp1)(15,"我是一只笨鸟 "); //用类的成员函数指针调用成员函数
using pFunc=void(CC::*)(int,const std::string&); //类成员函数指针起别名
pFunc fp2=&CC::Show; //类成员函数指针指向类成员函数
(cc.*fp2)(16,"我是一只笨鸟 ");
//cc.*fp2就是解引用出这个函数出来,得到这个函数
return 0;
}
输出结果:
PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 14, 我是一只笨鸟
亲爱的 15, 我是一只笨鸟
亲爱的 16, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>
可被转换为函数指针的类对象
类可以重载类型转换运算符 operator 数据类型() ,如果数据类型是函数指针或函数引用类型,那么该类实例也将成为可调用对象。
它的本质是类,调用的代码像函数。
在实际开发中,意义不大。
#include<iostream>
#include<string>
//定义函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<" ,"<<str<<std::endl;
}
struct DD{
//可以被转换成函数指针的类
using Func=void(*)(int,const std::string&);
operator Func(){
return Show;
}
};
int main(){
DD dd;
dd(17,"我是一只笨鸟 ");
return 0;
}
输出结果:
PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 17 ,我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>
包装器function
std::function 模板类是一个通用的可调用对象的包装器,用简单的、 统一的方式处理可调用对象。
template
class function……
Fty 是可调用对象的类型,格式: 返回类型(参数列表)。
包含头文件: #include
注意:
⚫ 重载了 bool 运算符,用于判断是否包装了可调用对象。
⚫ 如果 std::function 对象未包装可调用对象, 使用 std::function 对象将抛出 std::bad_function
call 异常。
#include<iostream>
#include<string>
#include<functional>
//普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
struct AA{
//类中静态成员函数
static void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
};
struct BB{
//类中仿函数
void operator()(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<" ,"<<str<<std::endl;
}
};
struct CC{
//类的普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
};
struct DD{
//类中可以被转换普通函数的类
using Func=void(*)(int,const std::string& );
operator Func(){
return Show; //返回普通函数的地址
}
};
int main(){
using Func=void(int,const std::string& str); //函数类型的别名
//普通函数
void(*fp1)(int,const std::string&)=Show; //声明函数指针,指向函数对象
fp1(1,"我是一只笨鸟 "); //用函数指针调用普通函数
std::function<void(int,const std::string&)> fn1=Show; //定义function对象包装普通函数Show
fn1(1,"我是一只笨鸟"); //用function对象调用普通函数
//类中静态成员函数
void(*fp2)(int,const std::string&)=AA::Show; //声明函数指针指向类中成员函数对象
fp2(2,"我是一只笨鸟 "); //用函数指针调用类中成员函数
std::function<void(int,const std::string&)> fn2=AA::Show; //定义function对象包装类中静态成员函数
fn2(2,"我是一只笨鸟"); //用function对象调用类中静态成员函数
//仿函数
BB bb;
bb(3,"我是一只笨鸟 "); //用仿函数对象调用仿函数
BB()(3,"我是一只笨鸟"); //仿函数匿名对象调用
std::function<void(int,const std::string&)> fn3=BB(); //定义function对象包装仿函数
fn3(3,"我是一只笨鸟 "); //用function对象调用仿函数
//lambad表达式
auto lb=[](int n,const std::string& str)->void{ //调用lambad表达式
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
};
lb(4,"我是一只笨鸟 ");
std::function<void(int,const std::string&)> fn4=lb;
std::function<void(int,const std::string&)> fn5=[](int n,const std::string& str)->void{
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl; //lambda可能是匿名的
};
fn5(4,"我是一只笨鸟 ");
//类中非静态成员函数 注意这里包装的时候要加对象的地址
CC cc;
void(CC::*fp3)(int,const std::string&)=&CC::Show; //定义类成员函数指针指向类成员函数
(cc.*fp3)(5,"我是一只笨鸟 "); //用类成员函数指针调用类成员函数
std::function<void(CC&,int,const std::string&)> fn6=&CC::Show; //定义function对象包装成员函数
fn6(cc,5,"我是一只笨鸟 "); //用function对象调用成员函数
//可以被转换为函数指针的对象
DD dd;
dd(6,"我是一只笨鸟 "); //用来被转换为函数指针的类对象的普通函数
std::function<void(int,const std::string&)> fn7=dd; //定义function对象包装可以被转换为函数指针的类
fn7(6,"我是一只笨鸟 ");
std::function<void(int,const std::string&)> fn8;
try{
fn8(7,"我是一只笨鸟 ");
}catch(std::bad_function_call e){
std::cout<<"抛出了 std::bad_function_call异常";
}
return 0;
}
输出结果:
PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 1, 我是一只笨鸟
亲爱的 1, 我是一只笨鸟
亲爱的 2, 我是一只笨鸟
亲爱的 2, 我是一只笨鸟
亲爱的 3 ,我是一只笨鸟
亲爱的 3 ,我是一只笨鸟
亲爱的 3 ,我是一只笨鸟
亲爱的 4, 我是一只笨鸟
亲爱的 4, 我是一只笨鸟
亲爱的 5, 我是一只笨鸟
亲爱的 5, 我是一只笨鸟
亲爱的 6, 我是一只笨鸟
亲爱的 6, 我是一只笨鸟
抛出了 std::bad_function_call异常
PS D:\可调用对象、包装器function、绑定器bind\Debug>
适配器(绑定器)bind
std::bind()模板函数是一个通用的函数适配器(绑定器) ,它用一个可调用对象及其参数,生成一个新的可调用对象,以适应模板。包含头文件: #include
函数原型:
template< class Fx, class… Args >
function<> bind (Fx&& fx, Args&…args);
**Fx:**需要绑定的可调用对象(可以是前两节课介绍的那六种,也可以是 function 对象) 。
**args:**绑定参数列表, 可以是左值、 右值和参数占位符 std::placeholders::_n,如果参数不是占位符,缺省为值传递, std:: ref(参数)则为引用传递。
std::bind()返回 std::function 的对象。
std::bind()的本质是仿函数。
#include<iostream>
#include<functional>
#include<string>
// void Show(int n,const std::string& str){
// std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
// }
//普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
struct AA{
//类中静态成员函数
static void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
};
struct BB{
//类中仿函数
void operator()(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<" ,"<<str<<std::endl;
}
};
struct CC{
//类的普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
};
struct DD{
//类中可以被转换普通函数的类
using Func=void(*)(int,const std::string& );
operator Func(){
return Show; //返回普通函数的地址
}
};
int main(){
//std::bind返回的function模板对象,所有可以用auto fn1=Show接收之类的
//两种输出结果是一样的
std::function<void(int,const std::string&)> fn1=Show;
std::function<void(int,const std::string& str)> fn2=std::bind(Show,std::placeholders::_1,std::placeholders::_2);
fn1(1,"我是一只笨鸟 ");
fn2(1,"我是一只笨鸟 ");
//当现有的函数类型Show与要求的函数类型不一样
//意思是现有函数类型参数顺序不一样或者要求的函数类型数量少
//这个时候就要用到std::bind绑定器(适配器),
//用std::bind对现有函数对象进行转换,生成新的函数对象,与要求的函数对象类型匹配上
//如果不用std::bind(适配器)的话,要实现需求的话就只能重载原来的函数达到要求,比较麻烦
// std::function<void(const std::string&,int)> fn3=Show;
// std::function<void(const std::string&)> fn3=Show;
std::function<void(const std::string&,int)>fn3=std::bind(Show,std::placeholders::_2,std::placeholders::_1);
fn3("我是一只笨鸟 ",2);
//直接提前绑定编号3,发行对象只有一个参数对应一个std::placeholders::_1一个占位符
std::function<void(const std::string&)> fn4=std::bind(Show,3,std::placeholders::_1);
fn4("我是一只笨鸟 ");
int n=4;
//用std::bind()绑定的参数缺省的话是之值传递,用传引用的话用std::ref(n)
std::function<void(const std::string&)>fn5=std::bind(Show,std::ref(n),std::placeholders::_1);
n=100;
fn5("我是一只笨鸟 ");
//要发行的对象参数比原来的函数对象多,std::bind绑定的时候不用管多余的参数,调用的时候随便加什么就行
std::function<void(int,const std::string&,int)> fn6=std::bind(Show,std::placeholders::_1,std::placeholders::_2);
fn6(5,"我是一只笨鸟",6);
//普通函数
std::function<void(int,const std::string&)> fn7=std::bind(Show,std::placeholders::_1,std::placeholders::_2);
fn7(6,"我是一只笨鸟 ");
//类的静态成员函数
std::function<void(int,const std::string&)> fn8=std::bind(AA::Show,std::placeholders::_1,std::placeholders::_2);
fn8(7,"我是一只笨鸟 ");
//仿函数(函数对象)
std::function<void(int,const std::string&)> fn9=std::bind(BB(),std::placeholders::_1,std::placeholders::_2);
fn9(8,"我是一只笨鸟 ");
//lambda表达式
std::function<void(int,const std::string&)> fn10=std::bind([](int n,const std::string& str)->void{
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
},std::placeholders::_1,std::placeholders::_2);
fn10(9,"我是一只笨鸟 ");
//类的非静态成员函数
CC cc;
std::function<void(CC&,int ,const std::string&)> fn11=std::bind(&CC::Show,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3);
fn11(cc,10,"我是一只笨鸟 ");
//在实际开发中,对于类的非静态成员函数,发行对象调用的时候不希望把对象传过去,不适合模板
// 为了解决这个问题,可以把对象提前绑定
std::function<void(int,const std::string&)> fn12=std::bind(&CC::Show,&cc,std::placeholders::_1,std::placeholders::_2);
fn12(10,"我是一只笨鸟 ");
//可以被转换为函数指针的类对象
DD dd;
std::function<void(int,const std::string&)> fn13=std::bind(dd,std::placeholders::_1,std::placeholders::_2);
fn13(11,"我是一只笨鸟 ");
return 0;
}
输出结果:
亲爱的 1, 我是一只笨鸟
亲爱的 1, 我是一只笨鸟
亲爱的 2, 我是一只笨鸟
亲爱的 3, 我是一只笨鸟
亲爱的 100, 我是一只笨鸟
亲爱的 5, 我是一只笨鸟
亲爱的 6, 我是一只笨鸟
亲爱的 7, 我是一只笨鸟
亲爱的 8 ,我是一只笨鸟
亲爱的 9, 我是一只笨鸟
亲爱的 10, 我是一只笨鸟
亲爱的 10, 我是一只笨鸟
亲爱的 11, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>
可变函数和参数
写一个函数,函数的参数是函数对象及参数, 功能和 thread 类的构造函数相同。
thread类的构造函数可以接收不同的对象以及对象不同的参数。
#include<iostream>
#include<thread>
#include<functional>
void Show1(){
//无参普通函数
std::cout<<"亲爱的,我是一只笨鸟 "<<std::endl;
}
void Show2(const std::string& str){
//带一个参数的普通函数
std::cout<<"亲爱的, "<<str<<std::endl;
}
struct CC{
void Show3(int n,const std::string& str){
//类中带两个参数的普通函数,非静态成员函数
std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}
};
template<class Fn,class...Args>
// void Show(Fn fn,Args...args){
//C++11一般要把返回类型写在后面
auto Show(Fn&& fn,Args&&...args)->decltype(std::bind(std::forward<Fn>(fn),std::forward<Args>(args)...)){
std::cout<<"开始 "<<std::endl;
//auto f=std::bind(fn,args...);
auto f=std::bind(std::forward<Fn>(fn),std::forward<Args>(args)...);
f();
std::cout<<"结束 "<<std::endl;
return f;
}
int main(){
std::cout<<"================"<<std::endl;
Show(Show1);
auto f1=std::bind(Show1);
f1();
Show(Show2,"我是一只笨鸟 ");
std::function<void(const std::string&)> f2=std::bind(Show2,std::placeholders::_1);
f2("我是一只笨鸟 ");
CC cc;
Show(&CC::Show3,&cc,3,"我是一只笨鸟 ");
std::function<void(int n,const std::string&)> f3=std::bind(&CC::Show3,&cc,std::placeholders::_1,std::placeholders::_2);
f3(3,"我是一只笨鸟 ");
std::cout<<"================"<<std::endl;
//std::thread t1();
// std::thread t2(Show1);
// std::thread t3(Show2,"我是一只笨鸟 ");
// CC cc;
// std::thread t4(&CC::Show3,&cc,3,"我是一只笨鸟 ");
// //t1.join();
// t2.join();
// t3.join();
// t4.join();
return 0;
}
输出结果:
PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
================
开始
亲爱的,我是一只笨鸟
结束
亲爱的,我是一只笨鸟
开始
亲爱的, 我是一只笨鸟
结束
亲爱的, 我是一只笨鸟
开始
亲爱的 3, 我是一只笨鸟
结束
亲爱的 3, 我是一只笨鸟
================
PS D:\可调用对象、包装器function、绑定器bind\Debug>
回调函数的实现
如何取代虚函数
map和unorder_map
#include<iostream>
#include<map>
#include<string>
/**
*
* map概念总结:
* map的用法和python中的字典用法是类似的。
* map是关联式容器,按照特定的顺序存储由key和value值组成的键值对。
* key:常用于对元素进行排序的唯一标识,元素总是按照其key进行排序的。
* 自动建立key-value的对应。key和value可以是任意的数据类型。
* map容器中没有两个元素具有相同的key。
*
* template <bool _Multi2 = _Multi, enable_if_t<!_Multi2, int> = 0>
pair<iterator, bool> insert(value_type&& _Val) {
const auto _Result = _Emplace(_STD move(_Val));
return {iterator(_Result.first, _Get_scary()), _Result.second};
}
_NODISCARD iterator find(const key_type& _Keyval) {
return iterator(_Find(_Keyval), _Get_scary());
}
删除某个迭代器位置
template <class _Iter = iterator, enable_if_t<!is_same_v<_Iter, const_iterator>, int> = 0>
iterator erase(iterator _Where) noexcept {
const auto _Scary = _Get_scary();
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Where._Getcont() == _Scary, "map/set erase iterator from incorrect container");
_STL_VERIFY(!_Where._Ptr->_Isnil, "cannot erase map/set end() iterator");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return iterator(_Erase_unchecked(_Where._Unwrapped()), _Scary);
}
删除某个迭代器中位置
iterator erase(const_iterator _Where) noexcept {
const auto _Scary = _Get_scary();
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Where._Getcont() == _Scary, "map/set erase iterator from incorrect container");
_STL_VERIFY(!_Where._Ptr->_Isnil, "cannot erase map/set end() iterator");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return iterator(_Erase_unchecked(_Where._Unwrapped()), _Scary);
}
删除迭代器某个区间
iterator erase(const_iterator _First, const_iterator _Last) noexcept {
return iterator(_Erase_unchecked(_First._Unwrapped(), _Last._Unwrapped()), _Get_scary());
}
删除key
size_type erase(const key_type& _Keyval) noexcept(noexcept(_Eqrange(_Keyval))) {
const auto _Where = _Eqrange(_Keyval);
const _Unchecked_const_iterator _First(_Where.first, nullptr);
const _Unchecked_const_iterator _Last(_Where.second, nullptr);
const auto _Num = static_cast<size_type>(_STD distance(_First, _Last));
_Erase_unchecked(_First, _Last);
return _Num;
}
*/
void Func1(){
//map中的插入元素
std::map<int,std::string> person;
person[0]="tom";
person.insert(std::pair<int,std::string>(1,"jery"));
person.insert(std::make_pair(3,"rose"));
person.insert(std::map<int,std::string>::value_type(4,"Speike"));
std::pair<int,std::string> p1(5,"mary");
person.insert(p1);
//map中的遍历元素方法1
std::map<int,std::string>::iterator iter1;
for(iter1=person.begin();iter1!=person.end();iter1++){
std::cout<<iter1->first<<" "<<iter1->second<<std::endl;
}
//map中的遍历元素方法2
for(auto iter2=person.begin();iter2!=person.end();iter2++){
std::cout<<iter2->first<<" "<<iter2->second<<std::endl;
}
//map中的遍历元素方法3
for(auto iter3:person){
std::cout<<(iter3).first<<" "<<(iter3).second<<std::endl;
}
//map的insert方法插入是否成功,insert插入的元素返回值是std::pari<iterator,bool>
std::pair<std::map<int,std::string>::iterator,bool> insert_pair;
insert_pair=person.insert(std::map<int,std::string>::value_type(6,"kuku"));
std::cout<<"是否插入成功: "<<std::boolalpha<<(insert_pair).second<<std::endl;
//获取map中的元素find(key),返回值是map迭代器
std::map<int,std::string>::iterator iter4;
iter4=person.find(6); //6是key值
if(iter4!=person.end()){
std::cout<<iter4->first<<" "<<iter4->second<<std::endl;
}else{
std::cout<<"not find"<<std::endl;
}
//删除map中的元素,erase(key),或者erase(iterator)两种方法,先用find(key)返回元素的迭代器,在调用erase(iterator)删除
std::cout<<"======================="<<std::endl;
auto iter5=person.find(5);
person.erase(iter5);
person.erase(6);
for(auto iter6:person){
std::cout<<(iter6).first<<" "<<(iter6).second<<std::endl;
}
std::cout<<"======================="<<std::endl;
//自动建立key-value的对应。key和value可以是任意的数据类型。这里改成std::string,std::string
std::map<std::string,std::string> fruits;
fruits["apple"]="苹果";
fruits.insert(std::pair<std::string,std::string>("banana","香蕉"));
fruits.insert(std::map<std::string,std::string>::value_type("pear","梨"));
std::map<std::string,std::string>::iterator iter7=fruits.find("banana");
if(iter7!=fruits.end()){
std::cout<<iter7->first<<" "<<iter7->second<<std::endl;
}else{
std::cout<<"not find"<<std::endl;
}
std::cout<<"========================="<<std::endl;
fruits.erase(iter7);
for(auto iter8:fruits){
std::cout<<(iter8).first<<" "<<(iter8).second<<std::endl;
}
}
int main(){
Func1();
return 0;
}