可调用对象

通俗来说,就是创建一个可以当函数作用一样调用的对象

包含一下几种:

  • 是一个函数指针
  • 是一个具有 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::_1placeholders::_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;
}
  • 注意上述的成员变量那里的 f2autof2 不等价,auto 自动类型推导为仿函数,另一个是包装器类型,因为 bind 返回值是一个仿函数,上面的成员函数等等同理

说明:本文是在 https://subingwen.cn/ 学习过程中的总结,这个 up 主 B 站讲得很好 !!

只管努力,剩下的交给天意