在C++中存在“可调用对象”这么一个概念。准确来说,可调用对象有如下几种定义:
是一个函数指针:
int print(int a, double b)
{
cout << a << b << endl;
return 0;
}
// 定义函数指针
int (*func)(int, double) = &print;
是一个具有operator()成员函数的类对象(仿函数):
#include
#include
#include
using namespace std;
struct Test
{
// ()操作符重载
void operator()(string msg)
{
cout << "msg: " << msg << endl;
}
};
int main(void)
{
Test t;
t("我是要成为海贼王的男人!!!"); // 仿函数
return 0;
}
是一个可被转换为函数指针的类对象 :
#include
#include
#include
using namespace std;
using func_ptr = void(*)(int, string);
struct Test
{
static void print(int a, string b)
{
cout << "name: " << b << ", age: " << a << endl;
}
// 将类对象转换为函数指针
operator func_ptr()
{
return print;
}
};
int main(void)
{
Test t;
// 对象转换为函数指针, 并调用
t(19, "Monkey D. Luffy");
return 0;
}
是一个类成员函数指针或者类成员指针:
#include
#include
#include
using namespace std;
struct Test
{
void print(int a, string b)
{
cout << "name: " << b << ", age: " << a << endl;
}
int m_num;
};
int main(void)
{
// 定义类成员函数指针指向类成员函数
void (Test::*func_ptr)(int, string) = &Test::print;
// 类成员指针指向类成员变量
int Test::*obj_ptr = &Test::m_num;
Test t;
// 通过类成员函数指针调用类成员函数
(t.*func_ptr)(19, "Monkey D. Luffy");
// 通过类成员指针初始化类成员变量
t.*obj_ptr = 1;
cout << "number is: " << t.m_num << endl;
return 0;
}
关于应该注意到的一些细节都在注释里面了:
#include
using namespace std;
/*
1.是一个函数指针
2.是一个具有operator()成员函数的类对象(仿函数)
3.是一个可被转换为函数指针的类对象
4.是一个类成员函数指针或者类成员指针
*/
//普通函数
void print(int num, string name)
{
cout << "id:" << num << ",name:" << name << '\n';
}
using funcptr = void(*)(int, string);
//类
class Test
{
public:
// 重载
void operator()(string msg)
{
cout << "仿函数:" << msg << '\n';
}
// 将类对象转化为函数指针
operator funcptr()// 后面的这个()不需要写任何参数
{
// 不能返回hello,虽然hello的参数也是int和string,
// 但是hello在未示例化之前是不存在的,world是属于类的
return world;// 虽然在定义的时候没有返回值但是在函数体里面必须要返回实际的函数地址
}
void hello(int a, string s)
{
cout << "number:" << a << ",name:" << s << '\n';
}
static void world(int a, string s)
{
cout << "number:" << a << ",name:" << s << '\n';
}
int m_id = 520;
string m_name = "luffy";
};
int main()
{
Test t;
t("我是要成为海贼王的男人");// 重载被执行
Test tt;
tt(19, "luffy");
// 类的函数指针
funcptr f = Test::world;// 可以让普通的函数指针指向类中的静态函数,不能指向非静态函数
// 给函数指针加上作用域就可以指向类中的非静态函数了
using fptr = void(Test::*)(int, string);
fptr f1 = &Test::hello;// 可调用对象
// 类的成员指针(变量)
using ptr1 = int Test::*;// 属于Test类中的指针
ptr1 pt = &Test::m_id;// 可调用对象
Test ttt;
(ttt.*f1)(20, "ace");// 前面加()的原因是*的优先级低于右侧的参数列表
ttt.*pt = 100;
cout << "m_id:" << ttt.m_id << '\n';
return 0;
}
上述程序的输出结果为:
C++11通过提供std::function 和 std::bind统一了可调用对象的各种操作。
std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。
std::function必须要包含一个叫做functional的头文件,可调用对象包装器使用语法如下:
#include
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;
接下来演示可调用对象包装器的基本使用方法:
#include
#include
using namespace std;
/*
1.是一个函数指针
2.是一个具有operator()成员函数的类对象(仿函数)
3.是一个可被转换为函数指针的类对象
4.是一个类成员函数指针或者类成员指针
*/
//普通函数
void print(int num, string name)
{
cout << "id:" << num << ",name:" << name << '\n';
}
using funcptr = void(*)(int, string);
//类
class Test
{
public:
// 重载
void operator()(string msg)
{
cout << "仿函数:" << msg << '\n';
}
// 将类对象转化为函数指针
operator funcptr()// 后面的这个()不需要写任何参数
{
// 不能返回hello,虽然hello的参数也是int和string,
// 但是hello在未示例化之前是不存在的,world是属于类的
return world;// 虽然在定义的时候没有返回值但是在函数体里面必须要返回实际的函数地址
}
void hello(int a, string s)
{
cout << "number:" << a << ",name:" << s << '\n';
}
static void world(int a, string s)
{
cout << "number:" << a << ",name:" << s << '\n';
}
int m_id = 520;
string m_name = "luffy";
};
int main()
{
#if 0
Test t;
t("我是要成为海贼王的男人");// 重载被执行
Test tt;
tt(19, "luffy");
// 类的函数指针
funcptr f = Test::world;// 可以让普通的函数指针指向类中的静态函数,不能指向非静态函数
// 给函数指针加上作用域就可以指向类中的非静态函数了
using fptr = void(Test::*)(int, string);
fptr f1 = &Test::hello;// 可调用对象
// 类的成员指针(变量)
using ptr1 = int Test::*;// 属于Test类中的指针
ptr1 pt = &Test::m_id;// 可调用对象
Test ttt;
(ttt.*f1)(20, "ace");// 前面加()的原因是*的优先级低于右侧的参数列表
ttt.*pt = 100;
cout << "m_id:" << ttt.m_id << '\n';
#endif
// 打包:
// C++中的function主要用于包装可调用的实体,
// 也就是函数。这些可调用的实体包括普通函数、函数指针、成员函数、静态函数、
// lambda表达式和函数对象。所以,你可以认为std::function包装的都是函数。
// 1.普通包装函数
// 只是对print函数进行打包,f1是不会被调用的,若想f1中的函数体执行还需要额外的再次调用
function f1 = print;
// 2.包装类的静态函数
function f2 = Test::world;
// 3.包装仿函数
Test ta;
function f3 = ta;
// 4.包装转化为函数指针的对象
Test tb;
function f4 = tb;
// 调用:
f1(1, "ace");
f2(2, "sabo");
f3("luffy");
f4(3, "robin");
return 0;
}
代码运行结果:
通过测试代码可以得到结论:std::function可以将可调用对象进行包装,得到一个统一的格式,包装完成得到的对象相当于一个函数指针,和函数指针的使用方式相同,通过包装器对象就可以完成对包装的函数的调用了。
function作为回调函数使用:
因为回调函数本身就是通过函数指针实现的,使用对象包装器可以取代函数指针的作用。
回调函数的基本概念和作用:
在C++中,回调函数(Callback Function)是指一种通过函数指针或函数对象传递给其他函数的函数。这种机制允许你在某个事件发生或条件满足时,通过调用指定的函数来实现定制的操作。
回调函数通常用于实现异步操作、事件处理、以及在框架或库中注册自定义行为。
回调函数的示例代码:
#include
// 定义回调函数的原型
typedef void (*CallbackFunction)(int);
// 接受回调函数作为参数的函数
void performOperation(int data, CallbackFunction callback) {
// 执行某些操作
std::cout << "Performing operation with data: " << data << std::endl;
// 调用回调函数
callback(data);
}
// 示例回调函数
void callbackFunction(int data) {
std::cout << "Callback function called with data: " << data << std::endl;
}
int main() {
// 使用回调函数调用 performOperation
performOperation(42, callbackFunction);
return 0;
}
输出结果为:
Performing operation with data: 42
Callback function called with data: 42
解释一下输出结果:
1. `performOperation` 函数被调用,传递了参数 `42`,然后输出了一条包含该数据的信息。
2. 在 `performOperation` 函数内部,回调函数 `callbackFunction` 被调用,将参数 `42` 传递给它。
3. `callbackFunction` 函数被执行,输出了一条包含传递给它的数据的信息。
因此,整体输出结果包括了两行信息,一行是在执行 `performOperation` 时的信息,另一行是在执行回调函数 `callbackFunction` 时的信息。这演示了回调函数的基本概念,其中一个函数在特定事件或条件发生时调用另一个函数。
接下来是关于function作为回调函数的使用的代码:
#include
#include
using namespace std;
/*
1.是一个函数指针
2.是一个具有operator()成员函数的类对象(仿函数)
3.是一个可被转换为函数指针的类对象
4.是一个类成员函数指针或者类成员指针
*/
//普通函数
void print(int num, string name)
{
cout << "id:" << num << ",name:" << name << '\n';
}
using funcptr = void(*)(int, string);
//类
class Test
{
public:
// 重载
void operator()(string msg)
{
cout << "仿函数:" << msg << '\n';
}
// 将类对象转化为函数指针
operator funcptr()// 后面的这个()不需要写任何参数
{
// 不能返回hello,虽然hello的参数也是int和string,
// 但是hello在未示例化之前是不存在的,world是属于类的
return world;// 虽然在定义的时候没有返回值但是在函数体里面必须要返回实际的函数地址
}
void hello(int a, string s)
{
cout << "number:" << a << ",name:" << s << '\n';
}
static void world(int a, string s)
{
cout << "number:" << a << ",name:" << s << '\n';
}
int m_id = 520;
string m_name = "luffy";
};
class A
{
public:
// 构造函数参数是一个包装器对象
// 这就意味着可以给这个构造函数传递四种类型的可调用对象
// 传进来的可调用对象并没有直接使用,而是存在callback中
// 在实例化对象后,调用notify函数,相当于一个回调操作
A(const function& f) : callback(f)
{
}
void notify(int id, string name)
{
callback(id, name);// 调用通过构造函数得到函数指针
}
private:
function callback;
};
int main()
{
#if 0
Test t;
t("我是要成为海贼王的男人");// 重载被执行
Test tt;
tt(19, "luffy");
// 类的函数指针
funcptr f = Test::world;// 可以让普通的函数指针指向类中的静态函数,不能指向非静态函数
// 给函数指针加上作用域就可以指向类中的非静态函数了
using fptr = void(Test::*)(int, string);
fptr f1 = &Test::hello;// 可调用对象
// 类的成员指针(变量)
using ptr1 = int Test::*;// 属于Test类中的指针
ptr1 pt = &Test::m_id;// 可调用对象
Test ttt;
(ttt.*f1)(20, "ace");// 前面加()的原因是*的优先级低于右侧的参数列表
ttt.*pt = 100;
cout << "m_id:" << ttt.m_id << '\n';
#endif
// 打包:
// C++中的function主要用于包装可调用的实体,
// 也就是函数。这些可调用的实体包括普通函数、函数指针、成员函数、静态函数、
// lambda表达式和函数对象。所以,你可以认为std::function包装的都是函数。
// 1.普通包装函数
// 只是对print函数进行打包,f1是不会被调用的,若想f1中的函数体执行还需要额外的再次调用
function f1 = print;
// 2.包装类的静态函数
function f2 = Test::world;
// 3.包装仿函数
Test ta;
function f3 = ta;
// 4.包装转化为函数指针的对象
Test tb;
function f4 = tb;
// 调用:
f1(1, "ace");
f2(2, "sabo");
f3("luffy");
f4(3, "robin");
A aa(print);
aa.notify(1, "ace");
A ab(Test::world);
ab.notify(2, "sabo");
// 包装仿函数也可以传参,这里不能是因为参数类型不一致
// 这里包装仿函数的参数为(int, string)
A ac(tb);
ac.notify(3, "luffy");
return 0;
}
上述代码的运行结果为:
通过上面的例子可以看出,使用对象包装器std::function可以非常方便的将仿函数转换为一个函数指针,通过进行函数指针的传递,在其他函数的合适的位置就可以调用这个包装好的仿函数了。
另外,使用std::function作为函数的传入参数,可以将定义方式不相同的可调用对象进行统一的传递,这样大大增加了程序的灵活性。
本文参考:可调用对象包装器、绑定器 | 爱编程的大丙 (subingwen.cn)