可调用对象
通俗来说,就是创建一个可以当函数作用一样调用的对象
包含一下几种:
- 是一个函数指针
- 是一个具有
operator()
成员函数的类对象(仿函数) - 是一个可被转换为函数指针的类对象
- 是一个类成员函数指针或者类成员指针
函数指针
int print(int a, double b) {
cout << a << " " << b << endl;
return 0;
}
// 定义函数指针
int (*func)(int, double) = &print;
// 调用
(*func)(10, 20);
func(10, 20);
- 这里两种皆可
- 第一种很好理解,就是定义了一个指针指向函数地址,这里加不加
&
都一样,因为函数名就是地址,然后调用的时候进行解引用就拿到函数地址,就能进行调用了 - 第二种是 C++ 的简化,C++编译器允许直接使用函数指针进行调用,而不需要显式地解引用。编译器会自动理解
func(10, 20)
是通过函数指针func
调用的,并将其翻译成等效的(*func)(10, 20)
operator() 成员函数的类对象 (仿函数)
struct Test {
// ()操作符重载
void operator()(string msg) {
cout << "msg: " << msg << endl;
}
};
int main()
{
Test t;
t("zhangsan"); // 仿函数
return 0;
}
- 这个没啥说的,就是重载都懂
转换为函数指针的类对象
// 定义函数指针类型,起别名,接下来就能够 func_ptr 变量名 = 函数地址
using func_ptr = void(*)(int, string);
struct Test {
static void print(int a, string b) {
cout << "name: " << b << ", age: " << a << endl;
}
void hello(int a, string b) {
cout << a << b << endl;
}
// 将类对象转换为函数指针
operator func_ptr() {
return print;
// return hello; 错误
}
};
int main(void)
{
Test t;
// 对象转换为函数指针, 并调用
t(19, "zhangsan");
return 0;
}
- 语法是,
operator 函数指针类型 () {}
- 调用是创建对象,传参数,然后就会自动转换为函数指针,执行
return
的函数地址
解释为啥
return hello
错误
- 如果写
return hello;
就是错误的,因为它是非静态成员函数,实例化对象后才有地址,且有个隐含的this
指针,也就是和对象有关(我的瞎理解:对象都转换成函数指针去了,对象肯定就无了,那么就不能访问内部成员函数了) - 两个指针类型不同,
hello
函数是void(Test::*)(int, string)
,而func_ptr = void(*)(int, string)
,而静态成员函数print = void(*)(int, string)
加个思考的结果
// 构造的优先级更高
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;
}
void operator()(int x, string msg) {
cout << "x: " << x << " " << "msg: " << msg << endl;
}
};
int main() {
Test t;
t(19, "zhangsan"); // 输出 x: 19 msg: zhangsan
}
- 优先调用
()
重载
类成员函数指针或者类成员指针
using func_ptr = void(*)(int, string);
struct Test {
void print(int a, string b) {
cout << "name: " << b << ", age: " << a << endl;
}
int m_num;
};
int main()
{
// 定义类成员函数指针指向类成员函数
func_ptr = Test::print; // 错误的,如果是静态就正确
// 定义非静态的
using fptr = void (Test::*)(int, string); // 这个函数指针属于 Test 类
fptr = Test::print;
// 注:这个就不能当成函数使用,这是对成员变量操作,但是借助绑定器就可
// 类成员指针指向类成员变量
int Test::*obj_ptr = &Test::m_num; // 也可以不使用 using 关键字
Test t;
// 通过类成员函数指针调用类成员函数
(t.*fptr)(19, "zhangsan");
// 通过类成员指针初始化类成员变量
t.*obj_ptr = 1;
cout << "number is: " << t.m_num << endl;
}
包装器
std::function
是可调用对象的包装器
- 它是一个类模板,可以容纳除了类 (非静态)成员(函数)指针之外的所有可调用对象,对此绑定器可以解决
- 通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们
// 语法规则
#include <functional>
std::function<返回值类型(参数类型列表)> fun_name = 可调用对象;
示例
#include <functional>
void print(string name, int age) {
cout << "一般函数 " << name << " : " << age << endl;
}
using func_ptr = void(*)(string, int);
class Test{
public:
void operator()(string msg) {
cout <<"函数重载 " << msg << endl;
}
operator func_ptr(){
return world;
}
static void world(string name, int age) {
cout << "函数指针 " << name << " : " << age << endl;
}
};
int main(void)
{
// 包装普通函数
function<void(string, int)> f1 = print;
// 包装类的静态成员函数
function<void(string, int)> f2 = Test::world;
// 包装仿函数
Test ta;
function<void(string)> f3 = ta;
// 包装转换为函数指针的类对象
Test tb;
function<void(string, int)> f4 = tb;
// 函数调用
f1("zhangsan", 3);
f2("lisi", 3);
f3("wangwu");
f4("sun", 2);
}
看了上述例子,感觉没啥用是吧,对此一举 实际上不是这么玩的,把它作为参数传参才是玩法,也就是回调函数 这里只是基础,需要先懂这,后面才好理解
void print(string name, int age) {
cout << "一般函数 " << name << " : " << age << endl;
}
using func_ptr = void(*)(string, int);
class Test{
public:
void operator()(string msg) {
cout <<"函数重载 " << msg << endl;
}
operator func_ptr(){
return world;
}
static void world(string name, int age) {
cout << "函数指针 " << name << " : " << age << endl;
}
};
// 可调用对象包装类
class A {
public:
// 构造函数参数是一个包装器对象
A(const function<void(string, int)>& f) : callback(f) {}
void notify(string name, int age) {
callback(name, age); // 调用通过构造函数得到的函数指针
}
private:
function<void(string, int)> callback;
};
int main(void)
{
// 包装普通函数
function<void(string, int)> f1 = print;
// 包装类的静态成员函数
function<void(string, int)> f2 = Test::world;
// 包装仿函数
Test ta;
function<void(string)> f3 = ta;
// 包装转换为函数指针的对象
Test tb;
function<void(string, int)> f4 = tb;
A aa(print);
aa.notify("zhangsan", 3);
A ab(Test::world);
ab.notify("lisi", 3);
A ac(tb);
ac.notify("sun", 2);
// ta 的参数类型和这里的类里面的不一致,就不调用了
}
理解它
- A 类通过初始化列表的方式初始化
callback
,也就成了callback = print
,也就是上述第一个例子的function<void(string, int)> f1 = print;
- 然后 A 类的对象调用
notify()
函数,也就是执行callback ()
,这就是上述第一个案例的f1("zhangsan", 3);
绑定器
std::bind
用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用 std::function
进行保存,并延迟调用到任何我们需要的时候
作用:
- 将可调用对象与其参数一起绑定成一个仿函数
- 将多元(参数个数为 n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数
- 占位符:
placeholders::_1
,placeholders::_2
.....,这表示第一位参数列表的占位符,要被传入的参数覆盖
语法
// 绑定非类成员函数/变量,还可以是静态成员函数
auto f = std::bind(可调用对象地址, 绑定的参数/占位符);
// 绑定类成员函数/变量
auto f = std::bind(类函数/成员地址, 类实例对象地址, 绑定的参数/占位符);
// 注意是变量的时候,后面的参数位如果不存在就不写
一般函数
void output(int x, int y) {
cout << x << " " << y << endl;
}
int main(void)
{
// 使用绑定器绑定可调用对象和参数, 并调用得到的仿函数
// 得到的是仿函数,直接 () 调用
bind(output, 1, 2)();
bind(output, placeholders::_1, 2)(10); // 10 2
bind(output, 2, placeholders::_1)(10); // 10 2 注意不是 2 10
// error, 调用时没有第二个参数
// bind(output, 2, placeholders::_2)(10);
// 调用时第一个参数 10 被吞掉了,没有被使用
bind(output, 2, placeholders::_2)(10, 20); // 2 20
bind(output, placeholders::_1, placeholders::_2)(10, 20); // 10 20
bind(output, placeholders::_2, placeholders::_1)(10, 20); // 20 10
}
成员函数和变量
class Test {
public:
void output(int x, int y) {
cout << "x: " << x << ", y: " << y << endl;
}
int m_number = 100;
};
int main(void)
{
Test t;
// 绑定类成员函数
// auto f1 = bind(&Test::output, &t, placeholders::_1, placeholders::_2);
function<void(int, int)> f1 =
bind(&Test::output, &t, placeholders::_1, placeholders::_2);
// 绑定类成员变量(公共)
// auto f2 = bind(&Test::m_number, &t);
// 注意这里是可读可写,要加引用符号
function<int&(void)> f2 = bind(&Test::m_number, &t);
// 调用
f1(520, 1314);
f2() = 2333;
cout << "t.m_number: " << t.m_number << endl;
}
- 注意上述的成员变量那里的
f2
与auto
的f2
不等价,auto
自动类型推导为仿函数,另一个是包装器类型,因为bind
返回值是一个仿函数,上面的成员函数等等同理
说明:本文是在 https://subingwen.cn/ 学习过程中的总结,这个 up 主 B 站讲得很好 !!