28. 可调用对象

一、什么是可调用对象

  在 C++ 中,可调用对象(Callable Object)是指可以作为函数调用的对象。C++ 中有多种类型的可调用对象,包括:

  • 函数:这是最直接的调用对象,可以是普通函数或成员函数。
  • 函数指针:指向函数的指针也可以作为可调用对象。
  • Lambda 表达式:C++11 引入了 Lambda 表达式,它允许你定义匿名函数。
  • 仿函数(Functors):这是重载了 ()操作符的类的对象,使得它们可以像函数一样被调用。
  • 成员函数指针:指向类成员函数的指针。
  • 绑定器(Binders):如 std::bind 函数返回的对象,可以绑定函数和参数,创建一个新的可调用对象。
  • 包装器(Call wrappers):如 std::function,它可以包装任何可调用对象,使其具有统一的调用接口。
  • 协程 (C++20引入):允许函数在保持状态的情况下暂停和恢复执行的特殊函数。

  可调用对象的使用极大地增强了 C++ 的灵活性和表达能力,特别是在编写模板代码和回调函数时。通过使用 std::functionstd::bind,可以创建更为灵活的函数对象,允许在运行时选择和组合不同的行为。此外,它们在编写并发代码和响应式编程时也非常有用,因为它们可以轻松地传递和处理异步操作。

【1】、函数指针

#include <iostream>

using namespace std;

int max(int a, int b);

int main(void)
{
    int num1 = 0,num2 = 0;
    int maxNum = 0;

    /**
     * 定义函数指针
     * 函数指针的名字是 pmax
     * int 表示该函数指针指向的函数是返回int类型的
     * (int,int)表示该函数指针指向的函数形参是接收两个int
     * 在定义函数指针时,也可以写上形参名int (*pmax)(int a,int b) = &max;
     * 对于函数来说,&函数名和函数名都是函数的地址
    */
    int (*pmax)(int,int) = max;

    cout << "请输入两个数,中间以空格分隔:" << endl;
    cin >> num1;
    cin >> num2;
    /**
     * (*pmax)(num1,num2)通过函数指针去调用函数
     * 也可以这样调用pmax(num1,num2);
    */
    maxNum = (*pmax)(num1,num2);

    cout << "the max num is : " << maxNum;
  
    return 0;
}

int max(int a, int b)
{
    return a>b ? a : b;
}

【2】、重载 operator() 成员函数的类对象

#include <iostream>

using namespace std;

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 构建函数
    Person(void);
    Person(string name, int age);
  
    // 运算符重载
    void operator()(void);
};

// 无参构造函数
Person::Person(void){}

// 有参构造函数,使用初始化列表进行初始化
Person::Person(string name, int age) : name(name), age(age){}

// 函数调用运算符重载
void  Person::operator()(void)
{
    cout << "{name: " << this->name << ", age: " << this->age << "}\n";
}

int main(void)
{
    Person p("Sakura", 10);

    p();

    return 0;
}

【3】、指向类成员函数的指针

#include <iostream>

using namespace std;

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 构建函数
    Person(void);
    Person(string name, int age);

    void say(string message);
};

// 无参构造函数
Person::Person(void){}

// 有参构造函数,使用初始化列表进行初始化
Person::Person(string name, int age) : name(name), age(age){}

void Person::say(string message)
{
    cout << this->name << " say: " << message << endl;
}

int main(void)
{
    Person p("Sakura", 10);

    // 指向类成员函数的指针
    void (Person::*ptr)(string) = Person::say;
    (p.*ptr)("hello world");

    return 0;
}

二、可调用对象包装器

  在 C++ 中,可调用对象包装器(Callable Wrapper)是一种设计模式,用于将可调用对象(如函数、函数对象、Lambda 表达式、成员函数指针等)包装起来,以便于存储、传递和调用。这种模式在回调机制、事件处理、任务调度等场景中非常有用。C++ 11 及更高版本的 C++ 标准库提供了一些机制来支持可调用对象的包装和存储,例如 std::function

  std::function 是一个可调用对象的包装器,它可以存储、复制和调用任何可调用对象,只要它们的调用签名相同。它是一种泛化的函数指针,可以指向任何类型的函数或可调用对象。

【1】、包装普通函数

#include <iostream>
#include <functional>

using namespace std;

int fmax(int a, int b);

int main(void)
{
    int num1 = 0,num2 = 0;
    int maxNum = 0;

    // 包装普通函数
    function<int(int,int)> pmax = fmax;

    cout << "请输入两个数,中间以空格分隔:" << endl;
    cin >> num1;
    cin >> num2;

    // 调用函数
    maxNum = pmax(num1,num2);

    cout << "the max num is : " << maxNum;
  
    return 0;
}

int fmax(int a, int b)
{
    return a>b ? a : b;
}

【2】、包装仿函数

#include <iostream>
#include <functional>

using namespace std;

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 构建函数
    Person(void);
    Person(string name, int age);
  
    // 运算符重载
    void operator()(void);
};

// 无参构造函数
Person::Person(void){}

// 有参构造函数,使用初始化列表进行初始化
Person::Person(string name, int age) : name(name), age(age){}

// 函数调用运算符重载
void  Person::operator()(void)
{
    cout << "{name: " << this->name << ", age: " << this->age << "}\n";
}

int main(void)
{
    Person p("Sakura", 10);

    // 包装仿函数
    function<void(void)> f = p;
    f();

    return 0;
}

三、可调用对象绑定器

  在 C++ 中,std::bind 是一个功能强大的工具,它可以将可调用对象与其参数绑定在一起,创建出一个新的可调用对象。这个新的可调用对象可以带有预绑定的参数,或者改变了参数的顺序,或者两者都有。std::bind 主要用于函数式编程和回调函数中,它可以在不修改原有函数的情况下,灵活地调整函数的参数列表。

  std::bind 可以用来绑定函数和参数,生成一个新的可调用对象。std::bind 使用 std::placeholders 命名空间中的占位符来表示绑定过程中的参数位置。_1, _2, _3, … 分别表示第一个、第二个、第三个参数,依此类推。

【1】、绑定普通函数

#include <iostream>
#include <functional>

using namespace std;

int fmax(int a, int b);

int main(void)
{
    int num1 = 0,num2 = 0;

    cout << "请输入两个数,中间以空格分隔:" << endl;
    cin >> num1;
    cin >> num2;

    // 使用绑定器绑定可调用对象和参数,并调用的到相应的仿函数
    auto pmax = bind(fmax, num1, num2);
    int maxNum = pmax();
  
    cout << "the max num is : " << maxNum;
  
    return 0;
}

int fmax(int a, int b)
{
    return a>b ? a : b;
}

  我们可以使用 std::placeholders 命名空间中的占位符来表示绑定过程中的参数位置。

#include <iostream>
#include <functional>

using namespace std;

int fmax(int a, int b);

int main(void)
{
    int num1 = 0,num2 = 0;

    cout << "请输入两个数,中间以空格分隔:" << endl;
    cin >> num1;
    cin >> num2;

    // 使用绑定器绑定可调用对象和参数,并调用的到相应的仿函数
    auto pmax = bind(fmax, placeholders::_1, placeholders::_2);
    int maxNum = pmax(num1, num2);
  
    cout << "the max num is : " << maxNum;
  
    return 0;
}

int fmax(int a, int b)
{
    return a>b ? a : b;
}
// 使用绑定器绑定可调用对象和参数,并调用的到相应的仿函数
maxNum = bind(fmax, num1, num2)();
// 绑定参数,使用占位符 placeholders::_x表示第x个参数
maxNum = bind(fmax, placeholders::_1, placeholders::_2)(num1, num2);
maxNum = bind(fmax, placeholders::_1, num2)(num1);
maxNum = bind(fmax, num1, placeholders::_1)(num2);

// 调用时,第一个参数使用绑定的数值,不会使用传递进来的数值
maxNum = bind(fmax, num1, placeholders::_2)(num2, num2);

【2】、绑定类的成员函数

#include <iostream>
#include <functional>

using namespace std;

class Person
{
public:
    // 属性的声明
    string name;
    int age;

public:
    // 构建函数
    Person(void);
    Person(string name, int age);

    void say(string message);
};

// 无参构造函数
Person::Person(void){}

// 有参构造函数,使用初始化列表进行初始化
Person::Person(string name, int age) : name(name), age(age){}

void Person::say(string message)
{
    cout << this->name << " say: " << message << endl;
}

int main(void)
{
    Person p("Sakura", 10);

    // 指向类成员函数的指针
    bind(Person::say, &p, placeholders::_1)("hello world");

    return 0;
}

  并且,可调用对象的包装器和可调用对象的绑定器可以结合起来使用。

// 指向类成员函数的指针
function<void(string)> f = bind(Person::say, &p, placeholders::_1);
f("Hello World!");
posted @   星光映梦  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示