• C++高级编程: 可调用对象


    什么是C++中的可调用对象?

    所谓对象,就是在内存中存在的具体实例。

    可调用对象,就是可以像函数一样调用的对象。

    C++中有哪些可调用对象?

    • 函数与函数指针 (继承自C)
    • 仿函数
    • Lambda表达式

    函数与函数指针

    最基本的可以调用的对象就是函数,而指向某个函数的函数指针,也是可调用的

    仿函数

    什么是仿函数?

    首先仿函数是一个自定义的class实例, 然后这个实例可以指定参数调用自己,实现像函数一样的行为

    仿函数原理

    调用自己,指的是可以以圆括号加参数的形式, 像函数一行执行, 执行的函数就是这个示例对应的圆括号函数重载. 如:

    // Less类型实现了圆括号的操作符重载,参数为int a, int b, 实现的功能是判断a是否小于b
    class Less {
    public:
    	bool operator()(int a, int b)
        {
            return a < b;
        }
    };
    
    
    Less less; // 定义一个Less对象实例less, 则less实例就是仿函数
    
    std::cout << less(0, 1) << std::endl; // 0 < 1, 输出1, 对应true
    std::cout << less(100, 50) << std::endl; // 100不小于50, 对应false
    std::cout << less(10, 10) << std::endl; // 10不小于10, 对应false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    为什么要用仿函数?

    疑问1: 已经有了函数指针了,为什么还需要仿函数?
    • 首先函数指针只能指向一般的函数,并且除了函数指定的参数外,没有办法传递多余的参数,使用场景有限
    • 函数指针属于运行时动态函数,调用前需要确保指针合法,仿函数一般不需要额外判断,且更安全
    • 函数指针调用时,需要对指针解引用,与仿函数相比,效率更低,仿函数属于编译期函数跳转
    • 仿函数除了调用参数外,可以通过类构造函数传入额外的参数,这一点函数指针做不到,比如:
    // 有一个外部函数, 用来过滤数据
    template<Func>
    std::vector<int> filter(Func&& func, const std::vector<int>& src);
    
    // 假设需要筛选某个范围的值
    std::vector<int> numbers{1, 4, 5, 2, 7, 8, 0};
    
    
    // 使用仿函数
    class Between {
    public:
        Between(int down, int up): down_(down), up_(up) {}
    	bool operator(int number) const
        {
            return down_ <= number && number <= up;
        }
    private:
        int down_;
        int up_;
    };
    
    // 筛选[1, 5]区间的整数并且筛选范围还可以使用变量来控制, 仿函数可以轻松实现,但是函数指针不行
    std::vector<int> filtered = filter(Between(1, 5), numbers); // 1, 4, 5, 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    疑问2: 已经有各种原生函数了,为什么还要仿函数?

    原生的函数,只能使用固定的参数,定义的形式确定后,调用的形式也是固定的,无法指定多余的参数, 所以在传统C时代,都会使用一个额外的void*参数传递额外参数,且类型需要开发者自己保证安全

    标准库的仿函数

    functional

    这是一个可以将同一调用规则的可调用对象使用统一类型封装的一个盒子,只要可调用对象的形式符合这个盒子的要求,就都可以放到这个盒子里,并且这个盒子还可以什么也不放(此时不可调用)

    可以统一类型有什么好处?
    • 可以像函数指针一样动态绑定可调用对象,比如绑定指定函数,绑定自定义仿函数等等
    • 可以容器化管理,比如某个业务需要职责链模式,那这个职责链里面的所有可调用对象就可以通过functional放到列表或者映射中
    bind
    • 可以简单的包装普通函数,类静态函数,与原函数用法一样

      int funcA();
      std::function<void()> func = std::bind(funcA);
      func(); // 与直接调用funcA()效果一样
      
      • 1
      • 2
      • 3
    • 可以将对象和成员函数绑定在一起,最为一个新的可调用对象,调用对象的参数形式与成员函数的参数保持一致

      class Bird {
      public:
          void fly() 
          {
              // ...
          }
      };
      
      Bird some_bird;
      Bird another_bird;
      
      std::function<void()> bird1_fly = std::bind(&Bird::fly, &some_bird);
      std::function<void()> bird2_fly = std::bind(&Bird::fly, &another_bird);
      
      bird1_fly();  // 转换成了普通函数调用的形式,并且内部隐含了固定bird对象作为内部参数
      bird2_fly();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • 除了可以将现有的成员函数变换形式, 也能将其他函数表换形式,适配目标调用形式

      // 可调用目标的形式
      std::function<bool(int)> filter_item;
      
      // 我们有如下几个函数
      bool less(int a, int b); // a < b
      bool is_times(int a, int b); // a % b == 0
      
      // 除了自定义仿函数将这几个函数再封装一层以外,我们可以使用bind来封装
      // 1. bind less函数,并且a参数固定为100,b作为过滤参数, 过滤大于100的数
      filter_item = std::bind(less, 100);
      
      filter_item(10); // => 100 < 10 ? false, 100不小于10
      filter_item(1000); // => 100 < 1000 ? true, 100小于1000
      
      // 2. bind less函数,并且参数b固定100, a作为参数,过滤小于100的数
      filter_item = std::bind(less, std::placeholders::_1, 100);
      filter_item(10); // => 10 < 100 ? true, 10小于100
      filter_item(1000); // => 1000 < 100 ? false, 1000不小于100
      
      // 3. 是否4的倍数
      
      filter_item = std::bind(is_times, std::placeholders::_1, 4);
      filter_item(8); // true
      filter_item(3); // false
      
      // 4. 是否1000的因数
      filter_item = std::bind(is_times, 1000);
      
      filter_item(10); // true
      filter_item(100); // true
      filter_item(101); // false
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32

      lambda表达式

    匿名仿函数类,功能与仿函数一致, 但是可以直接使用代码块生成一个仿函数, 并且代码块也可以绑定函数参数以外的变量作为额外的调用参数,绑定外部变量不外部值绑定(拷贝一份到仿函数内部)和引用绑定(将外部对象的引用传入仿函数内部)

    int upper = 100;
    std::function<bool(int)> filter = [upper](int number) {
      	return  number < upper;
    };
    
    upper = 50; // 重新绑定
    filter = [upper](int number) {
      	return  number < upper;
    };
    
    // 直接绑定引用
    filter = [&upper](int number) {
      	return  number < upper;
    };
    
    upper = 10;
    filter(1); // 1 < 10 ? true
    
    upper = 1;
    filter(100); // 100 < 1 ? false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    【前端进阶】-TypeScript类型声明文件详解及使用说明
    Interactive Tools Recommendation System integrating QT/ROS /Pytorch
    2.继承总结方法
    Java类的继承
    PyTorch 深度学习之处理多维特征的输入Multiple Dimension Input(六)
    神经网络的建立,回调函数与对神经网络进行参数调整
    计算机毕业设计Java超市管理系统(源码+系统+mysql数据库+lw文档)
    盘点AI的认证
    Apollo星火计划学习笔记第五讲——Apollo感知模块详解实践1
    (JavaEE) 多线程基础3——多线程的代码案例 (单例模式, 阻塞队列,定时器)详解!!!
  • 原文地址:https://blog.csdn.net/oyoung_2012/article/details/126840764